ReAct Prompting for Tool-Using Agents

La Lakshmi Priya Updated 07 Nov 2026
11 min read ·Lesson 108 of 140

From the ReAct Pattern to Production Agents Lesson 59 introduced ReAct, the loop of Thought → Action → Observation, implemented with a text format that code parsed. Lesson 78 introduced native tool calling, where the API returns structured tool calls. This lesson combines them: how to apply the ReAct idea in a modern agent built on native tool calling, so that the agent reasons about each step, acts through structured tools, observes results carefully, and leaves a reviewable record of what it did and why.

Element of ReAct In a Text-Based Loop (Lesson 59) In a Native Tool-Calling Agent Thought A "Thought:" line in the output A short reason argument on each tool call, or the model's own internal reasoning Action "Action: tool[input]" parsed with regex A structured function call from the API Observation "Observation:" text appended by code A function_call_output item sent back to the model Final answer "Final Answer:" line A normal text response with no tool calls Why Keep "Thought" When Using Native Tools? Many current models reason internally before responding, and native tool calling does not require any visible reasoning. So why ask for a reason at all?

Purpose Explanation Auditability A short stated reason for each action lets people review why the agent did what it did Debugging When an agent takes a wrong step, the reason often shows the misunderstanding Discipline Asking "why this tool now?" discourages random or repeated calls The reason should be short: one sentence. It is a record for people and a small nudge for the model, not a long chain of thought.

Adding a Reason Parameter to Tools One practical technique is to give every tool a reason parameter:

SEARCH_DOCS_TOOL = { "type": "function", "name": "search_docs", "description": "Search GOCOURSE help documents and policies. Returns the most relevant " "passages with their ids. Use this for questions about policies, schedules, or features.", "parameters": { "type": "object", "properties": { "query": {"type": "string", "description": "A short search query, e.g. 'refund window days'."}, "reason": {"type": "string", "description": "One sentence: why this search is needed now."}, }, "required": ["query", "reason"], "additionalProperties": False, }, "strict": True, } The reason is ignored by the tool itself, but it is recorded in the agent's trajectory, the step-by-step log of its actions.

The ReAct Instructions for an Agent The agent's instructions describe how to move through the loop:

How to work:

  1. Before each tool call, decide what information you still need and why. State this in the tool's "reason" field in one sentence.
  2. After each tool result, check: did it answer what you needed? Does it change your plan?
  3. Do not repeat the same tool call with the same arguments. If a search finds nothing useful, try different words once; if that also fails, say so or hand over.
  4. Use facts only from tool results. Do not assume what a tool would return.
  5. When you have enough information, stop calling tools and give your final answer.
  6. In your final answer, state the facts you relied on and which tool results they came from. Why Each Instruction Matters Instruction Prevents Decide what is needed and why Aimless tool calls Check each result Ignoring results that change the plan (Lesson 111) No identical repeats; limited retries Loops that waste steps and money Facts only from tool results Invented observations (Lesson 59) Stop when enough is known Unnecessary extra calls State sources in the final answer Untraceable answers Detecting Repeated Actions Agents sometimes call the same tool with the same arguments again and again, especially when a search returns nothing useful. Instructions help, but code should also detect repetition.

import json from collections import Counter

def call_signature(name: str, arguments_json: str) -> str: """A stable signature for a tool call, ignoring the free-text 'reason' field.""" arguments = {k: v for k, v in json.loads(arguments_json).items() if k != "reason"} return name + json.dumps(arguments, sort_keys=True)

class RepeatDetector: def init(self, max_repeats: int = 2): self.max_repeats = max_repeats self.counts: Counter = Counter()

def is_repeating(self, name: str, arguments_json: str) -> bool: signature = call_signature(name, arguments_json) self.counts[signature] += 1 return self.counts[signature] > self.max_repeats

detector = RepeatDetector(max_repeats=2) calls = [ ("search_docs", '{"query": "refund policy", "reason": "Find the refund rules."}'), ("search_docs", '{"reason": "Try again with the same query.", "query": "refund policy"}'), ("search_docs", '{"query": "refund window days", "reason": "Use different words."}'), ("search_docs", '{"query": "refund policy", "reason": "One more time."}'), ] for name, arguments in calls: status = "REPEATING" if detector.is_repeating(name, arguments) else "ok" print(json.loads(arguments)["query"], "->", status) Output:

refund policy -> ok refund policy -> ok refund window days -> ok refund policy -> REPEATING Code Explanation call_signature()

