如果你是刚开始接触 Agent 开发,大概率遇到过这种场景:一段“模型调用工具”的示例代码能跑通,但一旦任务变成“先查天气、再查航班、最后形成建议”,Agent 就会乱掉;或者答案看起来合理,却说不清它中间到底调用了几次工具、为什么这么调用。
项目标题里的langchain-6-9不用纠结是版本号还是课程目录编号,真正值得搞清楚的是:LangChain Agent 从接收用户输入,到最终返回结果,中间到底发生了什么。只有把执行流程拆解清楚,你才能真正控制 Agent,而不是靠运气调 Prompt。
先给一个判断:LangChain Agent 的核心不是“给模型加 tool”这个静态动作,而是一个循环调度机制。模型每一步只负责“决策”,真正执行外部动作的是工具,而把这些环节串起来的,是一套明确的执行框架。理解并掌控这个循环,是后续 Agent 开发、调试、面试和生产落地的基础。
文章会从基础概念讲到完整工作流,再给一个最小可运行示例,最后补充 LangGraph、常见报错排查和工程建议。建议收藏后跟着代码跑一遍。
1. 这篇文章真正要解决的问题
搜索“langchain agent 执行流程”的人,通常不是想知道 Agent 的定义,而是遇到了下面这些具体问题:
- 为什么我的 Agent 有时候调用工具,有时候不调用?
- Agent 和普通的大模型 Function Calling 到底有什么区别?
- 一次任务中 Agent 可以调用多少次工具?谁来决定停下来?
agent_scratchpad、intermediate_steps、AgentAction、AgentFinish分别是什么?- 既然已经有了 AgentExecutor,为什么社区又在推荐 LangGraph?
- 面试官问“Agent 的执行流程”,应该怎么回答?
这些问题背后都指向同一个核心:你需要对大模型、工具、执行器三者的协作顺序有确定性理解。
这篇文章不打算讲太多 Agent 理论,而是直接拆执行链路,然后给你一份能跑通的代码,再通过代码运行结果反推内部机制。适合以下读者:
- 刚学 LangChain,想搞懂 Agent 而不是停留在“会调 API”;
- 已经在项目里使用 AgentExecutor,但遇到迭代次数超限、解析失败、工具参数错误等问题;
- 准备 Agent 方向面试,需要系统梳理执行流程;
- 从传统链路(Prompt + LLM)转向 Agent 开发,想建立正确的抽象认知。
读完之后,你应该能回答三个问题:一次 Agent 调用经历了哪些阶段;中间状态存储在哪里;怎么调整执行过程来规避常见坑。
2. 基础概念:Agent、Tool、LLM、AgentExecutor
2.1 一句大白话理解 Agent
Agent 可以理解为“大模型 + 工具 + 循环决策”的组合。
普通 LLM 调用是“输入文本 -> 输出文本”,模型没有能力查数据库、调接口、执行代码。LangChain Agent 做的事情是:让模型在每一步决定“下一步做什么”,如果需要外部信息,就调用工具;拿到工具结果后,再判断是继续调用工具,还是整理结果回答用户。
所以 Agent 不是单个模型请求,而是一连串模型请求和工具调用的循环。
2.2 四个核心角色
| 概念 | 作用 | 类比 |
|---|---|---|
| LLM | 负责理解任务、生成决策,是“大脑” | 项目负责人 |
| Tool | 负责执行具体动作,比如查天气、搜索、执行 SQL | 一线执行员工 |
| Agent | 负责组织 Prompt、解析模型输出、决定调用哪个工具 | 中间协调层 |
| AgentExecutor | 负责驱动“思考 -> 行动 -> 观察”的循环,直到完成 | 项目进度盯办人 |
在实际代码里,create_openai_tools_agent会创建一个 Agent,AgentExecutor再把这个 Agent 和工具列表组合成可执行对象。很多初学者把 AgentExecutor 误认为“Agent”,严格来说它是执行器,不是决策器。
2.3 它和普通 Function Calling 的区别
Function Calling 是模型能力的一种:模型可以根据用户问题输出一个函数名和参数,比如get_weather(city="北京")。但模型只输出一次调用,不会自己拿着结果继续推理。
Agent 的差异在于:
- 模型可以连续多轮输出工具调用;
- 每一轮工具结果都会作为文本回填到下一轮 Prompt 中;
- 当模型认为信息足够时,才输出最终回答。
换句话说,Recursive 循环 + 中间状态维护,是 Agent 执行流程的关键。
3. 核心执行流程拆解:从用户输入到最终回答
不管用经典的AgentExecutor,还是新的LangGraph,Agent 的底层流程都遵循下面这个模式:
3.1 一次完整 Agent 调用的 8 个阶段
| 阶段 | 发生了什么 | 关键数据 |
|---|---|---|
| 1. 接收输入 | 用户提交 Query | input |
| 2. 组装 Prompt | 把 System Prompt、历史对话、用户输入、之前中间步骤组成新 Prompt | agent_scratchpad |
| 3. 模型决策 | LLM 输出“动作”或“最终答案” | AgentAction或AgentFinish |
| 4. 输出解析 | Agent 解析模型返回的文本或结构化输出 | 解析后的 Action |
| 5. 工具调用 | 根据 Action 选择并执行工具 | tool.run(tool_input) |
| 6. 生成观察 | 工具返回结果字符串,称为 Observation | Observation |
| 7. 状态回填 | 把(AgentAction, Observation)追加到中间步骤 | intermediate_steps |
| 8. 循环判断 | 如果是AgentFinish则结束,否则回到第 2 步 | max_iterations |
输入 -> 格式化 Prompt -> LLM 决策 -> 解析输出 -> 调用工具 -> 得到 Observation -> 追加中间状态 -> 继续循环 -> 直到 AgentFinish3.2AgentAction和AgentFinish
这两个类是整个执行流程的“出口信号”。
AgentAction(tool="get_weather", tool_input={"city": "北京"}, log="..."):表示模型决定调用工具。AgentFinish(return_values={"output": "今天晴,适合出门"}, log="..."):表示模型认为可以结束,直接返回答案。
执行器拿到AgentAction就调用工具;拿到AgentFinish就退出循环。理解这一点后,再看intermediate_steps就很简单了:它就是一个(AgentAction, Observation)的列表,记录整条决策链。
3.3 常见 Agent 范式
LangChain 里的 Agent 不只有一种范式。面试和实际开发中常提的有:
| 范式 | 思路 | 适合场景 |
|---|---|---|
| ReAct | 每一轮输出 Thought / Action / Observation | 需要逐步推理的工具调用 |
| OpenAI Tools | 使用原生 Function Calling 结构化调用 | 依赖 OpenAI、通义、智谱等大模型平台 |
| Plan-and-Execute | 先规划一个多步计划,再逐步执行 | 复杂任务、可拆解业务 |
| Conversational | 在 ReAct 基础上增加对话记忆 | 需要多轮聊天 + 工具 |
大多数 LangChain 入门示例使用的create_openai_tools_agent本质上是“OpenAI Tools 风格 + ReAct 循环”的组合。LangChain 中 Agent 可以用不同范式,关键是模型能力是否匹配,以及工具描述是否足够清晰。
4. 环境准备与最小示例
下面用一个最小可运行示例,把“执行流程”变成可见的日志和中间步骤。
4.1 安装依赖
pip install langchain langchain-openai python-dotenv如果你的环境还没有配置OPENAI_API_KEY,可以通过环境变量或.env文件设置:
export OPENAI_API_KEY=你的_api_key本文使用 OpenAI 兼容接口的ChatOpenAI。如果你使用其他模型平台,可以把模型类和地址换成平台对应的 LangChain 集成,执行流程思路不变。
4.2 编写最小 Agent
# 文件路径:agent_demo.py import os from dotenv import load_dotenv from langchain.agents import AgentExecutor, create_openai_tools_agent from langchain.tools import tool from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain_openai import ChatOpenAI load_dotenv() @tool def get_weather(city: str) -> str: """查询某个城市的天气。参数 city 是城市名,例如“北京”。""" # 演示用,生产环境请接入真实天气服务 return f"{city} 当前晴,温度 26 度" tools = [get_weather] llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) prompt = ChatPromptTemplate.from_messages( [ ("system", "你是一个友好的助手。请使用提供的工具回答问题。"), ("human", "{input}"), MessagesPlaceholder(variable_name="agent_scratchpad"), ] ) agent = create_openai_tools_agent(llm, tools, prompt) agent_executor = AgentExecutor( agent=agent, tools=tools, verbose=True, return_intermediate_steps=True, max_iterations=5, ) if __name__ == "__main__": result = agent_executor.invoke({"input": "北京今天适合出门吗?需要先查一下天气。"}) print("\n=== 中间步骤 ===") for i, (action, observation) in enumerate(result["intermediate_steps"], 1): print(f"第{i}步:工具={action.tool},参数={action.tool_input},观测={observation}") print("\n=== 最终回答 ===") print(result["output"])这段代码里有两个关键点:
agent_scratchpad是模型之前产生的 Action 和 Observation 的载体,必须放在 Prompt 中,否则执行器无法回填中间状态。return_intermediate_steps=True会让我们拿到(AgentAction, Observation)列表,这是观察执行流程的直接入口。
4.3 运行与验证
python agent_demo.py正常执行时,verbose=True会在控制台输出内部决策过程。你可能会看到类似内容:
> Entering new AgentExecutor chain... Invoking: `get_weather` with `{'city': '北京'}` 北京 当前晴,温度 26 度 > Finished chain. === 中间步骤 === 第1步:工具=get_weather,参数={'city': '北京'},观测=北京 当前晴,温度 26 度 === 最终回答 === 北京当前晴天,适合出门。这里的“中间步骤”就是 Agent 完整执行链路的直接证据:模型先决定调用天气工具,拿到观测后,再根据观测生成最终回答。
如果你的模型没有调用工具,直接返回了答案,需要先检查工具描述是否清晰、System Prompt 是否要求它使用工具,以及模型本身是否支持 Function Calling。
5. 通过 intermediate_steps 理解执行流程的细节
真实项目里,Agent 不会只调用一次工具。一次复杂的任务可能包括:搜索资料 -> 调用计算器 -> 查询数据库 -> 汇总答案。
intermediate_steps会按执行顺序记录每一步:
for i, (action, observation) in enumerate(result["intermediate_steps"], 1): print(f"第{i}步:工具={action.tool}") print(f"参数={action.tool_input}") print(f"模型日志={action.log}") print(f"观察结果={observation}") print("---")如果 Agent 第一次调用search,第二次调用calculate,那么你会在intermediate_steps中看到两条记录。这个列表也是排错的核心依据:你可以拿到模型的第一步决策、工具返回、第二步决策,全程可回放。
这里真正容易踩坑的地方是:action.log和observation都可能很长。尤其是在接数据库、读文件、调用外部 API 时,工具返回大段文本会迅速占用上下文窗口。后面章节会讲怎么控制。
6. LangGraph 与新一代执行流程
6.1 为什么 LangChain 社区转向 LangGraph
AgentExecutor把执行循环封装成了一个黑盒。它方便,但不够灵活。比如你想在“模型决策之后”插入一个人工确认环节,或者在工具执行前做权限检查,用AgentExecutor很难自然实现。
LangGraph 把执行流程显式建模为一张图:
agent节点:负责让模型决策;tools节点:负责执行工具;- 条件边:根据模型输出决定继续调用工具还是结束;
State:保存 messages 和中间状态,代替intermediate_steps。
因此,LangGraph 和 LangChain 的关系不是替代,而是编排层升级:LangChain 提供模型、提示词、工具集成;LangGraph 提供可控的“执行流程编排”。
6.2 AgentExecutor 与 LangGraph 执行流对比
| 对比维度 | 经典 AgentExecutor | LangGraph 执行流 |
|---|---|---|
| 执行逻辑 | 内部循环,黑盒 | 显式图结构,节点和边清晰 |
| 中间状态 | intermediate_steps列表 | 全局State,通常是 messages 列表 |
| 人工介入 | 难 | 可在任意节点之间插入中断 |
| 多 Agent 编排 | 吃力 | 容易 |
| 官方建议 | 适合学习和简单业务 | 新项目更推荐 |
从当前官方文档和社区趋势看,新项目可以优先考虑 LangGraph;但学习AgentExecutor对理解核心执行流程仍然非常有效。
6.3 LangGraph 最小示例
安装依赖:
pip install langgraph然后用create_react_agent快速创建一个执行图:
# 文件路径:langgraph_demo.py from dotenv import load_dotenv from langchain.tools import tool from langchain_openai import ChatOpenAI from langgraph.prebuilt import create_react_agent load_dotenv() @tool def get_weather(city: str) -> str: """查询某个城市的天气。参数 city 是城市名,例如“北京”。""" return f"{city} 当前晴,温度 26 度" llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) app = create_react_agent(llm, [get_weather]) result = app.invoke({"messages": [("user", "北京今天适合出门吗?")]}) for message in result["messages"]: print(f"{message.type}: {message.content}")在这个示例里,create_react_agent内部构建了一张图,result["messages"]保存了完整调用链:用户消息、模型行动、工具结果、最终回答。它比intermediate_steps更直观,因为每一步都作为消息保存在状态里。
7. 常见问题与排查思路
在实际开发里,Agent 跑不起来或者结果不对,大多不是模型问题,而是执行流程中的某一环出错。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| Agent 返回 “provider did not respond in time” | 模型服务或 Agent 执行服务响应超时 | 看服务方状态页、检查请求日志 | 设置更大的超时时间,检查模型服务可用性,增加重试 |
报错Could not parse LLM output | 模型输出不符合 Action 格式 | 打开verbose=True查看原始输出 | 改用 Function Calling 类 Agent,或优化输出解析器 |
| 达到最大迭代次数后停止 | 任务太复杂或工具没有提供足够信息 | 检查intermediate_steps是否在重复同一工具 | 增加max_iterations,优化工具描述,让工具返回更结构化 |
| 工具参数乱码或缺少字段 | 工具函数签名不明确,模型猜错了参数 | 打印模型输出的tool_input | 给工具函数写清楚 docstring,尽量使用 pydantic 定义入参结构 |
| Observation 太长导致上下文爆炸 | 工具返回大段文本或日志 | 查看工具返回内容长度 | 在工具内部截断、聚合、只返回关键信息和摘要 |
| 模型不调用工具直接回答 | Prompt 没有强调使用工具,或工具列表太多 | 看模型输出是否直接是答案 | System Prompt 中明确要求,减少无用工具,给工具加示例 |
| 工具执行报错但没有被捕获 | 工具内部抛异常,执行器中断 | 查看工具日志 | 在工具内部捕获异常并返回错误字符串,而不是直接抛出 |
这里特别想强调一个认知:Agent 的“下一步”不是代码写死的,而是模型根据当前状态生成的。所以排查时不要只盯着模型,要检查工具返回的 Observation 是否能让模型做正确判断。如果工具返回“查询失败”,模型很可能继续重试同一个工具,直到迭代上限。
8. 最佳实践与工程建议
8.1 工具设计决定执行上限
工具是 Agent 感知外部世界的窗口。工具描述写得越清晰,模型越不会用错。
- 工具名必须动词开头,例如
search_news、get_user_order; - docstring 要写清楚参数含义和返回值样例;
- 工具返回值尽量精简,返回结构化文本或 JSON;
- 工具内部要做异常兜底,避免让模型看到异常堆栈。
# 不推荐 def tool_a(s: str) -> str: return query(s) # 推荐 @tool def get_user_order(user_id: str) -> str: """根据用户 ID 查询最近一笔订单。 参数 user_id 是用户唯一标识,例如 U12345。 返回 JSON 字符串:{"order_id": "...", "status": "..."} """ try: data = query_order(user_id) return json.dumps(data, ensure_ascii=False) except Exception as exc: return f"查询失败:{exc}"8.2 限制迭代,避免失控成本
生产环境一定要设置max_iterations,防止模型陷入死循环或疯狂调用工具。
对于 AgentExecutor:
AgentExecutor( agent=agent, tools=tools, max_iterations=5, early_stopping_method="generate", )对于 LangGraph,可以通过recursion_limit控制:
app.invoke( {"messages": [("user", "帮我分析这个日志")]}, config={"recursion_limit": 10}, )同时,建议给工具调用加计数器或日志。如果某个用户在短时间内触发大量工具调用,需要监测是否有异常请求。
8.3 日志与可观测性
不要只依赖最终答案判断 Agent 是否正确。
建议至少记录:
- 用户原始输入;
intermediate_steps或 LangGraph 的messages;- 每次工具调用的耗时和返回长度;
- 模型 token 消耗;
- 是否达到迭代上限。
这些日志既是排查依据,也是后续评估 Prompt 和工具质量的数据来源。
8.4 安全边界与最小权限
如果你的 Agent 可以操作数据库、删除文件、发消息,必须遵守最小权限原则。
- 不要用高权限账号执行 Agent 工具;
- 涉及删除、写入等危险操作,增加人工审批节点;
- 工具入参要做校验和过滤;
- 生产环境变更前必须先备份,并确保有回滚方案。
尤其在使用 LangGraph 时,可以通过 “human-in-the-loop” 在工具执行前暂停图,让用户确认后再继续。这是 AgentExecutor 不太好做、LangGraph 天生适合做的事情。
8.5 记忆与上下文控制
Agent 的记忆不应该无限增长。对话历史、中间步骤、工具返回值都会占用上下文。
工程上更推荐:
- 使用 LangGraph 的
Checkpointer持久化消息; - 对历史消息做滑动窗口截断;
- 对工具 Observation 做摘要;
- 避免把大日志、大文件全文塞给模型。
一句话总结:上下文长度是 Agent 的稀缺资源,不要让工具返回垃圾占据窗口。
9. 总结与应用建议
这篇内容真正想讲清楚的,是 LangChain Agent 的执行流程:模型负责决策,工具负责执行,执行器负责循环;每一步的决策和观察都会作为下一轮的输入,直到模型输出AgentFinish。
从实践角度看,建议你按这个顺序往下推进:
- 先跑通上面的最小示例,打开
verbose=True,观察每一轮输出; - 给 Agent 加第二个工具,让它在一次任务中连续调用多个工具,感受
intermediate_steps的变化; - 改造一版 LangGraph 示例,把执行图显式画出来,加深理解;
- 再学习 Agent 记忆、RAG 接入、MCP 工具协议、Skill 封装和多 Agent 编排。
Agent 开发走到后面,拼的并不是谁能把 Prompt 写得更漂亮,而是谁能把“模型决策 + 工具执行 + 状态管理 + 安全边界”这套循环控制得更稳。先把执行流程看透,后面学 LangGraph、MCP、多 Agent 都会轻松很多。
如果你正在准备面试,建议把“从输入到输出经历了哪些阶段”和“中间状态存在哪里”这两个问题作为主线讲清楚,基本就能覆盖大多数 Agent 执行流程的问题。