智能体记忆(Agentic Memory)
执行摘要
本文描述 Semantic Router 中智能体记忆的概念验证(POC)。智能体记忆使 AI 智能体能够跨会话记住信息,从而提供连续性与个性化。
POC 范围: 本文为概念验证,非生产级设计。目标是验证核心记忆流(检索 → 注入 → 提取 → 存储)在可接受准确度下可行。生产加固(错误处理、扩展、监控)不在范围内。
核心能力
| 能力 | 说明 |
|---|---|
| 记忆检索 | 基于嵌入的检索与简单预过滤 |
| 记忆写入 | 基于 LLM 的事实与流程提取 |
| 跨会话持久化 | 记忆存于 Milvus(重启可保留;生产级备份/高可用未验证) |
| 用户隔离 | 按 user_id 划分(见下表) |
用户隔离与 Milvus 性能说明:
方式 POC 生产(1 万+ 用户) 简单过滤 检索后按 user_id过滤退化:先搜全库再过滤 分区键 POC 过重 物理隔离,每用户 O(log N) 标量索引 POC 过重 对 user_id建索引以加速过滤POC: 使用简单元数据过滤(测试足够)。
生产: 在 Milvus schema 中将user_id配为分区键或标量索引字段。
关键设计原则
- 简单预过滤 决定是否检索记忆
- 利用历史 上下文窗口 对查询消歧
- LLM 提取事实 并在保存时分类
- 对检索结果做 基于阈值的过滤
POC 明确假设
| 假设 | 含义 | 若错误的风险 |
|---|---|---|
| LLM 提取基本准确 | 可能存入错误事实 | 记忆污染(可用 Forget API 修复) |
| 0.6 相似度阈值为起点 | 可能需调参 | 可依检索质量日志调整 |
| Milvus 可用且已配置 | 宕机则功能关闭 | 优雅降级(不崩溃) |
| 嵌入模型输出 384 维向量 | 须与 Milvus schema 一致 | 启动失败(可检测) |
| 可通过 Response API 链获得历史 | 上下文所需 | 无历史则跳过记忆 |
目录
1. 问题陈述
现状
Response API 通过 previous_response_id 提供会话链,但跨会话知识会丢失:
Session A (March 15):
User: "My budget for the Hawaii trip is $10,000"
→ Saved in session chain
Session B (March 20) - NEW SESSION:
User: "What's my budget for the trip?"
→ No previous_response_id → Knowledge LOST ❌
目标状态
使用智能体记忆时:
Session A (March 15):
User: "My budget for the Hawaii trip is $10,000"
→ Extracted and saved to Milvus
Session B (March 20) - NEW SESSION:
User: "What's my budget for the trip?"
→ Pre-filter: memory-relevant ✓
→ Search Milvus → Found: "budget for Hawaii is $10K"
→ Inject into LLM context
→ Assistant: "Your budget for the Hawaii trip is $10,000!" ✅
2. 架构概览
┌─────────────────────────────────────────────────────────────────────────┐
│ AGENTIC MEMORY ARCHITECTURE │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ExtProc Pipeline │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Request → Fact? → Tool? → Security → Cache → MEMORY → LLM │ │
│ │ │ │ ↑↓ │ │
│ │ └───────┴──── signals used ────────┘ │ │
│ │ │ │
│ │ Response ← [extract & store] ←─────────────────┘ │ │
│ │ │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────┴─────────────────────┐ │
│ │ │ │
│ ┌─────────▼─────────┐ ┌────────────▼───┐ │
│ │ Memory Retrieval │ │ Memory Saving │ │
│ │ (request phase) │ │(response phase)│ │
│ ├───────────────────┤ ├────────────────┤ │
│ │ 1. Check signals │ │ 1. LLM extract │ │
│ │ (Fact? Tool?) │ │ 2. Classify │ │
│ │ 2. Build context │ │ 3. Deduplicate │ │
│ │ 3. Milvus search │ │ 4. Store │ │
│ │ 4. Inject to LLM │ │ │ │
│ └─────────┬─────────┘ └────────┬───────┘ │
│ │ │ │
│ │ ┌──────────────┐ │ │
│ └────────►│ Milvus │◄─────────────┘ │
│ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
组件职责
| 组件 | 职责 | 位置 |
|---|---|---|
| Memory Filter | 决策 + 检索 + 注入 | pkg/extproc/req_filter_memory.go |
| Memory Extractor | 基于 LLM 的事实提取 | pkg/memory/extractor.go(新建) |
| Memory Store | 存储接口 | pkg/memory/store.go |
| Milvus Store | 向量库后端 | pkg/memory/milvus_store.go |
| Existing Classifiers | Fact/Tool 信号(复用) | pkg/extproc/processor_req_body.go |
存储架构
Issue #808 提出了多层存储架构。本文分阶段实现:
┌─────────────────────────────────────────────────────────────────────────┐
│ STORAGE ARCHITECTURE (Phased) │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ PHASE 1 (MVP) │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ Milvus (Vector Index) │ │ │
│ │ │ • Semantic search over memories │ │ │
│ │ │ • Embedding storage │ │ │
│ │ │ • Content + metadata │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ PHASE 2 (Performance) │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ Redis (Hot Cache) │ │ │
│ │ │ • Fast metadata lookup │ │ │
│ │ │ • Recently accessed memories │ │ │
│ │ │ • TTL/expiration support │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ PHASE 3+ (If Needed) │ │
│ │ ┌───────────────────────┐ ┌───────────────────────┐ │ │
│ │ │ Graph Store (Neo4j) │ │ Time-Series Index │ │ │
│ │ │ • Memory links │ │ • Temporal queries │ │ │
│ │ │ • Relationships │ │ • Decay scoring │ │ │
│ │ └───────────────────────┘ └───────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
| 层 | 用途 | 何时需要 | 状态 |
|---|---|---|---|
| Milvus | 语义向量检索 | 核心能力 | MVP |
| Redis | 热缓存、快速访问、TTL | 性能优化 | Phase 2 |
| Graph (Neo4j) | 记忆关联 | 多跳推理查询 | 按需 |
| Time-Series | 时序查询、衰减 | 按时间的重要性打分 | 按需 |
设计决策: 先从仅 Milvus 开始。其余层按实证需求增加,而非臆测。
Store接口抽象存储,后续可换后端而不改检索/写入逻辑。
3. 记忆类型
| 类型 | 用途 | 示例 | 状态 |
|---|---|---|---|
| Semantic | 事实、偏好、知识 | "User's budget for Hawaii is $10,000" | MVP |
| Procedural | 步骤、流程 | "To deploy payment-service: run npm build, then docker push" | MVP |
| Episodic | 会话摘要、过往事件 | "On Dec 29 2024, user planned Hawaii vacation with $10K budget" | MVP(受限) |
| Reflective | 自省、经验教训 | "Previous budget response was incomplete - user prefers detailed breakdowns" | 未来 |
情景记忆(MVP 限制): 未实现会话结束检测。情景记忆仅在 LLM 提取显式产出摘要式内容时创建。可靠的会话结束触发推迟到 Phase 2。
反思记忆: 自省与经验教训。不在本 POC 范围内。见 附录 A。
记忆向量空间
记忆按内容/主题聚类,而非按类型。类型是元数据:
┌────────────────────────────────────────────────────────────────────────┐
│ MEMORY VECTOR SPACE │
│ │
│ ┌─────────────────┐ ┌─────────────── ──┐ │
│ │ BUDGET/MONEY │ │ DEPLOYMENT │ │
│ │ CLUSTER │ │ CLUSTER │ │
│ │ │ │ │ │
│ │ ● budget=$10K │ │ ● npm build │ │
│ │ (semantic) │ │ (procedural) │ │
│ │ ● cost=$5K │ │ ● docker push │ │
│ │ (semantic) │ │ (procedural) │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ ● = memory with type as metadata │
│ Query matches content → type comes from matched memory │
│ │
└────────────────────────────────────────────────────────────────────────┘
Response API 与智能体记忆:何时有价值?
关键区分: 当存在 previous_response_id 时,Response API 已把完整对话历史发给 LLM。智能体记忆的价值在于跨会话上下文。
┌─────────────────────────────────────────────────────────────────────────┐
│ RESPONSE API vs. AGENTIC MEMORY: CONTEXT SOURCES │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ SAME SESSION (has previous_response_id): │
│ ───────────────────────────────────────── │
│ Response API provides: │
│ └── Full conversation chain (all turns) → sent to LLM │
│ │
│ Agentic Memory: │
│ └── STILL VALUABLE - current session may not have the answer │
│ └── Example: 100 turns planning vacation, but budget never said │
│ └── Days ago: "I have 10K spare, is that enough for a week in │
│ Thailand?" → LLM extracts: "User has $10K budget for trip" │
│ └── Now: "What's my budget?" → answer in memory, not this chain │
│ │
│ NEW SESSION (no previous_response_id): │
│ ────────────────────────────────────── │
│ Response API provides: │
│ └── Nothing (no chain to follow) │
│ │
│ Agentic Memory: │
│ └── ADDS VALUE - retrieves cross-session context │
│ └── "What was my Hawaii budget?" → finds fact from March session │
│ │
└─────────────────────────────────────────────────────────────────────────┘
设计决策: 记忆检索在两种场景下都有价值——新会话(无链)与已有会话(查询可能引用其他会话)。预过滤通过时始终检索。
已知冗余: 若答案已在当前链中,仍会检索记忆(浪费约 10–30ms)。若不语义理解查询,无法廉价判断「答案是否已在历史中」。POC 接受该开销。
Phase 2 方案: 上下文压缩 可正确处理——不再由 Response API 发送全量历史,而发送压缩摘要 + 最近轮次 + 相关记忆。摘要在汇总时提取事实,从而消除冗余。
4. 流水线集成
当前流水线(main 分支)
1. Response API Translation
2. Parse Request
3. Fact-Check Classification
4. Tool Detection
5. Decision & Model Selection
6. Security Checks
7. PII Detection
8. Semantic Cache Check
9. Model Routing → LLM
集成智能体记忆后的增强流水线
REQUEST PHASE:
─────────────
1. Response API Translation
2. Parse Request
3. Fact-Check Classification ──┐
4. Tool Detection ├── Existing signals
5. Decision & Model Selection ──┘
6. Security Checks
7. PII Detection
8. Semantic Cache Check ───► if HIT → return cached
9. 🆕 Memory Decision:
└── if (NOT Fact) AND (NOT Tool) AND (NOT Greeting) → continue
└── else → skip to step 12
10. 🆕 Build context + rewrite query [~1-5ms]
11. 🆕 Search Milvus, inject memories [~10-30ms]
12. Model Routing → LLM
RESPONSE PHASE:
──────────────
13. Parse LLM Response
14. Cache Update
15. 🆕 Memory Extraction (async goroutine, if auto_store enabled)
└── Runs in background, does NOT add latency to response
16. Response API Translation
17. Return to Client
第 10 步说明: 查询改写策略(上下文前缀、LLM 改写、HyDE)见 附录 C。
5. 记忆检索
流程
┌─────────────────────────────────────────────────────────────────────────┐
│ MEMORY RETRIEVAL FLOW │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. MEMORY DECISION (reuse existing pipeline signals) │
│ ────────────────────────────────────────────────── │
│ │
│ Pipeline already classified: │
│ ├── ctx.IsFact (Fact-Check classifier) │
│ ├── ctx.RequiresTool (Tool Detection) │
│ └── isGreeting(query) (simple pattern) │
│ │
│ Decision: │
│ ├── Fact query? → SKIP (general knowledge) │
│ ├── Tool query? → SKIP (tool provides answer) │
│ ├── Greeting? → SKIP (no context needed) │
│ └── Otherwise → SEARCH MEMORY │
│ │
│ 2. BUILD CONTEXT + REWRITE QUERY │
│ ───────────────────────────── │
│ History: ["Planning vacation", "Hawaii sounds nice"] │
│ Query: "How much?" │
│ │
│ Option A (MVP): Context prepend │
│ → "How much? Hawaii vacation planning" │
│ │
│ Option B (v1): LLM rewrite │
│ → "What is the budget for the Hawaii vacation?" │
│ │
│ 3. MILVUS SEARCH │
│ ───────────── │
│ Embed context → Search with user_id filter → Top-k results │