Parses the arguments and removes the reason field, because the reason text changes even when the action is identical. json.dumps(..., sort_keys=True) puts keys in a fixed order, so {"query": ..., "reason": ...} and {"reason": ..., "query": ...} produce the same signature. RepeatDetector Counts each signature. The third call with the query "refund policy" exceeds max_repeats=2 and is reported as repeating. The different query, "refund window days", is a new action and is allowed.

What to Do When Repetition Is Detected Instead of running the tool again, return an observation that redirects the agent:

This exact call has already been made twice with no new information. Try a different approach, or tell the user what you could not find, or hand over. The Agent Loop With Native Tool Calling Requirements: pip install openai and an OPENAI_API_KEY environment variable. The loop uses RepeatDetector from above. The tool functions here are simplified examples.

from openai import OpenAI

client = OpenAI() MODEL = "gpt-5.4" # Replace with a model available in your account

DOCS = { "refund-policy#0": "Refunds are available within 7 days of purchase if less than 20% of the course is completed.", "refund-policy#1": "Approved refunds reach the original payment method within 5 to 7 business days.", } ENROLLMENTS = {"asha@example.com": {"course": "Django REST APIs", "purchased_days_ago": 4, "progress": 12}}

def search_docs(query: str, reason: str) -> str: words = set(query.lower().split()) hits = {k: v for k, v in DOCS.items() if words & set(v.lower().split())} return json.dumps(hits or {"result": "No matching documents."})

def get_enrollment(email: str, reason: str) -> str: return json.dumps(ENROLLMENTS.get(email.lower(), {"error": "No enrollment found for this email."}))

TOOL_FUNCTIONS = {"search_docs": search_docs, "get_enrollment": get_enrollment}

GET_ENROLLMENT_TOOL = { "type": "function", "name": "get_enrollment", "description": "Look up the enrollment of the current student by email. Returns course, " "days since purchase, and progress percentage. Use this before answering " "questions about the student's own course or refund eligibility.", "parameters": { "type": "object", "properties": { "email": {"type": "string", "description": "The student's email address."}, "reason": {"type": "string", "description": "One sentence: why this lookup is needed now."}, }, "required": ["email", "reason"], "additionalProperties": False, }, "strict": True, }

INSTRUCTIONS = """You are the GOCOURSE enrollment support agent. Before each tool call, state why in the tool's "reason" field, in one sentence. After each result, check whether you have what you need. Do not repeat identical calls. Use facts only from tool results. When you have enough information, answer the student, mentioning the facts you relied on."""

def run_agent(user_message: str, max_steps: int = 6) -> tuple[str, list[dict]]: input_items = [{"role": "user", "content": user_message}] trajectory = [] detector = RepeatDetector(max_repeats=1) for _ in range(max_steps): response = client.responses.create( model=MODEL, instructions=INSTRUCTIONS, tools=[SEARCH_DOCS_TOOL, GET_ENROLLMENT_TOOL], input=input_items, ) calls = [item for item in response.output if item.type == "function_call"] if not calls: return response.output_text, trajectory input_items += response.output for call in calls: arguments = json.loads(call.arguments) if call.name not in TOOL_FUNCTIONS: result = json.dumps({"error": f"Unknown tool: {call.name}"}) elif detector.is_repeating(call.name, call.arguments): result = json.dumps({"error": "This exact call was already made. Try a different " "approach, explain what is missing, or hand over."}) else: result = TOOL_FUNCTIONScall.name trajectory.append({"tool": call.name, "reason": arguments.get("reason", ""), "arguments": {k: v for k, v in arguments.items() if k != "reason"}, "result": result[:120]}) input_items.append({"type": "function_call_output", "call_id": call.call_id, "output": result}) return "I couldn't complete this request. A support agent will follow up.", trajectory

answer, trajectory = run_agent("Hi, I'm asha@example.com. Can I still get a refund for my course?") for step in trajectory: print(step) print(answer) Code Explanation Tools with reasons Both tools require a reason argument. The tool functions accept it but do not use it; it exists for the trajectory.

The loop

Each iteration sends the conversation, including previous tool calls and results, to the model (Lesson 78). If the response contains no function calls, the model has finished, and its text is the final answer. Otherwise, each call is checked: unknown tools and repeated calls receive an error observation instead of being executed. Repeat detection max_repeats=1 means an identical call is allowed once; a second identical call is blocked with an observation that tells the agent what to do instead.

The trajectory Each step records the tool, the stated reason, the arguments without the reason, and the start of the result. After the run, you can review the agent's reasoning step by step.

Step limit After max_steps, the agent stops with a safe fallback message (Lesson 107).

