🎯 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_iterationsreturn_intermediate_steps 等关键参数
  • 认识到 AgentExecutor 的局限——为 LangGraph 阶段(第三阶段)做铺垫

两个核心组件

构建一个 LangChain Agent 需要两个组件,分工明确:

create_tool_calling_agent() → 返回一个 Runnable("单步决策器") 输入:prompt 变量 + 历史 输出:AgentAction 或 AgentFinish "这一步该做什么?" AgentExecutor → 驱动循环的"发动机" 反复调用 agent 执行工具、喂回结果、直到 Finish "循环跑到结束" 装入
图 LC8.1 · agent 是"单步大脑",executor 是"循环发动机"
📌 关键区分

create_tool_calling_agent 返回的 agent 只负责一步:看输入,输出 Action 或 Finish。它不会自己循环。真正反复调用它、执行工具、喂回结果的,是 AgentExecutor。这个分工很重要。

create_tool_calling_agent:组装单步大脑

看源码,它把前 7 章的零件组装成一条 LCEL 管道:

📄 langchain_classic/agents/tool_calling_agent/base.py:18 · create_tool_calling_agent python
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):

📄 AgentExecutor 的核心循环(伪代码还原) python
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 章的三阶段模型

上面这个循环完美对应 F3 章的 Plan → Execute → Update 三阶段:

  • ① Plan:调 self.agent,组装 prompt + scratchpad
  • ②③ Execute:判断 Action/Finish;执行工具
  • ④ Update:把 AgentStep 加入 intermediate_steps

完整实战代码

🚀 跑通一个真实 Agent

需要 OpenAI API Key。pip install langchain langchain-openai。这段代码会展示 Agent 完整的"思考-行动-观察"轨迹。

📄 lc8_agent.py · 完整 Agent 实战 python
# 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.
Agent 的实际运行轨迹(3 步循环) 用户:"(3加5)乘以单词hello的长度?" 第1步: add(3,5)→8 (模型决定先算加法) 第2步: get_word_length(hello)→5 (模型决定查长度) 第3步: multiply(8,5)→40 → Finish: "结果是40" (模型决定结束)
图 LC8.2 · Agent 自主决定了执行顺序和何时结束——这就是 F1 章的"控制权反转"

AgentExecutor 关键参数

参数默认值作用
max_iterations15步数上限,防止死循环(F3 章的停止条件②)
verboseFalse打印每步思考过程,调试必备
return_intermediate_stepsFalse返回中间步骤列表,便于分析
handle_parsing_errorsFalse模型输出格式错误时如何处理
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 个核心模块。回顾依赖链,你已经掌握:

  1. 消息(LC1)—— 与 LLM 交流的原子
  2. 提示(LC2)—— 声明式构造消息序列
  3. 模型(LC3)—— 统一模型抽象 + bind_tools
  4. 解析(LC4)—— 把输出变成结构化数据
  5. LCEL(LC5)★ —— 用 | 组合一切,灵魂章节
  6. 工具(LC6)—— @tool 让函数变工具
  7. Schema(LC7)—— AgentAction/Finish 循环本质
  8. Agent(LC8)★ —— 组装成完整可运行 Agent

这些是构建任何 LLM 应用的积木式基础。接下来第三阶段,LangGraph 会把这些积木升级为有状态、可中断、可恢复、可并行的图。

小结

下一阶段 · LangGraph

进入 第三阶段 · LangGraph 系统!第一站 LG1 · StateGraph 基础:告别 AgentExecutor 的隐式循环,学习用显式建模 Agent 的控制流。5 行代码定义你的第一个状态图。