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

相关推荐
毕设源码-钟学长1 天前
【开题答辩全过程】以 基于SpringBoot的智能书城推荐系统的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
青春男大1 天前
Redis和RedisTemplate快速上手
java·数据库·redis·后端·spring·缓存
张张努力变强1 天前
C++ 类和对象(四):const成员函数、取地址运算符重载全精讲
开发语言·数据结构·c++·后端
不吃香菜学java1 天前
springboot左脚踩右脚螺旋升天系列-整合开发
java·spring boot·后端·spring·ssm
奋进的芋圆1 天前
Java 锁事详解
java·spring boot·后端
郑州光合科技余经理1 天前
技术架构:海外版外卖平台搭建全攻略
java·大数据·人工智能·后端·小程序·架构·php
科威舟的代码笔记1 天前
SpringBoot配置文件加载顺序:一场配置界的权力游戏
java·spring boot·后端·spring
血小板要健康1 天前
Spring IoC & DI (下)
java·前端·spring boot·后端·spring·servlet·java-ee
PP东1 天前
Flowable学习(一)——spring boot 部署
后端·学习·flowable
问今域中1 天前
Acwing的SpringBoot项目总结
java·spring boot·后端