[KongchangAI]
Tutorials· 2 min read· 1,456 words

LangChain Agent Executor Architecture: Building a ReAct Agent from Scratch

LangChain Agent Executor Architecture: Building a ReAct Agent from Scratch

Deep dive into LangChain Agent Executor internals and building a custom ReAct Agent from scratch.

This article explains the core mechanisms of the Agent Executor in LangChain, centered around the ReAct (Reasoning + Action + Observation) iteration pattern. It covers Prompt design, tool binding, Tool Choice strategies (recommending forced tool calling + Final Answer tool), and provides a complete custom Agent Executor implementation, emphasizing max iteration protection and the critical role of Tool Call IDs in parallel tool calling.

Introduction

In AI application development, an Agent is a core concept. It's not just a simple LLM call—it's a set of code logic that iteratively runs LLM calls, processes outputs, and executes tools. This article provides a deep dive into how the Agent Executor works in LangChain, and walks you through building a custom Agent Executor from scratch, helping you truly understand the mechanisms behind how Agents operate.

The ReAct Pattern: The Agent's Core Thinking Framework

What is ReAct?

ReAct is one of the most mainstream Agent design patterns today. Its name comes from the combination of Reasoning + Action. This pattern originates from the 2022 paper "ReAct: Synergizing Reasoning and Acting in Language Models" jointly published by Princeton University and Google Research. Through experiments on multi-hop reasoning benchmarks like HotpotQA and FEVER, the paper demonstrated that interleaving reasoning traces with action steps significantly improves LLM performance on multi-step tasks—compared to pure Chain-of-Thought reasoning, ReAct improves accuracy by approximately 10%~30% on tasks requiring external information retrieval. More importantly, this "think while doing" structure is inherently interpretable: every reasoning step is traceable, allowing you to clearly pinpoint where an Agent made a judgment error during debugging. Although this pattern was proposed several years ago, virtually all LLM providers, including OpenAI, follow a very similar pattern for their tool-calling Agents.

The core ReAct loop consists of three steps:

  1. Reasoning: The LLM analyzes current information and decides what to do next
  2. Action: Executes a specific tool call
  3. Observation: Retrieves the results of the tool execution

Here's a concrete example: Suppose a user asks "Besides the Apple Remote, what other device can control the program that the Apple Remote was originally designed to interact with?" The LLM first reasons that it needs to search for information about the Apple Remote, then calls a search tool and obtains the observation that "Apple Remote is used to control the Front Row media center." Then it enters a second loop, searches for Front Row, and ultimately arrives at the answer "keyboard function keys."

The Role of the Agent Executor

In the entire ReAct loop, the Agent itself is only responsible for reasoning and generating tool call instructions, while the Agent Executor is what actually executes tools, processes responses, and manages the iteration loop. Its workflow can be summarized as:

  • Pass user input to the LLM
  • Determine whether the LLM's output is a final answer or a tool call
  • If it's a tool call, execute the tool and feed the results back to the LLM
  • Repeat the above process until a final answer is obtained

One detail worth noting: each iteration may involve multiple LLM calls, which means Agents are more expensive than single LLM calls in terms of both latency and token costs, but this trades off for problem-solving capabilities far beyond what a regular LLM can achieve.

Building an Agent from Scratch: Prompt Design and Tool Binding

Defining the Prompt Template

The first step in building an Agent is designing a proper Prompt template. A typical Agent Prompt contains four parts:

  • System Message: Defines the Agent's behavioral rules, including when to use tools and when to answer directly
  • Chat History: Historical conversation records
  • User Input: The current user message
  • Agent Scratchpad: Intermediate reasoning steps, including tool call records and observation results

The Agent Scratchpad is key—tool calls generated by the LLM are stored as AI Messages, while results returned by tools are stored as Tool Messages, linked together via tool_call_id.

Agent tool execution and parameter passing

Tool Binding and Execution Mechanism

LangChain uses the @tool decorator to convert ordinary Python functions into structured tool objects containing metadata like name, description, and JSON Schema. The underlying principle of this mechanism is: the decorator automatically parses the Python function's type hints and docstrings, and leverages Pydantic models to convert them into JSON Schema format compliant with the OpenAI Function Calling specification. This means the quality of your function annotations directly determines how accurately the LLM understands and calls your tools—the more precise the descriptions and the clearer the parameter explanations, the higher the probability that the LLM will select the correct tool and pass valid arguments. Once tools are bound to the LLM, it can generate structured output containing tool names and parameters.