A Possible Trajectory {'tool': 'get_enrollment', 'reason': "Check the student's purchase date and progress.", 'arguments': {'email': 'asha@example.com'}, 'result': '{"course": "Django REST APIs", "purchased_days_ago": 4, "progress": 12}'} {'tool': 'search_docs', 'reason': 'Find the refund eligibility rules.', 'arguments': {'query': 'refund eligibility days completed'}, 'result': '{"refund-policy#0": "Refunds are available within 7 days ...'} Yes, you're eligible: you bought Django REST APIs 4 days ago and have completed 12%, and refunds are available within 7 days if less than 20% is completed. ... Each step has a clear reason, each fact in the answer comes from a tool result, and the agent stopped once it had what it needed.

Reviewing Trajectories Trajectories are the main tool for understanding and improving agents. When reviewing them, look for:

Pattern What It Suggests Calls that do not match their stated reason The agent misunderstands a tool; improve its description (Lesson 109) Repeated searches with slightly different words Retrieval is weak, or the documents lack the information Facts in the answer with no matching tool result Invented information; strengthen instructions and add checks Many steps for simple questions Instructions do not tell the agent when to stop Actions taken without checking prerequisites Add rules about order, such as "look up enrollment before discussing refunds" Store trajectories for a sample of real conversations, and review them regularly, as you would review support tickets.

Common Mistakes Mistake 1: No Record of Reasons What it is: Agents that call tools without any stated reason or trajectory.

Why it happens: Native tool calling works without it.

What it causes: Agent behavior cannot be reviewed or debugged.

How to fix it: Add a short reason argument and record trajectories.

Mistake 2: Long Visible Reasoning What it is: Asking for detailed reasoning before every call.

Why it happens: Chain-of-thought helps in other contexts.

What it causes: Higher cost and latency, with little benefit for models that reason internally.

How to fix it: Keep reasons to one sentence.

Mistake 3: Repeated Calls Without Detection What it is: Letting agents call the same tool with the same arguments repeatedly.

Why it happens: Repetition is not anticipated.

What it causes: Wasted steps and cost, and agents that never conclude.

How to fix it: Detect repeated signatures in code and return a redirecting observation.

Mistake 4: Executing Unknown Tool Names What it is: Calling whatever function name the model returns.

Why it happens: The model usually requests valid tools.

What it causes: Errors or, in badly designed systems, unintended function calls.

How to fix it: Execute only tools in an allowlist (Lesson 107).

Best Practices Keep the ReAct structure with native tools. Reason, act, observe, repeat, stop.

Ask for a one-sentence reason per action. For auditing, debugging, and discipline.

Tell the agent to check every result. And to change its plan when results require it.

Detect and block repeated actions. Redirect the agent with a helpful observation.

Use facts only from tool results. And cite them in the final answer.

Record and review trajectories. They show how the agent really works.

Try It Yourself Exercise 1: Run the Agent Run run_agent() with three messages: an eligible refund request, a request from an email with no enrollment, and a general question about live class times that is not in DOCS. Review each trajectory, and check that every fact in the final answer comes from a tool result.

Exercise 2: Force a Repetition Change search_docs() so it always returns "No matching documents." Run a question that needs the documents, and observe how the agent responds to the repeat-detection message. Improve the instructions so the agent explains what it could not find instead of continuing to search.

Exercise 3: Trajectory Review Run the agent on ten varied questions and save all trajectories. Using the review table above, identify the most common problem pattern, and make one change to the instructions or tool descriptions to address it. Run the questions again and compare.

0 Comments

Reviewed before they appear

No comments yet.

