LangChain Agent执行流程全解析:从工具调用到工程落地
LangChain 的 Agent 执行流程,是新手最容易卡住的地方。很多人把 Agent 理解成一个“自动变聪明的模型”,结果第一次跑通之后,发现它对工具的选择、参数的传递、什么时候停,全不是自己以为的那套逻辑。这篇文章把 Agent 的执行链路完整拆一遍:从 Agent 和 Chain 的差别开始,到单次工具调用的内部步骤,到记忆与规划,再到 LangGraph 下的执行器形态,最后落到批量任务和报错排查。适合正在学 LangChain、准备开发 AI Agent,或者面试前想把这些名词理顺的工程师。
下面直接进入正题。我不会只贴一堆概念,而是按“先理解链路,再跑通代码,再处理工程问题”的顺序来写。
1. 学 Agent 之前,先分清 Chain 和 Agent 的本质差别
1.1 Agent 不是一个单独模型,而是一条循环链路
LangChain 里的 Chain 和 Agent 最大的区别,不在代码写法上,而在决策权归属。
普通 Chain 是一条固定流水线。比如你写一个“总结链”,就是“读文档 -> 调用模型 -> 输出总结”,中间每一步都是预先定死的。模型没有权利决定中间要不要查数据库,也没有权利决定跳过某个环节。如果业务规则固定,用 Chain 反而更稳、更省钱。
Agent 不一样。Agent 是一个“模型 + 工具 + 循环控制”的组合体。模型可以自己决定:我要不要调用工具、调用哪个工具、传什么参数、调用完还要不要继续问模型。它本质上是把“决策”这件事交还给了语言模型。
在实际工程里,我见过很多团队把 LLM 直接当 Agent 用,结果发现模型经常乱调工具。原因很简单:没有给 Agent 设计清晰可执行的循环流程。
1.2 Agent 执行流程的六个关键环节
任何一次 Agent 调用,不管底层用 ReAct 还是 Function Calling,都会走下面这六个环节。我建议你先把这个主线刻在脑子里:
- 任务接收:拿到用户输入,转换成 messages 格式进入上下文。
- 模型决策:模型读取系统提示词、历史消息、工具描述,判断下一步要做什么。
- 工具选择:如果模型认为需要外部信息,就会输出一个工具调用意图,包括工具名和参数。
- 参数解析与执行:Agent 框架解析模型输出,去执行对应的 Python 函数或外部接口。
- 结果回填:工具返回的结果变成一条新消息,重新交给模型。
- 循环或终止:模型拿到工具结果后,要么生成最终回答,要么继续调用下一个工具。
后面所有复杂概念,比如记忆、规划、多 Agent,都是在这六个环节上做扩展。记住了这条主线,再去看相关代码就会顺很多。
2. 把最小 Agent 跑起来,再谈执行流程
2.1 环境准备:Python 版本、依赖和 API Key
我建议你先准备一个干净的虚拟环境,不要直接往全局环境里装一大堆依赖。实际操作时,我用的是 Python 3.10,LangChain 0.2 之后的版本,具体以你安装到的版本为准。
这里解释一下为什么要装这些包:
langchain:核心编排框架,提供 Chain、消息、工具注册这些基础能力。langchain-openai:OpenAI 模型的适配器,负责把 LangChain 的消息格式转成 OpenAI API 格式。langchain-community:社区集成工具箱。如果你不用第三方插件,这个包可以不装。langgraph:新版 Agent 执行器。虽然最小示例里不强制,但建议提前装上,后面对比新旧执行流程会用到。
API Key 用环境变量管理,不要硬编码在代码里。在项目根目录写一个 .env 文件,或者直接在终端导出:
实际测试时,我更习惯把 Key 放在 .env 里,然后用 dotenv 加载。这样至少不会因为一次无意的 git push 把 Key 传上仓库。
2.2 第一个 Agent:基于 ReAct 的 AgentExecutor 写法
ReAct 是“Reasoning + Acting”的缩写,核心思想是让模型先思考、再行动、再观察、再思考。下面这是一个非常典型的最小 Agent:
这套代码里有两个工具函数,一个查温度,一个查日期。模型读到用户问题“北京今天多少度?”后,会判断自己不知道实时温度,于是输出一个 Action,工具名是 get_city_temperature,参数是 北京。
执行器拿到这个 Action 后,会在已注册的工具列表里找同名函数,把参数解析出来并调用。结果返回后,模型再根据“北京当前温度 26 摄氏度”生成最终回答。
verbose=True 是个好东西,它会打印每一步的 Thought、Action、Observation,新人学习执行流程时一定要开着它看一轮日志。
2.3 更贴近生产环境的写法:LangGraph 执行器
如果你打开官方文档,会发现新版示例更常用 create_react_agent,而不是传统的 AgentExecutor。其实 LangGraph 也提供了一个便捷入口:
这个写法更接近 LangGraph 的 Graph 执行器。表面上它只是少了 Prompt 模板,但底层状态管理、节点流转和 AgentExecutor 完全不同。这一点我会在第 5 章展开。
先把上面任意一个 Demo 跑通。跑通之后再往下学,会轻松很多。
3. 一次工具调用的完整旅程,看模型和工具如何协作
3.1 模型怎么知道有哪些工具
很多第一次跑通 Agent 的人会问:模型怎么知道有 get_city_temperature 这个工具?答案不在代码逻辑里,而在工具描述和参数 Schema 里。
当你把 tools 列表传给 Agent 时,LangChain 会把每个工具的名称、描述、参数格式序列化,一起放进发给模型的请求里。模型看到的不是一个 Python 函数,而是一张类似这样的说明书:
| 工具名 | 描述 | 参数要求 |
|---|---|---|
| get_city_temperature | 根据城市名称返回当前温度,单位是摄氏度 | city: string 必填 |
| get_current_date | 获取今天的日期 | 无参数 |
模型根据这张“说明书”来判断要不要调用。所以工具描述写得越清楚,模型选错工具的概率就越低。
我见过一个坑:把工具描述写成“处理温度”。结果模型不知道该工具是查天气还是做温度转换,最后在多个温度相关工具之间反复横跳。工具描述的正确写法是:“这个工具负责什么,输入是什么,返回什么,适合什么场景。”
3.2 从用户问题到最终回答的六个内部步骤
下面以“北京今天多少度?”为例,把完整链路拆开。这里假设你用的是 Function Calling 或者 OpenAI Tools 模式。
第 1 步:用户消息进入上下文。
执行器把用户输入封装成一条 user message,和系统提示词、历史消息一起发给模型。
第 2 步:模型输出工具调用意图。
模型判断“北京今天的温度”不在自己的知识范围内,需要查询外部数据。于是它不生成普通文本,而是生成了一个结构化指令,大概意思就是:调用 get_city_temperature,参数是 {"city": "北京"}。
第 3 步:LangChain 解析模型输出。
框架拿到这个结构化指令后,先在已注册工具里查找名称。找不到就报错,找得到就做参数校验。这里的参数校验很重要,如果模型传了 {"city": 123},很多框架会尝试类型转换,或者直接抛错。
第 4 步:工具函数执行。
get_city_temperature("北京") 被调用,函数内部去查天气服务,返回“北京当前温度 26 摄氏度”。
第 5 步:工具结果回填。
LangChain 把工具返回结果包装成一条 tool message,加入消息列表。此时整个上下文里已经有两轮信息:模型调用工具的指令、工具返回的结果。
第 6 步:模型生成最终回答。
模型再次读取完整上下文,看到工具已经返回了实时温度,于是生成“北京今天 26 摄氏度”之类的最终回答。执行器把这条回答作为输出返回。
上面这六步,就是一次最小工具调用的完整旅程。如果你在代码里打开了 verbose 日志,能清楚看到每一步变化。
3.3 解析失败为什么那么常见
很多新手遇到“Agent 调用工具失败”,第一反应是模型不够聪明。实际上,更多时候是解析层出了问题。
比如老式 ReAct Agent 要求模型输出特定格式的文本,像“Action: get_city_temperature\nAction Input: 北京”。模型如果多输出了一些解释性文字,解析器就可能匹配失败。而 Function Calling 模式下,模型输出的是 JSON 结构,格式错误率会低很多,但仍有可能出现参数不符合 Schema 的情况。
排查逻辑一般是这样:
- 先看 verbose 日志里模型到底输出了什么内容。
- 如果模型输出的 Action 名称和工具列表不一致,检查工具名是不是太相似。
- 如果参数传错了类型,说明工具描述里没有写清参数格式,需要在描述里补一句“city 必须是字符串,例如:北京”。
- 如果解析器本身报错,先确认 LangChain 版本是否和使用的模型接口匹配。
注意:工具描述不是给人看的,而是给模型看的功能说明书。描述越含糊,模型越容易乱选工具。
4. Agent 的“记忆”和“规划”在流程里怎么生效
4.1 记忆在执行流程里到底存了什么
Agent 默认是没有跨轮记忆的。每调用一次 executor.invoke,都是一次独立请求。如果你在同一个对话里问两次“北京今天多少度?”,“上次查过”这个信息并不会自动出现在第二次请求里。
要让 Agent 记住前面对话,最简单粗暴的方式是把历史消息一起传给下一次调用。LangChain 早期版本里常用 ConversationBufferMemory,也就是把之前的 user 消息和 ai 消息全部拼接成一个列表,作为上下文传入。
LangGraph 的写法更严谨。它会维护一个 State,把 messages 一直累加。配合 checkpointer,还能在多次请求之间保存和恢复状态。这里的关键点在于:Agent 记忆的本质,是把历史消息维护在状态里,而不是某个神秘模型自动记住了你的话。
4.2 任务规划:先拆步骤,再逐个执行
单轮问答里,模型只需要回答一个问题。但复杂任务不一样,比如“帮我整理一份这周项目周报,并翻译成英文发到群里”。如果让 Agent 一次性做完,它会很吃力,而且容易漏步骤。
所以任务规划(Planning)在 Agent 执行流程里很重要。常见做法是 Plan-and-Execute:
- 第一个 Agent(或者同一条消息里)先生成一个计划。
- 计划里包含多个步骤,每个步骤可能对应一个工具调用。
- 执行器按顺序执行计划,每执行完一步,把结果放回上下文。
- 最后汇总所有结果,生成最终回答。
LangChain 没有把计划能力写得特别复杂,它本质上是提示词工程:让模型先把任务拆成步骤列表,再让执行器逐步执行。LangGraph 里可以用节点实现类似效果,一个节点负责规划,一个节点负责执行。
4.3 主从 Agent:子 Agent 本质上是一个特殊 Tool
现在网上讨论很多的主从 Agent 模式,核心思路是:主 Agent 负责接收用户任务、拆分目标、决定下一步要调谁;子 Agent 负责解决某个具体子任务。执行逻辑上,子 Agent 往往不是被主 Agent 直接“写代码调用”的,而是被注册成了一个 Tool。
也就是说,主 Agent 眼中只有一个一个工具,其中一个工具叫 call_sub_agent。当主 Agent 觉得该处理“客服对话”这类复杂问题时,就调用这个工具,实际执行的是另一个 Agent 的完整流程。
这个设计的价值在于:主 Agent 不需要理解子 Agent 的内部实现,只需要知道这个工具能完成什么任务、需要什么参数、返回什么结果。 这也是为什么很多框架里,多 Agent 复用单 Agent 的协议,把 Agent 能力工具化。
5. LangChain 和 LangGraph,Agent 执行流程的新旧形态
5.1 AgentExecutor 是旧形态,LangGraph 是新的编排方式
很多初学者会有一个疑问:LangChain 和 LangGraph 到底什么关系?我没见 LangChain 过时,只是 Agent 执行流程的编排方式变了。
传统写法里的 AgentExecutor 是一个封装好的执行器,内部实现了 ReAct 循环。它操作简单,适合教学,但缺点是流程固定,不容易在中间插入人工确认、条件分支、错误重试。
LangGraph 把执行流程重新定义成一张图。节点是动作,边是状态流转条件。你可以明确写出:
- 模型判断需要调用工具,就走到
call_tool节点。 - 工具调用完成,回到模型节点,再判断是否继续。
- 如果达到结束条件,走到结束节点。
这种形态最大的改进是可控。Agent 执行不再是黑盒循环,每一步都对应一个节点状态,中间还能插入人工确认节点。
5.2 两种执行流程的对比
| 对比项 | AgentExecutor | LangGraph |
|---|---|---|
| 流程表达 | 封装好的循环逻辑 | 显式的节点和条件边 |
| 中间介入 | 较难插入人工确认 | 可以设置中断节点 |
| 状态管理 | 需要单独维护 memory | 原生 State 和 checkpointer |
| 调试体验 | 靠 verbose 日志 | 可以逐节点查看状态 |
| 适用场景 | 学习、简单工具调用 | 生产级复杂流程 |
如果你只是快速验证十几个工具能不能跑通,用 AgentExecutor 完全够。但你要是做的是一个带多轮记忆、需要人工审批、或者要多 Agent 协作的真实项目,我更建议直接切换到 LangGraph。
5.3 先学哪一种
我的建议是:先学 AgentExecutor 理解执行流程,再学 LangGraph 做工程落地。
原因是两者的核心循环逻辑一样:模型决策 -> 工具调用 -> 结果回填 -> 再决策。你先在简单接口里搞清楚这个循环,再去看 Graph 的节点图,会很容易理解。反过来,如果一开始就上 LangGraph,容易被节点、边、状态、checkpointer 这些概念绕晕。
6. 从单任务到批量任务,执行链路要补齐哪些工程能力
6.1 单个 Agent 能跑,不代表批量任务也能跑
Demo 跑通之后,很多人会急着把几十个文件、几百条问题一次性丢给 Agent。结果不是 API 限流,就是任务互相串数据,或者某个长任务卡住整个队列。
批量任务不是简单 for 循环。你需要先想清楚几个问题:
- 每条任务最长允许执行多久?
- 单条任务失败后是跳过,还是重试?
- 并发量开到多少,不会触达 API 限流?
- 输出结果怎么命名,怎么保证不覆盖?
- 日志怎么区分哪一条输入对应哪一条输出?
我的习惯是:先用 3 到 5 条样例跑一轮,确认输入输出格式一致,再逐步增加并发。
6.2 资源占用和成本怎么评估
Agent 和普通 LLM 调用的最大区别是:一次完整任务可能触发多次模型调用。
| 任务类型 | 模型调用次数 | 工具执行次数 |
|---|---|---|
| 普通问答 | 1 次 | 0 次 |
| 单工具查询 | 至少 2 次 | 1 次 |
| 多步骤规划 | 多次 | 多次 |
所以评估成本时,不能只看单次模型调用的价格,要估算平均每个任务需要多少次模型调用。max_iterations 参数非常关键,它限制了 Agent 最多循环多少轮。设得太小,复杂任务容易半途而废;设得太大,模型异常时可能一直在循环,白白烧 token。
资源占用方面,LangChain 框架本身很轻,CPU 和内存开销都不大。真正吃资源的是模型推理这一环。如果接的是云端 API,瓶颈通常在网络延迟和并发限制;如果跑本地模型,瓶颈才是 GPU 显存和算力。
6.3 超时和重试要单独设计
线上 Agent 服务最常见的现象是执行超时。模型响应慢、工具接口慢、或者外部服务不稳定,都可能导致整条任务超时。这个问题看起来像执行器的问题,实际上要在工程层做处理。
通用做法是:
tenacity 是 Python 里很常用的重试库。遇到网络抖动或者 API 限流时,指数退避重试能有效提高成功率。但要注意,不是所有失败都适合重试。如果报错原因是参数错误、工具不存在,重试再多次也没用,只会浪费成本。
注意:不要一上来就开最大并发。先用小批量确认单条任务的耗时和成功率,再逐步提高并发数。
7. 常见报错与排查顺序,别一卡住就怀疑模型
7.1 报错类型和第一反应
我把平时遇到的 Agent 报错分成几类,每一类都有明确的排查方向:
| 现象 | 优先排查项 | 常见原因 |
|---|---|---|
| 工具调用后报错 | 工具本身的输入参数和返回值 | 必须参数没传、类型不对、函数内部异常 |
| Agent 反复重试同一个工具 | 工具描述和参数 Schema | 描述含糊,模型不知道参数怎么填 |
| 输出一直为空 | 日志里的模型原始输出 | 模型生成了空内容,或者被系统提示词限制 |
| 任务卡住不结束 | token 消耗和 max_iterations | 模型陷入循环,没有终止条件 |
| 第一次能跑,批量跑就失败 | API 限流、任务并发、环境变量 | 并发过高,或者每次任务间共享了可变状态 |
7.2 排查顺序:先日志,再输入,再环境
遇到问题,我一般不会第一时间改参数。先看日志,再看输入,再看环境,最后才轮到模型和框架。
第一步,打开 verbose 日志,确认模型在每个环节输出了什么。很多时候报错信息还没出现,日志里已经能看到模型选错了工具。
第二步,检查输入。用户消息格式是否正确,历史消息里有没有混入不该有的内容,工具函数是否被正确注册。
第三步,检查环境。依赖版本是否和代码示例一致,API Key 是否有效,网络是否能访问模型服务,输出目录是否可写。
最后才考虑是模型能力不足还是框架版本问题。大部分情况下,问题出在前三步。
7.3 一个典型的排查案例
假设你发现 Agent 调用天气工具时,报错 get_city_temperature() missing required argument: city。
先看 verbose 日志,确认模型生成的 Action Input 是空字符串。这时候不要急着骂模型,而是去查工具描述。如果描述里只写了“根据城市名称返回温度”,没有举例说明参数必须是字符串,模型很可能在犹豫后传了一个空值。
把描述改成“根据城市名称返回当前温度,单位是摄氏度,city 参数必须是城市中文名,例如:北京”,再跑一次,大概率就正常了。
这类问题不是模型蠢,而是你没有把工具说明书写清楚。
8. 学完执行流程之后,往真实 Agent 项目走的路
8.1 从 Demo 到项目的路线
学完 Agent 执行流程,下一步不是去写一个超复杂的多 Agent 系统,而是按下面的顺序逐步加能力:
- 单 Agent 单工具:把基本功练扎实。
- 单 Agent 多工具:学会让模型根据任务选择不同工具。
- 加入记忆:让 Agent 能记住前几轮对话。
- 加入人工确认:在关键步骤前让用户确认,而不是让模型自作主张。
- 加入规划:让 Agent 先拆步骤,再逐步执行。
- 加入主从模式:把子 Agent 封装成工具,交给主 Agent 调度。
每一步都先跑通最小样例,再扩大范围。不要一步跳到第 6 步。
8.2 几个值得自检的问题
如果你准备面试,或者想判断自己是不是真懂了,可以拿这几个问题自查:
- Agent 执行流程里有哪几个关键环节?
- LangChain 和 LangGraph 在 Agent 编排上的核心区别是什么?
- 工具描述对模型行为有什么影响?
- 记忆在 Agent 里如何实现,为什么不是模型自动记得?
- 主从 Agent 为什么经常把子 Agent 当作 Tool 调用?
- Tool、Skill、MCP 有什么区别?Tool 是最小的可调用函数单元,Skill 是面向特定任务组合出来的能力包,MCP 更偏一种标准化工具接入协议。三者定位不同,不冲突。
把这些问题的答案用自己的话讲清楚,比背一堆概念有用得多。
我自己带过的项目里,凡是 Agent 线上出问题的,回溯到最后,往往不是模型不够强,而是执行流程里某个环节没处理干净:工具描述含糊、超时策略缺失、状态没保存、并发开太高。把执行流程这条主线理顺,这些问题至少能避开一半。