Q:但是langchain 0.3的记忆组件有多种,比如ConversationBufferMemory,ConversationBufferWindowMemory, 那么create_react_agent用的是哪一种?或者是否可以显示的指定?
你提的这个问题非常精准,直击了 LangChain 模块化设计的核心!答案会让你对新版 LangChain 的设计有更深的理解。
核心答案:create_react_agent
本身不关心也不使用任何记忆组件。你可以,也必须**,在 AgentExecutor
中显式地指定你想要使用的记忆类型。**
这是一个非常重要的设计分离:
create_react_agent
的职责是创建 Agent 的核心决策逻辑。它只负责将 LLM、工具(Tools)和提示(Prompt)三者“粘合”起来,生成一个懂得如何“思考”和“行动”的Runnable
对象。它本身是无状态的。AgentExecutor
的职责是执行和管理 Agent 的运行循环。它负责调用 Agent、执行工具、处理错误,以及——正如你所问的——管理对话记忆。
如何为你的 Agent 指定记忆类型?
你可以在创建 AgentExecutor
时,将任何你想要的记忆组件实例通过 memory
参数传递进去。AgentExecutor
会自动处理好后续的一切:
- 在每次调用 Agent 之前,从
memory
对象中加载历史对话。 - 将加载的历史对话填充到提示模板中名为
chat_history
的占位符里。 - 在 Agent 完成一次交互后,将这次新的输入和输出保存回
memory
对象中。
示例:明确使用 ConversationBufferWindowMemory
让我们用一个具体的例子,来创建一个只记住最近 2 轮对话的 Conversational ReAct Agent。
from langchain_openai import ChatOpenAI
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain import hub
from langchain.agents import create_react_agent, AgentExecutor
from langchain.memory import ConversationBufferWindowMemory # 明确导入窗口记忆
# 1. 初始化 LLM 和工具
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
tools = [TavilySearchResults(max_results=1)]
# 2. 明确创建你想要的记忆实例
# 我们创建一个只保留最近 k=2 轮对话的窗口记忆
memory = ConversationBufferWindowMemory(
k=2,
memory_key="chat_history", # 必须和 prompt 中的占位符名称一致
return_messages=True
)
# 3. 拉取一个支持对话的 prompt
# "hwchase17/react-chat" 模板内置了 `MessagesPlaceholder(variable_name="chat_history")`
prompt = hub.pull("hwchase17/react-chat")
# 4. 创建 Agent (这里不涉及 memory)
agent = create_react_agent(llm, tools, prompt)
# 5. 创建 AgentExecutor,并在这里“注入”我们选择的记忆组件
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
memory=memory, # <--- 在这里显式指定!
verbose=True
)
# --- 开始对话,观察窗口记忆的效果 ---
# 第 1 轮对话 (记忆中: 1)
print("--- Interaction 1 ---")
agent_executor.invoke({"input": "Hi, my name is Bob."})
# 第 2 轮对话 (记忆中: 1, 2)
print("\n--- Interaction 2 ---")
agent_executor.invoke({"input": "I live in New York."})
# 第 3 轮对话 (记忆中: 2, 3。第 1 轮被挤掉了)
print("\n--- Interaction 3 ---")
agent_executor.invoke({"input": "What is my name?"})
输出分析:
当你运行到第三次交互时,你会发现 Agent 已经不记得你的名字是 “Bob” 了。这是因为 ConversationBufferWindowMemory(k=2)
的作用,它只保留了最近的两次交互(“我住在纽约” 和 “我叫什么名字?”),而最早的 “我叫 Bob” 已经被“挤出”了记忆窗口。
如果你把 memory = ConversationBufferWindowMemory(k=2, ...)
换成 memory = ConversationBufferMemory(...)
,那么在第三轮交互时,Agent 就会轻松地回答出你的名字。
常用记忆组件总结
你可以根据需求,选择并实例化不同的记忆组件传入 AgentExecutor
:
记忆组件 | 工作方式 | 适用场景 |
---|---|---|
ConversationBufferMemory |
存储所有对话历史。 | 需要完整上下文的短对话,但小心过长的对话会超出模型的 token 限制。 |
ConversationBufferWindowMemory |
只存储最近的 k 轮对话。 |
需要上下文但又想控制 token 数量的持续对话,是最常用的选项之一。 |
ConversationSummaryMemory |
随着对话进行,不断将旧的对话内容用 LLM 进行总结。 | 非常长的对话,既要保留早期信息又要节省 token 的场景。 |
ConversationSummaryBufferMemory |
结合了 Buffer 和 Summary,保留最近的几轮对话原文,并把更早的对话进行总结。 | 对近期对话细节和长期对话主线都有要求的复杂场景。 |
结论: LangChain 的新设计赋予了你完全的控制权。create_react_agent
只负责“思考逻辑”,而 AgentExecutor
负责“执行和记忆”,你可以像插拔 U 盘一样,轻松地为你的 Agent 配置任何类型的记忆模块。
Q:为什么在创建create_react_agent制定了tool,然后在AgentExecutor又指定了tool?
这是一个非常棒的观察,也是一个能帮助你彻底理解 LangChain 新版模块化设计的关键问题!
简单来说,Agent
和 AgentExecutor
是两个独立的角色,它们都需要知道 tools
的信息,但用途完全不同。
我们可以用一个 “经理” 和 “工人” 的比喻来理解:
create_react_agent
创建的是 “经理 (Agent)”:经理负责决策。他需要一份工具清单(包含每个工具的名称和功能描述),这样他才能在接到任务时,聪明地决定“哦,这个任务应该让‘搜索工人’去做”。他只下达指令,自己不动手。AgentExecutor
是 “负责执行的工头”:工头负责执行。他手下管理着所有真正的工人(工具对象)。当他收到经理的指令(例如“使用‘搜索工具’去查‘LangChain CEO’”),他需要根据指令中的工具名称,找到那个真正会干活的“搜索工人”对象,然后命令他去工作。
现在,我们把这个比喻对应到代码中:
1. create_react_agent(..., tools, ...)
的职责 (经理做决策)
create_react_agent
需要 tools
列表,是为了生成给 LLM 看的提示 (Prompt)。
它会遍历你传入的 tools
列表,提取出每个工具的 name
(名称) 和 description
(功能描述),然后将它们格式化成一段文本,插入到提示模板的 {tools}
和 {tool_names}
占位符中。
例如,提示模板中的这部分:
You have access to the following tools:
{tools}
Use the following format:
...
Action: the action to take, should be one of [{tool_names}]
...
会被 create_react_agent
填充成:
You have access to the following tools:
TavilySearchResults: A search engine optimized for comprehensive, accurate, and trusted results.
Use the following format:
...
Action: the action to take, should be one of [TavilySearchResults]
...
所以,传给 create_react_agent
的 tools
,是为了让 LLM (经理) 知道自己有哪些工具可用,以及它们分别能干什么,从而做出正确的决策。
2. AgentExecutor(..., tools, ...)
的职责 (工头去执行)
AgentExecutor
需要 tools
列表,是为了在接收到 LLM 的决策后,能真正地执行工具。
当 LLM (经理) 输出它的决策后,例如:
Thought: I need to find the CEO of LangChain.
Action: TavilySearchResults
Action Input: "Who is the CEO of LangChain?"
AgentExecutor
(工头) 会解析这段文本。它看到 Action: TavilySearchResults
,就会在自己持有的 tools
列表(一个包含真实工具对象的 Python 列表)中,查找那个 name
属性为 “TavilySearchResults” 的工具对象。找到后,它会调用这个对象的 .invoke("Who is the CEO of LangChain?")
方法来执行搜索。
所以,传给 AgentExecutor
的 tools
,是为了在 LLM 做出选择后,能够找到并运行那个具体的、可执行的工具对象。
总结
组件 | 角色 | 为什么需要 tools ? |
---|---|---|
create_react_agent |
决策者 (经理) | 为了“看”:读取工具的名称和描述,生成提示,让 LLM 知道有哪些选项可选。 |
AgentExecutor |
执行者 (工头) | 为了“用”:根据 LLM 输出的工具名称,查找并调用实际的工具对象来完成任务。 |
这种设计上的分离,正是 LangChain 模块化和灵活性的体现。它将“决策逻辑”和“执行逻辑”解耦,使得你可以独立地修改和配置它们,例如,你可以用同一个 AgentExecutor
来运行不同逻辑的 Agent
,反之亦然。