讲透 LangGraph:从状态图到 Agent 工程化|create_react_agent:Agent 循环如何建图

讲透 LangGraph:从状态图到 Agent 工程化|create_react_agent:Agent 循环如何建图
前面十二篇我们从StateGraph一路讲到了 reducer、条件边、并行、Send、checkpoint、interrupt、time travel、subgraph 和 Runtime。这些能力看起来很分散但当你调用一个预构建 Agent 工厂时它们会被组装到同一张图里。最典型的入口就是from langgraph.prebuilt import create_react_agent它表面上只接收 model 和 tools返回一个可以invoke()、stream()的 Agent。于是很多人会把它理解成一个封装好的“模型循环”模型想调用工具就调用拿到结果再问模型直到输出答案。这个理解没有错但不够完整。从源码结构看create_react_agent做的核心工作是根据 model、tools、hooks 和输出配置动态创建一张 StateGraph再把它编译成 CompiledStateGraph。换句话说预构建 Agent 并没有绕过 LangGraph。它只是替你写好了节点、状态、条件路由、Send 分发和停止条件。这一篇就把这层“预构建”重新展开。一、先说版本结论这个入口已经进入迁移期当前 1.2.8 分支仍然保留langgraph.prebuilt.create_react_agent源码和测试也完整存在。但函数已经带有明确的弃用标记create_react_agent has been moved to langchain.agents.Please update your import to:from langchain.agents import create_agent因此要区分两个目的。如果你在维护已有 LangGraph 代码这一篇可以帮助你准确理解现有 Agent 的运行图。如果你在新建项目推荐入口已经变成from langchain.agents import create_agent为什么还值得读旧工厂的源码因为它把 ReAct 循环如何映射为 StateGraph 展示得非常直接。理解这张图以后迁移到新的 Agent 工厂、使用 middleware 或自己定制图都会更有把握。本文分析的是 1.2.8 分支中仍然存在的实现不把弃用状态藏在文章最后。二、最小调用背后返回的是什么先看一个典型用法from langchain_core.tools import toolfrom langgraph.prebuilt import create_react_agenttooldef get_weather(city: str) - str: Return a simple weather description for a city. return f{city}晴24°Cmodel ... # 支持 tool calling 的 chat modelagent create_react_agent( model, tools[get_weather], prompt你是一个可靠的天气助手。,)调用方式和普通编译图一致result agent.invoke( { messages: [ { role: user, content: 北京天气怎么样, } ] })返回值不是一个独立 Agent 类而是CompiledStateGraph。因此它天然拥有我们前面已经讲过的能力invoke / ainvokestream / astreamcheckpointinterrupt_before / interrupt_afterstoresubgraphget_state / get_state_history预构建工厂只是省去了建图代码没有改变执行模型。三、函数参数可以分成六组create_react_agent的完整签名很长但按职责拆开后并不混乱。参数组主要参数决定什么模型与工具model、toolsAgent 能思考什么、能执行什么模型输入prompt、pre_model_hook每次模型调用前看到什么模型输出post_model_hook、response_format如何校验、审批或生成结构化结果状态与运行时state_schema、context_schema图里保存什么、run 依赖什么持久化与控制checkpointer、store、interrupt_before/after记忆、长期数据和人工控制图构建选项version、debug、name工具分发方式、调试和子图命名其中真正改变图拓扑的参数主要有tools 是否为空pre_model_hook 是否存在post_model_hook 是否存在response_format 是否存在version 是 v1 还是 v2是否存在 return_direct 工具工厂会根据这些条件选择节点和边而不是先创建一张固定图再关闭无用功能。四、AgentState循环为什么能不断追加消息这个 Agent 的核心 state 至少包含两项from collections.abc import Sequencefrom typing import Annotatedfrom langchain_core.messages import BaseMessagefrom typing_extensions import NotRequired, TypedDictfrom langgraph.graph.message import add_messagesfrom langgraph.managed import RemainingStepsclass AgentState(TypedDict): messages: Annotated[ Sequence[BaseMessage], add_messages, ] remaining_steps: NotRequired[RemainingSteps]4.1messages不是普通列表它使用add_messagesreducer。用户消息、AIMessage 和 ToolMessage 会在每个节点返回后进入同一个消息 channel如果消息 ID 相同reducer 还可以执行更新而不是无条件重复追加。ReAct 循环因此可以形成完整轨迹HumanMessage - AIMessage(tool_calls[...]) - ToolMessage - AIMessage(final answer)4.2remaining_steps是运行时管理值它大致表示recursion_limit - 已经执行的总步骤它不是让业务代码每轮手工减一的普通整数而是由图运行时管理。如果自定义state_schema源码会检查至少存在messagesremaining_steps如果还配置了response_format自定义 schema 还必须包含structured_response缺少这些 key工厂会在建图阶段直接报错而不是等运行到一半才失败。需要注意这里展示的 LangGraph 内部AgentState也已经标记迁移到langchain.agents新代码不应继续依赖旧导入路径。五、【正文插图】工厂最终创建了怎样一张图有工具时主路径可以概括成入口 - pre_model_hook可选 - agent - 是否存在 tool_calls 是tools - 回到入口 否post hook / structured response / END完整路由如下图图create_react_agent 构建的 ReAct StateGraph、v1/v2 工具分发与结束路径图中蓝色区域负责模型调用绿色区域负责工具分发橙色区域负责停止前处理。真正决定循环是否继续的是最后一条 AIMessage没有tool_calls进入结束路径有tool_calls进入工具节点工具结果作为 ToolMessage 写回messages图再次进入模型节点。如果没有任何工具工厂不会创建tools节点也不会创建这条条件循环。它会退化成一张以agent为核心的线性图再按配置接上 hooks 和结构化输出。六、agent 节点内部做了什么agent节点并不只是model.invoke(messages)。同步路径可以拆成五步1. 选择 messages 或 llm_input_messages2. 校验 AI tool_calls 是否都有对应 ToolMessage3. 解析静态或动态 model并应用 prompt4. 调用模型得到 AIMessage5. 检查 remaining_steps再把消息写回 state6.1 先校验聊天历史源码会检查已有 AIMessage 中的每一个 tool call是否存在同 ID 的 ToolMessage。如果历史里只有“模型要求调用工具”却没有对应工具结果大多数模型提供商都会拒绝这段消息序列。工厂会提前抛出INVALID_CHAT_HISTORY避免把损坏历史继续传给模型。6.2 再把 AIMessage 写回 state模型返回后节点使用return {messages: [response]}这里返回列表不是覆盖整个 history而是交给add_messagesreducer 合并。如果创建 Agent 时传入name返回的 AIMessage 也会带上这个 name把 Agent 作为多 Agent 系统里的子图时这有助于区分消息来源。七、静态模型和动态模型怎样选择model支持两种思路。7.1 静态模型可以传 chat model 实例也可以传字符串标识agent create_react_agent( openai:gpt-4.1-mini, toolstools,)字符串形式依赖langchain的init_chat_model。如果静态 model 尚未绑定这些工具工厂会根据情况调用bind_tools()。如果已经绑定源码会核对绑定工具的数量和名称避免 model 与tools参数描述两套不同能力。7.2 动态模型也可以传一个函数根据 state 和 Runtime 为每次调用选择模型from dataclasses import dataclassfrom langgraph.runtime import Runtimedataclass(frozenTrue)class ModelContext: tier: strdef select_model(state, runtime: Runtime[ModelContext]): selected premium_model if runtime.context.tier pro else fast_model return selected.bind_tools(tools)然后agent create_react_agent( select_model, toolstools, context_schemaModelContext,)动态函数可以是同步或异步函数。有一个重要要求动态返回的模型需要自己正确绑定工具而且绑定工具必须是tools参数声明集合的子集。这正好连接上一篇Context 不需要进入 AgentState却可以在每次模型调用前影响模型选择。八、Prompt 有四种形式prompt不只支持字符串。形式行为str转成 SystemMessage放在 state messages 前面SystemMessage直接放在 messages 前面callable接收完整 state返回模型输入Runnable接收完整 state返回模型输入最简单的是prompt你是一个可靠的助手。如果 prompt 需要读取自定义 state可以使用函数def build_prompt(state): return [ { role: system, content: f当前任务类型{state[task_type]}, }, *state[messages], ]Prompt Runnable 会和 model 组合成调用链但图里仍然只有一个名为agent的模型节点。也就是说prompt 处理是节点内部模型输入链的一部分不是默认单独持久化的 StateGraph 节点。九、v1 和 v2工具调用如何分发version默认是v2。两种版本都能处理一条 AIMessage 中的多个 tool call但图级调度方式不同。9.1 v1一次进入 tools 节点路由函数返回tools同一条 AIMessage 的全部 tool calls 交给一个 ToolNode 调用ToolNode 在内部并行执行它们。9.2 v2每个 tool call 生成一个 Send路由函数为每个调用创建Send( tools, ToolCallWithContext( tool_callcall, statestate, ),)概念上是AIMessage(tool_calls[A, B, C]) - Send(A) - tools - Send(B) - tools - Send(C) - tools这正是第 7 篇讲过的动态 fan-out。工具数量运行前未知Send 为每个调用创建独立任务再由消息 reducer 汇总 ToolMessage。v2 的好处是每个 tool call 都进入自己的图任务边界更适合独立追踪、状态注入和后续扩展。如果配置了post_model_hookv2 会先进入 hook再由 hook 后的 router 把尚未得到 ToolMessage 的 pending tool calls 分发出去。十、Tools 执行完以后为什么会回到模型默认情况下tools节点结束后会回到图的 entrypointtools - pre_model_hook如果存在- agent因此pre_model_hook不是只在第一次调用前执行而是在每轮模型调用前执行。工具结果已经作为 ToolMessage 进入messages。下一次模型调用看到自己刚才发出的 tool_calls对应的 ToolMessage 结果模型可以继续调用工具也可以生成最终答案。return_direct是例外如果某个工具设置return_directTrue工厂会额外创建工具结果路由。只要执行结果中命中这类工具图可以直接进入 END不再把结果交回模型润色。它适合工具输出本身就是最终响应的场景例如固定格式下载链接或已经完成渲染的业务结果。十一、Pre Hook 和 Post Hook 分别插在哪里11.1pre_model_hook它位于每次agent节点之前常用于裁剪过长消息总结历史构造单次 LLM 输入补充自定义 stateHook 必须至少返回messages或llm_input_messages。二者区别是messages 会更新 AgentStatellm_input_messages 只作为本次模型输入不更新 messages如果要用messages替换整个历史需要通过消息删除语义明确清空原列表不能把它当成普通覆盖字段因为messages有add_messagesreducer。11.2post_model_hook它位于模型生成 AIMessage 之后适合人工审批工具调用安全护栏输出校验修改或补充消息当前实现中它只支持versionv2。Post hook 后的 router 会重新检查 pending tool calls仍有待执行调用发送到 tools最后一条是 ToolMessage回到 entrypoint没有工具调用且配置结构化输出进入生成节点否则 END。因此 post hook 不是一条简单的尾部边而是工具循环中的二次决策点。十二、response_format 为什么会多调用一次模型配置agent create_react_agent( model, toolstools, response_formatAnswerSchema,)不会让每一轮 ReAct 输出都强制符合 schema。工厂会在 Agent 循环结束后增加generate_structured_response这个节点读取完整 messages再调用model.with_structured_output(schema)生成结果写入state[structured_response]因此它有三个明确成本和前提模型必须支持with_structured_output()循环完成后会发生一次额外模型调用自定义 state schema 必须声明structured_response。response_format还可以传(prompt, schema)元组。这里的 prompt 只服务于最终结构化输出调用不是 ReAct 主循环的 system prompt。十三、remaining_steps 如何防止循环失控ReAct Agent 可能不断产生 tool calls。真正的全局上限仍然来自图的recursion_limit而remaining_steps让 agent 节点在接近上限时提前识别“已经没有足够步骤完成工具往返”。当前实现中如果剩余步骤不足而模型仍要求调用工具Agent 不会直接把未完成的 tool call 留给执行器。它会改写为一条最终 AIMessageSorry, need more steps to process this request.特别是有普通 tool calls 且remaining_steps 2全部是return_direct工具但连执行一步都不够。这个保护避免了一部分循环在最后边界以GraphRecursionError结束但它不意味着可以忽略 recursion limit。工程上仍然要控制工具描述是否让模型反复选择同一工具工具错误是否被模型无意义重试每轮消息是否提供了明确停止信息recursion limit 是否与任务复杂度匹配十四、Compile 时哪些能力被原样传下去建图完成后工厂最终调用workflow.compile(...)。这些参数会直接进入编译图checkpointerstoreinterrupt_beforeinterrupt_afterdebugname因此可以在工具执行前暂停agent create_react_agent( model, toolstools, checkpointercheckpointer, interrupt_before[tools],)也可以把整个 Agent 作为另一个 StateGraph 的子图节点。name在这里不仅是日志标签还会成为多 Agent、子图和消息来源识别的一部分。这再次说明create_react_agent的产物不是一个脱离图运行时的黑盒而是一张标准的编译状态图。十五、从旧入口迁移时该看什么迁移到from langchain.agents import create_agent不要只做字符串替换。先盘点旧工厂中实际使用的能力自定义 state_schemacontext_schema 与动态模型pre_model_hook / post_model_hookresponse_formatinterrupt_before / interrupt_afterreturn_directv1 / v2 工具分发假设checkpointer 与 store新工厂强调 middleware旧 hooks 和自定义逻辑需要映射到新的扩展点。同时补齐回归测试单工具调用同一 AIMessage 多工具调用工具错误return_direct人工审批结构化输出达到 recursion limit 前后的行为理解旧图的节点和边正是为了让迁移不依赖猜测。十六、八条工程建议第一新项目优先评估langchain.agents.create_agent已有项目再结合版本计划迁移不要忽略当前弃用提示。第二把create_react_agent的产物当成 CompiledStateGraph 管理。checkpoint、stream 和 interrupt 都按图的语义测试。第三自定义 state schema 时保留messages、remaining_steps配置结构化输出时再加入structured_response。第四动态模型必须返回已正确绑定工具的模型且绑定集合不能超出工厂声明的 tools。第五默认理解 v2 的 Send 分发语义。升级旧项目时检查日志、并发和状态合并是否依赖 v1 行为。第六pre hook 中区分“更新历史消息”和“只改变本次模型输入”不要被 reducer 意外追加两份消息。第七所有副作用工具都要幂等。checkpoint replay、节点 retry 和人工恢复都可能让调用再次发生。第八把工具循环的停止条件写进测试而不是只验证模型最终碰巧给出了一次正确回答。小结预构建 Agent 仍然是一张状态图这一篇可以先记住十二句话create_react_agent 返回 CompiledStateGraph当前 1.2.8 中该入口已标记弃用新入口是 langchain.agents.create_agentAgentState 用 add_messages 合并消息remaining_steps 来自图运行时管理agent 根据最后一条 AIMessage 决定是否调用工具v1 一次进入 ToolNodev2 为每个 tool call 创建 Sendtools 默认执行后回到模型入口return_direct 可以直接结束post_model_hook 只支持 v2response_format 在循环结束后额外调用一次模型把工厂还原成图以后很多行为都不再神秘。工具并不是被模型“直接调用”而是 AIMessage 先写入 state条件边再创建工具任务工具结果也不是直接返回用户而是 ToolMessage 先经过 reducer 合并再决定回到模型还是结束。这条链路把前面讲过的 state、reducer、conditional edge、Send、Runtime、checkpoint 和 interrupt 全部串了起来。学AI大模型的正确顺序千万不要搞错了2026年AI风口已来各行各业的AI渗透肉眼可见超多公司要么转型做AI相关产品要么高薪挖AI技术人才机遇直接摆在眼前有往AI方向发展或者本身有后端编程基础的朋友直接冲AI大模型应用开发转岗超合适就算暂时不打算转岗了解大模型、RAG、Prompt、Agent这些热门概念能上手做简单项目也绝对是求职加分王给大家整理了超全最新的AI大模型应用开发学习清单和资料手把手帮你快速入门学习路线:✅大模型基础认知—大模型核心原理、发展历程、主流模型GPT、文心一言等特点解析✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑✅开发基础能力—Python进阶、API接口调用、大模型开发框架LangChain等实操✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经以上6大模块看似清晰好上手实则每个部分都有扎实的核心内容需要吃透我把大模型的学习全流程已经整理好了抓住AI时代风口轻松解锁职业新可能希望大家都能把握机遇实现薪资/职业跃迁这份完整版的大模型 AI 学习资料已经上传CSDN朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】