2. Agent记忆增强

任务目标

为LangChain Agent添加对话记忆能力,使其在多轮对话中能够记住用户偏好(如称呼、语言风格)和上下文(如之前讨论的主题),并在后续回答中正确引用这些信息。

环境依赖

需要安装 langchain、langchain- 以及 langchain-community(用于内存存储)。本案例使用SQLite作为记忆持久化后端,避免每次重启丢失对话历史。

pip install langchain langchain- langchain-community

核心实现

使用 ConversationSummaryBufferMemory 作为记忆机制:它既保留最近对话的完整内容,又对较早对话进行摘要压缩,在控制消耗的同时保留关键信息。通过 create_react_agent 将记忆组件注入Agent的提示词中,使模型每次推理时都能读取历史上下文。

from langchain.agents import create_react_agent, AgentExecutor
from langchain.agents.output_parsers import ReActSingleInputOutputParser
from langchain.tools import Tool
from langchain_ import ChatOpenAI
from langchain.memory import ConversationSummaryBufferMemory
from langchain_community.chat_message_histories import SQLChatMessageHistory
from langchain_core.prompts import PromptTemplate

# 1. 初始化带记忆的LLM
llm = ChatOpenAI(model="-mini", temperature=0.7)

# 2. 配置持久化消息历史(SQLite存储)
message_history = SQLChatMessageHistory(
    session_id="user_001",
    connection_string="sqlite:///memory.db"
)

# 3. 创建摘要缓冲记忆(保留最近2轮完整对话,更早的转为摘要)
memory = ConversationSummaryBufferMemory(
    chat_memory=message_history,
    llm=llm,
    max__limit=500,
    return_messages=True,
    memory_key="chat_history",
    output_key="output"
)

# 4. 定义Agent可用的工具
def get_current_time(_: str) -> str:
    """返回当前日期时间(演示用固定值)"""
    from datetime import datetime
    return datetime.now().strftime("%Y-%m-%d %H:%M")

tools = [
    Tool(
        name="当前时间",
        func=get_current_time,
        description="当用户询问时间或日期时使用"
    )
]

# 5. 构建ReAct Agent提示词(显式声明记忆变量)
prompt = PromptTemplate.from_template("""
你是一个有帮助的助手。请利用以下记忆中的历史对话信息回答问题。

历史对话:
{chat_history}

用户问题:{input}

可用工具:{tools}
工具名称列表:{tool_names}

请逐步思考并回答。如果用户提到之前的偏好或上下文,请直接引用记忆中的信息。
""")

# 6. 创建Agent并绑定记忆
agent = create_react_agent(
    llm=llm,
    tools=tools,
    prompt=prompt,
    output_parser=ReActSingleInputOutputParser()
)

agent_executor = AgentExecutor(
    agent=agent,
    tools=tools,
    memory=memory,
    verbose=False,
    handle_parsing_errors=True,
    max_iterations=3
)

# 7. 多轮对话演示
if __name__ == "__main__":
    # 第一轮:告知偏好
    r1 = agent_executor.invoke({"input": "我叫李明,请用'李工'称呼我,回答尽量简洁。"})
    print("用户:我叫李明,请用'李工'称呼我,回答尽量简洁。")
    print(f"助手:{r1['output']}\n")

    # 第二轮:提出新问题(应记住称呼)
    r2 = agent_executor.invoke({"input": "现在几点了?"})
    print("用户:现在几点了?")
    print(f"助手:{r2['output']}\n")

    # 第三轮:验证记忆(应主动使用'李工'并关联上下文)
    r3 = agent_executor.invoke({"input": "我刚才让你怎么称呼我?另外我关心时间,你觉得时间管理重要吗?"})
    print("用户:我刚才让你怎么称呼我?另外我关心时间,你觉得时间管理重要吗?")
    print(f"助手:{r3['output']}")

运行与验证

运行方式:直接执行上述Python脚本。首次运行会创建 memory.db 文件,后续重启程序时历史记录仍会保留。

验证步骤与通过标准:

  1. 记忆称呼:第二轮回答中,助手应使用“李工”称呼用户(例如“李工,当前时间是……”)。若未使用,说明记忆未正确注入。
  2. 上下文关联:第三轮回答中,助手应能复述“用户要求称呼李工”,并针对“时间管理”给出与时间相关的建议,证明它记住了前两轮对话内容。
  3. 持久化验证:运行完毕后,重新执行脚本(不删除memory.db),直接输入“我刚才让你怎么称呼我?”,若助手仍能回答“李工”,则SQLite持久化生效。

预期输出(确定性部分):由于LLM生成内容存在随机性,仅保证以下逻辑成立:第二轮输出中应包含“李工”字样;第三轮输出中应包含“李工”和“时间管理”两个关键信息点。具体措辞由模型决定。

常见问题

  • 记忆不生效:检查 memory_key 是否与提示词模板中的变量名一致(均为 chat_history)。
  • 超限:调低 max__limit 值(如200),或减少保留的完整轮次。
  • 多用户隔离:修改 session_id 即可为不同用户创建独立记忆空间。