纲要

  • 核心挑战:上下文窗口限制、多用户会话隔离
  • 技术选型:RedisRedisChatMessageHistory
  • 架构组件:ConversationBufferMemory、总结链、session_id 键空间隔离
  • 实现模块:MemoryManager 类、自动压缩触发逻辑
  • 关键设计:人格统一的第一人称摘要
  • 可运行示例与验证

记忆系统的核心挑战

在构建基于大语言模型的对话智能体时,记忆机制是实现连续、个性化交互的基石。一个健壮的对话记忆系统需要直面并解决两个核心问题:

  1. 上下文窗口限制:主流大语言模型对单次推理可处理的 Token 数量存在硬性上限(即上下文窗口)。随着对话轮次的增加,原始的历史消息逐条累积,极易触及窗口边界,导致早期关键信息被截断,或引发请求错误。
  2. 多用户会话隔离:在生产环境中,智能体需同时服务大量独立用户。不同用户的对话历史、个人偏好和任务状态必须严格隔离,防止发生信息“串扰”,即一个用户的数据被错误地关联到另一个用户的会话中。

一个设计良好的记忆系统,其核心目标是在“尽可能保留完整历史信息”与“严格控制在上下文窗口限制内”之间取得动态平衡,同时确保用户间数据的绝对隔离。

技术选型:为何选择 Redis

在众多存储方案中,Redis 作为内存数据结构存储系统,在对话记忆场景下具备显著优势:

  • 极速读写:基于内存的操作特性,使其能够轻松应对高并发、高频次的消息存取压力,满足实时交互的延迟要求。
  • 数据结构匹配:对话历史本质上是按时间顺序排列的消息序列,与 Redis 的 List(列表)数据结构在逻辑上高度契合,可实现高效的消息追加和范围读取。
  • 原生键空间隔离:通过设计合理的 Key 策略(如 conversation:{session_id}),即可轻松实现多用户、多会话的数据隔离,逻辑清晰且操作高效。

LangChain 生态官方提供了 RedisChatMessageHistory 组件,它封装了将对话消息以标准格式(如 HumanMessageAIMessage)存取至 Redis 的底层逻辑,使开发者无需手动处理序列化与反序列化细节。

系统架构设计

本方案的核心工作流如下:每次读取记忆时,系统会检查当前会话的消息总量。若条数超过预设的阈值(例如 80 条),则触发一个“总结链”,将全部历史消息压缩为一段结构化摘要。随后,系统会清空该会话的原始消息列表,仅保留该摘要作为后续对话的长期记忆锚点。此机制确保了上下文窗口长期处于可控状态,同时最大限度地留存了关键信息。

返回原始历史

用户发送消息

Agent 执行器

get_memory 读取历史

Redis

消息条数 > 阈值?

返回原始历史

调用总结链进行压缩

清空 Redis 中的旧记录

保存生成的摘要至 Redis

LLM 生成回复

save_memory 存储本轮消息

完整实现方案

本文实现基于以下技术栈版本:

  • langchain ≈ 0.3.x
  • langchain-openai ≈ 0.2.x
  • langchain-community ≈ 0.3.x
  • redis >= 5.0.0
  • python-dotenv >= 1.0.0

项目结构

memory_agent_demo/
├── config.py          # 配置管理
├── memory_manager.py  # 核心记忆管理器
├── main.py            # 示例主程序
└── .env               # 环境变量

核心依赖安装

pip install langchain langchain-openai langchain-community redis python-dotenv

配置管理模块 config.py

该模块负责从环境变量中加载配置,确保敏感信息(如 API Key)与代码分离。

# config.py
import os
from dotenv import load_dotenv

load_dotenv()

class Config:
    OPENAI_API_KEY: str = os.getenv("OPENAI_API_KEY", "")
    OPENAI_BASE_URL: str = os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1")
    REDIS_URL: str = os.getenv("REDIS_URL", "redis://localhost:6379/0")
    # 触发摘要压缩的历史消息数量阈值
    MAX_HISTORY_LENGTH: int = int(os.getenv("MAX_HISTORY_LENGTH", "80"))

核心记忆管理器 memory_manager.py

