LangChain 源码学习路线:第 5 篇 create_agent 与 ReAct Agent 运行机制源码解剖
核心问题: create_agent 如何把模型、工具、中间件和结构化输出策略装配成可持续运行的 ReAct Agent Runtime?
源码主线:
create_agent → CompiledStateGraph → model node → ToolNode → route → END前置文章: 第 1 篇 Runnable、第 2 篇 Prompt / Message / ChatModel、第 3 篇 OutputParser、第 4 篇 Tool Calling
依赖基线:
langchain==1.3.11、langchain-core==1.4.8、langgraph==1.2.7源码基线: LangChain commit
83e824922ac9bf3bf882347fecabd09614bccc37,LangGraph commit5931a5f0b313feff24e2516a586c55601b868ac1阅读边界: 本篇覆盖标准 ReAct Agent Harness,不展开自定义复杂业务图、持久化实现和分布式部署。
0. 本篇在源码学习主线中的位置
前四篇分别解决了统一执行协议、消息与模型对象流转、结构化输出,以及工具调用协议。本篇把这些能力收束成一个可以持续运行的 Agent Runtime:
Runnable 执行协议 + Prompt / Message / ChatModel + Structured Output + Tool Calling ↓create_agent ↓CompiledStateGraph ↓model → tools → model阅读时不要把 create_agent 理解成“帮你写好一个 while 循环”。更准确的心智模型是:
create_agent是一个图工厂:它把模型、工具、中间件、状态 Schema、结构化输出策略和运行时能力装配成可执行的 LangGraph 状态图。
全文按四条源码链路展开:
构建期:参数归一化 → State schema → 节点与边 → compile模型链:before_model → ModelRequest → wrap_model_call → ChatModel → after_model工具链:ToolNode 解析 → 并行调度 → 工具执行 → ToolMessage / Command控制链:条件路由 → structured_response → interrupt → recursion_limit1. 本篇问题、学习目标与能力边界
本篇目标是理解:
LangChain Agent 不是一个神秘的“智能体对象”,而是由
create_agent构造出来的 LangGraph 编译状态图。它通过模型节点、工具节点、条件边、回边和中间件,把 ReAct 类的“模型推理—工具调用—观察反馈—再次推理”循环工程化。
你需要掌握:
create_agentCompiledStateGraphAgentStatemodel nodetools nodeToolNodemiddlewarebefore_agentbefore_modelafter_modelafter_agentwrap_model_callwrap_tool_calltool loopstop conditionrecursion_limititeration limitstructured responseresponse_formatProviderStrategyToolStrategy
完成本篇后,你应该能回答:
create_agent返回的到底是什么对象?- 它内部是否构建了 LangGraph graph?
model node和tools node如何连接?AIMessage.tool_calls如何被识别?- 工具执行结果如何重新进入
messages? middleware在模型调用前后能做什么?wrap_model_call和wrap_tool_call为什么比普通 callback 更强?stop condition在哪里判断?iteration limit与 LangGraph 的recursion_limit是什么关系?structured_response如何进入最终 Agent State?- 为什么现代 LangChain Agent 不一定显式暴露
Thought? - 什么时候应该使用
create_agent,什么时候应该自己写 LangGraph?
2. 核心概念与最小心智模型
经典 ReAct 范式
经典 ReAct 可以抽象成:
Thought ↓Action ↓Observation ↓Thought ↓Action ↓Observation ↓Final Answer其中:
| ReAct 概念 | 含义 |
|---|---|
| Thought | 模型对下一步的推理 |
| Action | 模型决定调用工具 |
| Action Input | 模型生成工具参数 |
| Observation | 工具返回结果 |
| Final Answer | 模型最终回答 |
LangChain / LangGraph 现代映射
现代 LangChain 不一定强制模型输出显式的 Thought: 文本,而是使用结构化 message trace 表达 ReAct 行为:
ReAct→ model call→ AIMessage(tool_calls=[...])→ tools node→ ToolMessage→ model call→ final AIMessage对应关系如下:
| ReAct 经典概念 | LangChain / LangGraph 实现 |
|---|---|
| Thought | 不一定显式暴露;可通过 trace、reasoning block、metadata 观察 |
| Action | AIMessage.tool_calls[i]["name"] |
| Action Input | AIMessage.tool_calls[i]["args"] |
| Observation | ToolMessage.content |
| Action ID | ToolCall.id |
| Observation ID | ToolMessage.tool_call_id |
| Loop | model node → tools node → model node |
| Stop Condition | 最后一条 AIMessage 不再包含 tool_calls |
| Final Answer | AIMessage(content=...) |
| Structured Final | state["structured_response"] |
为什么生产中更推荐结构化 trace
旧式 ReAct 常要求模型输出:
Thought: 我需要搜索东京热门景点。Action: search_city_infoAction Input: 东京热门景点Observation: ...Final Answer: ...现代 Agent 工程更推荐观察:
AIMessageAIMessage.tool_callsToolMessagefinal AIMessageLangSmith / LangGraph trace原因:
tool_calls是结构化字段,比自然语言Action:更稳定;ToolMessage.tool_call_id可以精确关联工具请求与工具结果;- 多工具并行调用时,结构化 ID 比文本解析可靠;
- 显式 Thought 可能增加 token 成本,也可能带来安全与隐私风险;
- 生产排查更关心“调用了什么工具、传了什么参数、返回了什么结果、为什么停止”。
3. 完整执行链路
最小代码并入执行链,用于观察 create_agent 返回对象和 ReAct 循环。
最小代码
from langchain.agents import create_agentfrom langchain.tools import tool
@tooldef search_city_info(query: str) -> str: """Search city information for travel planning.""" return f"Search result for {query}"
agent = create_agent( model="openai:gpt-4.1-mini", tools=[search_city_info], system_prompt="你是一个旅行规划助手。")
result = agent.invoke({ "messages": [ { "role": "user", "content": "帮我规划东京 5 天行程,需要查一下热门景点" } ]})
print(result)你应该观察什么
不要只看最终回答,要打印完整 messages:
for i, message in enumerate(result["messages"]): print("=" * 20, i) print(type(message)) print(message) print("tool_calls:", getattr(message, "tool_calls", None)) print("tool_call_id:", getattr(message, "tool_call_id", None))你应该重点观察:
HumanMessage ↓AIMessage(tool_calls=[...]) ↓ToolMessage(tool_call_id=...) ↓AIMessage(content=最终回答)create_agent 的输出不是字符串
agent.invoke(...) 的返回值是最终 Agent State,常见结构是:
{ "messages": [ HumanMessage(...), AIMessage(tool_calls=[...]), ToolMessage(...), AIMessage(content="...") ]}如果配置了结构化输出:
{ "messages": [...], "structured_response": TravelPlan(...)}这说明:
create_agent 返回的是可执行状态图,不是 prompt | model | parser 那种简单线性链。执行流程
高层执行流程
用户 messages ↓model node ↓AIMessage 是否包含 tool_calls? ↓如果有:tools node 执行工具 ↓ToolMessage 加入 messages ↓回到 model node ↓直到模型输出 final answer 或达到停止条件细粒度执行流程
agent.invoke(input_state) ↓CompiledStateGraph.invoke ↓初始化 AgentState ↓进入 before_agent middleware ↓进入 before_model middleware ↓组装 model request ↓注入 system_prompt ↓绑定 tools / response_format ↓wrap_model_call middleware 包裹模型调用 ↓ChatModel.invoke(messages) ↓得到 AIMessage ↓after_model middleware ↓把 AIMessage 追加到 state["messages"] ↓route_after_model 判断是否有 tool_calls ↓有 tool_calls:进入 tools node ↓ToolNode 读取最后一条 AIMessage.tool_calls ↓根据 name 找到 BaseTool ↓构造 ToolRuntime / 注入 state、store、config、tool_call_id ↓wrap_tool_call middleware 包裹工具调用 ↓BaseTool.invoke(full_tool_call) ↓返回 ToolMessage ↓ToolMessage 追加到 state["messages"] ↓tools node 回到 model node ↓模型根据 ToolMessage 继续推理 ↓最后一条 AIMessage 没有 tool_calls ↓END ↓after_agent middleware ↓返回最终 Agent State4. 源码地图、关键文件与阅读顺序
核心目录
langchain/agents/langchain/agents/factory.pylangchain/agents/middleware/langchain/agents/structured_output.py
langgraph/prebuilt/langgraph/prebuilt/tool_node.py
langchain_core/messages/langchain_core/tools/langchain_core/language_models/重点文件
libs/langchain/langchain/agents/factory.pylibs/langchain/langchain/agents/middleware/libs/langchain/langchain/agents/structured_output.py
libs/prebuilt/langgraph/prebuilt/tool_node.py
libs/core/langchain_core/messages/ai.pylibs/core/langchain_core/messages/tool.pylibs/core/langchain_core/tools/base.pylibs/core/langchain_core/language_models/chat_models.py重点类与函数
create_agentAgentStateAgentMiddlewareModelRequestModelResponseToolCallRequest
before_agentbefore_modelafter_modelafter_agentwrap_model_callwrap_tool_call
ToolNodeToolRuntimeToolMessageAIMessageToolCall
ProviderStrategyToolStrategyAutoStrategy
StateGraphCompiledStateGraph推荐阅读顺序
1. factory.py::create_agent2. factory.py 中 model node / route / graph 构造相关函数3. structured_output.py 中 response_format 相关策略4. middleware/ 中 hook 与 wrapper 类型定义5. tool_node.py::ToolNode.__init__6. tool_node.py::ToolNode._parse_input7. tool_node.py::ToolNode._func / _afunc8. tool_node.py::ToolNode._run_one / _arun_one9. tool_node.py::ToolNode._execute_tool_sync / _execute_tool_async10. tool_node.py::ToolNode._combine_tool_outputs11. langchain_core/messages/ai.py::AIMessage12. langchain_core/messages/tool.py::ToolMessage13. langchain_core/tools/base.py::BaseTool.invoke / run5. 对象模型、继承关系与协议边界
核心对象关系
create_agent ↓StateGraph ├─ model node ├─ tools node / ToolNode ├─ middleware nodes and wrappers └─ conditional edges ↓CompiledStateGraph协议边界
| 对象 | 负责什么 | 不负责什么 |
|---|---|---|
create_agent | 装配标准 Agent 图 | 承载单次运行状态 |
| model node | 构造模型请求并保存 AIMessage | 执行本地工具 |
ToolNode | 执行 ToolCall 并返回状态更新 | 决定最终回答 |
| middleware | 包裹或拦截执行阶段 | 替代图拓扑 |
6. 源码阅读策略与证据标准
按执行边界追踪源码
前面的执行流程解决“Agent 如何运行”,接下来要解决“源码为什么这样组织”。不建议从 factory.py 第一行开始顺序阅读,那样很容易陷入参数分支和兼容逻辑。更高效的方式是围绕四个边界追踪:
| 阅读边界 | 起点 | 终点 | 需要回答的问题 |
|---|---|---|---|
| 工厂边界 | create_agent(...) | graph.compile(...) | 输入能力如何被装配成图 |
| 模型边界 | state["messages"] | AIMessage | 模型请求如何构造、包装与路由 |
| 工具边界 | AIMessage.tool_calls | ToolMessage / Command | 工具如何查找、注入、并行执行与归一化 |
| 控制边界 | route_after_model | END / interrupt / error | 循环如何继续、暂停或终止 |
每一节都使用同一套阅读模板:
职责 → 输入/输出 → 细粒度伪代码 → 分支解释 → 工程含义伪代码只保留影响理解的控制流,不追求逐字复刻源码。真正阅读本地版本时,应同时对照函数签名、类型定义和调用方,避免把内部实现细节误当成稳定 API。
阅读顺序
公开入口 ↓输入输出类型 ↓构建期对象 ↓运行时主链 ↓分支、异常与停止条件 ↓扩展接口证据标准
| 标记 | 使用条件 |
|---|---|
| 源码事实 | 当前正式版源码可以直接证明 |
| 官方契约 | 官方文档或 API Reference 明确承诺 |
| 简化伪代码 | 压缩真实控制流,且明确不是逐字源码 |
| 作者推断 | 根据调用关系得出,必须标注为推断 |
| 工程建议 | 说明适用条件,不写成框架保证 |
7. 构建期源码解剖
构建期的职责不是执行 Agent,而是把用户提供的能力转换成一张结构明确、可以被 LangGraph 驱动的状态图:
model / tools / middleware / response_format ↓参数与能力归一化 ↓合成 State schema ↓构造 model node 与 tools node ↓注册条件边、回边与 middleware 节点 ↓compile ↓CompiledStateGraphcreate_agent 顶层入口伪代码
伪代码
def create_agent( model, tools=None, *, system_prompt=None, middleware=(), response_format=None, state_schema=None, context_schema=None, checkpointer=None, store=None, interrupt_before=None, interrupt_after=None, debug=False, name=None, cache=None,): # 1. 标准化模型 chat_model = _init_chat_model(model)
# 2. 标准化工具 tool_objects = _normalize_tools(tools)
# 3. 标准化 response_format response_strategy = _normalize_response_format( response_format, chat_model, )
# 4. 标准化 middleware middleware_list = _normalize_middleware(middleware)
# 5. 合成最终 State schema final_state_schema = _resolve_state_schema( state_schema, middleware_list, response_strategy, )
# 6. 创建 StateGraph builder graph = StateGraph( final_state_schema, context_schema=context_schema, )
# 7. 添加 middleware node _add_middleware_nodes(graph, middleware_list)
# 8. 添加 model node graph.add_node("model", _make_model_node(...))
# 9. 添加 tools node if tool_objects: graph.add_node("tools", _make_tools_node(...))
# 10. 添加条件边和回边 _add_agent_edges(graph, has_tools=bool(tool_objects), ...)
# 11. 编译为 CompiledStateGraph return graph.compile( checkpointer=checkpointer, store=store, interrupt_before=interrupt_before, interrupt_after=interrupt_after, debug=debug, name=name, cache=cache, )逐行解释
第 1 步:标准化模型
model 可以是字符串,也可以是已经实例化的 BaseChatModel。
model="openai:gpt-4.1-mini"会被转换成:
ChatOpenAI(...)或其他供应商对应的 BaseChatModel 子类。
第 2 步:标准化工具
tools 中可能是:
BaseTool普通 Python functiondict schema它们会被统一成工具对象或模型可绑定的工具 schema。
第 3 步:标准化结构化输出
response_format 可能是:
NonePydantic modelTypedDictJSON SchemaProviderStrategyToolStrategy源码会把它归一成内部策略对象。
第 4 步:标准化 middleware
middleware 可能来自:
装饰器函数AgentMiddleware 子类实例预置 middleware内部会统一成 middleware 实例,并按 hook 类型注册。
第 5 步:合成 State schema
最终 Agent State 至少需要:
messages如果有结构化输出,还需要:
structured_response如果 middleware 声明额外 state 字段,也要合并进来。
第 6—11 步:构图与编译
这部分说明 create_agent 的本质:
不是创建一个 while-loop 对象,而是构建 StateGraph 并 compile。模型初始化源码拆解
伪代码
def _init_chat_model(model): if isinstance(model, str): return init_chat_model(model)
if isinstance(model, BaseChatModel): return model
raise TypeError( "model must be a model identifier or BaseChatModel" )解释
create_agent 支持:
create_agent(model="openai:gpt-4.1-mini", tools=[...])也支持:
from langchain.chat_models import init_chat_model
model = init_chat_model("openai:gpt-4.1-mini")agent = create_agent(model=model, tools=[...])源码这样设计是为了同时支持:
快速原型:传字符串生产配置:传已经配置好的 model 实例生产中更推荐传显式 model 实例,因为你可以设置:
timeoutmax_retriestemperaturemodel_kwargsbase_urlapi_keyprofilecallbackstagsmetadata工具归一化源码拆解
伪代码
def _normalize_tools(tools): normalized = []
for tool in tools or []: if isinstance(tool, BaseTool): normalized.append(tool) continue
if callable(tool): normalized.append(create_tool(tool)) continue
if isinstance(tool, dict): normalized.append(tool) continue
raise TypeError("Unsupported tool type")
return normalized解释
这一步连接第 4 篇 Tool Calling:
普通函数 ↓@tool / create_tool ↓StructuredTool ↓BaseTool例如:
def search_city_info(query: str) -> str: """Search city information.""" return "..."在工具归一化阶段会变成:
StructuredTool( name="search_city_info", description="Search city information.", args_schema=...)最终进入:
model.bind_tools([...])ToolNode([...])注意:
工具归一化不代表工具会被执行。此时只是将工具登记到 Agent 能力表。
response_format 归一化源码拆解
伪代码
def _normalize_response_format(response_format, model): if response_format is None: return None
if isinstance(response_format, ProviderStrategy): return response_format
if isinstance(response_format, ToolStrategy): return response_format
if is_schema_type(response_format): if model_supports_native_structured_output(model): return ProviderStrategy(schema=response_format)
return ToolStrategy(schema=response_format)
raise TypeError("Unsupported response_format")解释
response_format=TravelSummary 看起来只是传了一个 Pydantic 类,但源码会做策略选择:
模型支持原生结构化输出 → ProviderStrategy
模型不支持原生结构化输出 → ToolStrategy这与第 3 篇结构化输出一致。
ProviderStrategy 心智模型
TravelSummary schema ↓转换为 provider native schema ↓模型 API 原生约束输出 ↓框架校验 ↓state["structured_response"]ToolStrategy 心智模型
TravelSummary schema ↓转换为“虚拟工具” ↓模型通过 tool_call 输出结构化参数 ↓框架解析 tool call args ↓Pydantic 校验 ↓state["structured_response"]注意
如果同时有业务工具和结构化输出:
create_agent( model=model, tools=[search_city_info], response_format=TravelSummary,)模型需要同时支持:
工具调用结构化输出如果供应商能力不一致,源码需要选择 fallback 策略。
middleware 归一化源码拆解
伪代码
def _normalize_middleware(middleware): normalized = [] seen_names = set()
for m in middleware: instance = _coerce_to_middleware(m)
if instance.name in seen_names: raise ValueError("Duplicate middleware name")
seen_names.add(instance.name) normalized.append(instance)
return normalized解释
middleware 的作用不是简单 callback,而是 Agent loop 的扩展点。
一个 middleware 可能声明:
before_agentbefore_modelafter_modelafter_agentwrap_model_callwrap_tool_callstate_schematools源码会将它拆到不同位置:
node-style hook → 编译成图节点或节点前后逻辑
wrap-style hook → 包裹 model call 或 tool call
middleware.tools → 合并进 Agent 工具列表
middleware.state_schema → 合并进 Agent StateState schema 合成源码拆解
最小 AgentState
class AgentState(TypedDict): messages: Annotated[list[AnyMessage], add_messages]加 structured_response 后
class AgentStateWithStructuredResponse(TypedDict): messages: Annotated[list[AnyMessage], add_messages] structured_response: TravelSummary | None加 middleware state 后
class CustomAgentState(TypedDict): messages: Annotated[list[AnyMessage], add_messages] structured_response: TravelSummary | None tool_call_count: int risk_level: str伪代码
def _resolve_state_schema( user_state_schema, middleware_list, response_strategy,): if user_state_schema is not None: base_schema = user_state_schema else: base_schema = AgentState
if response_strategy is not None: base_schema = _add_structured_response_field( base_schema )
for middleware in middleware_list: if middleware.state_schema is not None: base_schema = _merge_state_schema( base_schema, middleware.state_schema, )
return base_schema解释
messages 字段最关键,因为它承载整个 ReAct trace:
HumanMessageAIMessage(tool_calls)ToolMessageAIMessage(final)messages 不能简单覆盖,必须用 reducer 追加。
构建期总结、伪代码与源码证据
以下为保留关键控制流的简化伪代码,不是源码逐字复制:
def create_agent(model, tools, middleware, response_format): state_schema = compose_state_schema(middleware, response_format) graph = StateGraph(state_schema) graph.add_node("model", build_model_node(model, middleware)) graph.add_node("tools", ToolNode(tools)) graph.add_conditional_edges("model", route_after_model) graph.add_edge("tools", "model") return graph.compile()源码证据:
8. 运行时主链源码解剖
本章合并 model node 与 ToolNode,形成完整的一次循环主链。
Model 调用链:从 State 到 AIMessage
model node 是 Agent Runtime 的决策中心。它不只是调用一次 ChatModel,而是负责把当前 State 转换为模型请求,让 middleware 有机会介入,再把模型输出转换为可路由的状态更新。
阅读这一链路时,始终跟踪三个对象:
Agent State ↓ModelRequest ↓AIMessage / structured responsemodel node 构造源码拆解
model node 的职责
读取 state["messages"] ↓注入 system_prompt ↓执行 before_model middleware ↓构造 ModelRequest ↓绑定 tools / response_format ↓执行 wrap_model_call 链 ↓调用 ChatModel ↓执行 after_model middleware ↓返回 {"messages": [AIMessage]}伪代码:model node 外壳
def _make_model_node( model, tools, system_prompt, middleware_list, response_strategy,): def model_node(state, runtime, config): # 1. 执行 before_model hooks state = _run_before_model_hooks( state, runtime, middleware_list, )
# 2. 构造模型输入 messages messages = _prepare_messages( state=state, system_prompt=system_prompt, )
# 3. 构造 ModelRequest request = ModelRequest( model=model, messages=messages, tools=tools, response_format=response_strategy, state=state, runtime=runtime, config=config, )
# 4. 调用模型,经过 wrap_model_call 链 response = _call_model_with_middleware( request, middleware_list, )
# 5. 将 response.message 转成 state update update = { "messages": [response.message] }
# 6. 执行 after_model hooks update = _run_after_model_hooks( state=state, update=update, runtime=runtime, middleware_list=middleware_list, )
return update
return model_nodebefore_model hooks 细拆
伪代码
def _run_before_model_hooks(state, runtime, middleware_list): current_state = state
for middleware in middleware_list: hook = middleware.before_model
if hook is None: continue
update = hook(current_state, runtime)
if update is None: continue
if "jump_to" in update: return _apply_jump(update)
current_state = _merge_state_update( current_state, update, )
return current_state每一步解释
读取 hook
hook = middleware.before_model有些 middleware 只实现工具 wrapper,不实现 before_model。
执行 hook
update = hook(current_state, runtime)它可以返回:
Nonestate update dict带 jump_to 的 dict合并 state
current_state = _merge_state_update(...)如果更新的是 messages,会走 reducer 追加或移除逻辑。
典型 before_model 用途
消息裁剪上下文摘要注入用户偏好动态 system prompt安全检查超过消息上限直接跳到 ENDsystem_prompt 注入源码拆解
伪代码
def _prepare_messages(state, system_prompt): messages = state["messages"]
if system_prompt is None: return messages
if isinstance(system_prompt, str): system_message = SystemMessage( content=system_prompt ) elif isinstance(system_prompt, SystemMessage): system_message = system_prompt else: raise TypeError("Invalid system_prompt")
return [system_message, *messages]解释
system_prompt 通常不是永久写入 state["messages"] 的历史消息,而是模型调用时注入。
这意味着:
每次 model node 调用 都能看到 system_prompt但 Agent State 中的 trace 仍主要记录:
HumanMessageAIMessageToolMessageModelRequest 构造源码拆解
伪代码
request = ModelRequest( model=model, messages=messages, tools=available_tools, response_format=response_strategy, state=state, runtime=runtime, config=config,)字段解释
| 字段 | 作用 |
|---|---|
model | 当前要调用的 ChatModel |
messages | 传给模型的上下文 |
tools | 当前模型可见工具 |
response_format | 当前结构化输出策略 |
state | 当前 Agent State |
runtime | LangGraph 运行时上下文 |
config | RunnableConfig,含 tags、metadata、callbacks 等 |
为什么要有 ModelRequest
因为 middleware 需要能够修改模型调用:
request = request.override( model=better_model, tools=filtered_tools, messages=trimmed_messages,)如果直接调用:
model.invoke(messages)中间件就很难优雅地修改模型、工具和消息。
wrap_model_call 链源码拆解
目标
wrap_model_call 不是普通日志钩子,它可以决定:
是否调用 handler调用几次 handler用哪个 model 调用用哪些 tools 调用失败后是否 fallback是否直接返回自定义 ModelResponse伪代码:包装链构造
def _call_model_with_middleware(request, middleware_list): def base_handler(req): return _invoke_model(req)
handler = base_handler
for middleware in reversed(middleware_list): wrapper = middleware.wrap_model_call
if wrapper is None: continue
previous_handler = handler
def handler(req, wrapper=wrapper, previous_handler=previous_handler): return wrapper(req, previous_handler)
return handler(request)解释
这类似洋葱模型:
wrap_model_call_1( wrap_model_call_2( base_model_invoke ))base_handler 伪代码
def _invoke_model(request): model = request.model
if request.tools: model = model.bind_tools( request.tools, **_tool_binding_kwargs(request), )
if request.response_format: model = _bind_response_format( model, request.response_format, )
message = model.invoke( request.messages, config=request.config, )
return ModelResponse(message=message)典型 wrap_model_call 示例
def dynamic_model_selection(request, handler): if len(request.messages) > 20: request = request.override(model=strong_model) else: request = request.override(model=fast_model)
return handler(request)源码理解重点
wrap_model_call 的核心不是“模型调用前后打日志”,而是:
对一次模型调用拥有控制权。它可以:
短路重试fallback改模型改工具改 messages改 response_formatChatModel 调用与 AIMessage 生成
伪代码
def _invoke_model(request): bound_model = request.model
if request.tools: bound_model = bound_model.bind_tools( request.tools, tool_choice=_resolve_tool_choice(request), )
if request.response_format: bound_model = _apply_structured_output( bound_model, request.response_format, )
ai_message = bound_model.invoke( request.messages, config=request.config, )
return ModelResponse(message=ai_message)模型返回的两种典型 AIMessage
需要工具
AIMessage( content="", tool_calls=[ { "name": "search_city_info", "args": {"query": "东京 热门景点"}, "id": "call_001", "type": "tool_call", } ])最终回答
AIMessage( content="下面是东京 5 天行程建议...", tool_calls=[])这就是 stop condition 能工作的基础:
最后一条 AIMessage 有没有 tool_calls?after_model hooks 细拆
伪代码
def _run_after_model_hooks( state, update, runtime, middleware_list,): current_update = update
for middleware in middleware_list: hook = middleware.after_model
if hook is None: continue
hook_update = hook( _preview_state_after_update(state, current_update), runtime, )
if hook_update is None: continue
if "jump_to" in hook_update: return _merge_updates( current_update, hook_update, )
current_update = _merge_updates( current_update, hook_update, )
return current_updateafter_model 能做什么
检查模型是否生成了危险工具调用检查 AIMessage.content 是否包含敏感信息记录 usage_metadata强制改写输出在不允许工具调用时清空 tool_callsjump_to="end"示例
def block_dangerous_tool(state, runtime): last = state["messages"][-1]
if not getattr(last, "tool_calls", None): return None
for call in last.tool_calls: if call["name"] in {"create_booking", "send_email"}: return { "messages": [ AIMessage(content="该操作需要人工确认。") ], "jump_to": "end", }
return Noneroute_after_model 源码拆解
最小伪代码
def route_after_model(state): messages = state["messages"] last_message = messages[-1]
if isinstance(last_message, AIMessage): if last_message.tool_calls: return "tools"
return END加 structured_response 后的伪代码
def route_after_model(state): last_message = state["messages"][-1]
if _has_business_tool_calls(last_message): return "tools"
if _has_structured_response_tool_call(last_message): parsed = _try_parse_structured_response(last_message)
if parsed.ok: return END
return "model" # 让模型修复结构化输出
if _response_format_required(state): if state.get("structured_response") is None: return "model"
return END解释
实际源码会比这个复杂,原因是需要区分:
业务工具调用结构化输出工具调用最终自然语言回答模型错误调用多 structured output 调用Schema validation error但核心判断仍然围绕:
最后一条 AIMessage和:
tool_calls 是否存在、属于哪一类。ToolNode 执行链:从 ToolCall 到状态更新
ToolNode 负责把模型产生的调用意图变成真实执行结果。这里的重点不是“调用一个函数”,而是执行协议的闭环:
解析 ToolCall ↓查找工具并注入 Runtime ↓经过 wrap_tool_call ↓同步或异步并行执行 ↓归一化为 ToolMessage / Command ↓合并为 State updateToolNode 初始化源码拆解
源码心智模型
ToolNode 是 tools node 的核心执行器。
伪代码
class ToolNode(RunnableCallable): name = "tools"
def __init__( self, tools, *, name="tools", tags=None, handle_tool_errors=_default_handle_tool_errors, messages_key="messages", wrap_tool_call=None, awrap_tool_call=None, ): super().__init__(self._func, self._afunc, name=name, tags=tags)
self._tools_by_name = {} self._injected_args = {} self._handle_tool_errors = handle_tool_errors self._messages_key = messages_key self._wrap_tool_call = wrap_tool_call self._awrap_tool_call = awrap_tool_call
for tool in tools: if not isinstance(tool, BaseTool): tool = create_tool(tool)
self._tools_by_name[tool.name] = tool self._injected_args[tool.name] = _get_all_injected_args(tool)逐行解释
RunnableCallable
ToolNode 本身是一个 Runnable 节点:
输入 state ↓输出 partial state update_tools_by_name
用于根据模型输出的:
call["name"]找到真实工具对象。
_injected_args
提前分析每个工具是否需要注入:
statestoreruntimeconfigtool_call_id这样执行时不用重复反射。
wrap_tool_call
如果存在中间件 wrapper,ToolNode 不直接执行工具,而是调用 wrapper:
wrap_tool_call(request, execute)ToolNode._parse_input 源码拆解
目标
ToolNode 支持三种输入:
1. Graph state dict: {"messages": [...]}2. Message list: [AIMessage(...)]3. Direct tool call list: [{"name": ..., "args": ..., "id": ..., "type": "tool_call"}]伪代码
def _parse_input(self, input): if isinstance(input, dict): messages = input[self._messages_key] input_type = "dict"
elif isinstance(input, list) and _is_message_list(input): messages = input input_type = "list"
elif isinstance(input, list) and _is_tool_call_list(input): return input, "tool_calls"
else: raise ValueError("Invalid input to ToolNode")
if not messages: raise ValueError("No messages found in input")
last_message = messages[-1]
if not isinstance(last_message, AIMessage): raise ValueError("Last message is not an AIMessage")
tool_calls = last_message.tool_calls
return tool_calls, input_type逐行解释
dict 输入
在 create_agent 中最常见:
{ "messages": [ HumanMessage(...), AIMessage(tool_calls=[...]) ]}message list 输入
适合手动测试 ToolNode:
ToolNode([tool]).invoke([ AIMessage(tool_calls=[...])])direct tool_calls 输入
适合跳过 message 解析,直接执行工具调用:
ToolNode([tool]).invoke([ { "name": "search_city_info", "args": {"query": "东京"}, "id": "call_001", "type": "tool_call", }])最后一条必须是 AIMessage
因为 tool_calls 是模型输出的一部分,只有 AIMessage 才应该携带:
tool_callsToolNode._func 同步执行源码拆解
伪代码
def _func(self, input, config, runtime): # 1. 解析 tool_calls tool_calls, input_type = self._parse_input(input)
# 2. 为每个 tool_call 分配 config config_list = get_config_list( config, len(tool_calls), )
# 3. 构造每个工具调用的 ToolRuntime tool_runtimes = []
for call, cfg in zip(tool_calls, config_list): state = self._extract_state(input, cfg)
tool_runtime = ToolRuntime( state=state, tool_call_id=call["id"], config=cfg, context=runtime.context, store=runtime.store, stream_writer=runtime.stream_writer, tools=list(self.tools_by_name.values()), execution_info=runtime.execution_info, server_info=runtime.server_info, )
tool_runtimes.append(tool_runtime)
# 4. 并行执行多个工具调用 with get_executor_for_config(config) as executor: outputs = list( executor.map( self._run_one, tool_calls, [input_type] * len(tool_calls), tool_runtimes, ) )
# 5. 合并输出 return self._combine_tool_outputs( outputs, input_type, )逐段解释
第 1 段:解析 tool_calls
tool_calls, input_type = self._parse_input(input)这一步决定:
要执行哪些工具输出应该按 dict 还是 list 格式返回第 2 段:配置列表
config_list = get_config_list(config, len(tool_calls))如果有多个工具调用,每个工具调用都应该拿到一份 config。
第 3 段:构造 ToolRuntime
ToolRuntime 让工具可以访问:
statecontextstoreconfigstream_writertool_call_idexecution_infoserver_info这解释了为什么工具不只是一个普通函数调用,而是在图运行时上下文中执行。
第 4 段:并行执行
executor.map(...)这意味着:
一条 AIMessage 中的多个 tool_calls 可以并行执行。例如模型一次输出:
query_weathersearch_attractionsToolNode 可以同时执行这两个工具。
第 5 段:合并输出
工具输出可能是:
ToolMessageCommandlist[ToolMessage | Command]所以必须统一合并。
ToolNode._afunc 异步执行源码拆解
伪代码
async def _afunc(self, input, config, runtime): tool_calls, input_type = self._parse_input(input) config_list = get_config_list(config, len(tool_calls))
tool_runtimes = []
for call, cfg in zip(tool_calls, config_list): state = self._extract_state(input, cfg) tool_runtimes.append( ToolRuntime( state=state, tool_call_id=call["id"], config=cfg, context=runtime.context, store=runtime.store, stream_writer=runtime.stream_writer, tools=list(self.tools_by_name.values()), execution_info=runtime.execution_info, server_info=runtime.server_info, ) )
coros = []
for call, tool_runtime in zip(tool_calls, tool_runtimes): coros.append( self._arun_one(call, input_type, tool_runtime) )
outputs = await asyncio.gather(*coros)
return self._combine_tool_outputs(outputs, input_type)解释
同步版用线程池或执行器并行:
executor.map(...)异步版用:
asyncio.gather(...)这要求工具最好实现原生异步:
async def否则可能退化为线程执行。
ToolNode._run_one 源码拆解
伪代码
def _run_one(self, call, input_type, tool_runtime): # 1. 根据工具名查找工具 tool = self.tools_by_name.get(call["name"])
# 2. 构造 ToolCallRequest request = ToolCallRequest( tool_call=call, tool=tool, state=tool_runtime.state, runtime=tool_runtime, )
config = tool_runtime.config
# 3. 没有 wrapper,直接执行 if self._wrap_tool_call is None: return self._execute_tool_sync( request, input_type, config, )
# 4. 有 wrapper,提供 execute 函数 def execute(req): return self._execute_tool_sync( req, input_type, config, )
# 5. wrapper 决定如何执行 try: return self._wrap_tool_call( request, execute, ) except Exception as e: if not self._handle_tool_errors: raise
content = _handle_tool_error( e, flag=self._handle_tool_errors, )
return ToolMessage( content=content, name=request.tool_call["name"], tool_call_id=request.tool_call["id"], status="error", )逐段解释
工具查找
tool = self.tools_by_name.get(call["name"])模型输出的是字符串工具名,执行器必须从注册表里找到真实工具。
ToolCallRequest
ToolCallRequest 是传给 wrap_tool_call 的对象,包含:
tool_calltoolstateruntimeexecute 函数
execute(req) 是真正执行工具的函数。
wrapper 可以选择:
不调用 execute:短路 / 缓存命中 / 拒绝执行调用一次 execute:正常执行调用多次 execute:重试修改 request 后调用 execute:参数改写wrap_tool_call 源码语义细拆
wrapper 示例
def cache_tool_call(request, execute): cache_key = build_cache_key(request.tool_call)
if cached := cache.get(cache_key): return ToolMessage( content=cached, name=request.tool_call["name"], tool_call_id=request.tool_call["id"], )
result = execute(request) cache.set(cache_key, result.content) return result源码语义
request 是当前工具调用请求
execute 是继续执行原始工具调用的函数wrapper 可以做什么
缓存重试熔断限流参数改写工具调用审计高风险操作人工确认调用前权限校验调用后结果校验与 callback 的区别
callback 通常只能观察:
on_tool_starton_tool_endon_tool_errorwrapper 可以控制:
是否执行执行几次修改什么参数返回什么结果这就是 middleware 在 v1 中更强的原因。
ToolNode._execute_tool_sync 源码拆解
伪代码
def _execute_tool_sync(self, request, input_type, config): call = request.tool_call tool = request.tool
# 1. 工具不存在时返回错误 ToolMessage if tool is None: invalid_tool_message = self._validate_tool_call(call) if invalid_tool_message: return invalid_tool_message
raise TypeError(f"Tool {call['name']} is not registered")
# 2. 注入 state / store / runtime injected_call = self._inject_tool_args( call, request.runtime, tool, )
# 3. 补上 type=tool_call,让 BaseTool 能识别完整 ToolCall call_args = { **injected_call, "type": "tool_call", }
try: try: # 4. 调用工具 response = tool.invoke(call_args, config)
except ValidationError as exc: # 5. 参数校验失败,过滤掉系统注入参数错误 injected = self._injected_args.get(call["name"]) filtered_errors = _filter_validation_errors( exc, injected, )
raise ToolInvocationError( call["name"], exc, call["args"], filtered_errors, ) from exc
# 6. 归一化工具返回 return self._normalize_tool_response( response, request.tool_call, input_type, )
except GraphBubbleUp: # 7. interrupt 等图控制异常必须向上冒泡 raise
except Exception as e: # 8. 判断是否处理异常 if not self._should_handle_error(e): raise
# 9. 转成 error ToolMessage content = _handle_tool_error( e, flag=self._handle_tool_errors, )
return ToolMessage( content=content, name=call["name"], tool_call_id=call["id"], status="error", )核心细节 1:补 type="tool_call"
第 4 篇讲过:
tool.invoke({ "name": "search_city_info", "args": {...}, "id": "call_001", "type": "tool_call",})只有传完整 ToolCall,BaseTool 才能保留:
tool_call_id并返回:
ToolMessage(...)所以 ToolNode 在执行前会确保传给工具的是完整 ToolCall。
核心细节 2:注入发生在真正执行前
injected_call = self._inject_tool_args(...)这一步会注入:
statestoreruntimetool_call_id模型不能控制这些参数。
核心细节 3:过滤 validation errors
如果工具有隐藏注入参数,模型不应该看到这些参数的错误。
所以源码会过滤:
只保留模型可控参数的校验错误。这样模型收到的错误更可修复。
ToolNode._normalize_tool_response 源码语义
伪代码
def _normalize_tool_response(response, original_call, input_type): if isinstance(response, ToolMessage): return response
if isinstance(response, Command): return response
if isinstance(response, list): return [ _normalize_tool_response(item, original_call, input_type) for item in response ]
return ToolMessage( content=msg_content_output(response), name=original_call["name"], tool_call_id=original_call["id"], )解释
工具可能返回:
字符串dictlistToolMessageCommandlist[ToolMessage | Command]ToolNode 需要统一输出成图能合并的格式。
为什么支持 Command
工具返回 Command 时,不只是给模型一个 observation,而是可以:
更新 state触发 goto发送 Send控制父图这已经超出普通 ReAct,进入 LangGraph 的高级控制流。
ToolNode._combine_tool_outputs 源码拆解
伪代码
def _combine_tool_outputs(outputs, input_type): # 1. 展平 list 输出 flat_outputs = []
for output in outputs: if isinstance(output, list): flat_outputs.extend(output) else: flat_outputs.append(output)
# 2. 如果没有 Command,保持传统 ToolMessage 输出 if not any(isinstance(o, Command) for o in flat_outputs): if input_type == "list": return flat_outputs
return { self._messages_key: flat_outputs }
# 3. 如果有 Command,需要让 LangGraph runtime 处理 combined_outputs = []
for output in flat_outputs: if isinstance(output, Command): combined_outputs.append(output) else: if input_type == "list": combined_outputs.append([output]) else: combined_outputs.append({ self._messages_key: [output] })
return combined_outputs解释
在 create_agent 的常规场景中,最常见返回:
{ "messages": [ ToolMessage(...), ToolMessage(...), ]}这会被 LangGraph reducer 追加到 state["messages"]。
如果工具返回 Command,说明工具不仅产生消息,还想控制图状态或流程。
运行时总结、伪代码与源码证据
以下为保留关键控制流的简化伪代码,不是源码逐字复制:
def run_agent(state): ai_message = call_model(state) state = append_message(state, ai_message) if ai_message.tool_calls: return execute_tools_and_loop(state) return state源码证据:
- https://github.com/langchain-ai/langchain/blob/83e824922ac9bf3bf882347fecabd09614bccc37/libs/langchain/langchain/agents/factory.py
- https://github.com/langchain-ai/langgraph/blob/5931a5f0b313feff24e2516a586c55601b868ac1/libs/prebuilt/langgraph/prebuilt/tool_node.py
9. 关键分支、异常与边界
Agent 是否“能调用工具”只是第一层能力,更关键的是它能否可靠收敛。循环控制需要同时处理正常结束、结构化结果、人工中断和框架级熔断,不能只依赖模型自觉停止。
stop condition 源码拆解
最小判断
def should_continue(state): last_message = state["messages"][-1]
if isinstance(last_message, AIMessage): if last_message.tool_calls: return "tools"
return END多情况判断
def should_continue(state): last = state["messages"][-1]
# 1. 不是 AIMessage,不能继续工具循环 if not isinstance(last, AIMessage): return END
# 2. 模型请求业务工具 business_tool_calls = _get_business_tool_calls(last) if business_tool_calls: return "tools"
# 3. 模型请求结构化响应工具 structured_calls = _get_structured_output_calls(last) if structured_calls: parse_result = _parse_structured_output(structured_calls)
if parse_result.success: return END
return "model" # 让模型修复
# 4. 没有工具调用,正常结束 return END解释
create_agent 的停止不是“循环次数到了就停”,而是:
模型不再请求工具这就是 ReAct 的自然终点。
但为了防止模型一直调用工具,LangGraph 运行时还有:
recursion_limitrecursion_limit 与 iteration limit
业务层停止条件
AIMessage 不再包含 tool_calls框架层保护条件
LangGraph 执行步数超过 recursion_limit设置方式
result = agent.invoke( { "messages": [ {"role": "user", "content": "..."} ] }, config={ "recursion_limit": 20 })重要区别
recursion_limit 不是“最多工具调用次数”。recursion_limit 限制的是图执行步数 / super-step。一次 ReAct 循环可能包括:
model node ↓tools node所以:
一次工具调用循环 ≈ 多个图执行步。无限循环场景
model 总是输出 tool_calls ↓tools 返回结果 ↓model 仍然输出同一个 tool_call ↓tools 再执行 ↓... ↓达到 recursion_limit这时需要排查:
工具结果是否让模型误解?工具描述是否诱导重复调用?stop condition 是否被结构化响应策略影响?system_prompt 是否要求必须查工具?模型是否缺少“何时停止”的指令?structured_response 源码链路
输入
class TravelSummary(BaseModel): destination: str days: int highlights: list[str]
agent = create_agent( model="openai:gpt-4.1-mini", tools=[search_city_info], response_format=TravelSummary,)ProviderStrategy 伪代码
def _call_with_provider_strategy(request): model = request.model
model = model.with_structured_output( request.response_format.schema, method="provider_native", )
parsed = model.invoke(request.messages)
return { "structured_response": parsed, "messages": [_make_final_message_if_needed(parsed)], }ToolStrategy 伪代码
def _call_with_tool_strategy(request): schema_tool = _schema_to_virtual_tool( request.response_format.schema )
model = request.model.bind_tools( [*request.tools, schema_tool], tool_choice="any", )
ai_message = model.invoke(request.messages)
if _contains_schema_tool_call(ai_message): parsed = _parse_schema_tool_call(ai_message) return { "messages": [ai_message], "structured_response": parsed, }
return { "messages": [ai_message] }解释
structured_response 不是从最终自然语言里手写正则解析出来的。
它来自:
Provider-native structured output或Tool calling structured output最后被写入:
state["structured_response"]interrupt_before / interrupt_after 与工具审批
配置
agent = create_agent( model=model, tools=[create_booking], interrupt_before=["tools"], checkpointer=checkpointer,)执行心智模型
model 输出 tool_calls ↓准备进入 tools node ↓interrupt_before["tools"] 触发 ↓图状态保存到 checkpoint ↓等待人工确认 ↓resume ↓tools node 真正执行适合场景
下单付款退款发邮件删文件提交工单修改数据库这说明:
高风险工具不应该只靠 prompt 控制,应该在 graph 层或 middleware 层强制中断审批。异常传播与恢复边界
| 异常类别 | 处理位置 | 默认行为 |
|---|---|---|
| 模型调用失败 | wrap_model_call 或模型节点 | 重试、fallback 或继续抛出 |
| 工具参数错误 | ToolNode / BaseTool | 按工具错误策略转成 ToolMessage 或抛出 |
| 中间件拒绝 | middleware hook | 跳转、返回状态更新或中断 |
| 图不收敛 | LangGraph runtime | 达到 recursion_limit 后抛出错误 |
分支语义总结、伪代码与源码证据
以下为保留关键控制流的简化伪代码,不是源码逐字复制:
def route_after_model(state): last_message = state["messages"][-1] if last_message.tool_calls: return "tools" if state.get("structured_response") is not None: return END return END源码证据:
- https://github.com/langchain-ai/langchain/blob/83e824922ac9bf3bf882347fecabd09614bccc37/libs/langchain/langchain/agents/factory.py
- https://github.com/langchain-ai/langgraph/blob/5931a5f0b313feff24e2516a586c55601b868ac1/libs/langgraph/langgraph/errors.py
10. 扩展机制与框架协作
扩展点总览
| 扩展点 | 执行时机 | 适用能力 |
|---|---|---|
before_agent / after_agent | Agent 生命周期边界 | 初始化、汇总和清理 |
before_model / after_model | 每次模型调用前后 | 上下文处理、输出审查 |
wrap_model_call | 包裹模型请求 | 重试、fallback、动态模型 |
wrap_tool_call | 包裹工具执行 | 审批、缓存、工具错误策略 |
response_format | 最终响应生成 | structured_response |
interrupt_before / interrupt_after | 节点执行边界 | Human-in-the-loop |
Middleware 包装链伪代码
以下为保留关键控制流的简化伪代码,不是源码逐字复制:
def call_model_with_middleware(request, middleware): handler = base_model_handler for item in reversed(middleware): handler = item.wrap_model_call(handler) return handler(request)与 LangGraph 的协作边界
create_agent 负责标准 Harness ↓CompiledStateGraph 负责图执行 ↓Checkpointer 负责持久化 ↓Store / Runtime 负责运行上下文何时使用扩展点,何时手写图
| 需求 | 推荐方式 |
|---|---|
| 修改模型或工具调用前后行为 | middleware |
| 增加标准状态字段 | middleware state schema |
| 改变主执行拓扑 | 手写 LangGraph |
| 多阶段确定性审批和并行汇合 | 手写 LangGraph |
源码证据
- https://github.com/langchain-ai/langchain/blob/83e824922ac9bf3bf882347fecabd09614bccc37/libs/langchain/langchain/agents/factory.py
- https://github.com/langchain-ai/langchain/tree/83e824922ac9bf3bf882347fecabd09614bccc37/libs/langchain/langchain/agents/middleware
- https://github.com/langchain-ai/langgraph/blob/5931a5f0b313feff24e2516a586c55601b868ac1/libs/prebuilt/langgraph/prebuilt/tool_node.py
11. 工程决策与适用场景
适用场景
create_agent 适合标准的“模型决定是否调用工具,工具结果回填模型,直到生成最终结果”循环。
不适用场景
复杂业务状态机、强确定性流程、多阶段并行汇合和跨节点审批,应直接使用 LangGraph 构建显式拓扑。
工程决策表
| 决策点 | 推荐 |
|---|---|
| 标准 ReAct 工具循环 | create_agent |
| 仅增加日志、重试或上下文处理 | middleware |
| 改变节点和边 | 手写 StateGraph |
| 高风险工具 | middleware + interrupt + checkpointer |
12. 常见误区与源码纠正
create_agent 返回的是 Chain
纠正:
create_agent 返回 CompiledStateGraph。create_agent 就是 while 循环
纠正:
它是 LangGraph 图。循环由 conditional edge + 回边表达。ReAct 必须显式输出 Thought
纠正:
现代实现更依赖 AIMessage.tool_calls、ToolMessage、trace。ToolMessage 是普通文本
纠正:
ToolMessage 携带 tool_call_id,必须和 AIMessage.tool_calls 中的 id 对应。middleware 只能记录日志
纠正:
middleware 可以修改 state、改模型、过滤工具、重试、fallback、跳转 END。recursion_limit 是工具调用次数限制
纠正:
recursion_limit 是 LangGraph 图执行步数上限,不是单纯工具调用次数。structured_response 是从自然语言里解析的
纠正:
create_agent 的 structured_response 通常来自 ProviderStrategy 或 ToolStrategy。create_agent 适合所有业务系统
纠正:
create_agent 适合标准 ReAct 工具循环;复杂业务流程、强状态机、多阶段审批更适合手写 LangGraph。13. 最终心智模型与掌握检查
构建期
model + tools + system_prompt + middleware + response_format ↓create_agent ↓StateGraph ↓model node ↓tools node ↓conditional edge ↓CompiledStateGraph运行期
HumanMessage ↓model node ↓AIMessage(tool_calls) ↓tools node ↓ToolMessage ↓model node ↓AIMessage(final answer)ToolNode 内部
state["messages"][-1] ↓AIMessage.tool_calls ↓_tools_by_name 查找工具 ↓ToolRuntime 注入 ↓wrap_tool_call ↓BaseTool.invoke(full_tool_call) ↓ToolMessage ↓{"messages": [ToolMessage]}停止条件
最后一条 AIMessage 有 tool_calls ↓tools node
最后一条 AIMessage 无 tool_calls ↓END
图执行超过 recursion_limit ↓GraphRecursionError一句话总结:
create_agent是 LangChain v1 的高层 Agent Harness。它通过 LangGraph 构建model node → tools node → model node的循环,用AIMessage.tool_calls表示 Action,用ToolMessage表示 Observation,用条件边判断是否继续循环,并用 middleware、structured_response、interrupt、checkpointer 与 recursion_limit 把 ReAct 类行为工程化为可观测、可恢复、可扩展的 Agent Runtime。
14. 参考资料与下一篇衔接
官方资料
官方文档
API Reference
官方源码
langchain/agents/factory.pylangchain/agents/middleware/langchain/agents/structured_output.pylanggraph/prebuilt/tool_node.pylangchain_core/messages/ai.pylangchain_core/messages/tool.py
下一篇衔接
下一篇建议进入:
第 6 篇:LangGraph StateGraph 源码解剖重点回答:
StateGraph 是 builder 还是 Runnable?add_node 如何注册节点?add_edge 如何注册固定边?add_conditional_edges 如何注册条件边?compile 之后生成什么?node 返回 partial state update 如何合并?messages reducer 如何追加消息?CompiledStateGraph.invoke 如何驱动图执行?