LangChain 会话记忆(Conversation Memory)

一、简介

在构建对话式AI应用时,会话记忆(Conversation Memory)是让AI真正"智能"的关键。没有记忆的AI每次对话都是"初次见面",而有记忆的AI能够记住上下文、理解指代、提供连贯的对话体验。

1.1 核心概念

会话记忆是指AI系统在多轮对话中存储和利用历史信息的能力。它包括:

  • 短期记忆:当前对话中的上下文
  • 长期记忆:跨会话的用户偏好和知识
  • 实体记忆:记住特定实体的属性

1.2 核心作用

大语言模型本身是无状态的------每次对话都是独立的,模型不会记住之前的交流。但在实际应用中,我们需要:

  • 上下文连续性:能够引用之前讨论的内容
  • 个性化体验:记住用户的偏好和历史
  • 多轮任务完成:逐步收集信息,完成复杂任务
  • 减少重复输入:不需要每次重复背景信息

会话记忆就是解决这些问题的关键技术。

二、LangChain 记忆架构

2.1 整体架构图

LangChain的记忆架构是一个分层、模块化、可扩展的系统,旨在解决LLM应用中的状态管理问题。

bash 复制代码
┌─────────────────────────────────────────────────────┐
│                    应用层                            │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐ │
│  │ Conversation │  │   Custom     │  │   Agent      │ │
│  │    Chain     │  │    Chain     │  │   with Memory│ │
│  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘ │
└─────────┼─────────────────┼─────────────────┼─────────┘
          │                 │                 │
┌─────────▼─────────────────▼─────────────────▼─────────┐
│                   接口层                              │
│  ┌─────────────────────────────────────────────────┐ │
│  │           BaseMemory 抽象基类                     │ │
│  │  ┌─────────────────────────────────────────────┐ │ │
│  │  │  load_memory_variables()  save_context()    │ │ │
│  │  │  clear()  dict()                            │ │ │
│  │  └─────────────────────────────────────────────┘ │ │
│  └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
                          │
┌─────────────────────────┼─────────────────────────────┐
│                 实现层                                │
│  ┌──────────────┐ ┌──────────────┐ ┌──────────────┐  │
│  │   Buffer     │ │   Window     │ │   Summary    │  │
│  │   Memory     │ │   Memory     │ │   Memory     │  │
│  ├──────────────┤ ├──────────────┤ ├──────────────┤  │
│  │ • 完整历史   │ │ • 滑动窗口   │ │ • LLM摘要    │  │
│  │ • 消息列表   │ │ • 固定轮数   │ │ • 压缩存储   │  │
│  └──────────────┘ └──────────────┘ └──────────────┘  │
│                                                      │
│  ┌──────────────┐ ┌──────────────┐ ┌──────────────┐  │
│  │   Entity     │ │   Vector     │ │   Combined   │  │
│  │   Memory     │ │   Memory     │ │   Memory     │  │
│  ├──────────────┤ ├──────────────┤ ├──────────────┤  │
│  │ • 实体提取   │ │ • 语义检索   │ │ • 多记忆组合 │  │
│  │ • 属性存储   │ │ • 相似度查询 │ │ • 加权集成   │  │
│  └──────────────┘ └──────────────┘ └──────────────┘  │
└─────────────────────────────────────────────────────┘
                          │
┌─────────────────────────┼─────────────────────────────┐
│                 存储层                                │
│  ┌──────────────┐ ┌──────────────┐ ┌──────────────┐  │
│  │  ChatMessage │ │  VectorStore │ │  External    │  │
│  │   History    │ │              │ │   Storage    │  │
│  ├──────────────┤ ├──────────────┤ ├──────────────┤  │
│  │ • 内存列表   │ │ • 向量数据库 │ │ • Redis      │  │
│  │ • 消息对象   │ │ • 相似度索引 │ │ • SQLite     │  │
│  │ • 序列化     │ │ • 检索器     │ │ • 文件系统   │  │
│  └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────┘

2.2 核心抽象:BaseMemory

所有记忆组件的 "父类",定义了记忆的核心接口,所有自定义 / 内置记忆都需实现这两个方法:

  • load_memory_variables(inputs):加载历史对话,返回键值对(如 {"history": "用户:你好!AI:你好呀~"});
  • save_context(inputs, outputs):保存新的对话轮次(inputs = 用户输入,outputs=AI 输出);
  • 可选:clear():清空记忆。
python 复制代码
from abc import ABC, abstractmethod
from typing import Dict, Any, List, Optional
from langchain.schema import BaseMessage

