Day17:LangChain Memory系统详解:让AI Agent拥有记忆,实现多轮对话

一、引言

在构建智能对话Agent时,记忆(Memory) 是决定其能否进行连贯对话的关键。没有记忆的Agent就像金鱼,每次对话都从零开始,无法理解“刚才提到的那个城市天气如何”这类上下文依赖的问题。LangChain提供了强大的Memory系统,可以轻松为Agent添加短期和长期记忆能力。

今天我们将深入探讨LangChain的Memory模块,以通义千问(Qwen) 大模型为例,从零实现一个能记住对话历史的Agent。你将学会:

  • 使用 ConversationBufferMemory 和 ConversationSummaryMemory 存储对话历史

  • 将Memory集成到Agent中,实现多轮对话

  • 编写一个天气查询示例,让Agent根据历史天气信息推荐着装

  • 使用 StructuredTool 替代简单的 @tool 装饰器,体验更灵活的工具定义方式

所有代码均基于以下版本验证通过:

langchain                0.1.17
langchain-classic        1.0.3
langchain-community      0.0.38
langchain-core           0.1.53
langchain-text-splitters 0.0.2

二、LangChain Memory 概述

LangChain将记忆分为两类:

  • 短期记忆:在单个会话中存储对话历史,如 ConversationBufferMemory 直接缓存所有消息。

  • 长期记忆:对历史进行摘要或向量化存储,如 ConversationSummaryMemory 定期总结对话,减少token消耗。

Memory模块的核心作用是在Agent调用LLM之前,将历史对话注入到Prompt中,让模型感知上下文。下图展示了Memory的工作流程:

image.png

Memory将历史消息附加到下一次LLM调用中

三、环境准备

首先安装所需依赖(推荐使用虚拟环境):

pip install langchain==0.1.17 langchain-community==0.0.38 langchain-core==0.1.53

由于我们使用通义千问模型,需要安装 dashscope SDK:

pip install dashscope

在代码中导入必要的模块:

import os
from langchain.memory import ConversationBufferMemory, ConversationSummaryMemory
from langchain.agents import initialize_agent, AgentType
from langchain.tools import StructuredTool
from langchain_community.chat_models import ChatTongyi

注意langchain-community 中的 ChatTongyi 封装了通义千问API,需配置环境变量 DASHSCOPE_API_KEY(获取方式:阿里云控制台)。

四、初始化千问大模型

# 设置API密钥(建议从环境变量读取)
os.environ["DASHSCOPE_API_KEY"] = "your-api-key-here"

# 创建千问模型实例
llm = ChatTongyi(
    model="qwen-turbo",      # 可选 qwen-turbo, qwen-plus, qwen-max
    temperature=0.7,
    max_tokens=1024
)

五、对话缓冲存储器(ConversationBufferMemory)

ConversationBufferMemory 是最简单的记忆形式,它将所有对话消息按顺序存储,并在每次调用时以字符串或消息列表的形式返回。

5.1 基本用法

memory = ConversationBufferMemory(return_messages=True)

# 保存用户消息和AI回复
memory.save_context({"input": "你好"}, {"output": "您好!有什么需要帮助的?"})
memory.save_context({"input": "今天天气怎么样?"}, {"output": "北京今天晴,15-25℃"})

# 加载记忆变量
print(memory.load_memory_variables({}))

5.2 在链中使用

通过 load_memory_variables 获取历史,然后将其注入Prompt模板。

六、对话摘要存储器(ConversationSummaryMemory)

当对话非常长时,ConversationBufferMemory 会导致token超限。ConversationSummaryMemory 通过LLM定期总结对话内容,只保留摘要。

summary_memory = ConversationSummaryMemory(llm=llm)

summary_memory.save_context({"input": "你好"}, {"output": "您好!"})
summary_memory.save_context({"input": "今天天气怎么样?"}, {"output": "北京今天晴,15-25℃"})

print(summary_memory.load_memory_variables({}))

七、使用 StructuredTool 定义工具

之前我们常用 @tool 装饰器快速定义工具,但 StructuredTool 提供了更强大的功能:你可以显式定义输入参数的名称、类型、描述,甚至返回值的schema。这对于复杂工具或需要精确控制参数描述的场景非常有用。

下面我们用 StructuredTool 创建一个天气查询工具:

def get_weather_func(city: str) -> str:
    """根据城市名返回天气信息(模拟数据)"""
    city_weather = {
        "北京": "晴,15-25℃",
        "上海": "多云,18-22℃",
        "广州": "阵雨,20-26℃",
        "深圳": "多云转阴,22-28℃"
    }
    return city_weather.get(city, "暂无该城市天气信息")

# 使用 StructuredTool.from_function 创建工具
weather_tool = StructuredTool.from_function(
    func=get_weather_func,
    name="get_weather",
    description="根据城市名查询当前天气",
    # 可以指定参数schema(可选)
    # args_schema: 可以传入一个pydantic模型来定义参数
)

StructuredTool 也可以直接实例化,但 from_function 是最便捷的方式。

八、将Memory集成到Agent中实现多轮对话

接下来我们实战:创建一个Agent,它拥有天气查询工具,并利用 ConversationBufferMemory 记住历史,从而回答“适合穿什么衣服”这类依赖上下文的问题。

8.1 初始化带记忆的Agent

# 创建记忆实例(返回消息格式,便于Agent处理)
memory = ConversationBufferMemory(
    memory_key="chat_history",      # 必须与Agent预期key一致
    return_messages=True
)

