create_agent
create_agent 是 LangChain v1 中用于创建 Agent 的核心入口。开发者只需要传入模型、工具以及一些可选配置,这个函数就会返回一个可执行的 Agent 对象。
agent = create_agent(llm, tools, debug=True)
表面上看,这只是一个“构造函数”;但从源码实现来看,create_agent 做的事情远不止把 LLM 和工具简单打包在一起。它真正完成的是一套“执行图”的构建工作:把模型调用、工具调用、中间件、结构化输出、状态管理和运行时配置统一编排成一个 StateGraph,最后再编译成可执行对象。
因此,理解 create_agent 的关键,不是只看它“接收了哪些参数”,而是要理解它是如何把一个 Agent 抽象成状态图的。
创建 StateGraph
在完成前面一系列参数解析、schema 整理和 middleware 预处理之后,create_agent 开始构建整个 Agent 的执行蓝图:
graph: StateGraph[
AgentState[ResponseT], ContextT, _InputAgentState, _OutputAgentState[ResponseT]
] = StateGraph(
state_schema=resolved_state_schema,
input_schema=input_schema,
output_schema=output_schema,
context_schema=context_schema,
)
这里的 StateGraph 可以理解为 Agent 的状态机骨架。它并不是一个普通的容器,而是一种“声明式流程定义”:先定义有哪些节点,再定义节点之间如何流转,最后再把整张图编译成真正可运行的对象。
这一步非常关键,因为 LangChain 并没有把 Agent 实现成简单的 while True + if/else 循环,而是抽象成了图结构。这样做至少有三个明显好处:
- 状态统一
Agent 在运行过程中会维护一份共享状态,例如
messages、structured_response、jump_to等。图结构天然适合在多个节点之间传递和更新这些状态。 - 流程统一 无论是 LLM 调用、工具执行,还是 middleware 的前后处理逻辑,最终都能以“节点”的形式被纳入同一套调度框架中。
- 扩展统一
当框架需要支持更多阶段,例如
before_model、after_model、after_agent等钩子时,不需要重写主循环,只需要在图中插入新的节点和边即可。
从这一层来看,create_agent 的本质并不是“创建一个能调用工具的模型”,而是在创建一个“可执行的 Agent 状态图”。
向图中添加节点
创建完 StateGraph 之后,create_agent 会开始向图中注册节点。最核心的两个节点是模型节点和工具节点:
graph.add_node("model", RunnableCallable(model_node, amodel_node, trace=False))
if tool_node is not None:
graph.add_node("tools", tool_node)
这里的 "model" 节点负责调用模型,完成一轮推理;而 "tools" 节点则负责执行模型产生的工具调用。
需要注意的是,这里的模型节点并不是“直接调用 LLM”这么简单。在前面的代码中,create_agent 已经封装了模型调用逻辑,包括:
- 是否绑定工具
- 是否启用结构化输出
- 是否自动选择
ProviderStrategy或ToolStrategy - 是否插入
system_message - 是否经过
wrap_model_callmiddleware 的包裹
也就是说,图中的 "model" 节点本质上代表的是“一次完整的模型调用阶段”,而不是一条单纯的 API 请求。
除了 model 和 tools 之外,源码还会根据 middleware 的实现情况,动态添加额外节点,例如:
before_agentbefore_modelafter_modelafter_agent
这些节点并不是固定存在的,只有当某个 middleware 重写了对应钩子时,create_agent 才会把它们注册进图中。也正因为如此,最终生成的 Agent 图并不是固定模板,而是会随着配置不同而变化。
节点不是简单堆叠,而是分成“开始、循环、结束”三个层次
如果只看到 add_node,很容易误以为这张图只是“把模型和工具塞进去”。但实际上,create_agent 在后面还定义了四个非常关键的概念:
entry_nodeloop_entry_nodeloop_exit_nodeexit_node
它们决定了整张图的执行骨架。
entry_node
entry_node 表示图从 START 进入后,第一次应该执行哪个节点。
如果存在 before_agent middleware,那么图会先进入 before_agent;否则会直接进入 before_model 或 model。
这意味着:before_agent 是一个“只在整个 Agent 启动时执行一次”的入口阶段。
loop_entry_node
loop_entry_node 表示每一轮循环重新开始时,从哪里重新进入。
通常这里会指向 before_model 或 model。
这一步很重要,因为 Agent 的本质是一个循环系统:模型推理一次,可能决定调用工具;工具执行完后,再回到模型继续推理。因此必须单独区分“第一次进入图”和“工具返回后重新进入下一轮”的入口。
loop_exit_node
loop_exit_node 表示每一轮模型执行结束后,从哪里离开这一轮。
如果存在 after_model middleware,那么模型输出后不会立刻路由,而是先进入 after_model。否则就直接从 model 节点向后判断。
换句话说,after_model 是介于“模型推理完成”和“下一步路由判断”之间的一层缓冲区。
exit_node
exit_node 表示整个 Agent 的最终出口。
如果存在 after_agent middleware,那么流程结束前会先经过 after_agent;否则直接进入 END。
这说明 after_agent 与 before_agent 类似,属于“整个 Agent 生命周期级别”的钩子,而不是每轮推理都执行的阶段。
从这里可以看出,create_agent 并不是构建一条简单链路,而是在先定义:
- 整体从哪里开始
- 每轮循环从哪里开始
- 每轮循环从哪里结束
- 最后从哪里退出
只有这四个位置确定下来,后续节点和边的组织才有清晰的结构。
graph.add_edge、add_conditional_edges
有了节点之后,还需要通过边来描述节点之间的流转关系。
最基本的一条边是:
graph.add_edge(START, entry_node)
这条边说明整个 Agent 从 START 出发,先进入前面确定好的入口节点。
但 Agent 的核心复杂性不在普通边,而在条件边。源码中最重要的是几处 add_conditional_edges,因为 Agent 不是线性流程,而是一个需要根据运行状态动态跳转的系统。
从 model 或 after_model 到下一步
当模型执行完成后,流程并不会固定进入工具节点,而是要根据模型输出决定:
- 是否结束
- 是否进入工具节点
- 是否重新回到模型节点
- 是否因为 middleware 的
jump_to指令跳转到别处
对应的判断逻辑主要在 _make_model_to_tools_edge 中。
这一段逻辑可以概括为:
- 如果状态中显式写了
jump_to,优先按jump_to跳转。 - 如果当前没有有效的
AIMessage,直接结束。 - 如果模型没有产生任何
tool_calls,说明这一轮推理已经完成,直接结束。 - 如果存在尚未执行的工具调用,则进入
tools节点。 - 如果已经得到了
structured_response,则可以结束。 - 如果有工具调用但没有待执行工具,通常意味着有中间件插入了额外消息,此时重新回到模型节点继续处理。
这意味着,Agent 的“继续还是结束”并不是写死的,而是由当前状态中的消息和工具调用结果共同决定的。
从 tools 回到 model 还是结束
当工具执行完成后,流程也不一定总是回到模型。源码中 _make_tools_to_model_edge 决定了工具节点之后的走向。
这一段大致可以总结为:
- 如果没有找到最近的
AIMessage,回到模型节点。 - 如果这轮工具调用全部是
return_direct=True的工具,那么可以直接结束。 - 如果执行的是结构化输出工具,也可以直接结束。
- 否则默认回到模型节点,让模型读取工具结果并继续下一轮推理。
这里的 return_direct=True 很关键,它意味着某些工具不需要再把结果交回模型总结,而是可以直接作为 Agent 的最终结果返回。也正因为如此,图中 tools 节点有时会存在一条通往 exit_node 的边。
没有工具时的特殊分支
如果 Agent 根本没有工具,但启用了结构化输出,那么模型节点之间也可能形成“模型 -> 模型”的循环,这由 _make_model_to_model_edge 控制。
这说明在 create_agent 中,“循环”并不一定依赖工具;只要框架认为还有必要继续生成合法输出,模型也可以自行进入下一轮。
Middleware 如何嵌入主流程
如果说 model 和 tools 决定了 Agent 的主干,那么 middleware 决定的就是 Agent 的可扩展性。
create_agent 并没有把 middleware 当成简单的前后回调,而是把它们显式建模成图中的正式节点,并通过 _add_middleware_edge 把它们接入主流程。
这背后反映出一个重要设计思想: middleware 不是“挂在外面的装饰器”,而是 Agent 生命周期中的正式阶段。
具体来说:
before_agent串联在启动阶段before_model串联在每轮模型调用前after_model串联在每轮模型调用后after_agent串联在整个流程结束前
而且 middleware 不只是“顺序执行”而已。如果某个 middleware 声明了可跳转目标,那么 _add_middleware_edge 会为它创建条件边,使它能够通过状态中的 jump_to 字段改变控制流,例如跳到:
modeltoolsend
因此,middleware 在这套设计中实际上拥有了“改变执行路径”的能力,而不只是“修改输入输出”的能力。
结构化输出如何影响图的行为
create_agent 的另一个复杂点在于结构化输出。
在源码中,结构化输出并不是一个独立插件,而是深度参与模型绑定、工具集合和路由判断的。
具体来说:
- 模型绑定阶段
create_agent会根据模型能力自动决定使用ProviderStrategy还是ToolStrategy。 - 工具集合阶段
如果采用
ToolStrategy,结构化输出会被包装成特殊工具,并加入最终的工具列表中。 - 输出处理阶段
模型返回结果后,
_handle_model_output会尝试解析结构化输出;如果校验失败,还可能生成错误消息并触发重试。 - 路由阶段
一旦状态中已经存在
structured_response,很多条件边都会直接把流程导向结束。
因此,结构化输出不是“模型返回后顺便做一下解析”,而是从绑定模型到决定退出条件,贯穿了整个 create_agent 的执行图设计。
compile
在所有节点和边都定义完成之后,create_agent 的最后一步是:
return graph.compile(
checkpointer=checkpointer,
store=store,
interrupt_before=interrupt_before,
interrupt_after=interrupt_after,
debug=debug,
name=name,
cache=cache,
).with_config(config)
这一步的意义并不只是“把图编译一下”,而是把前面定义好的状态图真正转化成运行时对象。
前面的 StateGraph 仍然是“声明式定义”,描述的是:
- 有哪些节点
- 节点之间如何连接
- 哪些地方需要条件判断
- 哪些地方需要中间件参与
而 compile() 之后,图才会变成一个真正可执行的 CompiledStateGraph。与此同时,一些运行时能力也会在这一步被装配进去,例如:
checkpointer:负责状态持久化与恢复store:提供外部存储能力interrupt_before/interrupt_after:支持在指定节点前后中断debug:开启调试能力cache:启用缓存能力name和config:附加运行时标识和元数据
因此,这一阶段更准确的理解应该是: 前面是在“构图”,这里是在“把图装配成可运行的 Agent”。
总结
综合整个实现过程来看,create_agent 的核心思路可以概括为:
- 先整理模型、工具、middleware、schema 和结构化输出配置;
- 用
StateGraph定义一张 Agent 状态图; - 把模型节点、工具节点以及 middleware 节点注册到图中;
- 用普通边和条件边把它们连接成完整的执行流程;
- 最后调用
compile(),把这张图转化为真正可运行的 Agent。
也正因为采用了这种“状态图 + 条件路由”的实现方式,LangChain 的 Agent 不只是一个简单的“模型调用封装”,而是一套可插拔、可扩展、可中断、可持久化的执行框架。
从源码层面看,create_agent 真正做的事,并不是“帮我们调用一次 LLM”,而是把 Agent 的执行过程抽象成了一台状态机:
它知道何时进入模型,何时调用工具,何时继续循环,何时提前结束,以及何时允许 middleware 接管控制流。这也是 LangChain v1 中 Agent 架构比传统串行实现更强大的根本原因。