class BaseMemory(ABC):
    """所有记忆类的抽象基类"""
    
    @property
    @abstractmethod
    def memory_variables(self) -> List[str]:
        """返回记忆变量名列表"""
        pass
    
    @abstractmethod
    def load_memory_variables(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
        """根据输入加载记忆变量"""
        pass
    
    @abstractmethod
    def save_context(self, inputs: Dict[str, Any], outputs: Dict[str, Any]) -> None:
        """保存对话上下文"""
        pass
    
    @abstractmethod
    def clear(self) -> None:
        """清除记忆"""
        pass
    
    def __call__(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
        """使记忆实例可调用"""
        return self.load_memory_variables(inputs)

2.3 记忆架构的核心组件

2.3.1 ConversationBufferMemory(基础缓冲记忆)

原理:

  • 最简单的记忆类型 ------ 按顺序保存所有对话历史,生成回答时完整加载所有历史。

核心特点:

  • 最简单、最常用;
  • 按顺序存储所有对话历史,无截断、无压缩;
  • 适合短对话(上下文窗口足够容纳所有历史)。

示例:

python 复制代码
from langchain_openai import ChatOpenAI
from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory

# 初始化LLM
llm = ChatOpenAI(model="gpt-3.5-turbo", api_key="YOUR_KEY")

# 初始化记忆组件(return_messages=True 返回Message对象,否则返回字符串)
memory = ConversationBufferMemory(return_messages=True)

# 初始化对话链(绑定记忆)
conversation = ConversationChain(
    llm=llm,
    memory=memory,
    verbose=True  # 打印Prompt详情,方便调试
)

# 多轮对话
# 第一轮
response1 = conversation.invoke({"input": "我叫张三,是一名程序员"})
print("AI回答1:", response1["response"])

# 第二轮(LLM能记住用户名)
response2 = conversation.invoke({"input": "我是做什么工作的?"})
print("AI回答2:", response2["response"])

# 查看记忆中的历史对话
print("\n对话历史:")
print(memory.load_memory_variables({}))

输出示例:

bash 复制代码
AI回答1: 你好张三!作为程序员,你平时主要做哪方面的开发呢?
AI回答2: 你是一名程序员呀😜 是做前端、后端还是全栈开发呢?

对话历史:
{
  "chat_history": [
    HumanMessage(content="我叫张三,是一名程序员"),
    AIMessage(content="你好张三!作为程序员,你平时主要做哪方面的开发呢?"),
    HumanMessage(content="我是做什么工作的?"),
    AIMessage(content="你是一名程序员呀😜 是做前端、后端还是全栈开发呢?")
  ]
}

2.3.2 ConversationBufferWindowMemory(窗口缓冲记忆)

原理:

  • 只保留最近 N 轮对话("窗口大小"),截断更早的历史,避免 Token 超限。

核心特点:

  • 只保留最近 k 轮对话,自动截断更早的历史;
  • 解决 "对话过长导致 Prompt 超限" 问题;
  • 适合中长对话。

示例:

python 复制代码
from langchain.memory import ConversationBufferWindowMemory

# 只保留最近2轮对话(k=2)
memory = ConversationBufferWindowMemory(
    k=2,
    return_messages=True
)

conversation = ConversationChain(llm=llm, memory=memory, verbose=True)

# 多轮对话(超过2轮后,最早的历史会被截断)
conversation.invoke({"input": "我叫张三"})
conversation.invoke({"input": "我喜欢编程"})
conversation.invoke({"input": "我用Python开发"})

# 查看记忆(仅保留最后2轮)
print(memory.load_memory_variables({}))

2.3.3 ConversationSummaryMemory(摘要记忆)

原理:

  • 不保存完整历史,而是用大模型对历史对话生成 "摘要",只保存摘要内容 ------ 大幅减少 Token 消耗。

核心特点:

  • 不存储原始对话,而是用 LLM 生成对话历史摘要;
  • 大幅减少 Prompt 长度,适合超长对话;
  • 缺点:有摘要损耗,可能丢失细节。

示例:

python 复制代码
from langchain.memory import ConversationSummaryMemory

# 初始化摘要记忆(指定用于摘要的LLM)
memory = ConversationSummaryMemory(
    llm=llm,  # 用gpt-3.5-turbo生成摘要
    return_messages=True
)

conversation = ConversationChain(llm=llm, memory=memory, verbose=True)

# 长对话
conversation.invoke({"input": "我叫张三,是一名Python程序员,有5年工作经验"})
conversation.invoke({"input": "我主要做后端开发,擅长Django和FastAPI"})
conversation.invoke({"input": "我最近在学LangChain,想做AI Agent"})

# 查看摘要(而非原始对话)
print("\n对话摘要:")
print(memory.load_memory_variables({}))

输出示例:

bash 复制代码
对话摘要:
{
  "chat_history": [
    SystemMessage(content="用户介绍自己叫张三,是有5年工作经验的Python后端程序员,擅长Django和FastAPI,最近在学习LangChain想做AI Agent。")
  ]
}

2.3.4 ConversationKGMemory(知识图谱记忆)

原理:

  • 把对话中的实体和关系提取出来,构建知识图谱(如 "小明 → 喜欢 → Python"),只存储结构化的知识,而非原始对话。

核心特点:

  • 把对话中的实体和关系提取为知识图谱(KG);
  • 只记忆关键实体(如 "张三 - Python 程序员"),而非所有话术;
  • 适合需要精准记忆实体信息的场景(如客服、知识库问答)。

示例:

python 复制代码
from langchain.memory import ConversationKGMemory

# 初始化实体记忆
memory = ConversationKGMemory(
    llm=llm,
    return_messages=True
)

conversation = ConversationChain(llm=llm, memory=memory, verbose=True)

# 对话(提取实体)
conversation.invoke({"input": "张三是一名Python程序员,他的公司叫阿里云"})

# 查看知识图谱
print("\n知识图谱实体:")
print(memory.kg.get_triples())  # 输出:[('张三', '职业', 'Python程序员'), ('张三', '公司', '阿里云')]

2.3.5 VectorStoreRetrieverMemory(向量记忆)

原理:

  • 把对话历史向量化后存入向量数据库,生成回答时,检索 "和当前问题最相关的历史对话",而非加载所有历史。

核心特点:

  • 结合 RAG 逻辑,只加载相关历史,精度更高;
  • 支持超长历史(向量检索效率高);
  • 适用场景:RAG 对话机器人、需要精准匹配历史的场景。

示例:

python 复制代码
from langchain.memory import VectorStoreRetrieverMemory
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings

# 1. 初始化向量数据库(存储对话历史向量)
embeddings = OpenAIEmbeddings()
vector_store = Chroma(embedding_function=embeddings, persist_directory="./memory_db")
retriever = vector_store.as_retriever(search_kwargs={"k": 1})  # 检索最近 1 条相关历史

# 2. 初始化向量记忆
memory = VectorStoreRetrieverMemory(retriever=retriever)

# 3. 保存对话历史(手动测试)
memory.save_context({"input": "我叫小明,喜欢编程"}, {"output": "很高兴认识你,小明!"})

# 4. 加载相关历史
print(memory.load_memory_variables({"input": "我叫什么?"}))  # 检索到相关历史,返回:{"history": "用户:我叫小明... AI:很高兴认识你..."}

三、记忆组件的核心操作

3.1 手动控制记忆(增删改查)

python 复制代码
# 1. 手动添加对话
memory = ConversationBufferMemory(return_messages=True)
memory.save_context(
    {"input": "手动添加的用户问题"},
    {"output": "手动添加的AI回答"}
)

# 2. 读取记忆
vars = memory.load_memory_variables({})
print("读取记忆:", vars)

# 3. 清空记忆
memory.clear()
print("清空后:", memory.load_memory_variables({}))

# 4. 修改记忆(需先读取,修改后重新设置)
vars = memory.load_memory_variables({})
vars["chat_history"][0].content = "修改后的用户问题"
memory.chat_memory.messages = vars["chat_history"]

3.2 自定义 Prompt 模板(注入记忆)

默认 Prompt 可能不满足需求,需自定义包含 {chat_history} 的模板:

python 复制代码
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

# 自定义Prompt模板
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个友好的助手,记住用户的所有信息。"),
    MessagesPlaceholder(variable_name="chat_history"),  # 注入对话历史
    ("human", "{input}")  # 注入新问题
])

