一、简介
在构建对话式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 性能优化技巧
- 限制记忆大小:避免无限增长
- 使用窗口记忆:只保留最近K轮
- 定期摘要:长对话及时总结
- 持久化存储:避免内存溢出
- 缓存机制:减少重复计算
- 异步保存:不阻塞主流程
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} 占位符;
-
解决方案:
pythonfrom 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:
pythondef reset_memory(memory): memory.clear() # 摘要记忆额外重置摘要 if hasattr(memory, "buffer"): memory.buffer = "" # 实体记忆额外重置知识图谱 if hasattr(memory, "kg"): memory.kg.clear()