MemoryManager 类封装了记忆读取、写入和自动压缩的核心逻辑。它与 LangChain 的记忆组件深度集成,对外提供简洁的 API。

  1. 初始化与总结链构建:在 __init__ 方法中,除了初始化 LLM 客户端,还构建了一条专用的“总结链”。该链的核心提示词模板会将系统提示词注入摘要生成过程,确保压缩后的摘要仍以智能体的第一人称视角叙述,从而维持角色人格的统一性。
  2. 记忆读取与自动压缩get_memory 方法是整个系统的关键。它首先通过 _get_history 获取 RedisChatMessageHistory 实例。当消息数量超过 MAX_HISTORY_LENGTH 时,会触发压缩流程:格式化所有历史消息 -> 调用总结链生成摘要 -> 清空 Redis 列表 -> 将摘要作为新的对话上下文存入 Redis。
  3. 记忆写入save_memory 方法负责将每一轮的用户输入和 AI 输出实时追加到 Redis 列表中。
# memory_manager.py
from typing import List, Optional
from langchain.memory import ConversationBufferMemory
from langchain_community.chat_message_histories import RedisChatMessageHistory
from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.messages import BaseMessage
from config import Config

class MemoryManager:
    """具备自动摘要压缩能力的对话记忆管理器。"""

    def __init__(self, system_prompt: str = ""):
        self.memory_key = "chat_history"
        self.system_prompt = system_prompt
        self.llm = ChatOpenAI(
            model="gpt-3.5-turbo",
            temperature=0.0,
            openai_api_key=Config.OPENAI_API_KEY,
            base_url=Config.OPENAI_BASE_URL,
        )
        # 构建总结链
        # 注意:将系统提示词嵌入摘要模板,以保持角色人格统一
        summary_template = (
            system_prompt + "\n\n"
            "以下是你和用户之间的对话记录。请以第一人称「我」的视角,对这段对话进行详细总结,"
            "需提取所有关键信息、用户的核心诉求以及你给出的处理结果。\n"
            "对话记录:\n{chat_history}\n\n摘要:"
        )
        self.summary_prompt = ChatPromptTemplate.from_template(summary_template)
        self.summary_chain = self.summary_prompt | self.llm | StrOutputParser()

    def _get_history(self, session_id: str) -> RedisChatMessageHistory:
        """获取指定会话 ID 对应的 Redis 历史记录管理器。"""
        return RedisChatMessageHistory(
            session_id=session_id,
            url=Config.REDIS_URL,
            # key_prefix 可选,用于区分不同应用或场景
        )

    def _format_messages(self, messages: List[BaseMessage]) -> str:
        """将消息列表格式化为供 LLM 处理的纯文本。"""
        chat_lines = []
        for msg in messages:
            role = "用户" if msg.type == "human" else "助手"
            chat_lines.append(f"{role}: {msg.content}")
        return "\n".join(chat_lines)

    def get_memory(self, session_id: str) -> ConversationBufferMemory:
        """获取会话记忆,并在消息超限时自动触发压缩。"""
        history = self._get_history(session_id)
        messages = history.messages

        if len(messages) > Config.MAX_HISTORY_LENGTH:
            # 1. 格式化历史消息
            chat_text = self._format_messages(messages)

            # 2. 调用总结链生成摘要
            summary = self.summary_chain.invoke({"chat_history": chat_text})

            # 3. 清空 Redis 中该会话的所有旧消息
            history.clear()

            # 4. 将摘要作为新的上下文存入 Redis
            # 通过 user -> assistant 的对话形式,使摘要自然融入对话流
            history.add_user_message("【历史对话摘要】")
            history.add_ai_message(summary)

        # 返回 LangChain 标准记忆对象
        # 设置 return_messages=True 确保返回的是消息对象列表,而非字符串
        return ConversationBufferMemory(
            memory_key=self.memory_key,
            chat_memory=history,
            return_messages=True,
        )

    def save_memory(self, session_id: str, user_input: str, ai_output: str) -> None:
        """保存一轮新的对话到记忆存储中。"""
        history = self._get_history(session_id)
        history.add_user_message(user_input)
        history.add_ai_message(ai_output)

示例主程序 main.py

该脚本模拟了多用户场景,用于验证会话隔离和自动压缩功能。它定义了一个简单的天气查询工具,并分别为两个用户创建了独立的智能体执行器。

# main.py
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain.tools import tool
from config import Config
from memory_manager import MemoryManager

@tool
def get_weather(city: str) -> str:
    """查询指定城市的实时天气情况。"""
    return f"{city} 当前天气晴朗,气温 25°C。"

tools = [get_weather]

