Gatekeeper 分层记忆提取在 LobeHub 中的实现
学习目标
理解 LobeHub 如何在 UserMemory 服务中实现 Gatekeeper 模式的 5 层分层记忆提取,包括 Gatekeeper 决策、多层独立模型配置和提取器并行调度。
前置知识
本章涉及记忆系统和分层记忆的通用原理,建议先阅读:
下文假设你已理解上述概念,直接聚焦 LobeHub 的具体实现。
项目实践
Gatekeeper → 5 层提取流水线
LobeHub 的 UserMemory 服务使用 Gatekeeper 作为门控,5 个提取器分别处理不同维度的记忆:
Gatekeeper 决策
Gatekeeper 使用 LLM 判断是否需要对当前交互进行记忆提取,以及哪些层需要提取:
// 伪代码:Gatekeeper 决策interface GatekeeperDecision { activity: { shouldExtract: boolean; reasoning: string }; context: { shouldExtract: boolean; reasoning: string }; experience: { shouldExtract: boolean; reasoning: string }; identity: { shouldExtract: boolean; reasoning: string }; preference: { shouldExtract: boolean; reasoning: string };}Gatekeeper 的 prompt 包含:
- 用户当前交互的摘要
- 已有的相关记忆(通过 context provider 检索)
- 现有身份信息
- 提取语言(用户偏好语言)
多层独立模型配置
LobeHub 允许每个记忆层使用不同的模型:
// 伪代码:多层模型配置interface MemoryExtractionLLMConfig { gateModel: string; // Gatekeeper 使用的模型 layerModels: { activity?: string; // 活动提取模型 context?: string; // 上下文提取模型 experience?: string; // 经验提取模型 identity?: string; // 身份提取模型 preference?: string; // 偏好提取模型 }; embeddingsModel?: string; // 嵌入向量模型 provider?: string;}设计决策:Gatekeeper 可以使用较便宜的模型(只需要分类判断),而 identity 和 preference 层可以使用更强的模型(需要复杂推理)。这样在保证质量的同时降低成本。
提取器架构
每个提取器实现统一的接口:
// 伪代码:提取器依赖interface BaseExtractorDependencies { agent: MemoryExtractionAgent; // "gatekeeper" | "layer-activity" | ... model: string; modelRuntime: ModelRuntime;}提取器使用 structuredCall 进行 LLM 调用,返回结构化输出:
// 伪代码:提取结果interface MemoryExtractionLayerOutputs { activity?: { data?: ExtractedActivity; error?: unknown }; context?: { data?: ExtractedContext; error?: unknown }; experience?: { data?: ExtractedExperience; error?: unknown }; identity?: { data?: ExtractedIdentity; error?: unknown }; preference?: { data?: ExtractedPreference; error?: unknown };}提取作业管理
LobeHub 使用 MemoryExtractionJob 管理提取任务:
// 伪代码:提取作业interface MemoryExtractionJob { userId: string; source: MemorySourceType; // 消息来源 sourceId: string; layers: LayersEnum[]; // 需要提取的层 force?: boolean; // 强制提取(跳过 Gatekeeper)}提取结果记录包含父级追踪键,支持离线分析时从单个 generateObject 调用回溯到作业级追踪:
// 伪代码:结果记录interface MemoryResultRecorder { recordComplete(job, result): Promise<void>; recordFail?(job, error): Promise<void>;}// parentMemoryTraceKey 传递到 llm_generation_tracing 表// 作为 metadata.parent_memory_trace_key问题与规避
| 问题 | 场景 | 规避策略 |
|---|---|---|
| Gatekeeper 误判 | Gatekeeper 判断某层不需要提取,但实际有重要信息 | force: true 强制提取模式,绕过 Gatekeeper |
| 某层提取失败 | 单模型调用异常 | 每层独立错误处理,不影响其他层,processedErrorsCount 记录 |
| Gatekeeper 自身 LLM 调用失败 | Gatekeeper 调用异常 | 回退到提取所有层(安全默认值) |
| 提取延迟 | 5 层串行执行延迟高 | 可并行执行互不依赖的层 |
设计取舍
为什么用 Gatekeeper 而不是每层都提取?
如果每次交互都运行 5 个 LLM 调用,成本过高且产生大量低价值记忆。Gatekeeper 通过一次 LLM 调用判断哪些层值得提取,平均减少 60%+ 的不必要调用。
代价:Gatekeeper 本身需要一次 LLM 调用。Gatekeeper 误判会导致信息遗漏。
对策:
- 对 Gatekeeper 使用较强模型(如 Sonnet)
- 定期用历史数据评估 Gatekeeper 准确率
- 支持
force: true手动触发全层提取
为什么各层可以配置独立模型?
不同层对 LLM 能力要求不同:
- Activity:简单分类和摘要 → 便宜模型即可
- Identity:复杂推理和自我理解 → 需要强模型
- Preference:模式识别 → 中等模型
统一使用最强模型浪费成本,统一使用最弱模型影响质量。
参考来源
- LobeHub UserMemory 类型定义 —
packages/memory-user-memory/src/types.ts - LobeHub UserMemory 提取器 —
packages/memory-user-memory/src/extractors/ - 记忆系统概述 — 通用记忆系统原理