langchain学习总结:Python + OpenAI 原生 SDK 实现记忆功能

Python + OpenAI 原生 SDK 实现记忆功能

一、这是什么

这是一个使用 PythonOpenAI 原生 SDK 从零实现的对话记忆系统,采用了摘要缓存混合记忆(ConversationSummaryBufferMemory)的策略。

它不依赖 LangChain 等高级框架,而是通过原生代码实现了类似于 LangChain 的记忆管理功能。

二、核心原理

摘要缓存混合记忆(Summary Buffer Memory)

这是一种将摘要记忆缓冲窗口记忆结合的混合策略:

css 复制代码
┌─────────────────────────────────────────────────┐
│  摘要部分(Summary)                             │
│  存储早期对话的提炼总结                           │
│  例如:用户叫张三,喜欢编程...                    │
└─────────────────────────────────────────────────┘
              ↓ 随对话增长动态生成
┌─────────────────────────────────────────────────┐
│  缓冲部分(Buffer)                              │
│  存储最近的完整对话记录                           │
│  例如:最近3轮的完整问答                          │
└─────────────────────────────────────────────────┘

工作流程

  1. 初始阶段:所有对话存储在缓存中
  2. 触发摘要 :当缓存中的 token 数量超过 max_tokens 阈值时
  3. 生成摘要:将最早的对话与现有摘要合并,生成新的摘要
  4. 移除旧对话:将已摘要的对话从缓存中删除
  5. 循环进行:保持对话历史的可控性

三、核心组件

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 记住与玩家的交互历史

六、实现亮点

  1. 原生实现:不依赖框架,理解底层原理
  2. 动态摘要:自动触发摘要生成,无需手动管理
  3. 关键信息保留:精心设计的 Prompt 确保重要信息不丢失
  4. 流式输出:支持 OpenAI 的流式响应,提升用户体验
  5. 模块化设计:各个功能独立,易于扩展

七、技术栈

  • 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 应用的核心技术之一。

相关推荐
小突突突4 小时前
Spring框架中的单例bean是线程安全的吗?
java·后端·spring
iso少年4 小时前
Go 语言并发编程核心与用法
开发语言·后端·golang
掘金码甲哥4 小时前
云原生算力平台的架构解读
后端
码事漫谈4 小时前
智谱AI从清华实验室到“全球大模型第一股”的六年征程
后端
码事漫谈4 小时前
现代软件开发中常用架构的系统梳理与实践指南
后端
Mr.Entropy4 小时前
JdbcTemplate 性能好,但 Hibernate 生产力高。 如何选择?
java·后端·hibernate
YDS8294 小时前
SpringCloud —— MQ的可靠性保障和延迟消息
后端·spring·spring cloud·rabbitmq
无限大65 小时前
为什么"区块链"不只是比特币?——从加密货币到分布式应用
后端
洛神么么哒5 小时前
freeswitch-初级-01-日志分割
后端
蝎子莱莱爱打怪5 小时前
我的2025年年终总结
java·后端·面试