5 LangChain Agent Types Explained (ZeroShot, ReAct, and More)
Understand every major LangChain agent type — ZeroShotAgent, ReAct, ConversationalAgent, and more — with Python code and agent trace walkthroughs.
Get more content like this on Telegram!
Daily AI tips, notes & resources — free
Agents are where LangChain gets genuinely interesting — and genuinely complicated. A chain follows a script you wrote. An agent improvises. You give it a goal and a set of tools, and it figures out which tools to call, in what order, and when to stop.
I've built agents for research automation, code generation pipelines, customer support systems, and data analysis workflows. Each time, the hardest part wasn't the code. It was choosing the right agent type for the task and understanding why the agent was making the decisions it was making.
This guide covers the five main LangChain agent types with complete code, agent trace walkthroughs, and a clear table of when to use each one. We'll also cover the AgentExecutor, tool binding, and the debugging approaches that actually help when things go wrong.
If you're new to agents entirely, the AI agents explained guide covers the conceptual foundations. For the broader context of how agents fit into multi-agent systems, see AI agent memory and planning.
How LangChain Agents Work
Before the code, a quick mental model. A LangChain agent has three components:
- LLM — makes decisions about what to do next
- Tools — functions the agent can call (search, calculator, API calls, etc.)
- AgentExecutor — the runtime loop that calls the LLM, executes tools, and manages state
The agent loop works like this:
- Give the LLM the user's goal and a description of available tools
- LLM decides: "I should call this tool with these arguments"
- AgentExecutor calls the tool, gets the result
- LLM sees the result and decides what to do next
- Repeat until LLM says "I have enough information to answer"
Different agent types differ in how the LLM expresses its decisions — through structured JSON (function calling), through specific text formats (ReAct), or through conversation history.
Setting Up Tools
All agent types share the same tool setup. Let's create a simple toolkit first:
from langchain_core.tools import tool
from langchain_community.tools import DuckDuckGoSearchRun
import math
# Tool 1: Web search
search = DuckDuckGoSearchRun()
# Tool 2: Calculator (custom tool using @tool decorator)
@tool
def calculator(expression: str) -> str:
"""Evaluate a mathematical expression. Input should be a valid Python math expression."""
try:
# Safe evaluation for math expressions
allowed_names = {k: v for k, v in math.__dict__.items() if not k.startswith('_')}
result = eval(expression, {"__builtins__": {}}, allowed_names)
return str(result)
except Exception as e:
return f"Error: {str(e)}"
# Tool 3: Current date/time
@tool
def get_current_date() -> str:
"""Returns the current date and time."""
from datetime import datetime
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# Tool 4: Text summarizer
@tool
def summarize_text(text: str) -> str:
"""Summarizes a long text into 2-3 key points. Useful when search results are too long."""
# In a real app, this would call an LLM or use a summarization algorithm
sentences = text.split('. ')
return '. '.join(sentences[:3]) + '...' if len(sentences) > 3 else text
tools = [search, calculator, get_current_date, summarize_text]
Agent Type 1: ZeroShot ReAct Agent
The ZeroShot ReAct agent is the classic LangChain agent. It uses the ReAct (Reasoning + Acting) framework: the LLM alternates between "Thought" (reasoning about what to do), "Action" (choosing a tool), and "Observation" (the tool's result).
from langchain_openai import ChatOpenAI
from langchain.agents import create_react_agent, AgentExecutor
from langchain import hub
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# Pull the standard ReAct prompt from LangChain hub
prompt = hub.pull("hwchase17/react")
# Create the agent
agent = create_react_agent(llm=llm, tools=tools, prompt=prompt)
# Wrap with executor
executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True, # Show the reasoning trace
max_iterations=10, # Prevent infinite loops
handle_parsing_errors=True,
early_stopping_method="generate"
)
result = executor.invoke({"input": "What is the square root of 144, and what is today's date?"})
print(result["output"])
Reading the ReAct Trace
With verbose=True, you'll see output like this:
Thought: I need to calculate the square root of 144 and get the current date.
Action: calculator
Action Input: math.sqrt(144)
Observation: 12.0
Thought: Now I need the current date.
Action: get_current_date
Action Input:
Observation: 2026-05-31 14:23:45
Thought: I have both pieces of information.
Final Answer: The square root of 144 is 12. Today's date is May 31, 2026.
That trace is the agent's reasoning process. When an agent goes wrong, the trace is where you look. Is it calling the right tool? Is it passing the right arguments? Is it misinterpreting the observation?
The "ZeroShot" in the name means the agent doesn't need examples of using tools — it infers tool usage from the descriptions. The quality of your tool descriptions directly affects agent performance.
Agent Type 2: Structured Chat Agent
The Structured Chat Agent is designed for tools that take multiple, structured inputs (not just a single string). It produces JSON-formatted tool calls instead of free-text actions.
from langchain.agents import create_structured_chat_agent, AgentExecutor
from langchain import hub
from langchain_core.tools import tool
from pydantic import BaseModel, Field
# Structured tool with typed inputs
class WeatherInput(BaseModel):
city: str = Field(description="The city name")
units: str = Field(default="celsius", description="Temperature units: celsius or fahrenheit")
@tool("weather_lookup", args_schema=WeatherInput)
def weather_lookup(city: str, units: str = "celsius") -> str:
"""Look up current weather for a city."""
# Mock response — real version would call a weather API
return f"Weather in {city}: 22°C, partly cloudy. (Units: {units})"
@tool
def translate_text(text: str, target_language: str) -> str:
"""Translate text to the specified language.
Args:
text: The text to translate
target_language: Target language (e.g., 'Spanish', 'French', 'German')
"""
return f"[Mock translation of '{text}' to {target_language}]"
structured_tools = [weather_lookup, translate_text, calculator]
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
prompt = hub.pull("hwchase17/structured-chat-agent")
agent = create_structured_chat_agent(
llm=llm,
tools=structured_tools,
prompt=prompt
)
executor = AgentExecutor(
agent=agent,
tools=structured_tools,
verbose=True,
max_iterations=8,
handle_parsing_errors=True
)
result = executor.invoke({
"input": "What's the weather in Paris in Fahrenheit? Then translate the result to French."
})
print(result["output"])
The Structured Chat Agent is better than ZeroShot ReAct when your tools have complex input schemas. The JSON formatting reduces parsing errors, especially for tools with optional parameters or enum inputs.
Agent Type 3: Conversational Agent
The Conversational Agent maintains memory across turns, enabling multi-turn interactions where each question can reference previous answers.
from langchain.agents import create_react_agent, AgentExecutor
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain import hub
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.3)
# Use the conversational ReAct prompt
prompt = hub.pull("hwchase17/react-chat")
agent = create_react_agent(llm=llm, tools=tools, prompt=prompt)
executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True,
max_iterations=10,
handle_parsing_errors=True
)
# Add message history
message_histories = {}
def get_history(session_id: str) -> ChatMessageHistory:
if session_id not in message_histories:
message_histories[session_id] = ChatMessageHistory()
return message_histories[session_id]
agent_with_history = RunnableWithMessageHistory(
executor,
get_history,
input_messages_key="input",
history_messages_key="chat_history"
)
config = {"configurable": {"session_id": "user_001"}}
# First turn
r1 = agent_with_history.invoke(
{"input": "My name is Sarah and I'm researching climate change."},
config=config
)
print(f"Turn 1: {r1['output']}")
# Second turn — references first
r2 = agent_with_history.invoke(
{"input": "What did I say my research topic was?"},
config=config
)
print(f"Turn 2: {r2['output']}") # Should remember "climate change"
# Third turn — actual research task
r3 = agent_with_history.invoke(
{"input": "Search for the latest data on global temperature increases."},
config=config
)
print(f"Turn 3: {r3['output']}")
This agent type is the backbone of any chatbot that uses tools. The build AI chatbot Python guide extends this pattern into a full chatbot implementation.
Agent Type 4: OpenAI Functions Agent
This is the most reliable agent type when you're using OpenAI models. Instead of parsing free-text "Action: tool_name" strings, it uses OpenAI's native function calling API to invoke tools with structured JSON.
from langchain.agents import create_openai_functions_agent, AgentExecutor
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
llm = ChatOpenAI(model="gpt-4o", temperature=0) # Works best with gpt-4o or gpt-4o-mini
# Prompt for function-calling agent
prompt = ChatPromptTemplate.from_messages([
("system", """You are a helpful research assistant with access to search, calculation, and date tools.
Use tools when needed to give accurate, up-to-date answers.
Always verify numerical claims with the calculator tool."""),
MessagesPlaceholder(variable_name="chat_history", optional=True),
("human", "{input}"),
MessagesPlaceholder(variable_name="agent_scratchpad")
])
agent = create_openai_functions_agent(
llm=llm,
tools=tools,
prompt=prompt
)
executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True,
max_iterations=10,
handle_parsing_errors=True,
return_intermediate_steps=True # Get full trace in the output
)
result = executor.invoke({
"input": "Search for the current population of Tokyo and calculate how many times larger it is than London's population of 9 million people."
})
print(f"Answer: {result['output']}")
print(f"\nSteps taken: {len(result['intermediate_steps'])}")
# View intermediate steps
for step in result['intermediate_steps']:
tool_call, observation = step
print(f"\nTool: {tool_call.tool}")
print(f"Input: {tool_call.tool_input}")
print(f"Output: {observation[:200]}")
Why OpenAI Functions Agent is Usually Better
The text-based ReAct format requires the LLM to produce exact string patterns like Action: tool_name\nAction Input: .... Models sometimes deviate from this format, causing parsing failures. Function calling uses a dedicated API parameter that returns structured JSON, which almost never has formatting errors.
For production systems using GPT models, OpenAI Functions Agent should be your default choice. It's faster, more reliable, and produces better traces.
Agent Type 5: OpenAI Tools Agent (Modern API)
The newer equivalent of the Functions agent, create_openai_tools_agent supports parallel tool calls — the model can call multiple tools simultaneously rather than sequentially.
from langchain.agents import create_openai_tools_agent, AgentExecutor
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
prompt = ChatPromptTemplate.from_messages([
("system", """You are an efficient research assistant. When you need multiple pieces
of information, request all tools simultaneously to save time. Be concise."""),
MessagesPlaceholder("chat_history", optional=True),
("human", "{input}"),
MessagesPlaceholder("agent_scratchpad")
])
agent = create_openai_tools_agent(llm=llm, tools=tools, prompt=prompt)
executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True,
max_iterations=8
)
# This should trigger parallel tool calls
result = executor.invoke({
"input": "Find out today's date AND calculate 2^32, both at the same time."
})
print(result["output"])
With parallel tool calls, if the agent needs both a search result and a calculation, it can request both in a single API call. This can cut agent response time significantly for multi-step tasks. The OpenAI API integration guide covers the parallel calling API in more detail.
Agent Types Comparison Table
| Agent Type | Routing Mechanism | Memory | Parallel Tools | Best For | Reliability |
|---|---|---|---|---|---|
| ZeroShot ReAct | Text parsing (Thought/Action) | None | No | General tasks, prototyping | Medium |
| Structured Chat | JSON tool calls | Optional | No | Multi-param tools | Medium-High |
| Conversational | Text parsing + history | Yes | No | Multi-turn chatbots | Medium |
| OpenAI Functions | Function calling API | Optional | No | GPT-based production agents | High |
| OpenAI Tools | Tools API | Optional | Yes | High-throughput GPT agents | High |
My honest opinion: for anything using OpenAI models, skip straight to OpenAI Tools Agent. The text-based agents (ZeroShot ReAct, Structured Chat) require the LLM to produce precise text formats that it sometimes gets wrong. Function/tool calling is purpose-built for this and simply works better.
The exception: if you're using a non-OpenAI model (Anthropic Claude, Mistral, Llama), the function calling API compatibility varies. In those cases, ReAct-style agents are more portable.
Custom Agents with LangGraph
For complex multi-step agents that go beyond what the built-in types offer, LangGraph is the better tool. It's LangChain's graph-based agent framework:
from langgraph.graph import StateGraph, END
from langgraph.prebuilt import ToolNode
from langchain_openai import ChatOpenAI
from typing import TypedDict, Annotated, List
import operator
llm = ChatOpenAI(model="gpt-4o-mini")
llm_with_tools = llm.bind_tools(tools)
class AgentState(TypedDict):
messages: Annotated[List, operator.add]
def should_continue(state: AgentState) -> str:
last_message = state["messages"][-1]
if hasattr(last_message, "tool_calls") and last_message.tool_calls:
return "tools"
return END
def call_model(state: AgentState) -> dict:
response = llm_with_tools.invoke(state["messages"])
return {"messages": [response]}
# Build the graph
workflow = StateGraph(AgentState)
workflow.add_node("agent", call_model)
workflow.add_node("tools", ToolNode(tools))
workflow.set_entry_point("agent")
workflow.add_conditional_edges("agent", should_continue)
workflow.add_edge("tools", "agent")
app = workflow.compile()
from langchain_core.messages import HumanMessage
result = app.invoke({"messages": [HumanMessage(content="Search for news about AI agents")]})
print(result["messages"][-1].content)
LangGraph gives you explicit control over the agent loop, conditional branching, parallel execution, and human-in-the-loop checkpoints. For production agents that need precise control over execution flow, this is where you want to be. The AI research agent build guide uses LangGraph for a real-world research automation agent.
Debugging Agent Failures
Agents fail in predictable ways. Here's how to handle each one.
Tool Formatting Errors
# Always include this — it handles common formatting mistakes
executor = AgentExecutor(
agent=agent,
tools=tools,
handle_parsing_errors=True,
verbose=True
)
# Custom error handling
executor = AgentExecutor(
agent=agent,
tools=tools,
handle_parsing_errors="I encountered a formatting error. Let me try a different approach.",
)
Agent Loops
# Set hard limits
executor = AgentExecutor(
agent=agent,
tools=tools,
max_iterations=10, # Hard stop after 10 iterations
max_execution_time=60, # Hard stop after 60 seconds
early_stopping_method="generate" # LLM generates final answer when limit hit
)
Observing Tool Calls
from langchain.callbacks.base import BaseCallbackHandler
class ToolUsageLogger(BaseCallbackHandler):
def on_tool_start(self, serialized, input_str, **kwargs):
print(f"[TOOL START] {serialized['name']}: {input_str[:100]}")
def on_tool_end(self, output, **kwargs):
print(f"[TOOL END] Output: {str(output)[:200]}")
def on_tool_error(self, error, **kwargs):
print(f"[TOOL ERROR] {str(error)}")
result = executor.invoke(
{"input": "Search for Python tutorials"},
config={"callbacks": [ToolUsageLogger()]}
)
For a full comparison of how LangChain agents stack up against other agent frameworks, see our CrewAI tutorial and the AutoGPT vs BabyAGI comparison — both cover different architectural approaches to the same agent design problems.
Conclusion
The five LangChain agent types each occupy a clear niche. ZeroShot ReAct and Structured Chat are portable across model providers. OpenAI Functions and Tools agents are more reliable when you're locked to GPT models. The Conversational agent adds memory to any of the above. LangGraph is where you go when the standard executors aren't flexible enough.
Pick the simplest type that meets your requirements and instrument it with verbose logging from the start. The agent trace is your primary debugging tool, and you'll use it constantly. When reliability matters most, function-calling agents and explicit iteration limits are your best defenses against unpredictable behavior.
Ready to build something real? Check out the AI research agent build guide for a complete agent that combines search, reasoning, and structured output into a working research automation system.
Frequently Asked Questions
What is the difference between a chain and an agent in LangChain?
A chain follows a fixed sequence of steps defined at build time. An agent uses an LLM to decide which steps to take at runtime, including which tools to call and in what order. Agents are more flexible but harder to predict and debug.
Which LangChain agent type should I start with?
Start with OpenAIFunctionsAgent (or its newer equivalent) if you're using GPT-4 or GPT-4o. It's the most reliable, uses structured function calling rather than text parsing, and is less prone to formatting errors than text-based agents like ZeroShot ReAct.
How do I prevent LangChain agents from running forever in loops?
Set max_iterations on the AgentExecutor (typically 10–15). Also set handle_parsing_errors=True to recover from formatting mistakes, and early_stopping_method='generate' to produce a final answer when the iteration limit is reached.
Frequently Asked Questions
AiTechWorlds Team
✓ Verified WriterThe AiTechWorlds team is passionate about AI, technology, and education. We create high-quality, research-backed content to help you learn, grow, and succeed in the modern digital world.
Related Articles
AutoGen vs LangChain: Which for Multi-Agent Systems in 2026?
AutoGen vs LangChain for multi-agent systems in 2026 — feature comparison, same use case in both frameworks, and an honest verdict on when each wins.
AutoGPT vs LangChain Agents: Which is More Autonomous?
Compare AutoGPT's zero-shot autonomy against LangChain's ReAct agents. Discover which handles complex tasks better and when to choose each framework.
10 LangChain Retrieval Strategies for Better RAG Results
Go beyond basic similarity search with ParentDocumentRetriever, MultiQueryRetriever, EnsembleRetriever, HyDE, and 6 more LangChain retrieval strategies — with code for each.
Build a LangChain Agent with Memory and Tools (Full Example)
Build a complete LangChain conversational agent with persistent memory, multiple tools, and step-by-step trace — from setup to a production-ready implementation with code.