第5课:短期记忆与长期记忆原理 - 学习笔记
📚 课程核心主题
本节课通过实际代码演示,深入讲解短期记忆和长期记忆的工作原理,包括消息加载机制、消息修剪、工具调用等核心概念。
🎯 第一部分:短期记忆的工作原理
短期记忆的消息加载机制
什么是消息加载?
是什么:
- 每次对话时,系统会从存储中自动加载历史消息
- 这些历史消息会被放入**上下文(Context)**中
- AI模型利用这些上下文信息来回答问题
工作原理:
- 开启短期记忆能力:通过Checkpoint(检查点)机制
- 每次对话时:从存储中加载历史信息
- 放入上下文:将历史消息放入对话窗口
- 利用上下文:AI根据历史信息回答问题
关键点:
- 短期记忆是**线程级别(Thread-level)**的
- 同一个线程ID(thread id)下的所有对话都会被加载
- 不同线程之间的消息是隔离的
实际演示:消息记录示例
演示场景
场景设置:
- 使用线程ID = 1 进行多轮对话
- 观察消息是如何累积和加载的
对话过程:
-
第一轮对话:
- 用户:"我叫什么?"
- AI:"我无法知道你的真实姓名"
- 消息数:2条
-
第二轮对话:
- 用户:"我是KEVIN"
- AI:"你好,KEVIN"
- 消息数:4条(包含前一轮的2条)
-
第三轮对话(同一线程):
- 用户:"我叫什么?"
- 系统自动加载了前4条消息
- AI:"你刚才告诉我你的名字是KEVIN"
- 消息数:6条(前4条历史 + 当前2条)
-
切换到新线程(thread id = 2):
- 用户:"我叫什么?"
- 只加载当前线程的消息(2条)
- AI:"我无法知道你的真实姓名"
- ✅ 验证了短期记忆是线程级别的
关键理解:
- 同一线程内的所有消息都会被加载
- 切换线程后,之前的消息不会被加载
- 消息数量会随着对话轮数增加而累积
🔧 第二部分:消息修剪(Message Pruning)
为什么需要消息修剪?
问题:
- 随着对话增加,历史消息会越来越多
- 如果每次都加载所有消息,会导致:
- ❌ Token消耗过大
- ❌ 上下文长度超限
- ❌ 性能下降
- ❌ 成本增加
实际场景:
- 预订酒店时,不需要加载之前所有关于"我叫什么"的对话
- 只需要加载与当前任务相关的消息
消息修剪的实现方式
两种修剪方案
方案1:按消息条数修剪
- 只保留最近的N条消息
- 例如:只保留最近4条消息
- 从最新的消息开始,向前取4条
方案2:按Token数量修剪
- 只保留最近生成的N个Token的消息
- 例如:只保留最近100个Token
- 根据Token大小动态调整
代码实现
关键代码结构:
python
# 在Agent前加入一个节点
# 绑定回调函数进行消息修剪
def trim_messages(messages):
# 根据消息条数修剪
# 只保留最近4条消息
return messages[-4:]
实际效果演示:
场景: 限制只加载最近4条消息
结果:
- 之前有11条消息(包括"我叫KEVIN"的信息)
- 修剪后只加载最近4条(都是关于预订酒店的)
- 用户问:"我叫什么?"
- AI回答:"我不知道你的名字"
- ✅ 因为"我叫KEVIN"的消息被修剪掉了
关键点:
- 修剪是在检索之后、放入上下文之前进行的
- 检索还是会检索所有消息
- 但放入上下文时,只放入修剪后的消息
🛠️ 第三部分:工具调用(Tool Calling)详解
工具调用的完整流程
实际演示:预订酒店
用户输入: "预定一个汉庭酒店"
Agent处理流程:
-
意图识别
- AI识别用户想要预订酒店
- 判断需要调用工具
-
工具调用
- 调用
book_hotel工具函数 - 从用户输入中提取参数(酒店名称:汉庭酒店)
- 调用
-
工具执行
- 工具函数执行预订逻辑
- 返回执行结果:"成功预定了汉庭酒店"
-
结果返回
- 工具返回的消息(Tool Message)给到Agent
- Agent将结果给到AI模型
- AI生成最终回复:"成功为您预订了汉庭酒店,如果有其他需要随时告诉我,KEVIN"
关键理解:
- 工具调用是自动的,AI会自动判断何时需要调用工具
- 工具返回的消息也会被记录到历史消息中
- 整个流程是ReAct架构的体现(推理 + 行动)
💾 第四部分:长期记忆的工作原理
为什么需要长期记忆?
实际需求场景
场景: 用户偏好设置
- 用户说:"我要预订酒店,需要有窗户,有WIFI"
- 这是用户的个人偏好
- 希望以后每次预订酒店时,都能自动应用这个偏好
问题:
- ❌ 用短期记忆无法实现
- ❌ 短期记忆只在当前会话有效
- ❌ 关闭会话后,偏好就丢失了
解决方案: 使用长期记忆
长期记忆的实现方式
存储结构
两个存储实例:
-
短期记忆存储(Checkpointer)
- 使用
PostgresSaver或RedisSaver - 存储当前线程的对话历史
- 通过
thread_id区分不同会话
- 使用
-
长期记忆存储(Store)
- 使用
PostgresStore或RedisStore - 存储用户的偏好、跨会话信息
- 通过
user_id区分不同用户
- 使用
关键区别:
| 特性 | 短期记忆(Checkpointer) | 长期记忆(Store) |
|---|---|---|
| 区分标识 | thread_id(线程ID) | user_id(用户ID) |
| 存储内容 | 当前会话的对话历史 | 用户偏好、跨会话信息 |
| 生命周期 | 会话级别 | 用户级别 |
| 类名 | PostgresSaver | PostgresStore |
长期记忆的存储和检索
存储用户偏好
步骤1:提取用户偏好
python
# 从用户输入中提取偏好信息
user_preferences = {
"住宿偏好": "有窗户,有WIFI"
}
步骤2:存储到长期记忆
python
# 使用 store.put() 存储
store.put(
user_id="1",
data=user_preferences
)
步骤3:检索用户偏好
python
# 使用 store.search() 检索
preferences = store.search(
user_id="1"
)
实际演示流程
场景:跨会话使用偏好
-
会话1(thread_id = 4):
- 用户:"我要预订酒店,需要有窗户,有WIFI"
- 系统提取偏好并存储到长期记忆
- 数据库中的
store表新增2条记录
-
会话2(thread_id = 5,新会话):
- 用户:"帮我预订酒店"
- 系统从长期记忆中检索用户偏好
- 检索到:"有窗户,有WIFI"
- AI回复:"已成功为您预订了汉庭酒店,并确保房间有窗户和WIFI"
关键点:
- ✅ 即使切换了线程(thread_id不同),也能检索到偏好
- ✅ 因为长期记忆是基于
user_id的,不是thread_id - ✅ 实现了"让AI更懂你"的目标
🔑 第五部分:关键概念对比
短期记忆 vs 长期记忆
| 特性 | 短期记忆 | 长期记忆 |
|---|---|---|
| 存储类 | PostgresSaver / RedisSaver | PostgresStore / RedisStore |
| 区分标识 | thread_id(线程ID) | user_id(用户ID) |
| 存储内容 | 当前会话的对话历史 | 用户偏好、跨会话信息 |
| 作用范围 | 当前会话线程 | 跨所有会话 |
| 生命周期 | 会话关闭后可能丢失 | 持久保存 |
| 用途 | 保持对话上下文连贯 | 个性化服务、用户偏好 |
| 类比 | RAM(内存) | 硬盘(Disk) |
⚙️ 第六部分:生产环境的考虑
线程ID的动态生成
问题:
- 在实际生产中,
thread_id不能写死 - 每次新建会话时,应该动态生成新的
thread_id
正确做法:
python
# 每次新建会话时生成新的thread_id
thread_id = generate_new_thread_id() # 动态生成
user_id = get_current_user_id() # 从会话中获取
类比理解:
thread_id类似于 Web 开发中的session_id- 每个会话都有唯一的标识
- 用于区分不同的对话会话
存储方案选择
PostgreSQL vs Redis
PostgreSQL(关系型数据库):
- ✅ 适合一般场景
- ✅ 数据持久化可靠
- ✅ 适合中小型应用
Redis(内存数据库):
- ✅ 高并发场景推荐
- ✅ 每秒10万级以上的高吞吐
- ✅ 支持数据过期设置(TTL)
- ✅ 可以设置哪些信息过期,哪些永不过期
选择建议:
- 如果并发量特别大,建议用 Redis
- 一般情况用 PostgreSQL 也可以
- 短期记忆和长期记忆可以分开存储(一个用PostgreSQL,一个用Redis)
💡 关键概念总结
| 概念 | 简单理解 |
|---|---|
| 消息加载 | 每次对话时,从存储中加载历史消息到上下文 |
| 消息修剪(Pruning) | 过滤历史消息,只保留相关的消息放入上下文 |
| 线程级别(Thread-level) | 短期记忆只在当前会话线程中有效 |
| 用户级别(User-level) | 长期记忆跨所有会话,基于用户ID |
| Checkpointer | 短期记忆存储,使用Saver类 |
| Store | 长期记忆存储,使用Store类 |
| 工具调用(Tool Calling) | AI自动判断并调用外部工具完成操作 |
| ReAct架构 | 推理(Reasoning)+ 行动(Acting)的Agent架构 |
| 动态thread_id | 生产环境中,每次会话动态生成线程ID |
❓ 思考题
-
为什么需要消息修剪?
- 答:随着对话增加,历史消息会越来越多,如果每次都加载所有消息,会导致Token消耗过大、上下文长度超限、性能下降。通过消息修剪,只保留与当前任务相关的消息,可以提高效率。
-
短期记忆和长期记忆在存储上有什么区别?
- 答:短期记忆使用Checkpointer(Saver类),通过thread_id区分不同会话;长期记忆使用Store类,通过user_id区分不同用户。短期记忆存储对话历史,长期记忆存储用户偏好和跨会话信息。
-
为什么切换线程后,AI就不记得之前的信息了?
- 答:因为短期记忆是线程级别的,只加载当前thread_id的消息。切换线程后,thread_id变了,系统只会加载新线程的消息,不会加载旧线程的消息。
-
如何实现跨会话的用户偏好?
- 答:使用长期记忆(Store),将用户偏好存储到数据库中,基于user_id而不是thread_id。这样无论用户开启多少个新会话,都能检索到之前存储的偏好信息。
-
生产环境中,为什么thread_id不能写死?
- 答:因为每个会话都需要唯一的标识。如果写死,所有用户都会共享同一个thread_id,导致消息混乱。应该每次新建会话时动态生成新的thread_id,类似于Web开发中的session_id。
📌 本节课重点回顾
✅ 短期记忆工作原理: 每次对话时自动加载历史消息到上下文,是线程级别的
✅ 消息修剪机制: 通过限制消息条数或Token数量,只保留相关消息,提高效率
✅ 工具调用流程: 意图识别 → 工具调用 → 工具执行 → 结果返回,是ReAct架构的体现
✅ 长期记忆实现: 使用Store类存储用户偏好,基于user_id实现跨会话记忆
✅ 存储方案选择: PostgreSQL适合一般场景,Redis适合高并发场景,可以分开使用
✅ 生产环境注意: thread_id必须动态生成,不能写死
笔记整理时间:2024年
建议:理解短期记忆和长期记忆的区别是构建智能AI系统的关键,需要通过实际代码演示加深理解