第4课:记忆与上下文管理

⭐⭐⭐⭐ 高频↔ Hermes 源码

← 上一章:第3课返回目录 →

一、为什么 Agent 需要"记忆"?

LLM 在对话中天然有上下文窗口(context window)——它能"看"到当前对话中所有消息。但这不是真正的记忆:

所以 Agent 的记忆系统本质上要解决的是:在有限的上下文中,保留最有价值的信息,并在需要的时候精准取回

二、三种记忆层次

┌─────────────────────────────────────────────┐ │ !工作记忆(Working Memory) │ │ 当前对话的全部内容 = 上下文窗口 │ │ 临时、完整、随时可用 │ │ 限制:Token 上限(128K / 200K) │ ├─────────────────────────────────────────────┤ │ ★ 短期记忆(Short-term Memory) │ │ 当前 session 的摘要/关键信息 │ │ 通过压缩/summarization 提炼 │ │ 生命周期:一个用户对话session │ ├─────────────────────────────────────────────┤ │ ♾ 长期记忆(Long-term Memory) │ │ 跨 session 持久化的知识 │ │ 通过外部存储(向量库、SQLite、文件) │ │ 生命周期:永久(直到被删除) │ └─────────────────────────────────────────────┘

三、核心方案 1:Token Budget 管理

这是每个 Agent 系统首先要解决的问题——别让上下文窗口撑爆

3.1 滑动窗口(Sliding Window)

最简单的策略:只保留最近的 N 条消息,扔掉最旧的。

完整对话(按时间): [M1][M2][M3][M4][M5][M6][M7][M8][M9][M10] ↑ 最新 滑动窗口(size=5): [M6][M7][M8][M9][M10] ← 旧的被丢弃 优点:实现极简单,O(1) 复杂度 缺点:可能丢失重要信息(用户5轮前说的需求)

3.2 上下文压缩 / Summarization(Hermes 的做法)

不是粗暴丢弃,而是用 LLM 把旧内容总结成摘要,塞回系统提示词里。

Hermes 的 compress_context()(conversation_compression.py,840行)就是做这个的:

// Hermes 压缩流程 (conversation_compression.py) def compress_context(agent, messages, system_message): // 1. 检查当前消息总量是否接近上下文窗口上限 // 2. 如果是,调用 aux LLM(辅助模型)生成摘要 // 3. 把摘要拼接成新的 system prompt // "以下是对之前对话的摘要:..." // 4. 把旧的轮次从 messages 中移除 // 5. 在 SQLite 中 split session(保存旧session记录) // 6. 返回 (compressed_messages, new_system_prompt) // // 失败回退:如果 aux LLM 生成的摘要不合格, // 返回原始 messages + system_prompt(不丢数据)
和 Hermes 的对照:
当你用 Hermes 跑一个长对话时,如果上下文接近满,你会看到一条提示:
"⚠️ 正在压缩上下文..."
这就是 compress_context() 在工作。

它还支持 focus_topic 参数——指定压缩时要优先保留的主题。
受 Claude Code 的 /compact <focus> 启发。

3.3 Token 预算的计算

Agent 需要在每次 LLM 调用前估算 Token 消耗:

// 估算公式(Hermes 中的 estimate_messages_tokens_rough) token_count ≈ sum(len(m.content) * charset_ratio) + len(tools) * tools_overhead + system_prompt_tokens + buffer (预留 ~2000 tokens 给输出) // 预算检查策略 if estimated_tokens > context_limit * 0.8: 触发压缩 // 达到 80% 就压缩 if estimated_tokens > context_limit: 强制压缩或截断 // 到了上限必须处理

四、核心方案 2:长期记忆(Persistence)

4.1 Hermes 的记忆系统架构

Hermes 的记忆系统有清晰的抽象层:

┌─────────────────────────────────────┐ │ MemoryManager │ ← 编排器(单例) │ - add_provider() │ │ - prefetch_all() → 每次对话前自动取回 │ │ - sync_all() → 每次对话后自动同步 │ ├─────────────────────────────────────┤ │ MemoryProvider (ABC) │ ← 抽象基类 │ initialize() │ │ prefetch(query) → recall before turn│ │ sync_turn(user, asst) │ │ get_tool_schemas() → 暴露工具给LLM │ │ handle_tool_call() │ │ shutdown() │ ├─────────────────────────────────────┤ │ ┌──────────┐ ┌──────────┐ │ │ │ Builtin │ │ External │ │ ← 具体实现 │ │ Memory │ │ Provider │ │ │ │ (系统记忆) │ │ (Honcho │ │ │ │ │ │ Mem0...)│ │ │ └──────────┘ └──────────┘ │ └─────────────────────────────────────┘

设计亮点:

4.2 内置记忆(Built-in Memory)

