深入拆解LangChain Agent执行流程:从AgentExecutor到LangGraph
很多人在学 LangChain 的时候都会遇到一个极其相似的分水岭调大模型 API 已经很熟练了但一写 Agent 就卡住。卡住的原因通常不是 Prompt 写不好也不是模型能力不够而是根本不清楚 Agent 被调用之后内部到底是怎么跑完一趟的。工具定义的顺序有没有影响Agent 为什么有时候不调用工具、有时候反复调用同一个工具为什么加了 Memory 之后还是“失忆”这些问题如果不理解执行流程就只能靠试错去猜。这篇文章要做的就是把 LangChain Agent 的执行流程完整拆开从入口开始一步步看到它如何决定调用工具、如何把工具结果送回模型、如何在一个循环里最终收敛。看完之后你不仅能读懂 AgentExecutor 的机制也能明白为什么现在的 LangGraph 会逐渐成为更推荐的编排方式。如果你是刚接触 Agent 开发或者准备面试但被问到底层执行链路时答不清楚这篇文章建议收藏备用。1. Agent 到底解决的是什么问题先回到一个最基础的问题为什么不能只靠大模型本身完成复杂任务大模型本质上是一个“纯文本推理器”。它可以根据上下文生成回答但它没有能力去做这些事查询实时天气、股票价格、数据库内容执行业务系统里的下单、审批、发送邮件等操作搜索最新的网络信息运行一段代码并获取结果。传统做法是开发者写死规则判断用户意图再调用对应接口。这种做法在小场景下没问题但一旦用户的说法千变万化规则就会膨胀到无法维护。Agent 的思路完全不同让大模型自己根据用户的问题决定“我现在需要调用哪个工具”再根据工具返回的结果继续推理下一步动作。用一个类比来理解普通 LLM 调用像和一个知识渊博但从不迈出房间的顾问对话。 Agent 像是给这个顾问配了一个办公室他可以随时拿起电话工具查数据、发指令、跑任务然后把新信息拿回来继续思考。LangChain 做的事就是把“思考、调用、观察结果、再思考”这个循环封装成一套可复用的执行机制。你不需要自己写 while 循环去处理模型返回的 tool_calls只需要定义好工具交给 Agent 执行即可。2. LangChain Agent 涉及的核心概念在拆执行流程之前先把几个容易混淆的概念说清楚。2.1 Agent 与 LLM 的关系Agent 不是一个新的模型它是在大模型之上构建的一套“推理 行动”系统。LLM 负责两件事理解用户的自然语言输入生成“下一步动作”的决策比如调用哪个工具、参数是什么。Agent 框架负责其余所有事情维护完整的消息历史调用 LLM解析 LLM 返回的结构化工具调用执行工具把工具结果作为新的消息追加进上下文判断继续循环还是返回最终答案。2.2 Tool 与 ToolExecutorTool 是可被调用的功能单元在 LangChain 里通常是一个函数加上一段描述。这个描述非常重要因为大模型要靠它来判断“什么时候该用这个工具”。Tool 的简单示例# 文件路径tools/current_time.py from langchain_core.tools import tool from datetime import datetime tool def get_current_time() - str: 返回当前系统时间用于查询当前日期和时间的场景。 return datetime.now().strftime(%Y-%m-%d %H:%M:%S)ToolExecutor 负责真正去执行这个工具。在 AgentExecutor 中模型返回工具名和参数后框架会根据工具名找到对应的函数并传入参数执行。2.3 AgentExecutor 与 Agent很多初学者把 AgentExecutor 和 Agent 当作同一个东西其实它们是两层Agent负责“思考和决策”的那部分逻辑输入是消息列表输出是 AgentAction 或 AgentFinishAgentExecutor负责“循环调度”的运行时反复调用 Agent执行工具直到 Agent 返回 AgentFinish。可以这样理解Agent 是大脑AgentExecutor 是躯干和循环泵。2.4 Memory 在 Agent 中的位置Memory 在 Agent 里比普通对话链更复杂。普通 ChatModel 对话只需要维护 user/assistant 消息列表而 Agent 的多轮交互中还要把每轮的工具调用和工具结果都塞进上下文。缺少记忆模块时Agent 只能“看到”当前这一次执行的消息无法跨会话保留信息。LangChain 的 Memory 模块负责把历史会话压缩、截断或汇总后放回提示词中。但要注意Memory 解决的是跨会话记忆同一个执行流程内的工具调用结果默认就在消息列表里不依赖 Memory 组件。3. 传统 FSM 写法和 Agent 写法的本质差异在没有 LangChain Agent 之前实现一个“自动查询工具”的需求通常使用有限状态机FSM或者硬编码条件分支来写。传统写法大概长这样# 传统思路伪代码仅用于对比 def handle_query(user_input: str): if 时间 in user_input: return get_current_time() elif 天气 in user_input: return get_weather(user_input) elif 搜索 in user_input: return search_web(user_input) else: return default_answer(user_input)这种写法的最大问题不是代码难看而是模式匹配的脆弱性用户说“现在几点了”能匹配到“时间”用户说“麻烦帮我看看现在时间方便吗”可能匹配不到用户说“今天适合出门吗”会命中天气分支但用户其实可能想要的是“天气 建议”的组合。Agent 的做法是让模型理解语义并自行判断工具不需要维护一个庞大的意图规则表。当然这也带来新的不确定性模型可能会选错工具也可能会在工具调用中迷路这就要靠 Prompt 设计、合理的工具描述和迭代次数限制来控制。从工程角度看传统 FSM 的优势是结果确定、可控LangChain Agent 的优势是灵活、扩展成本低。两者不是完全替代关系在业界实际项目中很多系统依然是规则兜底 Agent 组合使用。4. LangChain Agent 执行流程完整拆解这一节是全文的核心把 AgentExecutor 从收到用户输入到最后返回答案的完整链路拆开讲清楚。4.1 输入处理与消息组装当用户输入到达 AgentExecutor 时第一件事不是直接调用 LLM而是把输入整理成完整的消息列表。这个列表通常包括System Prompt定义 Agent 的身份和工具使用规则可选的 Memory 历史消息来自之前轮次的对话摘要或消息片段当前用户输入Agent 自身附加的 instructions比如“请根据以下工具决定下一步”。关键点工具描述并不是直接拼在系统提示词里而是通过工具绑定bind_tools的方式注入到模型请求的 tools 参数中。对支持工具调用的模型如 GPT、Claude、Qwen 的 tool-call 版本来说工具列表是结构化传入的不是普通文本。4.2 模型决策阶段生成工具调用或直接回答消息组装完成后会进入 Agent 的决策阶段。模型拿到上下文后有两种可能的输出返回 AgentFinish模型认为自己已经有足够信息回答问题直接输出最终回答返回一个或多个 AgentAction模型决定调用某个工具并给出了工具名和参数。以 OpenAI Function Calling 类模型为例当模型决定调用工具时返回的结构里会有一个 tool_calls 数组包含工具名称和参数字典。LangChain 会把这些结构解析成 AgentAction 对象。这里有一个新手容易误解的点模型“决定调用工具”时并不会真的执行工具。它只是输出一个“调用意图”。真正执行工具的是后面几步。4.3 工具执行阶段AgentExecutor 收到 AgentAction 后会遍历使用的工具集合根据 action.tool 找到匹配的 Tool 对象然后调用它。这一步有几个隐性规则工具名是精确匹配大小写敏感工具参数会按模型输出的 JSON 传入框架会对类型做基础转换工具执行的结果是一个字符串如果工具抛出异常LangChain 会把异常信息也作为结果返回给模型让模型尝试理解或修正。工具执行阶段最容易出的问题是模型“凭空捏造”一个不存在的工具名。这在模型能力弱或者工具描述不清时比较常见。缓解方式是使用 Strict Tools 校验模式部分模型支持或者增强 Prompt 中的工具使用说明。4.4 观察结果回填与循环迭代工具执行完毕后LangChain 会把工具结果封装成一条 ToolMessage或 Observation 类型追加到消息列表中。这时候消息列表里会出现这样一个完整片段user: 现在纽约几点 assistant: tool_call: get_current_time(locationNew York) tool: 2025-06-02 08:30:00这之后AgentExecutor 又会回到模型决策阶段把包含工具结果的全量消息再次发给模型。模型看到工具返回后有两种选择如果工具结果已经满足问题需求输出最终答案如果还需要更多信息继续输出下一个工具调用。这个“模型决策 → 工具执行 → 结果回填 → 再决策”的过程就是 Agent 的执行循环。AgentExecutor 通过max_iterations参数控制最大迭代次数默认值通常是 15 左右。超过限制后Agent 会强制停止并返回一条提示超时的信息。4.5 终止条件AgentExecutor 的退出条件有三个任何一个满足都会终止Agent 返回 AgentFinish正常结束达到 max_iterations 上限抛出未捕获的异常异常会向调用方传播。其中第二种是最常见的问题来源Agent 陷入死循环反复调用同一个工具或者工具结果没有让模型收敛。遇到这种问题不是简单地调大 max_iterations 就能解决的而是要检查工具描述是否清晰、工具返回信息是否足够、Prompt 是否让模型走偏甚至要考虑工具是不是太容易被误调用。分步骤理解 LangChain 中任务规划能力如何实现要看的就是这一整条执行链路Agent 的“规划”并不是一次性生成完整步骤表而是每轮决策下一步靠循环来逐步逼近目标。5. 完整示例用 LangChain Agent 实现一个带工具的任务执行前面讲了原理这一节用一个可运行的最小示例把流程串起来。示例需求实现一个 Agent能够根据用户提问自行决定使用“查询当前时间”和“计算字符串长度”两个工具最终输出答案。5.1 环境准备本文代码不绑定具体 LangChain 大版本因为不同版本的 import 路径和参数名有过调整。建议使用较新的稳定版本并保持依赖一致。pip install langchain pip install langchain-openai pip install langchain-core pip install langchain-community如果你使用的模型是 OpenAI 兼容接口可以是云厂商或本地模型服务。关键点是把 API Base 指向模型服务地址。# 文件路径config/env.py import os # 如果使用第三方或本地模型服务打开下面一行 # os.environ[OPENAI_BASE_URL] http://your-model-service:8000/v1 os.environ[OPENAI_API_KEY] your-api-key5.2 定义工具这里定义两个简单工具为了可验证性工具内部逻辑不强依赖外部服务。# 文件路径tools/custom_tools.py from langchain_core.tools import tool tool def get_current_time() - str: 返回当前系统时间用于查询时间、日期相关的场景。 from datetime import datetime return datetime.now().strftime(%Y-%m-%d %H:%M:%S) tool def get_string_length(text: str) - int: 计算输入字符串的长度适用于需要统计字符数的场景。参数 text 是需要计算的字符串。 return len(text) # 导出工具列表后面会绑定到 Agent tools [get_current_time, get_string_length]工具描述是模型决策的重要依据。例如get_current_time的注释里写了“查询时间、日期相关”模型遇到“现在几点了”就会优先选择它get_string_length的注释写清楚参数text的含义模型才能正确传参。5.3 创建 AgentExecutor接下来创建模型、Agent 和 AgentExecutor。# 文件路径agent/run_agent.py from langchain_openai import ChatOpenAI from langchain.agents import create_tool_calling_agent, AgentExecutor from tools.custom_tools import tools # 1. 创建支持工具调用的模型 llm ChatOpenAI( modelgpt-4o-mini, # 请根据你的模型服务修改 temperature0, ) # 2. 绑定工具列表 llm_with_tools llm.bind_tools(tools) # 3. 编写 Agent 的系统提示词 system_prompt ( 你是一个能调用工具完成任务的助手。 当用户的问题需要实时数据时请先调用工具获取结果再综合你已有的知识回答。 ) # 4. 创建 Agent agent create_tool_calling_agent(llm_with_tools, tools, system_prompt) # 5. 创建 AgentExecutor并打开中间过程输出 agent_executor AgentExecutor( agentagent, toolstools, verboseTrue, max_iterations5, )这里有几个值得解释的点bind_tools是关键步骤它会将工具列表作为结构化参数传入模型请求system_prompt不是传给 Agent 的普通对话消息而是作为 SystemMessage 进入消息列表verboseTrue可以在控制台打印出每一步的 AgentAction 和工具结果是调试执行流程最直接的手段max_iterations5是为了避免 Agent 陷入循环时消耗过多 token。5.4 运行 Agent# 文件路径agent/run_agent.py续 if __name__ __main__: result agent_executor.invoke( {input: 请告诉我当前时间顺便计算一下「LangChain Agent」这个字符串的长度。} ) print(最终输出, result[output])运行命令python agent/run_agent.py如果一切正常控制台会打印类似下面的中间过程 Entering new AgentExecutor chain... Invoking: get_current_time with {} 2025-06-02 08:30:00 Invoking: get_string_length with {text: LangChain Agent} 16 Finished chain.这说明 Agent 先调了时间工具又调了字符串长度工具最后模型基于两次工具结果生成最终回答。6. AgentExecutor 执行细节与常见误区这一节补充一些新手很容易忽略的细节把这些搞明白排查问题会快很多。6.1 verbose 输出怎么读开启verboseTrue后日志中出现的 “Invoking” 代表模型决策出要调用某个工具“Finished chain.” 代表 AgentExecutor 正常终结。如果看到工具被连续调用了一次以上说明模型拿到工具结果后仍然认为信息不足。6.2 工具返回报错会怎样工具内部如果抛异常默认不会让整个 Agent 崩溃。LangChain 会把异常信息封装成工具执行结果的一部分返回给模型。模型看到类似 “Error: xxx” 的内容后可能会重新调整参数再次调用或者直接告诉用户执行失败了。这个设计的初衷是增强容错但也带来一个副作用模型如果连续出错可能会用更多迭代次数消耗 token。生产环境建议在工具内部做防御性捕获返回简洁清晰的错误信息。6.3 create_tool_calling_agent 与 create_react_agentLangChain 提供了多种创建 Agent 的函数最容易混淆的是这两个create_tool_calling_agent依赖模型原生支持 tool calling返回结构化工具调用解析稳定是当前主流用法create_react_agent不依赖原生 tool calling通过让模型输出 ReAct 格式的文本Thought/Action/Action Input来决策兼容更老的模型但解析更容易出错。如果你使用 Qwen、GPT、Claude、GLM 等支持工具调用的新模型优先用 create_tool_calling_agent。如果模型不支持原生 tool calling再考虑 ReAct 方式。7. LangGraph 与 LangChain Agent 的关系现在很多文章会同时出现 LangChain 和 LangGraph初学者容易把两者对立起来。实际上 LangGraph 是 LangChain 团队推出的状态化编排框架可以理解为更底层、更灵活的 Agent 运行时。7.1 AgentExecutor 与 LangGraph 的对比AgentExecutor 把整个执行循环封装成了黑盒你给它输入它返回输出中间过程虽然可通过 verbose 查看但定制能力有限。LangGraph 则不同它把 Agent 的执行流程显式地建模成一个图节点模型节点、工具节点、条件判断节点边节点之间的流转关系状态全局可读写在节点之间传递。你可以自己定义“判断是否继续调用工具”的条件也可以插入人工审核节点、自定义日志节点、失败重试节点。这些都很难在 AgentExecutor 里实现。7.2 什么时候该选 LangGraph简单判断标准项目只需要一个 LLM 两三个工具的简单助理AgentExecutor 足够项目包含多个 Agent 协作、需要人工审批、需要精细控制在某一步暂停或回退用 LangGraph项目需要把执行状态持久化、恢复或展示给前端尤其是后台任务场景LangGraph 更方便。更准确地说AgentExecutor 是 LangGraph 的一个特殊形态当你只需要“决策 → 执行 → 循环”的默认流程时用一个已经实现好的 AgentExecutor 就够了当你需要打破默认流程时就应该考虑用 LangGraph 手动搭一个。从 LangChain 团队近期的演进方向看AgentExecutor 相关代码逐渐不再作为新功能的主要迭代对象LangGraph 会成为更长期的选择。8. 常见问题与排查思路Agent 执行中的问题非常依赖上下文下面整理几类高频问题按排查优先级排列。问题现象可能原因排查方式解决方案模型拒绝调用任何工具工具描述不清开启 verbose观察模型输出增强工具注释在 Prompt 中举例说明什么时候用工具模型反复调用同一个工具直到超限工具结果不满足模型预期或 Prompt 缺少终止约束打印工具返回内容确认返回是否为空/报错改进工具结果内容在 Prompt 中强调“信息足够时直接回答”工具调用时报“工具不存在”工具名大小写或拼写不一致打印 tools 列表名称统一工具命名启动时做一次名称检查Agent 无法正确处理多工具组合结果模型能力不足或上下文过长检查中间日志看工具结果是否完整进入上下文精简工具结果切换更强模型调用超时长期无响应模型服务连接慢或工具执行外部请求慢看日志停在哪个环节为工具增加超时控制把耗时的工具体验改为异步任务Memory 不生效多轮对话“失忆”没有配置 Memory 模块或会话没有复用同一个 AgentExecutor检查消息列表确认历史消息是否被传入使用记忆组件或由上层应用统一维护会话历史最常见且容易被忽视的是第一个模型不调用工具。很多人的第一反应是换一个大模型但实际上工具描述写得是否足够清楚对模型决策的影响非常大。比如把工具描述从 “获取时间” 改成 “当用户询问当前时间、日期、几点了时调用此工具获取最新系统时间不要根据你已有的知识推测”模型的工具调用触发率会明显提升。9. 最佳实践与工程建议结合执行流程的机制整理几条对实际项目最有效的建议。9.1 工具设计的三条原则单一职责一个工具只做一件事避免一个工具内部塞多个不相关逻辑描述即 Prompt工具的描述是给模型看的要写清楚“什么时候用”和“怎么传参数”参数尽量少参数越多模型传错的概率越高。能设计成无参数的就不要让模型填参数。9.2 给工具加超时与容错工具执行阶段可能调用第三方 API不可不加超时。如果不加模型等待工具结果时会一直悬挂白白浪费 token 和用户时间。from langchain_core.tools import tool import time tool def call_external_api(url: str) - str: 调用外部 HTTP 接口适用于需要获取实时外部数据的场景。 import requests try: resp requests.get(url, timeout5) resp.raise_for_status() return resp.text[:500] except Exception as e: return f请求失败: {str(e)}注意最后把异常转成字符串返回这样模型能理解发生了什么而不是让整个链路崩溃。9.3 开启结构化执行与可观测性生产环境使用 Agent 时强烈建议保留执行日志尤其是每轮的 tool_calls、tool 返回值、token 数把 verbose 的输出接入日志系统而不是只在本地打印对 Agent 的输入输出做敏感信息过滤避免工具把用户的隐私内容传给第三方服务对 Agent 的权限做最小化设计能只读就不要给写权限能只查一个表就不要连整个库。9.4 别让 Agent 直接操作生产库Agent 的灵活性是把双刃剑。模型可能因为 Prompt 注入或用户误导生成一个意想不到的 SQL 或删除操作。在实际业务中不要让 Agent 直接连接生产数据库执行写操作。正确的做法是Agent 生成操作意图提交到审批队列由人工或受限服务执行。9.5 为 Agent 设定成本上限使用工具调用时每一轮 LLM 调用都会产生 token 消耗。一个陷入死循环的 Agent 可能短时间内消耗比普通对话高一个数量级的成本。除了设置 max_iterations建议在模型客户端层再设一层 token 限额超限直接中断。10. 总结与后续学习方向把这篇文章读完你应该已经掌握了几个关键点Agent 不是新模型而是“LLM 工具 循环调度”的组合AgentExecutor 的默认执行链路是组装消息 → 模型决策 → 工具执行 → 结果回填 → 再次决策工具描述的质量直接影响模型能否正确调用工具当默认执行链路无法满足需求时LangGraph 是更可控的替代方案。下一步的实践建议是不急着搭建复杂框架先用一个最小 Agent 跑通“时间查询”场景打开 verbose 观察每一轮输出再把工具数量逐步增加观察模型在不同工具数量下的决策质量变化。亲手过一遍agent_executor.invoke()的调用栈比背任何框架文档都更有用。如果你已经能熟练使用 AgentExecutor建议开始研究 LangGraph从写一个“模型节点 工具节点 条件边”的最小图开始理解状态如何流转、循环如何退出。这一步的投入会直接迁移到你后面所有 Agent 项目的架构设计中。