LangChain AgentExecutor 完全指南:ReAct循环+Memory+LLM实战

LangChain AgentExecutor 完全指南:ReAct循环+Memory+LLM实战

关键词: LangChain、AgentExecutor、ReAct、create_react_agent、ConversationBufferWindowMemory、DeepSeek、Agent教程、大模型


前言

学完 LangChain 工具模块之后,很多人卡在同一个地方:

工具定义好了,但怎么让 LLM 自动决定调哪个工具、调几次、把结果串起来

答案就是 AgentExecutor。它是 LangChain Agent 的执行引擎,把工具调用、推理循环、记忆管理全部自动化。

本文从零讲清楚:

  • bind_toolsAgentExecutor 的本质区别
  • ReAct 循环是怎么跑的(Thought → Action → Observation)
  • 四个关键参数各自的作用,不加会发生什么
  • react vs react-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 结合才有基础。

建议按这个顺序验证:

  1. 先跑最小示例,看 verbose 输出理解 ReAct 循环
  2. 故意让工具一直报错,看 max_iterations 生效
  3. react 接 Memory 看记忆失效,换 react-chat 看记忆生效
  4. 测试场景4「刚才那个...」验证多轮记忆

如果有问题欢迎评论区交流,觉得有用点个赞 👍


如需转载请注明出处

相关推荐
数智前线1 小时前
百灵大模型认领“Elephant”:Ling-2.6-flash定价每百万token 0.1美元
前端·javascript·microsoft
weixin199701080161 小时前
《采购与招标商品详情页前端性能优化实战》
前端·性能优化
Mintopia2 小时前
计算机架构演进:适应不断变化的计算需求
前端
之歆2 小时前
Day01_HTML 基础知识完全指南:从零开始的 Web 开发之旅
前端·html
IT_陈寒2 小时前
React状态管理这个坑,我终于爬出来了
前端·人工智能·后端
深海鱼在掘金2 小时前
Next.js从入门到实战保姆级教程(第二章):环境配置与项目初始化
前端·typescript·next.js
深海鱼在掘金2 小时前
Next.js从入门到实战保姆级教程(第三章):项目结构与文件系统约定
前端·typescript·next.js
水木流年追梦2 小时前
CodeTop Top 300 热门题目3-字符串相加
java·前端·算法
编码七号2 小时前
使用playwright做前端项目的端对端自动化测试
前端·功能测试·自动化