LangChain 第6章 Agent 智能体核心:create_agent 统一入口与 ReAct 循环

**摘要:**本文围绕 LangChain 1.0 的 Agent 智能体核心展开,重点讲解统一入口 create_agent 的用法、ReAct 循环(Thought--Action--Observation)的工作原理,以及工具绑定、递归限制、错误处理、LLM-as-judge、人工接管等生产级话题,并给出从最小可用到生产级的完整代码实战与面试高频考点。

目录

  • 一、本章定位
  • [二、Agent 的四要素](#二、Agent 的四要素)
  • [三、从 0.3 到 1.0:统一入口 create_agent](#三、从 0.3 到 1.0:统一入口 create_agent)
  • [四、绑定工具与 ReAct 循环](#四、绑定工具与 ReAct 循环)
  • [五、高阶参数 name 与 system_prompt](#五、高阶参数 name 与 system_prompt)
  • [六、常见问题 FAQ](#六、常见问题 FAQ)
  • 七、知识点速查
  • 深度拓展:本章完整知识体系与实战进阶
  • [深度拓展补充:进阶案例与 FAQ](#深度拓展补充:进阶案例与 FAQ)

LangChain 第6章 Agent 智能体核心:create_agent 统一入口与 ReAct 循环

一句话总结:1.0 用 create_agent 一个入口统一造智能体,模型当大脑、工具当手脚,Agent 自动跑完 Thought--Action--Observation 的 ReAct 循环,是全课最重要的一章。

📎 参考资料一:李沐深度学习191集课程全解析(模块拆解、学习路径)

李沐深度学习191集课程全解析:模块拆解、学习路径-CSDN博客

📎 参考资料二:吴恩达《面向开发者的提示词工程》

吴恩达《面向开发者的提示词工程》-CSDN博客

📎 参考资料三:吴恩达 MCP 教程(Model Context Protocol)

吴恩达 MCP 教程(Model Context Protocol)(一)-CSDN博客

📎 参考资料四:多模态大模型教程学习笔记(ViT · CLIP · SAM · GLIP · Stable Diffusion)

多模态大模型教程学习笔记 --- ViT · CLIP · SAM · GLIP · Stable Diffusion

本章可视化图谱

📎 知识图谱在线链接(点击打开 / 一键复制 / 分享他人):

https://mcnj5s80gkfh.doubaoapps.com/app/app_17fqgk5txwc

一、本章定位

第 51--63 集是全课正餐。AGI(通用人工智能)是 AI 的终极形态,而当下大模型工程应用的终极形态就是 Agent------以大模型为推理决策核心,结合记忆与工具,能规划、决策、执行复杂任务。前面学的模型、提示词、工具、结构化输出都是在给智能体赋能。

二、Agent 的四要素

要素 必要性 说明
行动 Action 必须存在 要有推理与产出
工具 Tools 几乎总是 极大拓展能力边界,让模型能动手
规划决策 有条件 复杂任务才需要拆解,简单任务可省
记忆 Memory 最易省略 但可理解为近乎必须;压缩后最核心 = 工具 + 记忆

此外,多智能体之间可以通信协作,是未来大模型应用开发的主战场。

三、从 0.3 到 1.0:统一入口 create_agent

0.3 时代要"具体问题具体分析":思维链建 ReAct agent、结构化建对应 agent,还要再套一层 AgentExecutor 才能 invoke,导致每种 agent 都要单独记 API 参数、多智能体难以统一调度、生态碎片化。1.0 用 create_agent 一个函数统一所有创建方式,其底层是 LangGraph 的 CompiledStateGraph,可用 get_graph().draw_mermaid_png() 查看结构图。

python 复制代码
from dotenv import load_dotenv
from langchain.agents import create_agent
load_dotenv()
model 方式一:字符串 provider:model(自动读 .env)
agent = create_agent(
model="deepseek:deepseek-chat",
tools=[get_weather],
system_prompt="你是天气助手,无关问题回答不知道",
)
result = agent.invoke({"messages": [{"role": "user", "content": "北京天气如何"}]})

model 支持两种传入:字符串 "provider:model"(底层自动读 .env 找 key)或模型对象(init_chat_model / ChatOpenAI 实例,便于精细配置)。

四、绑定工具与 ReAct 循环

工具绑定分静态(tools=...,本章)与动态(中间件,第 7 章)。工具可以自定义(@tool),也可以用内置工具,例如 langchain_community 的 TavilySearch 联网搜索(官网每月 1000 次免费,需 TAVILY_API_KEY,可设 max_results=2),一次可绑定多个。

  1. Thought 思考:模型分析,AIMessage 中带 tool_calls;

  2. Action 行动:Agent 自动执行对应工具;

  3. Observation 观察:工具返回 ToolMessage;

  4. 回灌再思考:结果喂回模型,可能继续调工具;

  5. 结束:AIMessage.tool_calls 为空,输出最终答案。

ReAct = Reasoning + Action,是一个会循环的过程。两种典型循环:工具递进调用(先 search_products 找最流行耳机,再 check_inventory 查库存);同一工具重试(工具前两次返回"天气服务暂不可用",Agent 反复重试到第三次成功)。

五、高阶参数 name 与 system_prompt

  • name:给 Agent 起名,主要用于多智能体区分,也用于流式输出来源标记、审计日志、前端角色展示与 LangSmith trace 可读性,体现在返回 AIMessage 的 name 字段。

  • system_prompt:可传字符串或 SystemMessage,写清角色定位、输出格式、何时使用工具;静态设置(动态靠中间件)。注意它不会出现在返回的 messages 里,响应从 human message 开始,system 在后台起约束作用。

  • 第 57--59 集讲 Agent 上的结构化输出 4 种策略与 ToolStrategy 的 schema 设置;第 60 集后讲流式输出及多种模式,最后以多功能智能助手实战收尾。

六、常见问题 FAQ

问题 原因 / 对策
如何选工具 靠 docstring/description 与问题描述匹配
为何没调工具 描述不清、问题表述模糊、或模型认为不需要
为何选错工具 多个工具描述太相似、工具太多;最佳同时给 2--5 个,边界写清,在 system prompt 说明场景
何时算完成 AIMessage.tool_calls 为空列表
为何提前结束 超时、达到 token 上限、模型自行终止
如何限制次数 invoke(config={"recursion_limit": 5})

第 53、57--63 集本地字幕未导出,本节据课程集目录与 LangChain 1.x 官方体系整理:53 为 invoke() 调用、57--59 为 Agent 结构化输出策略(ToolStrategy 的 Pydantic 及其它 schema)、60 集后为错误处理与流式输出、63 集为多功能智能助手实战。具体代码以课件/视频为准。

七、知识点速查

主题 要点
统一入口 create_agent(model, tools, system_prompt, middleware)
底层 LangGraph CompiledStateGraph,可画 mermaid 图
核心循环 ReAct:Thought→Action→Observation,直到 tool_calls 为空
工具 静态 tools=...,内置如 TavilySearch(免费额度+Key)
高阶 name 区分多智能体;system_prompt 后台约束不回显
限流 config={recursion_limit: N}

深度拓展:本章完整知识体系与实战进阶

本章我们正式进入 LangChain 1.0 的灵魂组件------Agent。在 1.0 版本之前,官方长期维护的是 AgentExecutor、initialize_agent 这一套基于"执行器"的旧 API,它们把 prompt 拼装、工具分发、循环控制、异常吞掉都藏在一个黑盒里,学习者很难看清内部到底发生了什么。1.0 版本把这一切收敛成一个函数:create_agent。它返回的不再是一个臃肿的执行器对象,而是一个标准的 LangChain Runnable,从而可以和整个生态的 stream、batch、astream、checkpoint、middleware 无缝衔接。本篇深度拓展将从源码视角把 Agent 的每一环拆开讲清楚:参数到底怎么传、ReAct 循环内部消息是怎么流转的、工具异常为什么不会把整个 Agent 崩掉、递归限制是怎么挡住死循环的、LLM-as-judge 怎么让 Agent 学会自评与反思,以及可观测性 trace 到底记录了什么。

深度拓展一:核心原理与源码解读

1.1 create_agent 的统一入口与返回值

在 LangChain 1.0 中,创建一个 Agent 只需要调用一个工厂函数:from langchain.agents import create_agent。它接受的核心参数可以分成五组:模型组、工具组、提示词组、中间件组、运行时组。模型组最关键的就是 model,传入一个 ChatModel(例如 ChatOpenAI、ChatDeepSeek、ChatTongyi 的实例);工具组是 tools,传入一个由 @tool 装饰器标注的函数列表;提示词组是 prompt,通常传一段系统提示字符串或者一个 ChatPromptTemplate;中间件组是 middleware,传入一组中间件实例;运行时组包括 checkpointer(用于持久化对话状态)、response_format(用于结构化最终输出)等。

理解 create_agent 最关键的一点是:它返回的是一个 Runnable,而不是一个"会自己跑起来的对象"。这个 Runnable 的输入是一条或一组消息,输出是一条消息列表。你可以像调用任何 LangChain 链一样,用 invoke 同步调用、用 stream 流式输出、用 ainvoke/astream 异步调用,也可以把它再包进另一个链里。这种"一切皆 Runnable"的设计,正是 1.0 相对旧版最大的架构跃迁。

python 复制代码
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
@tool
def add(a: int, b: int) -> int:
"""两个整数相加"""
return a + b
model = ChatOpenAI(model="gpt-4o-mini", temperature=0)
agent = create_agent(
model=model,  # 语言模型
tools=[add],  # 工具列表
prompt="你是一个乐于助人的助手",  # 系统提示
)
agent 是一个 Runnable,可以直接 invoke
result = agent.invoke({"messages": [("user", "12 加 30 等于几?")]})
print(result["messages"][-1].content)

1.2 ReAct 循环:Thought → Action → Observation 的状态机

Agent 的"智能"本质上来自一个反复执行的循环,学术界称之为 ReAct(Reasoning + Acting)。它把推理(Thought)和行动(Action)交织在一起:模型先根据当前消息历史"想"一下该干什么,如果需要外部信息或计算,就输出一个工具调用(Action);系统真正去执行这个工具,把结果作为一条 Observation 塞回消息历史;模型再基于这条 Observation 继续想,直到它认为信息已经足够,直接输出一段自然语言答案(Finish)。

在 LangChain 1.0 内部,这个循环是用一个图(graph)实现的:一个"调用模型"节点,一个"执行工具"节点,以及一条条件边判断"这一步模型有没有发起工具调用"。如果有工具调用,就走到工具节点执行,再绕回模型节点;如果没有工具调用,就走到终点返回结果。整个过程没有任何魔法,就是一个 while 循环加上分支判断。

阶段 模型输出 系统动作 新增消息
Thought 思考该怎么回答 --- AIMessage(含思考)
Action 发起 tool_call 路由到对应工具函数 AIMessage(含 tool_calls)
Observation --- 执行工具,拿到返回值 ToolMessage(工具结果)
Finish 不再调工具,输出答案 结束循环,返回 最终 AIMessage

1.3 工具结果如何被包成 ToolMessage 再喂回模型

这是理解 Agent 最容易被忽略、却最核心的一环。当模型决定调用某个工具时,它并不是直接"调用函数",而是输出一段结构化的 tool_call:里面包含工具名、参数 JSON 以及一个唯一的 call_id。LangChain 拿到这段 tool_call 后,会真正去执行你注册的那个 Python 函数,把函数的返回值序列化成字符串,然后包装成一条 ToolMessage,并且这条 ToolMessage 的 tool_call_id 必须和之前 AIMessage 里的 tool_call.id 一一对应。最后,这条 ToolMessage 被追加到消息列表末尾,连同之前的所有历史一起,再次发给模型。

为什么要这么严格地用 call_id 对应?因为现代大模型的 chat 协议(OpenAI 兼容协议)规定:每一条 tool 角色的消息,都必须能通过 tool_call_id 回指到发起它的那条 assistant 消息。如果对应不上,模型要么报错,要么会出现"人格分裂"------它看到一个工具结果却不知道这是回答自己哪次提问的。这也是初学者最容易踩的坑之一。

python 复制代码
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage
一次典型的消息历史长这样:
history = [
HumanMessage(content="北京现在几度?"),
# 模型第一次回复:决定调用天气工具
AIMessage(
content="",
tool_calls=[{"name": "get_weather", "args": {"city": "北京"}, "id": "call_001"}],
),
# 系统执行工具后,包成 ToolMessage,id 必须对上
ToolMessage(content="北京当前 22 摄氏度,晴", tool_call_id="call_001"),
# 模型第二次回复:拿到结果,直接给答案
AIMessage(content="北京现在 22 摄氏度,天气晴朗。"),
]

1.4 递归限制:max_iterations 与 recursion_limit 如何挡住死循环

如果模型一直"想"调用工具、一直不满意上一次结果,循环可能永远不结束,造成 token 烧穿和费用失控。LangChain 在两层做了防护。第一层是图层面的 recursion_limit:LangGraph 底层把每一次节点跳转都算一步,默认 recursion_limit 通常是 25,超过就抛出 GraphRecursionError。第二层是语义层面的 max_iterations 概念:你可以理解为"最多允许模型连续发起几轮工具调用"。当达到上限时,Agent 不会再死循环,而是要么返回当前最后一条消息,要么抛出一个可被捕获的异常,由你的代码决定是兜底回答还是重试。

实践建议:生产环境一定要显式控制 recursion_limit,不要用默认值放任模型跑。对于简单问答类 Agent,3 到 5 轮工具调用通常足够;对于复杂研究类任务,可以放宽到 10 到 15,但要配合预算告警。

1.5 错误处理:工具异常为什么不会崩掉整个 Agent

旧版 AgentExecutor 一个广为人诟病的问题是:工具函数一旦抛异常,整个执行就中断,用户看到的是一段栈追踪。1.0 的 create_agent 把这件事做得更优雅:默认情况下,工具执行抛出的异常会被捕获,异常信息会被序列化成一条 ToolMessage(通常内容是"工具执行失败:xxx"),再喂回模型。这样模型有机会看到"刚才那一步失败了,原因是参数不对",从而换一种方式重试,而不是整个对话直接挂掉。这本质上是把错误也变成了一种可被模型理解的"观察"。

你也可以通过中间件或工具内部的 try/except 进一步精细控制:是返回一个友好的错误字符串,还是触发一次自动重试,还是直接向上抛出让外层捕获。这就引出了下一个话题------LLM-as-judge 与反思。

1.6 LLM-as-judge:让 Agent 学会自评与反思

LLM-as-judge 的思路是:用另一个(或同一个)大模型充当"裁判",对 Agent 生成的答案从准确性、完整性、格式规范性等维度打分,并给出修改意见。把它嵌入 Agent 流程后,就形成了一个"生成---评判---修改"的反思循环:先生成一版答案,裁判模型给反馈,如果反馈不达标,Agent 带着反馈再改一版,直到裁判满意或达到轮次上限。这种模式在写作、代码生成、复杂推理任务里能显著提升质量,但代价是 token 消耗翻倍甚至三倍。

1.7 人工接管(Human-in-the-loop)与可观测 trace

生产级 Agent 不能只靠模型自己跑。Human-in-the-loop 机制允许在关键节点(比如执行一个会真正花钱、删库、发邮件的危险工具前)暂停,把当前状态抛给人类确认,人类批准或修改后再继续。配合 checkpointer,这种暂停甚至可以跨进程、跨天持久化。而可观测 trace 则要求把 ReAct 循环里每一步------每一条消息、每一次工具调用、每一次耗时和 token 消耗------都记录下来,方便事后复盘"Agent 为什么这次答错了"。这部分在中间件章节会再展开。

深度拓展二:代码实战------从最小可用到生产级

2.1 最小可用:一个带搜索和计算工具的 Agent

下面这个例子用 create_agent 写一个智能体,它拥有两个工具:一个模拟的"网络搜索"工具和一个"数学计算"工具。我们会逐步打印每一步的中间消息,让你亲眼看到 ReAct 循环是怎么转起来的。

python 复制代码
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage, ToolMessage
@tool
def search_web(query: str) -> str:
"""根据关键词联网搜索,返回一段摘要"""
# 这里用假数据模拟真实搜索
db = {
"北京天气": "北京今日晴,22 摄氏度,微风。",
"珠峰高度": "珠穆朗玛峰最新高程为 8848.86 米。",
}
return db.get(query, "未找到相关结果。")
@tool
def calculator(expression: str) -> str:
"""计算一个数学表达式,例如 '12 * (3 + 5)'。只支持数字与 + - * / ( )"""
allowed = set("0123456789+-*/(). ")
if not set(expression).issubset(allowed):
return "错误:表达式包含非法字符。"
try:
# 注意:真实生产环境不要用 eval,这里仅作教学演示
return str(eval(expression))
except Exception as e:
return f"计算失败:{e}"
model = ChatOpenAI(model="gpt-4o-mini", temperature=0)
agent = create_agent(
model=model,
tools=[search_web, calculator],
prompt="你是一个助手,可以联网搜索和做数学计算。请尽量调用工具而不是瞎猜。",
)
一个需要两步工具调用的问题:先查珠峰高度,再算一半是多少
question = "珠穆朗玛峰的高度除以 2 是多少?"
print("用户问:", question)
for chunk in agent.stream(
{"messages": [HumanMessage(content=question)]},
stream_mode="values",
):
last = chunk["messages"][-1]
# 打印每一步最后一条消息的类型和内容摘要
print("----", type(last).name, "----")
if hasattr(last, "tool_calls") and last.tool_calls:
for tc in last.tool_calls:
print(" [发起工具调用]", tc["name"], tc["args"])
else:
content = last.content
print(" 内容摘要:", (content[:80] + "...") if len(content) > 80 else content)

运行上面这段代码,你会在控制台依次看到:一条 HumanMessage,然后是一条带 tool_calls 的 AIMessage(发起 search_web("珠峰高度")),接着是一条 ToolMessage(返回 8848.86 米),再是一条带 tool_calls 的 AIMessage(发起 calculator("8848.86 / 2")),再是一条 ToolMessage(返回 4424.43),最后是一条不再调工具的 AIMessage 给出答案。这就是一个完整的 ReAct 循环。

2.2 生产级:加上错误处理、递归限制和最终自评

下面这个版本在最小可用的基础上,加上了:工具内部的异常兜底、外层 GraphRecursionError 捕获、以及一个简单的 LLM-as-judge 自评环节。它更接近真实生产代码的样子。

python 复制代码
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langgraph.errors import GraphRecursionError
@tool
def calculator(expression: str) -> str:
"""计算数学表达式"""
try:
allowed = set("0123456789+-*/(). ")
if not set(expression).issubset(allowed):
return "错误:包含非法字符,已中止。"
return str(eval(expression))
except Exception as e:
# 关键:把异常变成一条友好的观察,而不是直接崩
return f"工具执行失败:{e}。请检查表达式。"
model = ChatOpenAI(model="gpt-4o-mini", temperature=0)
judge = ChatOpenAI(model="gpt-4o-mini", temperature=0)  # 自评用的裁判模型
agent = create_agent(
model=model,
tools=[calculator],
prompt="你是一个严谨的计算助手。",
)
def run_with_guard(question: str, max_rounds: int = 8):
try:
result = agent.invoke(
{"messages": [("user", question)]},
config={"recursion_limit": max_rounds * 2},  # 每轮约占两步
)
except GraphRecursionError:
return "抱歉,思考轮次过多,已中止。请换一个更简单的问法。"
answer = result["messages"][-1].content
# LLM-as-judge:自评答案是否回答了问题
review = judge.invoke([
("system", "你是严格的裁判。只回答'通过'或'不通过:原因'。"),
("user", f"问题:{question}\n答案:{answer}\n这个答案是否准确回答了问题?"),
])
if "不通过" in review.content:
# 这里可以把反馈喂回 Agent 再改一版,此处简化为直接返回提示
return f"(自评反馈:{review.content})\n初步答案:{answer}"
return answer
print(run_with_guard("1234 乘以 5678 等于多少?"))

2.3 结构化最终输出:让 Agent 直接返回 Pydantic 对象

在很多业务场景里,你不想要一大段自然语言,而是想要一个结构化的对象。create_agent 支持通过 response_format 参数(或在模型上提前 with_structured_output)让 Agent 在最后一步强制按 Pydantic 模型输出。

python 复制代码
from pydantic import BaseModel, Field
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
class Answer(BaseModel):
"""结构化答案"""
summary: str = Field(description="一句话总结")
confidence: float = Field(description="置信度 0 到 1")
model = ChatOpenAI(model="gpt-4o-mini")
agent = create_agent(
model=model,
tools=[],
response_format=Answer,  # 最终输出按 Answer 结构
)
out = agent.invoke({"messages": [("user", "用一句话介绍 LangChain。")]})
structured = out["structured_response"]  # 这就是一个 Answer 实例
print(structured.summary, structured.confidence)

深度拓展三:常见坑与调试技巧

**坑 1:模型一直不调工具,空口瞎答。**现象:明明注册了工具,模型却直接凭记忆回答。原因:系统提示里没有明确要求"必须调用工具",或者工具的 docstring 写得太模糊,模型不知道什么时候该用它。解决:在 prompt 里明确写"遇到计算或实时信息必须调用工具",并把每个工具的 docstring 写成"什么时候用这个工具"。

**坑 2:ToolMessage 和 AIMessage 的 call_id 对不上,模型报错。**现象:openai 报错"tool message without matching tool call"。原因:你手动构造消息历史时,漏写了 tool_call_id 或者 id 写错了。解决:不要手动拼带 tool_calls 的历史,尽量让 create_agent 自己管理;如果必须手动构造,务必保证 ToolMessage.tool_call_id 与 AIMessage.tool_callsi.id 完全一致。

**坑 3:工具一抛异常整个 Agent 崩掉。**现象:某次工具函数内部报错,整个对话直接终止。原因:异常没有被工具内部或中间件兜底,向上穿透到了图执行。解决:在工具函数内部 try/except,把异常转成友好字符串返回;或配置错误处理中间件。

**坑 4:Agent 死循环,token 烧光。**现象:同一个工具被反复调用,停不下来。原因:模型对结果不满意,或工具每次返回相似内容。解决:设置 recursion_limit / max_iterations,并在工具里加缓存或去重逻辑。

**坑 5:以为 create_agent 会自动记住多轮对话。**现象:第二轮提问时,Agent 完全忘了上一轮说过什么。原因:没有传 checkpointer,也没有自己维护消息历史。解决:传入 InMemorySaver 作为 checkpointer,并在 invoke 时用 config={"configurable": {"thread_id": "xxx"}} 指定会话。

**坑 6:stream 出来的内容和 invoke 最终结果对不上。**现象:流式看到的是一段中间思考,最终拿到的却是另一段答案。原因:stream_mode 选择不当,你看到的是中间节点的输出而不是最终聚合。解决:用 stream_mode="values" 看每一步完整状态,或用 "messages" 看逐 token 流。

**坑 7:eval 工具被注入恶意代码。**现象:calculator 工具被传入了恶意字符串。原因:用 eval 执行模型生成的表达式。解决:教学演示可以用 eval,生产环境必须用 ast.literal_eval 或专门的数学解析库(如 simpleeval),并做字符白名单。

**坑 8:以为旧代码 AgentExecutor / initialize_agent 在 1.0 还能用。**现象:import 报错或行为诡异。原因:1.0 已把这些旧 API 收敛进 create_agent,旧写法要么被移除要么进入兼容层。解决:全部迁移到 create_agent。

**调试技巧:**把 stream_mode 设为 "debug" 或在调用时加上 langsmith 追踪,每一步的输入输出、耗时、token 数都会被记录,这是定位"Agent 为什么答错"最有效的手段。

深度拓展四:最佳实践与性能优化

**超时与重试:**对每个外部工具(搜索、数据库、API)都要设超时,并对幂等操作做有限次重试。模型调用本身也建议配一个带指数退避的重试器。

**缓存:**对确定性工具(同样输入同样输出,如数学计算、查静态知识库)开启结果缓存,可以大幅降低重复 token 和延迟。LangChain 的 CacheBackedEmbeddings、以及对 ChatModel 的 LLM 缓存都可以复用。

**Token 控制:**ReAct 循环每多一轮,消息历史就越长,越容易撑爆上下文。当历史超过一定长度时,应该对早期的 ToolMessage 做摘要裁剪(这正是下一章中间件要解决的问题),只保留最近若干轮的完整内容。

**并发:**当模型在一次 AIMessage 里同时发起多个 tool_calls,LangChain 会并行执行这些工具。利用这一点,可以把"同时查 A、B、C"的任务并行化,而不是串行等。

**可观测性:**生产环境务必接 LangSmith 或自建 trace,记录每次 Agent 运行的 thread_id、每步耗时、每步 token、工具调用参数与结果、最终答案、用户反馈。没有 trace 的 Agent 等于黑盒。

**成本治理:**用小模型做规划、大模型做最终评判的"大小模型分工",能在质量和成本间取得平衡。对简单任务直接用小模型,不要一律上最大模型。

深度拓展五:面试高频考点

Q1:create_agent 返回的是什么?为什么说它是一个 Runnable?

A:返回一个可执行的图(编译后的 LangGraph 应用),它实现了 Runnable 接口,因此支持 invoke / stream / astream / batch,也能被叠加、被 middleware 包装、被 checkpointer 持久化。

Q2:ReAct 循环里 Thought、Action、Observation 分别对应什么消息?

A:Thought 对应模型带思考内容的 AIMessage;Action 对应模型发起 tool_calls 的 AIMessage;Observation 对应系统执行工具后产生的 ToolMessage;Finish 对应不再调工具、直接给最终答案的 AIMessage。

Q3:为什么 ToolMessage 必须带 tool_call_id?

A:因为 chat 协议要求工具结果必须回指到发起它的那次 assistant 调用,否则模型无法把结果和自己的提问对应起来,会出现上下文错乱甚至报错。

Q4:max_iterations / recursion_limit 解决什么问题?

A:防止模型在工具调用循环里死循环,避免 token 无限消耗和费用失控。达到上限后会中止并返回或抛可捕获异常。

Q5:工具抛异常时 Agent 为什么不会整体崩溃?

A:默认异常会被捕获并序列化成一条 ToolMessage(内容为错误信息)喂回模型,让模型有机会看到错误并换方式重试,而不是中断整个对话。

Q6:LLM-as-judge 是什么?有什么代价?

A:用一个模型当裁判,对生成答案按准确性、完整性打分并给反馈,形成"生成---评判---修改"的反思循环。代价是 token 消耗翻倍,延迟变长。

Q7:Human-in-the-loop 一般在什么环节介入?

A:在执行高风险工具(删数据、花钱、发消息)之前暂停,把状态交给人类确认或修改,批准后再继续,常配合 checkpointer 实现跨会话持久化。

Q8:create_agent 和旧版 AgentExecutor 最大的区别?

A:旧版是黑盒执行器,内部逻辑难拆解、难扩展;1.0 的 create_agent 返回标准 Runnable,循环、中间件、检查点、流式都开放,可观测、可组合性更强。

Q9:怎么让 Agent 记住多轮对话?

A:传入 checkpointer(如 InMemorySaver),并在每次 invoke 时用 config 指定同一个 thread_id,状态会被自动持久化和恢复。

Q10:怎么让 Agent 最终输出结构化对象?

A:用 response_format 传入一个 Pydantic 模型(或在模型上预先 with_structured_output),最后一步会强制按该结构输出,结果在 structured_response 字段。

Q11:一次 AIMessage 里有多个 tool_calls 时会怎样?

A:LangChain 会并行执行这些工具,每条结果生成一条对应的 ToolMessage,适合把多个独立查询同时发出以降低延迟。

Q12:怎么调试一个"Agent 为什么答错"的问题?

A:开启 trace(LangSmith 或 debug 流式),逐步看每条消息和工具结果,定位是模型推理错、工具返回错、还是上下文被截断。

深度拓展六:课后练习与思考

  1. 用 create_agent 写一个"待办事项助手",提供 add_task / list_tasks / done_task 三个工具,要求多轮对话间用 checkpointer 记住任务列表。提示:用 InMemorySaver + thread_id。

  2. 给一个计算器工具加上"表达式白名单校验",防止 eval 注入恶意代码。提示:用字符集白名单 + ast 解析,而不是直接 eval。

  3. 观察一个会连续发起三次工具调用的问题,打印每一步消息,数一数一共产生了几条 AIMessage、几条 ToolMessage。思考:为什么工具调用轮次越多,上下文越长?

  4. 设计一个 LLM-as-judge 反思循环:先生成答案,裁判打分,不达标就带着反馈再生成一版,最多三轮。提示:把裁判反馈作为额外 HumanMessage 重新 invoke。

  5. 故意把 recursion_limit 设成 2,跑一个需要三轮工具调用的问题,观察会发生什么异常,再想想生产代码里该怎么兜底这种异常。

  6. 思考:为什么"把错误也变成一条 Observation 喂回模型"比"直接中断"更好?在哪些高风险场景下,你反而希望它直接中断而不是继续重试?

深度拓展补充:进阶案例与 FAQ

前面六节我们把 create_agent 的主干循环、参数、错误处理和可观测性讲透了。这一节作为补充,集中回答在真实项目里反复出现的进阶问题,并给出几个可以直接迁移到业务里的完整思路,帮助你把本章知识从"会跑 demo"提升到"敢上生产"。

补充一:Agent 的记忆到底存在哪里

很多初学者有个误解,以为 Agent 会自己记住对话。实际上,create_agent 生成的 Runnable 本身是无状态的:每次 invoke 传入什么消息,它就处理什么消息,处理完就把结果交还给你,不会在内部偷偷保存历史。真正负责"记住"的是 checkpointer。当你传入一个 checkpointer(比如 InMemorySaver,或者基于 Postgres 的持久化实现),并在调用时用 config={"configurable": {"thread_id": "某用户"}} 指定会话标识,LangGraph 就会在每一步结束后,把当前完整的消息历史存到 checkpointer 里;下一次同一个 thread_id 再来时,它会先把历史读出来拼到新消息前面,再继续跑。

这里有两个实战要点。第一,InMemorySaver 只在进程内存里存,服务一重启就全丢,适合开发调试;真上生产要用持久化的 checkpointer,否则用户第二天回来聊天,Agent 就"失忆"了。第二,thread_id 要选对粒度:它通常对应一个用户的一段连续会话,而不是整个人一个 id,否则不同话题会互相污染上下文。一个常见做法是 thread_id = 用户 id 加会话 id 的组合。

还要注意一点:记忆不是越长越好。消息历史会随着轮次线性增长,迟早撞上下文窗口。所以生产系统一般会在记忆之上再做一层"压缩"------用小模型把早期的多轮对话总结成一段摘要,只保留最近若干轮的完整消息加这段摘要。这件事可以自己写,也可以借助中间件在每次调用模型前自动裁剪,我们在下一章的中间件里会再用到这个思路。

补充二:一次发起多个工具调用的并行化

当用户问"北京、上海、深圳三个城市今天的天气分别怎样",聪明的模型不会笨到串行查三次,而是在一条 AIMessage 里同时发出三个 tool_calls,每个 call 对应一个城市。LangChain 拿到这组并行 tool_calls 后,会并发执行这些工具,把三条结果分别包成三条 ToolMessage 再一起喂回模型。这种并行化能把三次串行调用的延迟压缩到接近单次,是提升 Agent 响应速度的重要手段。

但并行也带来两个要注意的点。其一,工具函数要尽量是线程安全的,多个工具同时跑时别共享会被改写的全局状态。其二,并行工具如果都要访问同一个限流接口(比如搜索 API),要在工具内部做好限流和排队,别因为并行而触发对方的频率限制。如果你用的是异步模型和异步工具,这种并发收益会更明显,因为异步下多个 IO 型工具调用可以真正重叠等待。

补充三:流式输出到底在流什么

初学者最困惑的问题之一是:为什么我用 stream 看到的内容,和最后 invoke 拿到的最终答案不一样?答案在于 stream_mode 的选择。如果你用 stream_mode="values",它每一步都把"当前完整状态"吐出来------你会先看到一条只包含用户消息的状态,然后看到模型发起工具调用后的状态(AIMessage 带 tool_calls),再看到工具执行完加上 ToolMessage 的状态,最后才看到最终答案。这适合调试,让你看清整个循环。

如果你想要的是"像聊天框那样一个字一个字往外蹦"的效果,应该用 stream_mode="messages",它会把模型生成的每个 token 实时吐出来,你在前端逐字渲染即可。但要注意:在 ReAct 循环里,模型会分多次生成(先思考、再调工具、最后给答案),你需要区分哪些 token 是"思考/工具调用"、哪些是"最终答案",只把最终答案那一段流给用户,否则用户会看到内部推理过程。生产聊天框一般是:内部过程用 trace 记录,前端只流最终 AIMessage 的 content。

补充四:结构化输出与函数调用的关系

有人会混淆"工具调用(tool calling)"和"结构化输出(structured output)"。它们底层都依赖模型的结构化生成能力,但用途不同。工具调用是 Agent 主动"向外求助":模型输出一个带名字和参数的调用,由系统去执行一个外部函数。结构化输出则是"向内收束":你告诉模型"最后给我一个符合这个 Pydantic 模型的对象",模型会在答案阶段强制按结构产出,常用于让 Agent 直接返回可被程序消费的 JSON。create_agent 的 response_format 参数就是后者的入口,最终结果会放在 structured_response 字段里,类型就是你传入的 Pydantic 模型实例。

实践中一个常见组合是:Agent 先自由地调用若干工具收集信息,最后一步再按 response_format 产出结构化结论。比如一个"客服工单分诊"Agent,先查知识库、查订单,最后输出一个结构化对象,包含问题类别、紧急程度、建议处理人。这样下游系统拿到的不是一大段要解析的自然语言,而是一个干净的对象。

补充五:常见 FAQ

问:create_agent 和旧版 AgentExecutor 到底该用哪个? 答:新项目一律用 create_agent。旧版 API 在 1.0 里要么被移除要么进入兼容层,继续用旧写法会越来越难和新的流式、中间件、检查点生态配合。迁移成本不高:把 initialize_agent 的模型和工具原样搬进 create_agent,把 AgentExecutor 的 callbacks 改成 trace 或中间件即可。

问:为什么我的 Agent 总是"想太多",绕好几个弯才回答? 答:通常是系统提示里鼓励过度思考,或者工具太弱、模型不得不反复试。解决办法是在提示里明确"简单问题直接回答,不必强行调工具",并检查工具设计是否合理------一个好用的工具应该让模型一次就拿到足够信息。

问:Agent 回答很慢怎么办? 答:先开 trace 看时间花在哪。多半是某一轮工具调用特别慢,或者轮次太多。能并行的工具改成并行,能裁剪的中间结果做裁剪,简单任务换小模型,必要时对高频查询加缓存。

问:怎么防止 Agent 被用户"诱导"去调用危险工具? 答:这是 prompt 注入和越权调用的问题。光靠提示词拦不住,要在工具层做防护:危险工具挂人工确认中间件,参数做白名单校验,高权限操作要求二次授权。把"安全"放在代码层而不是提示词层,是本章反复强调的原则。

问:Agent 跑出来的结果时好时坏,怎么定位? 答:一定要有 trace。把每次失败案例的完整消息历史、工具调用、token 消耗存下来,对比成功和失败案例,往往能发现是某条工具返回了误导信息,还是模型在某一步推理跑偏。没有 trace,调 Agent 就像闭着眼睛找针。

问:一个 Agent 该挂多少个工具合适? 答:工具不是越多越好。工具列表越长,模型要"读"的描述越多,选错工具的概率也越高。一般建议个位数到十几个为宜;工具多了,可以做一个"路由 Agent"先判断该用哪组工具,再把任务交给对应的子 Agent,而不是把几十个工具一股脑塞给一个 Agent。

补充六:本章小结

这一章我们从 create_agent 这个统一入口出发,一路拆开了 Agent 的内部引擎。记住几个核心认知:第一,create_agent 返回的是一个标准 Runnable,它不是黑盒,你可以 invoke、stream、checkpoint、中间件层层包裹;第二,ReAct 循环本质上就是"模型想一步、系统跑工具、把结果包成 ToolMessage 喂回去"的反复,直到模型不再发起工具调用;第三,ToolMessage 必须用 tool_call_id 和之前的 AIMessage 对应上,这是协议层面的硬约束;第四,递归限制和错误兜底是防止 Agent 烧钱、崩溃的两道保险,工具异常会被转成一条可被模型理解的观察,而不是让整个对话中断;第五,LLM-as-judge 让 Agent 学会自评和反思,但要权衡它带来的额外 token 成本;第六,生产环境必须接 trace,把每一步消息、工具调用、耗时和 token 都记录下来,否则 Agent 答错了你连原因都查不到。

把这些认知串起来,你就具备了从"会写一个 Agent demo"到"能设计一个可靠、可观测、可维护的生产级 Agent"的关键能力。下一章我们会看到,这些横切的可靠性能力(裁剪、重试、确认、动态提示)是如何被中间件机制优雅地组织起来的。

相关推荐
不可能片场1 小时前
Electron 发布进程被回收 用脱离进程组托住
前端·electron
kaixin_learn_qt_ing1 小时前
hexdump -C /path/to/binary | less 看十六进制内容
前端·css·less
java1234_小锋1 小时前
【技术专题】HTML5 网页技术 - HTML5 链接与图片
前端·html·html5
sun_weitao1 小时前
Chrome 扩展插件开发指南
前端·javascript·chrome
小王师傅661 小时前
LangChain 系统化学习入门
学习·langchain
小马不起床2 小时前
零基础玩转 FastAPI_12:前端详情增强与角色权限分配
前端·状态模式·fastapi
for_ever_love__2 小时前
LangChain 多工具协作:一个 Agent 不够用时怎么办
人工智能·langchain·langgraph·协作·多agent·多工具·工具编排
前端snow2 小时前
ai agent --- 切割文件 RecursiveCharacterTextSplitter
前端
for_ever_love__2 小时前
LangChain 效果评测:没有评测,优化就是在瞎调
人工智能·学习·langchain·rag·评估·langsmith·评测指标