LangChain AgentExecutor 完全指南:ReAct循环+Memory+LLM实战
关键词: LangChain、AgentExecutor、ReAct、create_react_agent、ConversationBufferWindowMemory、DeepSeek、Agent教程、大模型
前言
学完 LangChain 工具模块之后,很多人卡在同一个地方:
工具定义好了,但怎么让 LLM 自动决定调哪个工具、调几次、把结果串起来?
答案就是 AgentExecutor。它是 LangChain Agent 的执行引擎,把工具调用、推理循环、记忆管理全部自动化。
本文从零讲清楚:
bind_tools和AgentExecutor的本质区别- ReAct 循环是怎么跑的(Thought → Action → Observation)
- 四个关键参数各自的作用,不加会发生什么
reactvsreact-chat怎么选,选错记忆全失效- Memory 两种类型的区别,
k=10是 10 轮还是 10 条 - 完整生产级客服 Agent 代码,可直接运行
所有代码均基于 DeepSeek + LangChain 最新版。
一、bind_tools vs AgentExecutor:本质区别
很多人刚开始用 bind_tools 调工具,以为这就够了。其实两者有根本区别:
python
# bind_tools 方式 --- 半自动,只调一次工具
response = llm_with_tools.invoke("查 ORD-12345")
# 你自己判断 tool_calls,自己 invoke,结果不会喂回 LLM
# AgentExecutor 方式 --- 全自动,可调多次工具
result = executor.invoke({"input": "查 ORD-12345"})
# 工具结果自动喂回 LLM,LLM 继续推理直到给出 Final Answer
核心差异:工具结果会不会喂回 LLM 继续推理。
bind_tools 是一问一答,适合简单场景。AgentExecutor 是完整的推理循环,适合需要多步推理、多工具协作的生产场景。
二、ReAct 循环:AgentExecutor 的工作原理
AgentExecutor 跑的是 ReAct(Reasoning + Acting) 循环。
每一步的含义:
| 输出行 | 谁产生 | 含义 |
|---|---|---|
Thought |
LLM | 思考下一步做什么 |
Action |
LLM | 决定调用哪个工具 |
Action Input |
LLM | 决定传什么参数 |
Observation |
工具 | 工具执行后的返回结果 |
Final Answer |
LLM | 认为信息足够,给出最终回答 |
开启 verbose=True 时,你能在终端看到完整的推理过程:
> Entering new AgentExecutor chain...
Thought: 用户想查询订单状态,我应该调用 query_order 工具
Action: query_order
Action Input: ORD-12345
Observation: 已发货,快递单号 SF123,预计明天送达
Thought: 我已经得到答案了
Final Answer: 您的订单 ORD-12345 已发货,快递单号是 SF123,预计明天送达。
> Finished chain.
这就是 LLM 自主推理的过程,完全不需要你写 if/else 判断。
三、最小可运行示例
先跑通,再讲参数。
python
from langchain.tools import tool
from langchain.agents import AgentExecutor, create_react_agent
from langchain_openai import ChatOpenAI
from langchain import hub
@tool
def query_order(order_id: str) -> str:
"""查询订单物流状态。当用户询问订单进度时调用。输入订单号如 ORD-12345"""
fake_db = {
"ORD-12345": "已发货,快递单号 SF123,预计明天送达",
"ORD-67890": "打包中,预计今晚发货",
}
return fake_db.get(order_id, f"未找到订单 {order_id}")
tools = [query_order]
llm = ChatOpenAI(
model="deepseek-chat",
openai_api_key="你的key",
openai_api_base="https://api.deepseek.com/v1",
temperature=0,
)
prompt = hub.pull("hwchase17/react") # ReAct 推理模板
agent = create_react_agent(llm, tools, prompt) # LLM + 工具 + prompt 组合
executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True, # 打印每步推理,学习阶段必开
)
result = executor.invoke({"input": "帮我查一下 ORD-12345 到哪了"})
print("最终回答:", result["output"])
三行核心代码:
python
prompt = hub.pull("hwchase17/react") # 第一行:拉取 ReAct prompt 模板
agent = create_react_agent(llm, tools, prompt) # 第二行:组装 agent
executor = AgentExecutor(agent=agent, tools=tools) # 第三行:创建执行器
四、四个关键参数详解
4.1 verbose --- 推理过程可见性
python
# 不加(默认 False)→ 只看到最终结果,出错无从调试
executor = AgentExecutor(agent=agent, tools=tools)
# 加了 verbose=True → 能看到每步 Thought/Action/Observation
executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
结论:开发阶段必须开,上线后关掉。
4.2 max_iterations --- 防止无限循环
不加这个参数,遇到工具一直报错或返回"请重试"时,LLM 会无限循环,token 全部烧光:
python
@tool
def broken_tool(order_id: str) -> str:
"""查询订单"""
return "系统繁忙,请重试" # 永远返回这个 → LLM 无限重试
# 不加 max_iterations → 无限循环,token 烧光
executor = AgentExecutor(agent=agent, tools=[broken_tool])
# 加了之后 → 最多循环 5 次,超了强制停止
executor = AgentExecutor(agent=agent, tools=tools, max_iterations=5)
结论:生产环境必须加,推荐设 3-5。
4.3 handle_parsing_errors --- LLM 格式乱时自动重试
LLM 的输出偶尔不符合 ReAct 格式(比如多说了废话、格式不标准),不加这个参数会直接报错崩掉:
# LLM 某次输出
Thought: 我需要查询订单
好的,我来帮您查询一下,请稍等... ← 多了这行
Action: query_order
Action Input: ORD-12345
# 不加 handle_parsing_errors → 直接抛异常,用户看到 500 报错
# 加了之后 → 把错误告诉 LLM,让它重新生成,对话继续
python
executor = AgentExecutor(
agent=agent,
tools=tools,
handle_parsing_errors=True, # 生产必须加
)
结论:开发阶段可不加(能看到原始报错),上线必须加。
4.4 return_intermediate_steps --- 返回中间推理过程
python
executor = AgentExecutor(
agent=agent, tools=tools,
return_intermediate_steps=True,
)
result = executor.invoke({"input": "查 ORD-12345"})
# result["intermediate_steps"] 包含每步详情
for i, (action, observation) in enumerate(result["intermediate_steps"], 1):
print(f"第{i}步 | 工具:{action.tool} | 参数:{action.tool_input}")
print(f" | 返回:{observation}")
结论:需要记录 Agent 行为日志、做审计追踪时加,平时不需要。
参数速记
verbose → 开发 True,上线 False
max_iterations → 必须加,推荐 3-5,防无限循环
handle_parsing_errors → 生产必须加,开发可不加
return_intermediate_steps → 需要审计日志时加
五、react vs react-chat:选错记忆全失效
5.1 两者区别
python
from langchain import hub
prompt_react = hub.pull("hwchase17/react") # 无 chat_history 占位符
prompt_react_chat = hub.pull("hwchase17/react-chat") # 有 chat_history 占位符
react-chat 的模板里有 {chat_history} 占位符,Memory 存的历史对话会填进去,LLM 每次都能看到之前说了什么。
react 没有这个占位符,Memory 存了也没地方放,LLM 永远看不到历史。
5.2 选错的后果
python
# ❌ 用了 react,但接了 Memory
prompt = hub.pull("hwchase17/react")
memory = ConversationBufferWindowMemory(k=10, memory_key="chat_history", return_messages=True)
# ...
executor.invoke({"input": "帮我查一下 ORD-12345"})
executor.invoke({"input": "刚才那个订单号是多少?"})
# 输出:我不知道你在说哪个订单 ❌ 记忆没生效
python
# ✅ 换成 react-chat,记忆正常
prompt = hub.pull("hwchase17/react-chat")
# ...
executor.invoke({"input": "帮我查一下 ORD-12345"})
executor.invoke({"input": "刚才那个订单号是多少?"})
# 输出:刚才查的是 ORD-12345 ✅
5.3 选择口诀
单轮问答(每次独立) → react
多轮对话(需要记忆) → react-chat + Memory
六、Memory 两种类型
6.1 两种类型对比
python
from langchain.memory import ConversationBufferMemory, ConversationBufferWindowMemory
# 类型一:存全部历史
memory = ConversationBufferMemory(
memory_key="chat_history",
return_messages=True,
)
# 类型二:只存最近 k 轮(推荐生产)
memory = ConversationBufferWindowMemory(
k=10,
memory_key="chat_history",
return_messages=True,
)
| BufferMemory | WindowMemory | |
|---|---|---|
| 保存内容 | 全部历史 | 最近 k 轮 |
| 适合场景 | 测试 / 短对话 | 生产环境 |
| 风险 | context 随对话变长,token 爆炸 | 超出 k 轮的历史丢失 |
6.2 k=10 是 10 轮还是 10 条?
是 10 轮,不是 10 条。
一轮 = 用户说一句 + AI 回一句 = 2 条消息
k=10 = 保留最近 10 轮 = 最近 20 条消息
6.3 return_messages=True 为什么必须加
python
# return_messages=False(默认)→ 返回字符串格式
# "Human: 你好\nAI: 你好!\n..."
# react-chat 需要 Message 对象,字符串格式会报错
# return_messages=True → 返回 Message 对象列表
# [HumanMessage("你好"), AIMessage("你好!"), ...]
# react-chat 的 prompt 需要这种格式
6.4 生产标准写法
python
memory = ConversationBufferWindowMemory(
k=10, # 保留最近 10 轮
memory_key="chat_history", # 必须和 prompt 占位符一致
return_messages=True, # react-chat 需要 Message 格式
)
七、完整生产级客服 Agent
把前面所有知识点串在一起,配置三个工具,支持多轮对话。
python
import os
from pathlib import Path
from dotenv import load_dotenv
from pydantic import BaseModel, Field
from langchain.tools import tool, StructuredTool
from langchain.agents import AgentExecutor, create_react_agent
from langchain.memory import ConversationBufferWindowMemory
from langchain_openai import ChatOpenAI
from langchain import hub
load_dotenv(dotenv_path=Path(__file__).resolve().parents[1] / ".env")
# ===== 工具定义 =====
@tool
def query_order(order_id: str) -> str:
"""查询订单的物流状态和快递单号。
当用户询问订单到哪了、什么时候到、发货了吗时调用。
不要用这个工具查询订单金额或商品信息。
输入格式:订单号字符串,如 ORD-12345"""
try:
fake_db = {
"ORD-12345": "已发货,快递单号 SF123,预计明天送达",
"ORD-67890": "打包中,预计今晚发货",
"ORD-11111": "已签收",
}
return fake_db.get(order_id, f"未找到订单 {order_id},请确认订单号")
except Exception as e:
return f"查询失败:{e}"
@tool
def query_points(user_id: str) -> str:
"""查询用户的会员积分和会员等级。
当用户询问积分、会员等级时调用。
输入格式:用户ID字符串,如 U001"""
try:
fake_db = {
"U001": "积分 1580 分,黄金会员",
"U002": "积分 320 分,普通会员",
}
return fake_db.get(user_id, f"未找到用户 {user_id}")
except Exception as e:
return f"查询失败:{e}"
class RefundInput(BaseModel):
order_id: str = Field(description="订单号,格式 ORD-XXXXX")
reason: str = Field(description="退款原因", min_length=5)
amount: float = Field(description="退款金额,必须大于 0", gt=0)
def _apply_refund(order_id: str, reason: str, amount: float) -> str:
try:
return f"退款申请已提交:订单 {order_id},金额 ¥{amount},原因「{reason}」,预计3个工作日到账"
except Exception as e:
return f"退款失败:{e}"
apply_refund = StructuredTool.from_function(
func=_apply_refund,
name="apply_refund",
description="发起退款申请。仅在用户明确表示要退款时调用,不要主动建议退款。需要订单号、退款原因、退款金额。",
args_schema=RefundInput,
)
tools = [query_order, query_points, apply_refund]
# ===== 组装 Agent =====
llm = ChatOpenAI(
model="deepseek-chat",
openai_api_key=os.getenv("OPENAI_API_KEY") or "",
openai_api_base=os.getenv("OPENAI_API_BASE") or "https://api.deepseek.com/v1",
temperature=0,
)
prompt = hub.pull("hwchase17/react-chat") # 多轮必须 react-chat
memory = ConversationBufferWindowMemory(
k=10,
memory_key="chat_history", # 和 prompt 占位符一致
return_messages=True,
)
agent = create_react_agent(llm, tools, prompt)
executor = AgentExecutor(
agent=agent,
tools=tools,
memory=memory,
verbose=True,
max_iterations=5,
handle_parsing_errors=True,
return_intermediate_steps=True,
)
# ===== 测试多轮对话 =====
def chat(user_input: str):
print(f"\n{'='*50}")
print(f"用户:{user_input}")
result = executor.invoke({"input": user_input})
print(f"回答:{result['output']}")
if result["intermediate_steps"]:
print("── 工具调用 ──")
for i, (action, obs) in enumerate(result["intermediate_steps"], 1):
print(f" {i}. {action.tool}({action.tool_input}) → {obs}")
chat("帮我查一下 ORD-12345 到哪了")
chat("U001 这个用户有多少积分?")
chat("我要对 ORD-67890 退款,原因是质量有问题,金额 299 元")
chat("刚才那个退款的订单号是多少?") # 测试记忆
chat("你们客服几点上班?") # 不需要工具,LLM 直接回答
场景4「刚才那个退款的订单号」是关键验证点------LLM 直接从 Memory 里找到答案,没有调用任何工具,说明记忆生效了。
八、开发 vs 生产配置对比
python
# 开发阶段
executor = AgentExecutor(
agent=agent, tools=tools, memory=memory,
verbose=True, # 开:看推理过程
max_iterations=5,
return_intermediate_steps=True, # 开:看每步详情
# handle_parsing_errors 不加:看原始报错方便调试
)
# 生产阶段
executor = AgentExecutor(
agent=agent, tools=tools, memory=memory,
verbose=False, # 关:减少日志
max_iterations=5, # 必须有
handle_parsing_errors=True, # 必须有
return_intermediate_steps=False,
)
九、常见问题速查
| 问题现象 | 原因 | 解决方法 |
|---|---|---|
| Agent 一直循环不停 | 没设置上限 | 加 max_iterations=5 |
| 记忆不生效 | prompt 用了 react | 换成 react-chat |
| 记忆还是不生效 | memory_key 对不上 | 确认 memory_key="chat_history" |
| 解析报错崩溃 | LLM 输出格式乱 | 加 handle_parsing_errors=True |
| 调用了错误的工具 | description 不够精准 | 重写 description,加"不要用这个工具做 XX" |
| 工具返回内容太长 | context 溢出 | 工具内部截断 return result[:500] |
十、核心配置速记
python
# 多轮生产级 Agent 标准配置
prompt = hub.pull("hwchase17/react-chat") # 多轮必须
memory = ConversationBufferWindowMemory( # 生产用 Window
k=10, # 10 轮 = 20 条
memory_key="chat_history", # 和 prompt 一致
return_messages=True) # 必须加
agent = create_react_agent(llm, tools, prompt)
executor = AgentExecutor(
agent=agent, tools=tools, memory=memory,
max_iterations=5, # 防无限循环
handle_parsing_errors=True, # 防崩溃
)
环境配置
bash
pip install langchain langchain-openai langchainhub pydantic python-dotenv
.env 文件:
OPENAI_API_KEY=你的deepseek_key
OPENAI_API_BASE=https://api.deepseek.com/v1
DeepSeek API 申请:https://platform.deepseek.com
写在最后
AgentExecutor 是 LangChain Agent 的核心引擎,搞清楚它之后,后面的多 Agent 协作、工具链编排、RAG+Agent 结合才有基础。
建议按这个顺序验证:
- 先跑最小示例,看 verbose 输出理解 ReAct 循环
- 故意让工具一直报错,看
max_iterations生效 - 用
react接 Memory 看记忆失效,换react-chat看记忆生效 - 测试场景4「刚才那个...」验证多轮记忆
如果有问题欢迎评论区交流,觉得有用点个赞 👍
如需转载请注明出处