# 绑定模板到对话链
conversation = ConversationChain(
    llm=llm,
    memory=memory,
    prompt=prompt,
    verbose=True
)

3.3 多用户隔离(会话 ID)

生产环境需为不同用户维护独立记忆,用 ConversationBufferMemory + 会话 ID:

python 复制代码
from langchain.memory import ConversationBufferMemory
from langchain_core.runnables import RunnableWithMessageHistory
from langchain_core.chat_history import InMemoryChatMessageHistory

# 存储所有用户的对话历史(key=会话ID,value=ChatMessageHistory)
store = {}

# 获取用户的记忆
def get_session_history(session_id: str):
    if session_id not in store:
        store[session_id] = InMemoryChatMessageHistory()
    return store[session_id]

# 绑定会话ID到对话链
chain = RunnableWithMessageHistory(
    conversation,
    get_session_history,
    input_messages_key="input",
    history_messages_key="chat_history"
)

# 不同用户的独立对话
# 用户1(session_id=user1)
response1 = chain.invoke(
    {"input": "我叫张三"},
    config={"configurable": {"session_id": "user1"}}
)

# 用户2(session_id=user2)
response2 = chain.invoke(
    {"input": "我叫李四"},
    config={"configurable": {"session_id": "user2"}}
)

# 用户1的记忆(只包含自己的对话)
print("用户1记忆:", store["user1"].messages)
# 用户2的记忆(只包含自己的对话)
print("用户2记忆:", store["user2"].messages)

