LangChain v1 Agent 执行流程源码解析

6 阅读11分钟

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 循环,而是抽象成了图结构。这样做至少有三个明显好处:

  1. 状态统一 Agent 在运行过程中会维护一份共享状态,例如 messagesstructured_responsejump_to 等。图结构天然适合在多个节点之间传递和更新这些状态。
  2. 流程统一 无论是 LLM 调用、工具执行,还是 middleware 的前后处理逻辑,最终都能以“节点”的形式被纳入同一套调度框架中。
  3. 扩展统一 当框架需要支持更多阶段,例如 before_modelafter_modelafter_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 已经封装了模型调用逻辑,包括:

  • 是否绑定工具
  • 是否启用结构化输出
  • 是否自动选择 ProviderStrategyToolStrategy
  • 是否插入 system_message
  • 是否经过 wrap_model_call middleware 的包裹

也就是说,图中的 "model" 节点本质上代表的是“一次完整的模型调用阶段”,而不是一条单纯的 API 请求。

除了 modeltools 之外,源码还会根据 middleware 的实现情况,动态添加额外节点,例如:

  • before_agent
  • before_model
  • after_model
  • after_agent

这些节点并不是固定存在的,只有当某个 middleware 重写了对应钩子时,create_agent 才会把它们注册进图中。也正因为如此,最终生成的 Agent 图并不是固定模板,而是会随着配置不同而变化。

节点不是简单堆叠,而是分成“开始、循环、结束”三个层次

如果只看到 add_node,很容易误以为这张图只是“把模型和工具塞进去”。但实际上,create_agent 在后面还定义了四个非常关键的概念:

  • entry_node
  • loop_entry_node
  • loop_exit_node
  • exit_node

它们决定了整张图的执行骨架。

entry_node

entry_node 表示图从 START 进入后,第一次应该执行哪个节点。 如果存在 before_agent middleware,那么图会先进入 before_agent;否则会直接进入 before_modelmodel

这意味着:before_agent 是一个“只在整个 Agent 启动时执行一次”的入口阶段。

loop_entry_node

loop_entry_node 表示每一轮循环重新开始时,从哪里重新进入。 通常这里会指向 before_modelmodel

这一步很重要,因为 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_agentbefore_agent 类似,属于“整个 Agent 生命周期级别”的钩子,而不是每轮推理都执行的阶段。

从这里可以看出,create_agent 并不是构建一条简单链路,而是在先定义:

  • 整体从哪里开始
  • 每轮循环从哪里开始
  • 每轮循环从哪里结束
  • 最后从哪里退出

只有这四个位置确定下来,后续节点和边的组织才有清晰的结构。

graph.add_edgeadd_conditional_edges

有了节点之后,还需要通过边来描述节点之间的流转关系。

最基本的一条边是:

graph.add_edge(START, entry_node)

这条边说明整个 Agent 从 START 出发,先进入前面确定好的入口节点。

但 Agent 的核心复杂性不在普通边,而在条件边。源码中最重要的是几处 add_conditional_edges,因为 Agent 不是线性流程,而是一个需要根据运行状态动态跳转的系统。

modelafter_model 到下一步

当模型执行完成后,流程并不会固定进入工具节点,而是要根据模型输出决定:

  • 是否结束
  • 是否进入工具节点
  • 是否重新回到模型节点
  • 是否因为 middleware 的 jump_to 指令跳转到别处

对应的判断逻辑主要在 _make_model_to_tools_edge 中。

这一段逻辑可以概括为:

  1. 如果状态中显式写了 jump_to,优先按 jump_to 跳转。
  2. 如果当前没有有效的 AIMessage,直接结束。
  3. 如果模型没有产生任何 tool_calls,说明这一轮推理已经完成,直接结束。
  4. 如果存在尚未执行的工具调用,则进入 tools 节点。
  5. 如果已经得到了 structured_response,则可以结束。
  6. 如果有工具调用但没有待执行工具,通常意味着有中间件插入了额外消息,此时重新回到模型节点继续处理。

这意味着,Agent 的“继续还是结束”并不是写死的,而是由当前状态中的消息和工具调用结果共同决定的。

tools 回到 model 还是结束

当工具执行完成后,流程也不一定总是回到模型。源码中 _make_tools_to_model_edge 决定了工具节点之后的走向。

这一段大致可以总结为:

  1. 如果没有找到最近的 AIMessage,回到模型节点。
  2. 如果这轮工具调用全部是 return_direct=True 的工具,那么可以直接结束。
  3. 如果执行的是结构化输出工具,也可以直接结束。
  4. 否则默认回到模型节点,让模型读取工具结果并继续下一轮推理。

这里的 return_direct=True 很关键,它意味着某些工具不需要再把结果交回模型总结,而是可以直接作为 Agent 的最终结果返回。也正因为如此,图中 tools 节点有时会存在一条通往 exit_node 的边。

没有工具时的特殊分支

如果 Agent 根本没有工具,但启用了结构化输出,那么模型节点之间也可能形成“模型 -> 模型”的循环,这由 _make_model_to_model_edge 控制。

这说明在 create_agent 中,“循环”并不一定依赖工具;只要框架认为还有必要继续生成合法输出,模型也可以自行进入下一轮。

Middleware 如何嵌入主流程

如果说 modeltools 决定了 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 字段改变控制流,例如跳到:

  • model
  • tools
  • end

因此,middleware 在这套设计中实际上拥有了“改变执行路径”的能力,而不只是“修改输入输出”的能力。

结构化输出如何影响图的行为

create_agent 的另一个复杂点在于结构化输出。 在源码中,结构化输出并不是一个独立插件,而是深度参与模型绑定、工具集合和路由判断的。

具体来说:

  1. 模型绑定阶段 create_agent 会根据模型能力自动决定使用 ProviderStrategy 还是 ToolStrategy
  2. 工具集合阶段 如果采用 ToolStrategy,结构化输出会被包装成特殊工具,并加入最终的工具列表中。
  3. 输出处理阶段 模型返回结果后,_handle_model_output 会尝试解析结构化输出;如果校验失败,还可能生成错误消息并触发重试。
  4. 路由阶段 一旦状态中已经存在 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:启用缓存能力
  • nameconfig:附加运行时标识和元数据

因此,这一阶段更准确的理解应该是: 前面是在“构图”,这里是在“把图装配成可运行的 Agent”。

总结

综合整个实现过程来看,create_agent 的核心思路可以概括为:

  1. 先整理模型、工具、middleware、schema 和结构化输出配置;
  2. StateGraph 定义一张 Agent 状态图;
  3. 把模型节点、工具节点以及 middleware 节点注册到图中;
  4. 用普通边和条件边把它们连接成完整的执行流程;
  5. 最后调用 compile(),把这张图转化为真正可运行的 Agent。

也正因为采用了这种“状态图 + 条件路由”的实现方式,LangChain 的 Agent 不只是一个简单的“模型调用封装”,而是一套可插拔、可扩展、可中断、可持久化的执行框架。

从源码层面看,create_agent 真正做的事,并不是“帮我们调用一次 LLM”,而是把 Agent 的执行过程抽象成了一台状态机: 它知道何时进入模型,何时调用工具,何时继续循环,何时提前结束,以及何时允许 middleware 接管控制流。这也是 LangChain v1 中 Agent 架构比传统串行实现更强大的根本原因。