The execution flow is very intuitive: extract the tool name and parameters from the LLM's output, find the corresponding function through name mapping, then execute that function with keyword arguments.

Obtaining observation results after tool execution

Tool Choice Strategies: Comparing Two Termination Approaches

Approach 1: Auto Mode

When you set tool_choice="auto", the LLM can freely choose to use tools or answer the user directly in the content field. This approach is simple and intuitive, but has a practical problem: the LLM sometimes chooses to answer directly when it should use tools, especially when using smaller models, where this "tool-skipping" behavior becomes more frequent.

Setting tool_choice="any" (equivalent to "required") forces the LLM to call a tool every time. To allow the Agent to output a final answer, you need to create an additional final_answer tool.

This approach has two significant advantages:

  1. Stronger control: Eliminates the possibility of the LLM bypassing tools to answer directly; output is always in the tool_calls field with a unified format
  2. Structured output: You can define a precise output Schema in the final_answer tool, such as including an answer field and a tools_used field, ensuring downstream logic can reliably parse the output

Definition and usage of the Final Answer tool

This structured output is extremely important in production environments—when the Agent's output needs to be passed to frontend displays or downstream processing pipelines, a predictable output format significantly reduces system fragility.

Complete Implementation of a Custom Agent Executor Class

Core Architecture

Encapsulate all the manually executed steps from before into a CustomAgentExecutor class. The core logic is as follows:

class CustomAgentExecutor:
    def __init__(self, llm, tools, prompt, max_iterations=3):
        self.chat_history = []
        self.max_iterations = max_iterations
        # Initialize agent and tool mapping
    
    def invoke(self, input: str):
        count = 0
        while count < self.max_iterations:
            # 1. Call agent to generate tool calls
            # 2. Execute tools, get observation results
            # 3. Add results to scratchpad
            # 4. If it's final_answer, extract answer and return
            count += 1

Key Design Considerations

Maximum iteration protection: This is an easily overlooked but critically important safety mechanism. If the Agent's logic has issues (for example, observation results aren't properly fed back to the LLM), the Agent might infinitely loop LLM calls, leading to exorbitant API costs. Setting max_iterations effectively prevents this.

The importance of Tool Call ID: Each tool call has a unique ID, and when a tool returns results, it must carry the corresponding ID. This is especially critical in parallel tool calling scenarios. Starting with GPT-4 Turbo, OpenAI introduced Parallel Function Calling capability, allowing the model to generate multiple call instructions simultaneously in the tool_calls array within a single response. This feature can compress the total latency of multiple independent queries from "serial accumulation" to "parallel execution," with particularly notable effects in scenarios requiring simultaneous queries to multiple data sources. However, parallel execution means the return order of tools is no longer deterministic, making tool_call_id the only reliable correlation anchor—the LLM relies on it to precisely match each observation result back to its corresponding call request, ensuring the integrity of the reasoning context.

Tool Call ID correlation mechanism in parallel tool calls

Handling parallel calls: OpenAI's models support generating multiple parallel tool calls in a single response. In simple scenarios, you can assume there's only one tool call per response (accessed via index [0]), but in production environments, you need additional logic to handle parallel call scenarios.

Summary and Practical Recommendations

By manually building an Agent Executor, we can clearly see that the core of an Agent isn't mysterious at all—it's essentially an iterative control loop around LLM calls. Once you understand this, you can flexibly customize Agent behavior logic according to specific business requirements.

A few practical recommendations:

  • Prefer the forced tool calling + Final Answer tool pattern for more controllable output
  • Always set a maximum iteration count to prevent unexpected infinite loops
  • Handle parallel tool calls in production environments—don't assume there's only one tool call per response
  • Understand the underlying principles before using LangChain's abstractions, so you can quickly locate and resolve issues when they arise

Although LangChain provides a highly abstracted AgentExecutor ready for direct use, deeply understanding its internal mechanisms will give you the confidence to build more complex Agent systems that better fit your business scenarios.

#LangChain Agent Executor#ReAct pattern#AI agent architecture#tool calling#parallel function calling#custom agent executor#LLM agent development
Share:

Related articles