3.4 持久化记忆(避免重启丢失)

默认记忆存在内存中,重启后丢失,需持久化到文件 / 数据库:

python 复制代码
# 方式1:持久化到本地文件(JSON)
import json
from langchain.memory import ConversationBufferMemory

memory = ConversationBufferMemory(return_messages=True)

# 保存到文件
def save_memory(memory, file_path):
    messages = memory.chat_memory.messages
    serializable_messages = [{"role": m.type, "content": m.content} for m in messages]
    with open(file_path, "w", encoding="utf-8") as f:
        json.dump(serializable_messages, f, ensure_ascii=False)

# 从文件加载
def load_memory(memory, file_path):
    with open(file_path, "r", encoding="utf-8") as f:
        messages = json.load(f)
    for msg in messages:
        if msg["role"] == "human":
            memory.chat_memory.add_user_message(msg["content"])
        elif msg["role"] == "ai":
            memory.chat_memory.add_ai_message(msg["content"])

# 示例:保存/加载
memory.save_context({"input": "我叫张三"}, {"output": "你好张三!"})
save_memory(memory, "memory.json")

# 新建记忆并加载
new_memory = ConversationBufferMemory(return_messages=True)
load_memory(new_memory, "memory.json")
print("加载后的记忆:", new_memory.load_memory_variables({}))

3.5 结合 RAG 使用记忆

让 RAG 既 "记住对话历史",又 "检索文档":

python 复制代码
from langchain.chains import RetrievalQAWithSourcesChain
from langchain_community.vectorstores import Chroma

# 1. 初始化 RAG 检索器
rag_vector_store = Chroma(persist_directory="./rag_db", embedding_function=embeddings)
retriever = rag_vector_store.as_retriever(k=3)

# 2. 初始化记忆
memory = ConversationBufferWindowMemory(k=3, memory_key="history", return_messages=True)

# 3. 构建带记忆的 RAG 链
chain = RetrievalQAWithSourcesChain.from_chain_type(
    llm=llm,
    chain_type="stuff",
    retriever=retriever,
    memory=memory,
    chain_type_kwargs={
        "prompt": ChatPromptTemplate.from_messages([
            ("system", "结合历史对话和文档回答问题:{history}"),
            ("human", "{question}"),
            ("system", "文档参考:{context}")
        ])
    }
)

# 4. 多轮 RAG 对话
chain.invoke({"question": "RAG 文本分割器的核心参数?"})
chain.invoke({"question": "我刚才问的是什么问题?"})  # 记住历史,同时能检索文档

3.6 控制记忆长度(避免 Token 超限)

  • 用 ConversationBufferWindowMemory 限制轮数;
  • 用 ConversationSummaryMemory 压缩历史;
  • 自定义截断逻辑:
python 复制代码
from langchain_core.messages import HumanMessage, AIMessage

def truncate_history(history, max_tokens=1000):
    """自定义截断历史,控制 Token 数"""
    truncated = []
    total_tokens = 0
    # 倒序遍历,保留最近的内容
    for msg in reversed(history):
        tokens = len(embeddings.embed_query(msg.content))
        if total_tokens + tokens > max_tokens:
            break
        truncated.append(msg)
        total_tokens += tokens
    return list(reversed(truncated))

# 重写 load_memory_variables 方法
class CustomMemory(ConversationBufferMemory):
    def load_memory_variables(self, inputs):
        history = self.chat_memory.messages
        truncated_history = truncate_history(history)
        return {"history": truncated_history}

3.7 自定义记忆中间件

python 复制代码
from langchain.agents.middleware import AgentMiddleware
from langchain.memory import ConversationBufferMemory