Prompt Engineering
Prompt Engineering Introduction How Large Language Models Respond to Prompts Prompting Basics Elements of an Effective Prompt Writing Clear Instructions for AI Models Adding Context to a Prompt Providing Input Data in Prompts Using Output Indicators and Response Formats Prompt Formatting: Questions, Instructions, and Templates General Tips for Designing Better Prompts Common Prompt Engineering Mistakes Iterative Prompt Design and Refinement Understanding Instruction, Context, Input, and Output How Specificity and Conciseness Affect Prompt Quality Breaking Large Tasks into Smaller Prompting Tasks Zero-Shot Prompting Zero-Shot Prompting Examples for Everyday Tasks Zero-Shot Text Classification Zero-Shot Question Answering Zero-Shot Summarization Zero-Shot Translation Zero-Shot Prompting for Information Extraction When to Use Zero-Shot Prompting Limitations of Zero-Shot Prompting Improving Weak Zero-Shot Prompts Few-Shot Prompting One-Shot Prompting vs Few-Shot Prompting Choosing Good Examples for Few-Shot Prompts Formatting Examples in Few-Shot Prompts Few-Shot Classification with Examples Few-Shot Prompting for Structured Output Few-Shot Prompting Zero-Shot vs Few-Shot Prompting Role Prompting and Persona Instructions Instruction-Based Prompting Task-Oriented Prompt Templates Using Delimiters to Separate Prompt Sections Prompt Templates with Variables Chain-of-Thought Prompting Zero-Shot Chain-of-Thought Prompting Few-Shot Chain-of-Thought Prompting Automatic Chain-of-Thought (Auto-CoT) When Chain-of-Thought Prompting Helps Limitations of Chain-of-Thought Prompting Self-Consistency Prompting Comparing Chain-of-Thought and Self-Consistency Tree of Thoughts (ToT) Prompting Exploring Multiple Solutions with Tree of Thoughts Least-to-Most Prompting Complexity-Based Prompting Maieutic Prompting Self-Consistency: Sampling Multiple Reasoning Paths Self-Consistency for Arithmetic and Logic Tasks Tree of Thoughts: Search, Evaluation, and Backtracking Meta Prompting Generate Knowledge Prompting Directional Stimulus Prompting Program-Aided Language Models (PAL) React: Reasoning and Acting with Language Models Reflexion: Improving Agents Through Feedback Prompt Chaining Designing Multi-Step Prompt Chains Decomposing Complex Tasks into Prompt Chains Automatic Prompt Engineer (APE) Active-Prompt: Selecting Useful Reasoning Examples Optimizing Prompts Systematically Prompt Iteration and Versioning Building Reusable Prompt Libraries Testing Prompt Variants Prompt Chaining Fundamentals Passing Outputs Between Prompt Steps Prompt Chains for Summarization and Extraction Validating Intermediate Results in a Prompt Chain Debugging a Multi-Step Prompt Workflow Graph Prompting Multimodal Chain-of-Thought Prompting Automatic Reasoning and Tool-Use (ART) Prompting for Tool Selection and Use Combining Prompting Techniques for Complex Tasks Prompts for Text Summarization Prompts for Information Extraction Prompts for Text Classification Prompts for Question Answering Prompts for Sentiment Analysis Prompts for Rewriting and Editing Prompts for Translation and Localization Prompts for Brainstorming and Creative Writing Prompts for Generating Structured Data Prompts for Explaining Technical Concepts Prompts for Generating and Reviewing Code Prompting for Code Generation Prompting for Code Explanation Prompting for Debugging and Error Analysis Prompting for Unit Test Generation Prompting for SQL Query Generation Prompting for API Documentation Prompting for Technical Documentation Using Prompting to Extract Requirements Creating JSON-Style Structured Responses with Prompts Designing Prompts for Repeatable Developer Workflows Retrieval-Augmented Generation (RAG) How Prompt Engineering Supports RAG Writing Prompts That Use Retrieved Context Grounding Answers in Provided Documents Handling Missing Information in RAG Prompts Reducing Unsupported Claims in Context-Based Answers Prompt Engineering for AI Agents ReAct Prompting for Tool-Using Agents Writing Tool Instructions for AI Agents Prompting Agents to Plan Multi-Step Tasks Prompting Agents to Observe Tool Results Using Feedback and Reflection in Agent Workflows Prompting for Agent Error Recovery How to Evaluate Prompt Quality Creating Test Cases for Prompts Comparing Prompt Outputs with a Rubric Evaluating Accuracy, Relevance, and Completeness Regression Testing for Prompt Changes Building a Prompt Evaluation Dataset Common Causes of Inconsistent Prompt Outputs Prompt Injection: Introduction and Examples Recognizing Prompt Injection Attempts Prompt Leaking and Instruction Disclosure Risks Jailbreaking: Understanding Adversarial Prompts Writing Safer Prompts for Untrusted Inputs Reducing Hallucinations with Clear Instructions and Context Prompt Safety: Limitations and Best Practices Build a Zero-Shot Sentiment Classifier Project Goal Build a Few-Shot Text Classification Prompt Build a Prompt-Based Text Summarizer Project Goal Build a Structured Information Extraction Prompt Build a Prompt Template for Blog Outlines Build a Prompt-Based SQL Assistant Build a Code Explanation Prompt Workflow Build a Multi-Step Research and Summarization Workflow Build a Prompt Comparison and Evaluation Pipeline Build a Prompt-Chaining Workflow for Document Analysis Build a RAG Prompt for Question Answering over Documents Build a Tool-Using ReAct Assistant Create a Reusable Prompt Library for a Web Application
Ask about this post
AI Ask about this post

Ask questions about ReAct Prompting for Tool-Using Agents and get answers drawn from it.

Signed-in readers only.