你正在用的 Hermes 内置记忆就是 memory 工具(你刚才就用了):

五、核心方案 3:RAG(检索增强生成)

RAG = Retrieval-Augmented Generation。本质上是"用搜索来扩展记忆"。

5.1 RAG 工作流程

User: "我们公司去年Q3的营收是多少?" Step 1: Embedding "去年Q3的营收" → [0.23, 0.87, -0.12, ...] Step 2: 向量检索 在向量数据库中搜索相似度 > 0.85 的文档 → 找到 "2024-Q3-financial-report.pdf" Step 3: 内容提取 "Q3 营收 1,280 万元,同比增长 23%" Step 4: 注入上下文 System prompt + [RAG结果] + User message → LLM 基于检索到的数据回答 LLM: "根据财报数据,去年Q3营收为1,280万元..."

5.2 RAG vs Fine-tuning 的选择

维度RAGFine-tuning
数据更新秒级(换数据库就行)小时~天(重新训练)
幻觉风险低(基于检索结果回答)中高(模型记住了但可能记错)
实现复杂度中(需要向量库 + 检索管线)高(需要训练数据 + GPU)
适用场景Agent 需要访问最新/特定知识模型需要掌握固定领域的"技能"
面试关系⭐⭐⭐⭐⭐ Agent 面试常考🔄 不是 Agent 的核心

5.3 RAG 的工程陷阱

面试加分:RAG 不是简单的"搜索 + 拼接"。工程上的 5 个关键陷阱:

1. Chunk 大小:太大(>1000 tokens)→ 噪音多;太小(<100 tokens)→ 上下文不足
2. Embedding 模型选择:通用模型 vs 领域特定模型(差距可能很大)
3. 检索策略:单向量检索 → 混合检索(向量 + BM25 关键词)效果更好
4. Context 窗口污染:检索回来的内容不能全塞进去,需要 reranker 筛选 top-k
5. Agent 场景特殊之处:Agent 需要的不是单次检索,而是迭代检索——先搜一次,根据结果精化 query 再搜

六、面试问答

🎯 Q1:"Agent 的上下文窗口满了怎么办?有哪些优化策略?"

回答框架(从简单到复杂排列):

1. 滑动窗口:只保留最近 N 轮消息,丢弃最旧的。最简单,但可能丢信息
2. 摘要压缩:用 LLM 把旧内容总结成摘要放回 system prompt。信息密度更高(Hermes 的做法)
3. 分层记忆:工作记忆(当前对话)+ 短期记忆(session 摘要)+ 长期记忆(跨 session 持久化)
4. RAG:对需要特定知识的问题,从外部知识库检索相关内容注入上下文

面试时可以补充:实际生产中通常是组合使用——滑动窗口 + 压缩 + RAG 三层同时工作。
🎯 Q2:"Agent 的长期记忆怎么设计?"

框架:
1. 存储:SQLite / 向量数据库 / 文件系统(选型看数据规模和查询模式)
2. 写入时机:每次对话结束时自动 sync(post-turn hook)
3. 读取时机:每次对话开始前 prefetch(pre-turn hook),把相关记忆注入 system prompt
4. 更新策略:覆盖更新(直接替换)/ 版本追加(保留历史)/ 重要性评分(只保留高重要性)
5. 限流:控制注入 system prompt 的记忆大小(一般不超过 2000 tokens)
🎯 Q3:"RAG 在 Agent 里怎么用?和普通的 RAG 有什么区别?"

框架:
Agent 场景下 RAG 的特殊之处在于迭代检索——Agent 不是一次检索就完事,而是:
检索 → LLM 评估是否够用 → 不够就精化 query → 再检索 → 循环

此外,Agent 可以:
- 根据用户意图自动选择检索源(搜 DB?搜网页?搜内部 wiki?)
- 结合工具调用做后处理(搜到数据 → 计算 → 画图 → 回复)
- 多轮对话中记忆"用户问过什么"来优化后续检索

七、课后行动

  1. ✅ 理解三层记忆模型:工作记忆 vs 短期 vs 长期
  2. ✅ 记住 Token budget 管理的 3 种策略(滑动窗口/压缩/RAG)
  3. ✅ 准备 Q1(上下文满怎么办)——这是面试最高频的问题之一
  4. ⏭ 下节课:多 Agent 系统——编排器 vs 蜂群、任务分发、协作模式

📁 0004-memory-context.html
📁 源码参考:/usr/local/lib/hermes-agent/agent/memory_manager.py (948行)
📁 源码参考:/usr/local/lib/hermes-agent/agent/conversation_compression.py (840行)
📁 源码参考:/usr/local/lib/hermes-agent/agent/memory_provider.py (296行)
📝 有问题随时问。

← 上一章:第3课返回目录 →