class MemoryMiddleware(AgentMiddleware):
    """自定义记忆中间件"""
    
    def __init__(self, memory):
        self.memory = memory
    
    def before_agent(self, state, runtime):
        """在智能体执行前加载记忆"""
        history = self.memory.load_memory_variables({})
        
        # 将历史插入到消息列表开头
        if history.get("history"):
            state["messages"] = history["history"] + state["messages"]
        
        return None
    
    def after_agent(self, state, runtime):
        """在智能体执行后保存新消息"""
        messages = state.get("messages", [])
        if len(messages) >= 2:
            # 保存最后一轮对话
            self.memory.save_context(
                {"input": messages[-2].content},
                {"output": messages[-1].content}
            )
        return None

# 使用
memory = ConversationBufferMemory()
middleware = MemoryMiddleware(memory)

agent = create_agent(
    model="openai:gpt-4o",
    tools=[],
    middleware=[middleware]
)

四、最佳实践与优化

4.1 记忆选择指南

场景 推荐记忆类型 理由
简短对话 BufferMemory 简单直接
长对话 SummaryMemory / SummaryBufferMemory 避免Token超限
需要记住实体 EntityMemory 专门提取实体
长期记忆 VectorStoreRetrieverMemory 可扩展、可检索
多场景组合 CombinedMemory 发挥各自优势
生产环境 自定义持久化记忆 可控、可扩展

4.2 性能优化技巧

  1. 限制记忆大小:避免无限增长
  2. 使用窗口记忆:只保留最近K轮
  3. 定期摘要:长对话及时总结
  4. 持久化存储:避免内存溢出
  5. 缓存机制:减少重复计算
  6. 异步保存:不阻塞主流程
python 复制代码
class OptimizedMemory:
    """优化记忆性能"""
    
    def __init__(self, max_size=1000, cache_size=50):
        self.max_size = max_size
        self.cache_size = cache_size
        self.messages = []
        self.cache = {}  # 最近访问的缓存
    
    def add_message(self, message):
        # 限制总大小
        if len(self.messages) >= self.max_size:
            # 移除最旧的消息
            self.messages.pop(0)
        
        self.messages.append(message)
        
        # 更新缓存
        if len(self.cache) >= self.cache_size:
            # 移除最久未使用的缓存
            oldest = min(self.cache.keys(), key=lambda k: self.cache[k]['last_access'])
            del self.cache[oldest]
    
    def get_context(self, query=None):
        # 如果有查询,尝试从缓存获取
        if query and query in self.cache:
            self.cache[query]['last_access'] = datetime.now()
            return self.cache[query]['result']
        
        # 构建上下文
        context = "\n".join([m.content for m in self.messages[-10:]])  # 只取最近10条
        
        # 存入缓存
        if query:
            self.cache[query] = {
                'result': context,
                'last_access': datetime.now()
            }
        
        return context
    
    def batch_save(self, messages):
        """批量保存消息"""
        self.messages.extend(messages)
        # 可以在这里添加批量写入数据库的逻辑

4.3 记忆清理与维护

python 复制代码
class MemoryMaintenance:
    """记忆维护工具"""
    
    def __init__(self, memory, retention_days=30):
        self.memory = memory
        self.retention_days = retention_days
    
    def clean_old_memories(self):
        """清理过期记忆"""
        cutoff = datetime.now() - timedelta(days=self.retention_days)
        
        # 假设消息有timestamp属性
        self.memory.messages = [
            m for m in self.memory.messages 
            if m.timestamp > cutoff
        ]
    
    def deduplicate(self):
        """去重"""
        seen = set()
        unique_messages = []
        
        for m in self.memory.messages:
            content_hash = hash(m.content)
            if content_hash not in seen:
                seen.add(content_hash)
                unique_messages.append(m)
        
        self.memory.messages = unique_messages
    
    def summarize_old(self, threshold=100):
        """将旧消息摘要"""
        if len(self.memory.messages) <= threshold:
            return
        
        old_messages = self.memory.messages[:-threshold]
        recent_messages = self.memory.messages[-threshold:]
        
        # 生成摘要
        summary = self._generate_summary(old_messages)
        
        # 替换为摘要
        self.memory.messages = [
            Document(content=f"[历史摘要] {summary}"),
            *recent_messages
        ]
    
    def _generate_summary(self, messages):
        # 使用LLM生成摘要
        text = "\n".join([m.content for m in messages])
        # ... 调用LLM生成摘要
        return "历史对话摘要..."

4.4 多用户记忆管理

