Python + OpenAI 原生 SDK 实现记忆功能
一、这是什么
这是一个使用 Python 和 OpenAI 原生 SDK 从零实现的对话记忆系统,采用了摘要缓存混合记忆(ConversationSummaryBufferMemory)的策略。
它不依赖 LangChain 等高级框架,而是通过原生代码实现了类似于 LangChain 的记忆管理功能。
二、核心原理
摘要缓存混合记忆(Summary Buffer Memory)
这是一种将摘要记忆 和缓冲窗口记忆结合的混合策略:
css
┌─────────────────────────────────────────────────┐
│ 摘要部分(Summary) │
│ 存储早期对话的提炼总结 │
│ 例如:用户叫张三,喜欢编程... │
└─────────────────────────────────────────────────┘
↓ 随对话增长动态生成
┌─────────────────────────────────────────────────┐
│ 缓冲部分(Buffer) │
│ 存储最近的完整对话记录 │
│ 例如:最近3轮的完整问答 │
└─────────────────────────────────────────────────┘
工作流程
- 初始阶段:所有对话存储在缓存中
- 触发摘要 :当缓存中的 token 数量超过
max_tokens阈值时 - 生成摘要:将最早的对话与现有摘要合并,生成新的摘要
- 移除旧对话:将已摘要的对话从缓存中删除
- 循环进行:保持对话历史的可控性
三、核心组件
ConversationSummaryBufferMemory 类
这个类实现了完整的记忆管理功能:
| 组件 | 作用 |
|---|---|
summary |
存储对话摘要信息 |
chat_histories |
存储历史对话列表 |
max_tokens |
触发摘要生成的 token 阈值 |
get_num_tokens() |
计算文本的 token 数量 |
save_content() |
保存新的对话到缓存 |
get_buffer_string() |
将历史对话转换为字符串 |
load_memory_variables() |
加载记忆变量供模型使用 |
summary_text() |
使用 LLM 生成新的摘要 |
关键方法说明
1. save_content() - 保存对话并触发摘要
python
def save_content(self, human_query: str, ai_content: str) -> None:
# 1. 保存新对话
# 2. 检查 token 数量
# 3. 超过阈值则生成摘要并移除旧对话
2. summary_text() - 生成新摘要
使用精心设计的 Prompt 让 LLM 将旧摘要和新对话合并成新摘要,并保留关键信息(姓名、爱好、重要事件等)。
3. load_memory_variables() - 加载记忆
返回格式化的上下文,包含摘要和缓存中的历史对话。
四、有什么用
1. 实现长期对话记忆
- AI 可以记住之前的对话内容
- 跨会话保持上下文连贯性
- 记住用户的重要信息(姓名、偏好等)
2. 控制 Token 成本
- 不会无限制地发送所有历史对话
- 通过摘要减少早期对话的 token 占用
- 只保留最近对话的完整内容
3. 平衡信息完整性与成本
- 摘要部分:保留关键信息,占用 token 少
- 缓存部分:保留完整细节,确保近期对话准确
4. 学习价值
- 理解大语言模型应用中记忆管理的核心概念
- 学习如何从零实现复杂的对话状态管理
- 掌握提示词工程在摘要生成中的应用
五、应用场景
| 场景 | 优势 |
|---|---|
| 智能客服 | 记住用户问题和历史投诉 |
| 个性化助手 | 记住用户偏好和习惯 |
| 教育辅导 | 跟踪学习进度和知识点 |
| 心理咨询 | 保持长期咨询的连贯性 |
| 游戏 NPC | 记住与玩家的交互历史 |
六、实现亮点
- 原生实现:不依赖框架,理解底层原理
- 动态摘要:自动触发摘要生成,无需手动管理
- 关键信息保留:精心设计的 Prompt 确保重要信息不丢失
- 流式输出:支持 OpenAI 的流式响应,提升用户体验
- 模块化设计:各个功能独立,易于扩展
七、技术栈
- Python 3.x
- OpenAI SDK(兼容 Moonshot 等 OpenAI 格式 API)
- dotenv(环境变量管理)
- 大语言模型:Moonshot-v1-8k
八、完整代码实现
8.1 核心类实现
python
from typing import Any
import dotenv
from openai import OpenAI
dotenv.load_dotenv()
class ConversationSummaryBufferMemory:
"""
摘要缓存混合记忆类
结合了摘要记忆和缓冲窗口记忆的优点
"""
def __init__(self, summary: str = '', chat_histories: list = None, max_tokens: int = 300):
"""
初始化记忆组件
Args:
summary: 初始摘要内容
chat_histories: 历史对话列表
max_tokens: 触发摘要生成的最大token数
"""
self.summary = summary # 存储摘要
self.chat_histories = [] if chat_histories is None else chat_histories # 存储对话缓存
self.max_tokens = max_tokens # token阈值
self.client = OpenAI(base_url='https://api.moonshot.cn/v1') # LLM客户端
8.2 工具方法
python
@classmethod
def get_num_tokens(cls, query: str) -> int:
"""
计算文本的token数量
这里简化为字符长度,实际可以使用tiktoken精确计算
"""
return len(query)
def get_buffer_string(self) -> str:
"""
将历史对话列表转换为字符串格式
格式: human: xxx\nai: xxx\n
"""
buffer_string = ''
for chat in self.chat_histories:
buffer_string += f"human: {chat.get('human')}\nai: {chat.get('ai')}\n"
print(f"buffer_string: {buffer_string}")
return buffer_string.strip()
8.3 核心方法:保存对话
python
def save_content(self, human_query: str, ai_content: str) -> None:
"""
保存新的对话到缓存,并检查是否需要生成摘要
工作流程:
1. 将新对话添加到缓存
2. 计算缓存的token数量
3. 如果超过阈值,则生成摘要并移除最早的对话
"""
# 1. 保存新对话
self.chat_histories.append({
'human': human_query,
'ai': ai_content,
})
# 2. 检查token数量
buffer_string = self.get_buffer_string()
tokens = self.get_num_tokens(buffer_string)
# 3. 超过阈值则生成摘要
if tokens > self.max_tokens:
first_chat = self.chat_histories[0] # 获取最早的对话
print(f"生成新的摘要中")
# 生成新的摘要(旧摘要 + 最早对话)
self.summary = self.summary_text(
self.summary,
f"human: {human_query}\nai: {first_chat.get('ai')}"
)
print(f"新的摘要: {self.summary}")
# 移除已摘要的对话
del self.chat_histories[0]
8.4 核心方法:生成摘要
python
def summary_text(self, original_summary: str, new_chat: str) -> str:
"""
使用LLM生成新的摘要
关键点:
1. 将旧摘要和新对话合并
2. 保留关键信息(姓名、爱好、性别、重要事件等)
3. 尽可能还原对话记录
Args:
original_summary: 旧的摘要
new_chat: 新的对话内容
Returns:
新生成的摘要
"""
prompt = f"""你是一个强大的聊天机器人,请根据用户提供的谈话内容,总结摘要,并将其添加到先前提供的摘要中,返回一个新的摘要,除了新摘要其他任何数据都不要生成,如果用户的对话信息里有一些关键的信息,比方说姓名、爱好、性别、重要事件等等,这些全部都要包括在生成的摘要中,摘要尽可能要还原用户的对话记录。
请不要将<example>标签里的数据当成实际的数据,这里的数据只是一个示例数据,告诉你该如何生成新摘要。
<example>
当前摘要:人类会问人工智能对人工智能的看法,人工智能认为人工智能是一股向善的力量。
新的对话:
Human:为什么你认为人工智能是一股向善的力量?
AI:因为人工智能会帮助人类充分发挥潜力。
新摘要:人类会问人工智能对人工智能的看法,人工智能认为人工智能是一股向善的力量,因为它将帮助人类充分发挥潜力。
</example>
=====================以下的数据是实际需要处理的数据=====================
当前摘要:{original_summary}
新的对话:
{new_chat}
请帮用户将上面的信息生成新摘要。"""
completion = self.client.chat.completions.create(
model="moonshot-v1-8k",
messages=[{"role": "user", "content": prompt}]
)
return completion.choices[0].message.content
8.5 核心方法:加载记忆变量
python
def load_memory_variables(self) -> dict[str, Any]:
"""
加载记忆变量,返回格式化的上下文
Returns:
包含chat_history的字典,格式为:
{
"chat_history": "摘要: xxx\n历史信息: xxx"
}
"""
buffer_string = self.get_buffer_string()
return {
"chat_history": f"摘要: {self.summary}\n历史信息:{buffer_string}",
}
8.6 完整使用示例
python
# 创建客户端和记忆对象
client = OpenAI(base_url='https://api.moonshot.cn/v1')
memory = ConversationSummaryBufferMemory(summary="", chat_histories=[], max_tokens=300)
# 对话循环
while True:
# 1. 获取用户输入
human_query = input("请输入: ")
if human_query == 'q':
break
# 2. 加载历史记忆
memory_variables = memory.load_memory_variables()
# 3. 构建包含历史的提示词
answer_prompt = (
"你是一个强大的聊天机器人,请根据对应的上下文和用户提问解决问题。\n\n"
f"{memory_variables.get('chat_history')}\n\n"
f"用户的提问是: {human_query}"
)
# 4. 调用LLM(流式输出)
response = client.chat.completions.create(
model="moonshot-v1-8k",
messages=[{"role": "user", "content": answer_prompt}],
stream=True
)
# 5. 打印AI响应
print("AI: ", end="")
ai_content = ""
for chunk in response:
content = chunk.choices[0].delta.content
if content is None:
break
ai_content += content
print(content, end="")
print("\n")
# 6. 保存对话到记忆
memory.save_content(human_query, ai_content)
8.7 代码执行流程图
scss
用户输入
↓
加载历史记忆 (load_memory_variables)
↓
构建提示词 (包含摘要 + 缓存对话)
↓
调用LLM API (流式输出)
↓
保存新对话 (save_content)
↓
检查token数 → 超过阈值?
↓ ↓
否 是
↓ ↓
结束 生成新摘要 (summary_text)
↓
移除最早对话
↓
结束
8.8 关键代码说明
| 代码片段 | 作用 | 关键点 |
|---|---|---|
self.chat_histories.append() |
添加新对话 | 使用字典存储 human/ai 对 |
get_num_tokens() |
计算token数 | 简化为字符长度,生产环境可用tiktoken |
summary_text() |
生成摘要 | 精心设计的prompt确保保留关键信息 |
del self.chat_histories[0] |
移除旧对话 | FIFO(先进先出)策略 |
stream=True |
流式输出 | 提升用户体验,类似打字效果 |
8.9 优化建议
python
# 1. 使用tiktoken精确计算token数
import tiktoken
def get_num_tokens(cls, query: str) -> int:
encoding = tiktoken.encoding_for_model("gpt-3.5-turbo")
return len(encoding.encode(query))
# 2. 添加持久化存储(JSON/数据库)
def save_to_disk(self, filepath: str):
data = {
'summary': self.summary,
'chat_histories': self.chat_histories
}
with open(filepath, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
# 3. 添加清空记忆功能
def clear_memory(self):
self.summary = ""
self.chat_histories = []
# 4. 添加异常处理
try:
self.summary = self.summary_text(self.summary, new_chat)
except Exception as e:
print(f"生成摘要失败: {e}")
# 保留原摘要,不删除对话
九、总结
这个学习案例展示了如何使用 OpenAI 原生 SDK 实现一个生产级的对话记忆系统。通过摘要缓存混合策略,既保证了对话的连贯性,又控制了 API 调用成本,是构建长期对话 AI 应用的核心技术之一。