LangChain AI Agent: create_agent, Middleware, and LangGraph Integration Guide
TL;DR — LangChain AI agent: create_agent is the new standard. LangChain Docs: "Agent = model calling tools in a loop. create_agent: highly configurable harness with middleware system. At simplest: create_agent(model, tools, system_prompt). Middleware: each piece handles one concern, hooks into loop, composes freely." LangGraph v1 Migration: "create_react_agent deprecated → langchain.agents.create_agent. AgentState, ValidationNode, MessageGraph deprecated. Python 3.10+ required." RS Chandra Tech Blog: "AgentExecutor = legacy black box. create_agent = modern standard with LangGraph runtime, middleware, first-class persistence. create_react_agent = prebuilt, being superseded." GitHub PR #32705: "Structured output in main loop. Tool errors: invocation fails → retry with ToolMessage, execution fails → raise ToolException. handle_tool_errors configures behavior." Learn more with what is AI agent, build agent, tool loop, and multi-agent.
LangChain Docs defines the modern standard: "An agent is a model calling tools in a loop until a given task is complete. create_agent is a highly configurable harness. At its simplest, you can create one with: create_agent(model, tools, system_prompt). Building on that, you can configure the basics directly with the model=, tools=, and system_prompt= parameters. For more advanced capabilities, extend the harness with middleware."
LangGraph v1 Migration announces the key change: "LangGraph v1 is largely backwards compatible with previous versions. The main change is the deprecation of create_react_agent in favor of LangChain's new create_agent function."
LangChain Agent Architecture
ChatOpenAI, ChatAnthropic
or any LangChain chat model"] Tools["Tools
Python callables
LangChain @tool
or tool dicts"] Prompt["System Prompt
role, instructions
tool guidance, constraints"] Checkpointer["Checkpointer
InMemorySaver (local)
database-backed (prod)"] end subgraph Create["create_agent()"] Factory["create_agent(
model, tools,
system_prompt,
checkpointer)"] end subgraph Loop["Agent Loop (LangGraph Runtime)"] AgentNode["Agent Node
calls LLM with messages
applies system prompt"] ToolCheck{"Tool Calls?"} ToolNode["Tools Node
executes tools
1 per tool_call
returns ToolMessage"] StructuredOutput["Structured Output
response_format
artificial tool calling
or provider native"] FinalAnswer["Final Answer
no tool_calls
return messages"] end subgraph Middleware["Middleware System"] MW1["Human-in-the-Loop
approval gates
InterruptOnConfig"] MW2["Fault Tolerance
rate limits, timeouts
transient API errors"] MW3["Summarization
context compaction
long conversations"] MW4["Logging
LangSmith tracing
debug tool calls"] MW5["Subagents
delegate to child agents
create_deep_agent"] end subgraph State["Agent State"] Messages["Messages
conversation history
system + user + assistant + tool"] ThreadID["thread_id
scopes conversation
checkpoints, history"] Context["context
per-run data
tools and middleware read"] end Model --> Factory Tools --> Factory Prompt --> Factory Checkpointer --> Factory Factory --> AgentNode AgentNode --> ToolCheck ToolCheck -->|Yes| ToolNode ToolNode --> AgentNode ToolCheck -->|No| FinalAnswer AgentNode --> StructuredOutput StructuredOutput --> FinalAnswer MW1 --> Loop MW2 --> Loop MW3 --> Loop MW4 --> Loop MW5 --> Loop Messages --> AgentNode ThreadID --> State Context --> Middleware
Agent Factory Comparison
| Feature | AgentExecutor (Legacy) | create_agent (v1.0) | create_react_agent (LangGraph) |
|---|---|---|---|
| Architecture | Python loop (black box) | Graph-based (LangGraph) | Prebuilt StateGraph |
| Customization | Hard to modify loop | High (middleware) | High (modify graph) |
| Persistence | Session-bound (temporary) | First-class (durable) | First-class (durable) |
| Use case | Simple prototypes | Production | Rapid prototyping |
| Status | Legacy / classic | Modern standard | Deprecated (superseded) |
| Middleware | No | Yes | Limited |
| Structured output | No | Yes (in main loop) | Yes (extra LLM call) |
Implementation
from dataclasses import dataclass
from typing import Optional
from enum import Enum
class AgentFactory(Enum):
CREATE_AGENT = "create_agent"
CREATE_REACT_AGENT = "create_react_agent"
AGENT_EXECUTOR = "AgentExecutor"
@dataclass
class LangChainAgentGuide:
"""LangChain AI agent implementation guide."""
def get_create_agent(self) -> str:
"""Modern create_agent (LangChain v1.0)."""
return (
"from langchain.agents import (\n"
" create_agent\n"
")\n"
"from langchain_openai import ChatOpenAI\n"
"from langchain_core.tools import tool\n"
"from langgraph.checkpoint.memory \\\n"
" import InMemorySaver\n"
"\n"
"# 1. Define tools\n"
"@tool\n"
"def search(query: str) -> str:\n"
" '''Search the web.'''\n"
" return f'Results: {query}'\n"
"\n"
"@tool\n"
"def calculate(expression: str) -> str:\n"
" '''Do math.'''\n"
" return str(eval(expression))\n"
"\n"
"# 2. Create agent\n"
"model = ChatOpenAI(model='gpt-4o')\n"
"tools = [search, calculate]\n"
"\n"
"agent = create_agent(\n"
" model=model,\n"
" tools=tools,\n"
" system_prompt=(\n"
" 'You are a helpful'\n"
" ' assistant. Use tools'\n"
" ' when needed.'\n"
" ),\n"
" checkpointer=InMemorySaver()\n"
")\n"
"\n"
"# 3. Invoke with thread_id\n"
"# for persistence\n"
"result = agent.invoke(\n"
" {'messages': [\n"
" {'role': 'user',\n"
" 'content': 'What is'\n"
" ' 2+2?'}\n"
" ]},\n"
" config={\n"
" 'configurable': {\n"
" 'thread_id': 'session-1'\n"
" }\n"
" }\n"
")\n"
"\n"
"print(result['messages'][-1]\n"
" .content)\n"
"\n"
"# 4. Continue conversation\n"
"# (same thread_id)\n"
"result2 = agent.invoke(\n"
" {'messages': [\n"
" {'role': 'user',\n"
" 'content': 'Now'\n"
" ' multiply that by 3'}\n"
" ]},\n"
" config={\n"
" 'configurable': {\n"
" 'thread_id': 'session-1'\n"
" }\n"
" }\n"
")\n"
"print(result2['messages'][-1]\n"
" .content)"
)
def get_middleware_example(self) -> str:
"""Middleware system for customization."""
return (
"from langchain.agents import (\n"
" create_agent\n"
")\n"
"from langchain.agents.middleware \\\n"
" import (\n"
" AgentMiddleware\n"
" )\n"
"from langchain.agents.middleware \\\n"
" .human_in_the_loop import (\n"
" InterruptOnConfig,\n"
" HITLRequest\n"
" )\n"
"\n"
"# Custom middleware\n"
"class LoggingMiddleware(\n"
" AgentMiddleware\n"
"):\n"
" '''Log each agent step.'''\n"
" \n"
" def before_model(\n"
" self, state, runtime\n"
" ):\n"
" print(f'Before model:'\n"
" f' {len(state[\"messages\"])}'\n"
" f' messages')\n"
" \n"
" def after_tool(\n"
" self, state, runtime\n"
" ):\n"
" last = state['messages'][-1]\n"
" print(f'Tool result:'\n"
" f' {last.content[:80]}')\n"
"\n"
"# Human-in-the-loop middleware\n"
"class ApprovalMiddleware(\n"
" AgentMiddleware\n"
"):\n"
" '''Require approval for\n"
" sensitive tools.'''\n"
" \n"
" def before_tool(\n"
" self, state, runtime\n"
" ):\n"
" last_msg = state[\n"
" 'messages'][-1]\n"
" if last_msg.tool_calls:\n"
" for call in \\\n"
" last_msg.tool_calls:\n"
" if call['name'] in \\\n"
" ['delete_file',\n"
" 'send_email']:\n"
" # Request human\n"
" # approval\n"
" return HITLRequest(\n"
" tool_name=\n"
" call['name'],\n"
" args=call['args']\n"
" )\n"
"\n"
"# Create agent with\n"
"# middleware\n"
"agent = create_agent(\n"
" model=model,\n"
" tools=tools,\n"
" system_prompt='...',\n"
" middleware=[\n"
" LoggingMiddleware(),\n"
" ApprovalMiddleware()\n"
" ]\n"
")"
)
def get_structured_output(self) -> str:
"""Structured output with create_agent."""
return (
"from langchain.agents import (\n"
" create_agent\n"
")\n"
"from pydantic import BaseModel\n"
"\n"
"class ResearchResult(BaseModel):\n"
" topic: str\n"
" summary: str\n"
" sources: list[str]\n"
" confidence: float\n"
"\n"
"# Structured output in\n"
"# main loop — no extra\n"
"# LLM call (unlike legacy\n"
"# create_react_agent)\n"
"agent = create_agent(\n"
" model=model,\n"
" tools=tools,\n"
" system_prompt='Research'\n"
" ' the topic thoroughly.',\n"
" response_format=ResearchResult\n"
")\n"
"\n"
"result = agent.invoke(\n"
" {'messages': [\n"
" {'role': 'user',\n"
" 'content': 'Research AI'\n"
" ' agents'}\n"
" ]},\n"
" config={'configurable': {\n"
" 'thread_id': 'r1'\n"
" }}\n"
")\n"
"# Result includes\n"
"# structured ResearchResult\n"
"# alongside messages"
)
def get_deep_agent(self) -> str:
"""Pre-assembled deep agent stack."""
return (
"from langchain.agents import (\n"
" create_deep_agent\n"
")\n"
"\n"
"# Pre-assembled middleware\n"
"# stack for long-running\n"
"# coding and research tasks\n"
"# Includes: filesystem,\n"
"# summarization, subagents,\n"
"# prompt caching\n"
"agent = create_deep_agent(\n"
" model=model,\n"
" tools=tools,\n"
" system_prompt=(\n"
" 'You are a deep'\n"
" ' research agent.'\n"
" )\n"
")\n"
"\n"
"# Built-in features:\n"
"# - Filesystem access\n"
"# - Auto summarization\n"
"# - Subagent delegation\n"
"# - Prompt caching\n"
"# - Fault tolerance"
)
def get_migration(self) -> dict:
"""Migration from legacy to create_agent."""
return {
"create_react_agent": (
"Deprecated. Use "
"langchain.agents.create_agent "
"instead. Import: from "
"langchain.agents import "
"create_agent."
),
"AgentState": (
"Deprecated. Use "
"langchain.agents.AgentState. "
"No more pydantic state — "
"AgentStatePydantic merged."
),
"ValidationNode": (
"Deprecated. Tools "
"automatically validate "
"input with create_agent."
),
"MessageGraph": (
"Deprecated. Use StateGraph "
"with messages key, like "
"create_agent provides."
),
"HumanInterruptConfig": (
"Replaced by "
"langchain.agents.middleware"
".human_in_the_loop"
".InterruptOnConfig"
),
"python_version": (
"Python 3.9 support dropped. "
"All LangChain packages "
"require Python 3.10+. "
"Python 3.9 EOL October 2025."
),
}
def get_tool_error_handling(self) -> dict:
"""Tool error handling."""
return {
"invocation_error": (
"When tool invocation fails "
"(bad args), agent returns "
"artificial ToolMessage asking "
"model to correct and retry."
),
"execution_error": (
"When tool execution fails, "
"agent raises ToolException by "
"default instead of retrying. "
"Prevents unwanted loops."
),
"handle_tool_errors": (
"Configure retry behavior via "
"handle_tool_errors arg to "
"ToolNode. Options: retry, "
"raise, or custom handler."
),
"structured_output_errors": (
"Two common problems: model "
"calls wrong tool, or output "
"does not match schema. "
"handle_errors arg to "
"ToolStrategy controls behavior."
),
}
def get_production_tips(self) -> dict:
"""Production tips."""
return {
"langsmith": (
"Set up LangSmith tracing "
"to trace each step, debug "
"tool calls, and evaluate "
"agent outputs. Monitoring "
"Engine detects issues and "
"proposes fixes."
),
"checkpointer": (
"Use database-backed "
"checkpointer for production. "
"InMemorySaver for local dev. "
"LangSmith provisions "
"automatically when deployed."
),
"thread_id": (
"Scope conversations with "
"thread_id. Each thread has "
"its own message history "
"and checkpoints."
),
"context": (
"Pass per-run data via "
"context. Tools and middleware "
"read context at invocation "
"time. Separate from thread_id."
),
"fault_tolerance": (
"Fault tolerance middleware "
"handles rate limits, model "
"timeouts, transient API "
"errors at infrastructure "
"level — no try/catch in "
"business logic."
),
}
LangChain AI Agent Checklist
- [ ] create_agent is the new standard factory for building agents (LangChain v1.0)
- [ ] Import: from langchain.agents import create_agent
- [ ] Install: pip install -U langgraph langchain-core (Python 3.10+ required)
- [ ] Agent = model calling tools in a loop until task is complete
- [ ] create_agent(model, tools, system_prompt) — simplest form
- [ ] Configure with model=, tools=, system_prompt= parameters
- [ ] Extend with middleware for advanced capabilities
- [ ] create_react_agent deprecated → use langchain.agents.create_agent
- [ ] AgentExecutor legacy → black-box Python loop, session-bound, less flexible
- [ ] create_react_agent (LangGraph) → prebuilt StateGraph, being superseded
- [ ] AgentState deprecated → use langchain.agents.AgentState (no more pydantic state)
- [ ] ValidationNode deprecated → tools automatically validate with create_agent
- [ ] MessageGraph deprecated → use StateGraph with messages key
- [ ] HumanInterruptConfig → InterruptOnConfig from human_in_the_loop middleware
- [ ] Python 3.9 support dropped — all packages require Python 3.10+
- [ ] Middleware: primitive for customization — each piece handles one concern
- [ ] Middleware: hooks into agent loop before model call or after tool execution
- [ ] Middleware: composes freely with any other middleware
- [ ] Middleware: human-in-the-loop (approval gates), fault tolerance, summarization, logging
- [ ] create_deep_agent: pre-assembles middleware stack for coding/research (filesystem, summarization, subagents, prompt caching)
- [ ] Tools: pass any Python callable, LangChain @tool, or tool dict
- [ ] Tools: @tool decorator with type hints and docstring for automatic schema
- [ ] Tools: ToolNode executes 1 tool per tool_call, returns ToolMessage objects
- [ ] Tool errors: invocation fails → ToolMessage asking model to correct and retry
- [ ] Tool errors: execution fails → ToolException raised by default (prevents loops)
- [ ] Tool errors: handle_tool_errors arg to ToolNode configures retry behavior
- [ ] Structured output: response_format with artificial tool calling or provider native
- [ ] Structured output: implemented in main loop (no extra LLM call, unlike legacy)
- [ ] Structured output: handle_errors arg to ToolStrategy controls error behavior
- [ ] Persistence: checkpointer with thread_id for conversation history
- [ ] Persistence: InMemorySaver() for local, database-backed for production
- [ ] Persistence: LangSmith provisions checkpointer automatically when deployed
- [ ] thread_id: scopes conversation (message history, checkpoints)
- [ ] context: per-run data for tools and middleware (separate from thread_id)
- [ ] Agent loop: agent node calls LLM → if tool_calls, tools node executes → repeat until no tool_calls
- [ ] Agent loop: returns full messages list as dict with 'messages' key
- [ ] LangSmith: trace each step, debug tool calls, evaluate outputs
- [ ] LangSmith: Monitoring Engine detects issues and proposes fixes
- [ ] Fault tolerance middleware: handles rate limits, timeouts, transient API errors at infrastructure level
- [ ] Dynamic model selection: callable with (state, runtime) signature, return ChatModel with bind_tools
- [ ] create_react_agent no longer supports pre-bound models with tools
- [ ] Read what is AI agent for architecture
- [ ] Read build agent for no-framework approach
- [ ] Read tool loop for ReAct pattern
- [ ] Read multi-agent for coordination
- [ ] Test: create_agent invokes tools correctly
- [ ] Test: thread_id persists conversation across invocations
- [ ] Test: middleware hooks fire at correct points (before_model, after_tool, before_tool)
- [ ] Test: structured output matches schema
- [ ] Test: tool invocation error returns ToolMessage for retry
- [ ] Test: tool execution error raises ToolException by default
- [ ] Test: handle_tool_errors configures retry behavior
- [ ] Test: create_deep_agent includes filesystem, summarization, subagents
- [ ] Document model, tools, system_prompt, middleware, checkpointer, thread_id strategy
FAQ
How do you create an AI agent with LangChain?
Use create_agent from langchain.agents — the new standard factory for building agents on LangGraph runtime. LangChain Docs: "An agent is a model calling tools in a loop until a given task is complete. create_agent is a highly configurable harness. At its simplest: create_agent(model, tools, system_prompt). Building on that, configure with model=, tools=, and system_prompt= parameters. For advanced capabilities, extend with middleware." LangGraph v1 Migration: "LangGraph v1 deprecates create_react_agent in favor of LangChain new create_agent function. create_react_agent moved to langchain.agents.create_agent." RS Chandra Tech Blog: "create_agent is the new standard factory for building agents. Designed to work seamlessly with LangGraph runtime. Introduces powerful middleware system for customization. Defaults to tool-calling architectures." Steps: (1) pip install langchain langgraph. (2) from langchain.agents import create_agent. (3) Define tools as Python callables or LangChain tools. (4) agent = create_agent(model, tools, system_prompt='You are a helpful assistant.'). (5) result = agent.invoke({messages: [{role: 'user', content: 'Hello'}]}, config={configurable: {thread_id: '1'}}).
What is the difference between create_agent, create_react_agent, and AgentExecutor?
AgentExecutor is legacy, create_react_agent is deprecated, create_agent is the modern standard. RS Chandra Tech Blog: "AgentExecutor: legacy black-box class that wraps agent and tools. Handles loop internally. Status: legacy, less flexible. create_react_agent (Classic): legacy factory requiring Thought/Action/Observation prompt template. create_agent (LangChain 1.0): new standard factory, works with LangGraph runtime, middleware system for customization, defaults to tool-calling architectures. create_react_agent (LangGraph): high-level helper in langgraph.prebuilt, being superseded by create_agent." LangGraph v1 Migration: "create_react_agent deprecated in favor of langchain.agents.create_agent. AgentState, AgentStatePydantic deprecated. ValidationNode deprecated — tools automatically validate with create_agent. MessageGraph deprecated — use StateGraph with messages key." Comparison: AgentExecutor = Python loop (black box, session-bound, legacy). create_agent = graph-based (LangGraph runtime, middleware, first-class persistence, modern standard). create_react_agent = prebuilt StateGraph (rapid prototyping, being superseded).
What is the LangChain middleware system?
Middleware is the primitive for customizing agent behavior — each piece handles one concern and composes freely. LangChain Docs: "create_agent is highly extensible. Middleware is the primitive for customization: each piece handles one concern, hooks into the agent loop at the right moment, and composes freely with any other. Take exactly what your use case needs and skip the rest. create_deep_agent pre-assembles this stack for long-running coding and research tasks (filesystem, summarization, subagents, prompt caching included by default)." LangGraph v1 Migration: "HumanInterruptConfig replaced by langchain.agents.middleware.human_in_the_loop.InterruptOnConfig. ActionRequest replaced by InterruptOnConfig. HumanInterrupt replaced by HITLRequest." Middleware: (1) Hooks into agent loop before model call or after tool execution. (2) Each middleware handles one concern (logging, approval, summarization, fault tolerance). (3) Composes freely with any other middleware. (4) Fault tolerance middleware handles rate limits, timeouts, transient API errors at infrastructure level. (5) Human-in-the-loop middleware for approval steps. (6) create_deep_agent pre-assembles middleware stack for coding/research.
How do you add persistence and memory to a LangChain agent?
Use a checkpointer with thread_id for conversation persistence. LangChain Docs: "Persisting conversation history with thread_id requires the agent to be configured with a checkpointer. When deployed on LangSmith, a checkpointer is provisioned automatically. Locally, pass one explicitly: create_agent(..., checkpointer=InMemorySaver()). thread_id scopes the conversation (message history, checkpoints), while context carries per-run data your tools and middleware read at invocation time. Both are commonly passed together." Persistence: (1) Checkpointer: InMemorySaver() for local dev, database-backed for production. (2) thread_id: scopes conversation history and checkpoints. (3) context: per-run data for tools and middleware. (4) LangSmith: provisions checkpointer automatically when deployed. (5) State includes messages sequence — agent resumes from last checkpoint. (6) Durable persistence: first-class in create_agent, unlike session-bound AgentExecutor.
How do you add tools to a LangChain agent?
Pass any Python callable, LangChain tool, or tool dict to create_agent. LangChain Docs: "To provide the agent with tools, pass any Python callable, LangChain tool, or tool dict. See Tools for tool definition, context access, and dynamic tool selection." LangGraph Reference: "The agent node calls the language model with the messages list. If the resulting AIMessage contains tool_calls, the graph calls the tools node. The tools node executes the tools (1 tool per tool_call) and adds responses as ToolMessage objects. The agent node then calls the language model again. Process repeats until no more tool_calls are present." GitHub PR: "By default, when tool invocation fails, the react agent returns an artificial ToolMessage to the model asking it to correct its mistakes. When tool execution fails, the react agent raises ToolException by default. Developers can configure behavior via handle_tool_errors arg to ToolNode." Tools: (1) Python callable: any function with type hints and docstring. (2) LangChain tool: @tool decorator or BaseTool subclass. (3) Tool dict: JSON schema format. (4) Tool errors: handle_tool_errors arg controls retry behavior. (5) Structured output: response_format with artificial tool calling or provider native support.
Want a self-hosted AI company brain that does all of this out of the box?
Book a demo →