python 复制代码
class MultiUserMemoryManager:
    """多用户记忆管理器"""
    
    def __init__(self, memory_class=ConversationBufferMemory):
        self.memory_class = memory_class
        self.user_memories = {}
        self.default_memory = memory_class()
    
    def get_memory(self, user_id):
        """获取用户的记忆实例"""
        if user_id not in self.user_memories:
            self.user_memories[user_id] = self.memory_class()
        return self.user_memories[user_id]
    
    def save_context(self, user_id, input_dict, output_dict):
        """保存用户上下文"""
        memory = self.get_memory(user_id)
        memory.save_context(input_dict, output_dict)
    
    def load_memory(self, user_id, variables):
        """加载用户记忆"""
        memory = self.get_memory(user_id)
        return memory.load_memory_variables(variables)
    
    def clear_user_memory(self, user_id):
        """清除用户记忆"""
        if user_id in self.user_memories:
            self.user_memories[user_id].clear()
    
    def save_all(self, storage_path):
        """保存所有用户记忆"""
        import pickle
        with open(storage_path, "wb") as f:
            pickle.dump(self.user_memories, f)
    
    def load_all(self, storage_path):
        """加载所有用户记忆"""
        import pickle
        import os
        if os.path.exists(storage_path):
            with open(storage_path, "rb") as f:
                self.user_memories = pickle.load(f)

# 使用
manager = MultiUserMemoryManager()

# 用户A对话
manager.save_context("user_a", {"input": "你好"}, {"output": "你好A"})
manager.save_context("user_a", {"input": "我叫小明"}, {"output": "你好小明"})

# 用户B对话
manager.save_context("user_b", {"input": "你好"}, {"output": "你好B"})

# 分别加载
history_a = manager.load_memory("user_a", {})
history_b = manager.load_memory("user_b", {})

五、完整实战:智能客服记忆系统

python 复制代码
import json
import sqlite3
from datetime import datetime
from typing import Optional, List, Dict
from langchain.memory import ConversationBufferMemory, ConversationSummaryMemory
from langchain_openai import ChatOpenAI
from langchain.schema import HumanMessage, AIMessage

