📖 Day 9 学习页:记忆系统与 RAG
Day 9:记忆系统与 RAG
🎯 今日目标:让 Agent "记住"该记的东西——短期记忆管对话历史(带裁剪), 长期记忆/RAG 管知识检索。理解了今天的内容,你就理解了 ChatGPT 的"记忆"和"知识库问答"的全部原理。
⏱ 今日安排(约 2 小时)
| 时间 | 内容 | 方式 |
|---|---|---|
| 40min | 理论学习:三种记忆 + RAG 五步 | 阅读 + 编码 |
| 40min | RAG 实战:向量检索 + 检索器 | 编码 |
| 40min | 动手实操:带记忆的 Agent + 上下文裁剪 | 编码 |
📖 第一步:Agent 的三种记忆
| 类型 | 存什么 | 实现方式 | 生命周期 |
|---|---|---|---|
| 短期记忆 | 当前对话的消息历史 | 消息列表 + 滑动窗口裁剪 | 本次会话 |
| 长期记忆 | 用户偏好、事实、文档知识 | 向量库检索(RAG) | 跨会话持久 |
| 工作记忆 | 当前任务的草稿/中间结果 | scratchpad / state 字段 | 本次任务 |
为什么短期记忆要裁剪:模型上下文窗口有限(且越长越贵),10 轮对话后历史可能已超限—— 所以要滑动窗口(只留最近 N 条)或摘要压缩(把旧历史总结成一段话)。
class ShortTermMemory:
def add(self, role, content):
self.messages.append(ConversationMessage(role, content))
if len(self.messages) > self.max_messages:
# 裁剪最旧的非 system 消息(system 是人设,必须永远保留)
for i, m in enumerate(self.messages):
if m.role != "system":
del self.messages[i]
break
📖 第二步:RAG(检索增强生成)五步流程
① 知识入库 文档 → 切块(chunk) → 每块算 embedding → 存进向量库
│
用户提问 ──► ② 问题也算 embedding ──────┤
③ 相似度检索:找出最相关的 top_k 块
④ 把检索到的知识拼进 Prompt:
"请参考以下资料回答:\n{检索结果}\n\n问题:{用户问题}"
⑤ LLM 生成 —— 答案基于真实资料,不再靠幻觉
本练习用关键词重合度代替 embedding(纯标准库能跑);原理完全一致——
正式项目把 SimpleVectorStore 换成 ChromaDB/FAISS + embedding 模型即可,接口不变。
注意:英文按空格分词,中文没有空格,要按字/bigram 或 jieba 分词,否则检索直接失效(模板已处理)。
🛠 第三步:动手实操
- 打开模板:day09_practice_template.py:
- TODO 1.1-1.4:
ShortTermMemory添加/裁剪/格式转换/清空 - TODO 2.1-2.2:
SimpleVectorStore添加与检索(关键词重合度打分即可) - TODO 3.1-3.2:
RAGRetriever知识入库 + 检索并格式化成可拼接的文本 - TODO 4.1:
MemoryAgent.chat()—— 先检索相关知识注入 prompt,再走对话,回答写回短期记忆 - TODO 5.1:
compress_conversation()—— 按估算 token(1 个中文字 ≈ 2 token)从新到旧保留,system 不丢
- TODO 1.1-1.4:
- 运行自测:
python day09_practice_template.py。 测试 4 是记忆的"图灵测试":先告诉它"我叫张三,我喜欢 Python",再问"我叫什么名字?"—— 答对了说明记忆链路通了。 - 验收:
python day09_practice_validator.py—— 验收会真的裁剪你的记忆、 检索你的向量库、跑两轮对话检查记忆是否留存。 - 想清楚三个问题:裁剪为什么保留 system?检索为什么比"把所有知识塞 prompt"好?压缩和滑动窗口各适合什么场景?
⚠️ 避坑
- ❌ 对话历史无限制地append → 超出上下文窗口直接报错;必须有裁剪或压缩策略
- ❌ 中文文本用空格分词做检索 → 整句一个'词',检索永远失灵(要用分词/字符级匹配)
- ❌ Embedding 模型中英文不匹配 → 中文用
bge-large-zh或text-embedding-3-small - ✅ 记忆不是越多越好——塞一堆无关历史反而稀释重点,相关性检索才是关键