🎯 Agent 实战 ★
LangChain 系统的收官章!前 7 章我们学了所有零件——现在用 create_tool_calling_agent + AgentExecutor 把它们组装成一个完整可运行的 Agent。你将亲手跑通 F3 章画的那个 Agent 循环图。
本章目标
- 用
create_tool_calling_agent创建 Agent(把 model + tools + prompt 串成 Runnable) - 理解
AgentExecutor如何自动驱动 AgentAction/Finish 循环 - 跑通一个真实的工具调用 Agent,观察完整的"思考-行动-观察"轨迹
- 掌握
max_iterations、return_intermediate_steps等关键参数 - 认识到 AgentExecutor 的局限——为 LangGraph 阶段(第三阶段)做铺垫
两个核心组件
构建一个 LangChain Agent 需要两个组件,分工明确:
create_tool_calling_agent 返回的 agent 只负责一步:看输入,输出 Action 或 Finish。它不会自己循环。真正反复调用它、执行工具、喂回结果的,是 AgentExecutor。这个分工很重要。
create_tool_calling_agent:组装单步大脑
看源码,它把前 7 章的零件组装成一条 LCEL 管道:
def create_tool_calling_agent(
llm: BaseLanguageModel,
tools: Sequence[BaseTool],
prompt: ChatPromptTemplate,
*,
message_formatter: MessageFormatter = format_to_tool_messages,
) -> Runnable:
"""创建一个使用工具的 agent。
Args:
llm: 作为 agent 大脑的 LLM
tools: agent 可用的工具
prompt: 提示模板 —— 【必须】包含 agent_scratchpad 变量
Returns:
一个 Runnable:输入 prompt 变量+历史,输出 AgentAction 或 AgentFinish
"""
# 校验 prompt 必须有 agent_scratchpad(LC7 讲过为什么)
missing_vars = {"agent_scratchpad"}.difference(prompt.input_variables)
if missing_vars:
raise ValueError(f"Prompt 缺少必需变量: {missing_vars}")
# 必须支持 bind_tools(LC3 学过)
if not hasattr(llm, "bind_tools"):
raise ValueError("LLM 必须实现 bind_tools()")
# 内部组装:本质是一条 LCEL 管道(LC5)
# prompt(含 scratchpad)| bind_tools(model) | 解析出 Action/Finish
llm_with_tools = llm.bind_tools(tools)
agent = (
{
# 把 intermediate_steps(历史步骤)转成消息,塞进 scratchpad
"agent_scratchpad": lambda x: format_to_tool_messages(x["intermediate_steps"]),
"input": lambda x: x["input"],
"chat_history": lambda x: x.get("chat_history", []),
}
| prompt
| llm_with_tools
| ToolCallingAgentOutputParser() # 解析 AIMessage → AgentAction/Finish
)
return agent
看,agent 本质就是 LC5 的 LCEL 管道!核心是 format_to_tool_messages 把历史步骤(AgentStep 列表)转成 scratchpad 消息(LC7 讲的 .messages 互转)。
AgentExecutor:循环发动机
AgentExecutor 的核心是一个 while 循环(伪代码还原,源码在 langchain_classic/agents/agent.py):
class AgentExecutor(Chain):
agent: Runnable # create_tool_calling_agent 的返回值
tools: Sequence[BaseTool]
max_iterations: int = 15 # 步数上限(防死循环,对应 F3 章)
def _call(self, inputs):
intermediate_steps = [] # AgentStep 历史列表(LC7)
for iteration in range(self.max_iterations): # F3 的"步数上限"停止条件
# ① Plan:调 agent(单步大脑),拿到 Action 或 Finish
output = self.agent.invoke({
**inputs,
"intermediate_steps": intermediate_steps,
})
if isinstance(output, AgentFinish): # ② Finish → 循环结束
return output.return_values # F3 的"自然完成"停止条件
# ③ Action → 执行工具
action = output # AgentAction
tool = find_tool(self.tools, action.tool)
observation = tool.invoke(action.tool_input)
# ④ Update:记录这一步,进入下一轮
intermediate_steps.append(AgentStep(action=action, observation=observation))
return {"output": "达到最大步数限制"}
上面这个循环完美对应 F3 章的 Plan → Execute → Update 三阶段:
- ① Plan:调
self.agent,组装 prompt + scratchpad - ②③ Execute:判断 Action/Finish;执行工具
- ④ Update:把 AgentStep 加入 intermediate_steps
完整实战代码
需要 OpenAI API Key。pip install langchain langchain-openai。这段代码会展示 Agent 完整的"思考-行动-观察"轨迹。
# pip install langchain langchain-openai
# export OPENAI_API_KEY="sk-..."
from langchain_classic.agents import (
create_tool_calling_agent, AgentExecutor, tool,
)
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
# ============ ① 定义工具(LC6 的 @tool)============
@tool
def add(a: int, b: int) -> int:
"""计算两个数的和。用于加法运算。"""
return a + b
@tool
def multiply(a: int, b: int) -> int:
"""计算两个数的乘积。用于乘法运算。"""
return a * b
@tool
def get_word_length(word: str) -> int:
"""返回一个单词的字符长度。"""
return len(word)
tools = [add, multiply, get_word_length]
# ============ ② 定义 prompt(LC2 + LC7 的 scratchpad)============
# 注意必须包含 agent_scratchpad 占位符!
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个数学和文字助手。你可以使用工具。"),
("placeholder", "{chat_history}"), # 可选:对话历史
("human", "{input}"),
("placeholder", "{agent_scratchpad}"), # 必须!历史步骤注入这里
])
# ============ ③ 创建 agent(单步大脑)+ executor(循环发动机)============
model = ChatOpenAI(model="gpt-4o-mini")
agent = create_tool_calling_agent(model, tools, prompt)
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True, # 打印每一步的思考过程!
max_iterations=10, # 步数上限
return_intermediate_steps=True, # 返回中间步骤(调试用)
)
# ============ ④ 运行!观察完整的循环轨迹 ============
result = agent_executor.invoke({
"input": "(3加5)乘以单词hello的长度,结果是多少?"
})
print("\n===== 最终结果 =====")
print("答案:", result["output"])
print("\n===== 中间步骤(Agent 的思考轨迹)=====")
for i, step in enumerate(result["intermediate_steps"], 1):
action = step.action
print(f"第{i}步: 调用 {action.tool}({action.tool_input}) → {step.observation}")
# 典型输出(verbose 会打印详细过程):
# > Entering new AgentExecutor chain...
# 第1步: 调用 add({'a': 3, 'b': 5}) → 8
# 第2步: 调用 get_word_length({'word': 'hello'}) → 5
# 第3步: 调用 multiply({'a': 8, 'b': 5}) → 40
# 答案: 结果是 40
# > Finished chain.
AgentExecutor 关键参数
| 参数 | 默认值 | 作用 |
|---|---|---|
max_iterations | 15 | 步数上限,防止死循环(F3 章的停止条件②) |
verbose | False | 打印每步思考过程,调试必备 |
return_intermediate_steps | False | 返回中间步骤列表,便于分析 |
handle_parsing_errors | False | 模型输出格式错误时如何处理 |
early_stopping_method | "force" | 达到步数上限时如何收尾 |
AgentExecutor 的局限
恭喜你已经能构建 Agent 了!但 AgentExecutor 有明显局限,这正是第三阶段 LangGraph 要解决的:
| 局限 | LangGraph 如何解决 |
|---|---|
| 状态管理简单(只有 intermediate_steps 列表) | 显式 State,支持任意字段、reducer 聚合(LG1-LG2) |
| 无法中途暂停/恢复 | Checkpointer 持久化,支持 interrupt + 恢复(LG6) |
| 控制流固定(线性循环) | 图建模,条件边/循环/并行任意编排(LG3) |
| 无法人在回路(执行前需批准) | interrupt_before + Command(resume=)(LG6) |
| 流式只支持文本 | 7 种 StreamMode,节点级流式(LG7) |
| 难以构建多 Agent 协作 | 子图、Send 并行、supervisor 模式 |
AgentExecutor 的本质是 "agent 单步决策 + while 循环"。当需求变复杂(需要状态、需要中断、需要复杂控制流),这种"隐式循环"就力不从心了。LangGraph 的思路是把循环和控制流显式建模成"图"——节点是处理函数,边是控制流。这是质的飞跃。
与生产实践对照
| LangChain 概念 | OpenCode 对应 | |
|---|---|---|
| create_tool_calling_agent(单步) | → | runLoop 内单次 handle.process()(调一次 LLM) |
| AgentExecutor(循环发动机) | → | runLoop 的 while(true)(session/prompt.ts:1081) |
| intermediate_steps 历史 | → | Session 数据库里的 Message + Part 持久化历史 |
| max_iterations 步数限制 | → | agent.steps 配置 + doom_loop 检测 |
| verbose 思考过程 | → | 流式输出实时显示(text-delta/tool-call 事件) |
| 无法人在回路 | → | Permission.ask(每次危险工具都询问,OC4) |
LangChain 阶段总结
恭喜你系统学完了 LangChain 的 8 个核心模块。回顾依赖链,你已经掌握:
- 消息(LC1)—— 与 LLM 交流的原子
- 提示(LC2)—— 声明式构造消息序列
- 模型(LC3)—— 统一模型抽象 + bind_tools
- 解析(LC4)—— 把输出变成结构化数据
- LCEL(LC5)★ —— 用
|组合一切,灵魂章节 - 工具(LC6)—— @tool 让函数变工具
- Schema(LC7)—— AgentAction/Finish 循环本质
- Agent(LC8)★ —— 组装成完整可运行 Agent
这些是构建任何 LLM 应用的积木式基础。接下来第三阶段,LangGraph 会把这些积木升级为有状态、可中断、可恢复、可并行的图。
小结
create_tool_calling_agent组装"单步大脑"(本质是 LCEL 管道);AgentExecutor是"循环发动机"。- AgentExecutor 的核心是 Plan→Execute→Update 循环,完美对应 F3 章的三阶段模型。
- Agent 自主决定执行顺序和何时结束——这就是 Agent 的"控制权反转"。
- AgentExecutor 的局限(状态/中断/控制流/人在回路)正是 LangGraph 要解决的。
进入 第三阶段 · LangGraph 系统!第一站 LG1 · StateGraph 基础:告别 AgentExecutor 的隐式循环,学习用图显式建模 Agent 的控制流。5 行代码定义你的第一个状态图。