class CustomerServiceMemory:
    """智能客服记忆系统"""
    
    def __init__(self, user_id: str, db_path: str = "customer_service.db"):
        self.user_id = user_id
        self.db_path = db_path
        self.short_term_memory = ConversationBufferMemory(
            return_messages=True,
            memory_key="recent_history",
            input_key="input",
            output_key="output"
        )
        self.long_term_memory = ConversationSummaryMemory(
            llm=ChatOpenAI(model="gpt-3.5-turbo"),
            memory_key="user_profile"
        )
        self._init_db()
    
    def _init_db(self):
        """初始化数据库"""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        # 会话表
        cursor.execute("""
            CREATE TABLE IF NOT EXISTS sessions (
                session_id TEXT PRIMARY KEY,
                user_id TEXT,
                start_time DATETIME,
                end_time DATETIME,
                summary TEXT
            )
        """)
        
        # 消息表
        cursor.execute("""
            CREATE TABLE IF NOT EXISTS messages (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                session_id TEXT,
                user_id TEXT,
                role TEXT,
                content TEXT,
                timestamp DATETIME,
                metadata TEXT
            )
        """)
        
        # 用户画像表
        cursor.execute("""
            CREATE TABLE IF NOT EXISTS user_profiles (
                user_id TEXT PRIMARY KEY,
                name TEXT,
                preferences TEXT,
                history_summary TEXT,
                last_active DATETIME,
                total_sessions INTEGER
            )
        """)
        
        conn.commit()
        conn.close()
    
    def start_session(self, session_id: Optional[str] = None):
        """开始新会话"""
        import uuid
        self.session_id = session_id or str(uuid.uuid4())
        self.start_time = datetime.now()
        
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        cursor.execute("""
            INSERT INTO sessions (session_id, user_id, start_time)
            VALUES (?, ?, ?)
        """, (self.session_id, self.user_id, self.start_time))
        conn.commit()
        conn.close()
        
        return self.session_id
    
    def add_message(self, role: str, content: str, metadata: Optional[Dict] = None):
        """添加消息"""
        # 保存到数据库
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        cursor.execute("""
            INSERT INTO messages (session_id, user_id, role, content, timestamp, metadata)
            VALUES (?, ?, ?, ?, ?, ?)
        """, (
            self.session_id,
            self.user_id,
            role,
            content,
            datetime.now(),
            json.dumps(metadata or {})
        ))
        conn.commit()
        conn.close()
        
        # 更新短期记忆
        if role == "user":
            self.short_term_memory.save_context(
                {"input": content},
                {"output": ""}  # 暂时没有AI回答
            )
        else:
            # 找到上一个用户消息并更新回答
            last_user_msg = self._get_last_user_message()
            if last_user_msg:
                self.short_term_memory.save_context(
                    {"input": last_user_msg},
                    {"output": content}
                )
    
    def _get_last_user_message(self) -> Optional[str]:
        """获取最后一条用户消息"""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        cursor.execute("""
            SELECT content FROM messages
            WHERE user_id = ? AND role = 'user'
            ORDER BY timestamp DESC
            LIMIT 1
        """, (self.user_id,))
        result = cursor.fetchone()
        conn.close()
        return result[0] if result else None
    
    def end_session(self):
        """结束会话"""
        self.end_time = datetime.now()
        
        # 生成会话摘要
        session_messages = self.get_session_messages()
        session_text = "\n".join([f"{m[2]}: {m[3]}" for m in session_messages])
        
        summary_prompt = f"请总结以下客服对话:\n{session_text}"
        summary = ChatOpenAI().invoke(summary_prompt).content
        
        # 更新会话记录
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        cursor.execute("""
            UPDATE sessions
            SET end_time = ?, summary = ?
            WHERE session_id = ?
        """, (self.end_time, summary, self.session_id))
        
        # 更新用户画像
        cursor.execute("""
            INSERT OR REPLACE INTO user_profiles (user_id, last_active, total_sessions)
            VALUES (?, ?, COALESCE(
                (SELECT total_sessions + 1 FROM user_profiles WHERE user_id = ?),
                1
            ))
        """, (self.user_id, datetime.now(), self.user_id))
        
        conn.commit()
        conn.close()
    
    def get_session_messages(self, limit: int = 50) -> List:
        """获取当前会话消息"""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        cursor.execute("""
            SELECT timestamp, role, content FROM messages
            WHERE session_id = ?
            ORDER BY timestamp
            LIMIT ?
        """, (self.session_id, limit))
        results = cursor.fetchall()
        conn.close()
        return results
    
    def get_user_history(self, limit: int = 100) -> str:
        """获取用户历史对话"""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        cursor.execute("""
            SELECT role, content FROM messages
            WHERE user_id = ?
            ORDER BY timestamp DESC
            LIMIT ?
        """, (self.user_id, limit))
        
        messages = cursor.fetchall()
        conn.close()
        
        # 格式化为对话
        history = []
        for role, content in reversed(messages):  # 时间正序
            prefix = "用户:" if role == "user" else "客服:"
            history.append(f"{prefix}{content}")
        
        return "\n".join(history)
    
    def get_user_preferences(self) -> Dict:
        """获取用户偏好"""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        cursor.execute("""
            SELECT preferences FROM user_profiles
            WHERE user_id = ?
        """, (self.user_id,))
        result = cursor.fetchone()
        conn.close()
        
        if result and result[0]:
            return json.loads(result[0])
        return {}
    
    def update_user_preferences(self, preferences: Dict):
        """更新用户偏好"""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        # 获取现有偏好
        cursor.execute("""
            SELECT preferences FROM user_profiles
            WHERE user_id = ?
        """, (self.user_id,))
        result = cursor.fetchone()
        
        if result and result[0]:
            existing = json.loads(result[0])
            existing.update(preferences)
            new_prefs = existing
        else:
            new_prefs = preferences
        
        cursor.execute("""
            UPDATE user_profiles
            SET preferences = ?
            WHERE user_id = ?
        """, (json.dumps(new_prefs, ensure_ascii=False), self.user_id))
        
        conn.commit()
        conn.close()
    
    def get_context(self, current_query: str) -> str:
        """获取完整上下文"""
        context_parts = []
        
        # 1. 用户画像摘要
        profile = self.long_term_memory.load_memory_variables({})
        if profile.get("user_profile"):
            context_parts.append(f"【用户画像】\n{profile['user_profile']}")
        
        # 2. 近期对话
        recent = self.short_term_memory.load_memory_variables({})
        if recent.get("recent_history"):
            context_parts.append(f"【近期对话】\n{recent['recent_history']}")
        
        # 3. 用户偏好
        prefs = self.get_user_preferences()
        if prefs:
            pref_text = "\n".join([f"{k}: {v}" for k, v in prefs.items()])
            context_parts.append(f"【用户偏好】\n{pref_text}")
        
        return "\n\n".join(context_parts)