# 初始化Agent
agent = initialize_agent(
    tools=[weather_tool],           # 使用StructuredTool创建的tool
    llm=llm,
    agent=AgentType.CONVERSATIONAL_REACT_DESCRIPTION,  # 支持对话的Agent类型
    memory=memory,
    verbose=True   # 打印思考过程,便于调试
)

关键点memory_key 必须设置为 "chat_history",因为 CONVERSATIONAL_REACT_DESCRIPTION 类型的Agent默认从该变量获取历史。

8.2 测试多轮对话

# 第一轮:询问天气
response1 = agent.run("今天北京天气怎么样?")
print("AI:", response1)

# 第二轮:基于天气提问
response2 = agent.run("那适合穿什么衣服?")
print("AI:", response2)

九、进阶:使用 ConversationSummaryMemory 节省Token

如果对话轮次非常多,可以将 memory 替换为 ConversationSummaryMemory,但需要注意它返回的是字符串而非消息列表,因此需要调整 return_messages=False

summary_memory = ConversationSummaryMemory(
    llm=llm,
    memory_key="chat_history",
    return_messages=False   # 返回字符串摘要
)

agent_summary = initialize_agent(
    tools=[weather_tool],
    llm=llm,
    agent=AgentType.CONVERSATIONAL_REACT_DESCRIPTION,
    memory=summary_memory,
    verbose=True
)

十、常见问题与注意事项

  1. Agent类型选择CONVERSATIONAL_REACT_DESCRIPTION 专为对话设计,它会自动将历史作为Prompt的一部分。如果使用 ZERO_SHOT_REACT_DESCRIPTION,需要手动拼接历史。

  2. Memory Key匹配:确保 memory_key 与Agent期望的变量名一致(通常是 chat_history)。

  3. StructuredTool vs @toolStructuredTool 提供了更丰富的参数定义能力,例如你可以通过 args_schema 指定输入参数的pydantic模型,从而实现自动验证和文档生成。而 @tool 装饰器更简洁,适合快速开发。

  4. 版本兼容:LangChain 0.1.x 中 initialize_agent 仍可用,更高版本可能推荐使用 create_react_agent 函数。

  5. 千问模型配置:如果遇到超时或API错误,检查网络环境和API密钥有效性。

十一、总结与展望

今天我们学习了LangChain的Memory系统,并成功为千问Agent添加了记忆能力,使其能够进行上下文关联的多轮对话。主要收获:

  • ConversationBufferMemory 直接存储对话历史,简单直观。

  • ConversationSummaryMemory 通过摘要压缩记忆,适合长对话。

  • 通过 memory 参数将记忆注入Agent,实现状态保持。

  • 使用 StructuredTool 定义工具,体验更灵活的工具定义方式。

附录:完整可运行代码

import os
from langchain.memory import ConversationBufferMemory
from langchain.agents import initialize_agent, AgentType
from langchain.tools import StructuredTool
from langchain_community.chat_models import ChatTongyi

# 设置API密钥
os.environ["DASHSCOPE_API_KEY"] = "your-dashscope-api-key"

# 定义工具函数
def get_weather_func(city: str) -> str:
    """根据城市名返回天气信息(模拟数据)"""
    weather_data = {
        "北京": "晴,15-25℃",
        "上海": "多云,18-22℃",
        "广州": "阵雨,20-26℃",
        "深圳": "多云转阴,22-28℃"
    }
    return weather_data.get(city, "暂无该城市天气信息")

# 使用StructuredTool创建工具
weather_tool = StructuredTool.from_function(
    func=get_weather_func,
    name="get_weather",
    description="根据城市名查询当前天气"
)

# 初始化模型和记忆
llm = ChatTongyi(model="qwen-turbo", temperature=0.7)
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)

# 创建Agent
agent = initialize_agent(
    tools=[weather_tool],
    llm=llm,
    agent=AgentType.CONVERSATIONAL_REACT_DESCRIPTION,
    memory=memory,
    verbose=True
)

# 多轮对话
if __name__ == "__main__":
    print("开始对话(输入 'quit' 退出)")
    while True:
        user_input = input("\n用户:")
        if user_input.lower() == 'quit':
            break
        response = agent.run(user_input)
        print(f"AI:{response}")

运行结果:

开始对话(输入 'quit' 退出)

用户:今天上海天气怎么样


> Entering new AgentExecutor chain...
Thought: Do I need to use a tool? Yes  
Action: get_weather  
Action Input: "上海"  
Observation: 多云,18-22℃
Thought:AI: 今天上海天气多云,气温在18到22摄氏度之间,比较舒适。

> Finished chain.
AI:今天上海天气多云,气温在18到22摄氏度之间,比较舒适。

用户:今天我适合穿什么衣服


> Entering new AgentExecutor chain...
Thought: Do I need to use a tool? No  
AI: 今天上海天气多云,气温在18到22摄氏度之间,属于温和舒适的初秋天气。建议穿着长袖衬衫、薄针织衫、薄外套或风衣等轻便透气的衣物;下装可选长裤或薄款裙子(搭配打底袜更稳妥)。早晚稍凉,可备一件薄外套以防温差。整体以“叠穿”为宜,方便根据室内外温度灵活增减。
> Finished chain.
AI:今天上海天气多云,气温在18到22摄氏度之间,属于温和舒适的初秋天气。建议穿着长袖衬衫、薄针织衫、薄外套或风衣等轻便透气的衣物;下装可选长裤或薄款裙子(搭配打底袜更稳妥)。早晚稍凉,可备一件薄外套以防温差。整体以“叠穿”为宜,方便根据室内外温度灵活增减。
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