def run_agent_for_user(user_id: str, memory_manager: MemoryManager):
    """为指定用户创建并运行一个完整的对话 Agent。"""
    print(f"\n--- 开始新会话: {user_id} ---")

    # 1. 获取该用户的独立记忆
    memory = memory_manager.get_memory(user_id)

    # 2. 构建提示词模板
    system_prompt = "你是小浪助手,一位友好且知识渊博的助理。"
    prompt = ChatPromptTemplate.from_messages([
        ("system", system_prompt),
        MessagesPlaceholder(variable_name="chat_history"),
        ("human", "{input}"),
        MessagesPlaceholder(variable_name="agent_scratchpad"),
    ])

    # 3. 创建 Agent 和执行器
    llm = ChatOpenAI(
        model="gpt-3.5-turbo",
        temperature=0.0,
        openai_api_key=Config.OPENAI_API_KEY,
        base_url=Config.OPENAI_BASE_URL,
    )
    agent = create_tool_calling_agent(llm, tools, prompt)
    executor = AgentExecutor(
        agent=agent,
        tools=tools,
        memory=memory,
        verbose=False,
        handle_parsing_errors=True,
    )

    # 4. 模拟多轮对话
    # 第一轮:自我介绍,让 Agent 记住用户名字
    input1 = f"你好,我叫 {user_id}。请记住我的名字。"
    response1 = executor.invoke({"input": input1})
    print(f"用户: {input1}")
    print(f"助手: {response1['output']}")
    memory_manager.save_memory(user_id, input1, response1['output'])

    # 第二轮:询问名字,测试记忆能力
    input2 = "你还记得我叫什么名字吗?"
    response2 = executor.invoke({"input": input2})
    print(f"用户: {input2}")
    print(f"助手: {response2['output']}")
    memory_manager.save_memory(user_id, input2, response2['output'])

    # 第三轮:查询天气,测试工具调用
    input3 = "北京今天天气怎么样?"
    response3 = executor.invoke({"input": input3})
    print(f"用户: {input3}")
    print(f"助手: {response3['output']}")
    memory_manager.save_memory(user_id, input3, response3['output'])

    # 输出该用户的 Redis 消息总数,用于观察压缩是否触发
    history = memory_manager._get_history(user_id)
    print(f"--- {user_id} 当前 Redis 中存储的消息总数: {len(history.messages)} ---")

def main():
    # 初始化全局记忆管理器(不同的用户会通过 session_id 自然隔离)
    memory_manager = MemoryManager(system_prompt="你是小浪助手。")

    # 模拟两个不同用户
    run_agent_for_user("alice", memory_manager)
    run_agent_for_user("bob", memory_manager)

if __name__ == "__main__":
    main()

运行与验证

  1. 启动 Redis 服务:确保本地或远程的 Redis 服务已运行,且 REDIS_URL 配置正确(例如,redis://localhost:6379/0)。
  2. 配置环境变量:在项目根目录创建 .env 文件,填入有效的 OpenAI API 密钥。示例内容如下:
OPENAI_API_KEY="your-api-key"
OPENAI_BASE_URL="your-base-url" # 可选
REDIS_URL="redis://localhost:6379/0"
MAX_HISTORY_LENGTH=80
  1. 执行程序:在终端中运行 python main.py
  2. 观察输出:可以看到两个用户(alicebob)的对话完全独立,Agent 能够准确记住各自的名字。通过调整 MAX_HISTORY_LENGTH 为一个较小值(如 3),可以快速触发压缩逻辑并观察其效果。

API 速览

组件 / API 所属库 说明
RedisChatMessageHistory langchain_community.chat_message_histories 核心存储适配器,实现了将对话消息持久化至 Redis 列表的增删改查。关键参数:session_id (会话ID), url (Redis连接字符串)。
ConversationBufferMemory langchain.memory 基础记忆缓冲区,用于在内存中缓存对话历史。本方案中将其 chat_memory 参数设为 RedisChatMessageHistory 实例,实现了内存与持久化存储的结合。
ChatPromptTemplate.from_template langchain.prompts 用于从模板字符串创建提示词模板,支持变量插值。
StrOutputParser langchain_core.output_parsers 标准输出解析器,用于将 LLM 的生成结果解析为字符串。

参考文档

总结

本文详细剖析了构建智能体长期记忆系统的关键技术路径,并提供了一个基于 Redis 和 LangChain 的完整实现。该方案通过巧妙的“触发式摘要压缩”机制,有效解决了上下文窗口受限的痛点,同时利用 session_id 实现了严格的会话隔离。

特别地,通过在摘要生成提示词中注入系统角色设定,确保了压缩后的记忆仍能保持智能体的统一人格。此设计模式可直接应用于生产环境的单智能体项目,为构建具有长时记忆能力的对话应用提供了坚实的技术基座。

Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