# 使用示例
def customer_service_chat():
    """智能客服对话示例"""
    
    # 初始化记忆系统
    memory_system = CustomerServiceMemory(user_id="user_123")
    
    # 开始新会话
    session_id = memory_system.start_session()
    print(f"开始会话: {session_id}")
    
    # 模拟对话
    messages = [
        ("user", "你好,我叫张三"),
        ("ai", "你好张三!我是客服小智,有什么可以帮您?"),
        ("user", "我想查询我的订单状态"),
        ("ai", "好的,请提供您的订单号"),
        ("user", "订单号是 ORDER-2024-001"),
        ("ai", "您的订单已发货,预计明天送达"),
        ("user", "谢谢,我习惯晚上8点后联系客服"),
        ("ai", "好的,已记录您的偏好")
    ]
    
    for role, content in messages:
        memory_system.add_message(role, content)
        print(f"{role}: {content}")
    
    # 更新用户偏好
    memory_system.update_user_preferences({
        "contact_time": "晚上8点后",
        "preferred_channel": "在线客服"
    })
    
    # 查看上下文
    context = memory_system.get_context("订单状态")
    print("\n【完整上下文】")
    print(context)
    
    # 结束会话
    memory_system.end_session()
    
    # 查看历史
    print("\n【用户历史】")
    history = memory_system.get_user_history(limit=10)
    print(history)

# 运行
customer_service_chat()

六、常见问题与解决方案

Q1:对话历史过长导致 Prompt 超限

  • 现象:LLM 抛出 ContextWindowExceededError;
  • 根因:记忆组件注入了过多历史,超过模型上下文窗口;
  • 解决方案:
    • 用 ConversationBufferWindowMemory(限制轮数);
    • 用 ConversationSummaryMemory(摘要压缩);
    • 用 VectorStoreRetrieverMemory(相关性检索)。

Q2:记忆和 Agent 结合时丢失上下文

  • 现象:Agent 能调用工具,但记不住对话历史;

  • 根因:Agent 的 Prompt 未包含 {chat_history} 占位符;

  • 解决方案:

    python 复制代码
    from langchain.agents import create_openai_functions_agent, AgentExecutor
    from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
    
    # Agent Prompt 必须包含 chat_history
    prompt = ChatPromptTemplate.from_messages([
        ("system", "你是一个助手,使用提供的工具回答问题。"),
        MessagesPlaceholder(variable_name="chat_history"),  # 关键:注入记忆
        ("human", "{input}"),
        MessagesPlaceholder(variable_name="agent_scratchpad"),  # Agent思考过程
    ])
    
    agent = create_openai_functions_agent(llm, tools, prompt)
    agent_executor = AgentExecutor(agent=agent, tools=tools, memory=memory)

Q3:多用户记忆串号

  • 现象:用户 A 能看到用户 B 的对话历史;
  • 根因:所有用户共用一个记忆实例;
  • 解决方案:用 RunnableWithMessageHistory + 会话 ID 隔离(见上文 "多用户隔离")。

Q4:摘要记忆丢失关键信息

  • 现象:摘要后,LLM 忘记用户的核心信息;

  • 根因:默认摘要模板太简单,未强调关键实体;

  • 解决方案:自定义摘要 Prompt:

    python 复制代码
    def reset_memory(memory):
        memory.clear()
        # 摘要记忆额外重置摘要
        if hasattr(memory, "buffer"):
            memory.buffer = ""
        # 实体记忆额外重置知识图谱
        if hasattr(memory, "kg"):
            memory.kg.clear()
相关推荐
Vital3 小时前
AI Agent(写一个简易的MCP天气查询工具)
langchain·ai编程·cursor
逸尘谈PM5 小时前
智能体框架对比:OpenClaw、LangChain、AutoGPT、CrewAI 深度对比
人工智能·ai·langchain·职场·2026年
前进的李工7 小时前
LangChain使用之Model IO(提示词模版之ChatPromptTemplate)
java·前端·人工智能·python·langchain·大模型
张张123y9 小时前
知识图谱从0到1:AI应用开发的核心技术
人工智能·langchain·transformer·知识图谱
勇往直前plus11 小时前
大模型开发手记(九):LangChain Agent 中间件-提升Agent的可靠性与可控性
中间件·langchain
java1234_小锋11 小时前
基于LangChain的RAG与Agent智能体开发 - 使用LangChain调用大模型设置流式输出
langchain·rag
诗酒当趁年华11 小时前
langchain核心组件5-短期记忆
langchain
啊巴矲11 小时前
白从零开始勇闯人工智能:LangChain中的检索增强生成(RAG)
langchain
张张123y12 小时前
AI Agent Memory:从理论到实战,掌握长短期记忆的核心技术【2】
人工智能·python·langchain·transformer