# 记忆模块实施计划(面试优先版 -> 产品可用版) ## 1. 文档目标 1. 在 3 天内交付一个“可演示、可讲清楚、可继续演进”的记忆系统 MVP。 2. 兼容当前单体工程,不引入高风险拆分,不破坏现有聊天主链路。 3. 复用现有 Outbox 异步基础设施,避免重复造轮子。 4. 形成可直接用于面试讲述的架构故事线、指标体系与演示脚本。 5. 在不增加过度复杂度的前提下,吸收 Mem0 中已被验证的关键机制(抽取、决策、检索、降级、防幻觉)。 ## 2. 背景与约束 1. 当前系统是单体 Go 项目,已有稳定的 `Outbox + Kafka + 消费事务` 通路。 2. 当前项目定位先是日程助手,长期演进为陪伴型助手。 3. 短期目标是快速做出“真的可用”的记忆能力,不追求一次做成完整通用平台。 4. 风险约束: - 不能让重型 LLM 处理阻塞聊天实时响应。 - 不能在 Outbox 消费主循环里堆重计算,避免拖垮其他事件消费。 - 不能牺牲数据一致性与可审计性。 ## 3. 总体方案 ### 3.1 核心思路 采用“同步快路径 + 异步慢路径”: 1. 同步快路径:回复前快速读取可用记忆(以 MySQL 结构化事实为主),保证“下一轮能用”。 2. 异步慢路径:通过 Outbox 触发记忆抽取任务,执行去重、冲突消解、打分、向量化等重操作。 3. 读写解耦:写路径确保可靠入队,读路径优先稳定可控,再做语义增强。 ### 3.2 存储职责分层 1. MySQL:事实主库(偏好、约束、任务上下文、TTL、置信度、敏感级别、来源)。 2. Milvus:语义召回(同义表达匹配、模糊语义联想)。 3. Redis(可选):热数据缓存(后续优化,不作为 MVP 必选项)。 ### 3.3 编排层职责 `Memory Orchestrator` 负责两条链路: 1. 写入链路:候选抽取 -> 去重/冲突 -> 打分 -> 分流落库(MySQL/Milvus)。 2. 读取链路:硬约束优先 -> 语义召回补充 -> 重排 -> 门控 -> 注入上下文。 ### 3.4 借鉴 Mem0 的关键机制(已裁剪版) 1. 双阶段去重决策:先向量召回候选旧记忆,再由 LLM 决策 `ADD/UPDATE/DELETE/NONE`,而不是只靠相似度阈值硬判。 2. UUID 映射防幻觉:把真实 `memory_id` 映射成临时整数给 LLM,回收结果时再反查,防止模型编造不存在 ID。 3. 结构化输出刚性约束:抽取与决策都用 JSON 结构,失败时走 `extract_json -> normalize_facts` 容错链,不让解析失败直接污染主流程。 4. 动作分型嵌入:嵌入接口显式传入 `memory_action`(`add/search/update`),为后续差异化 embedding 策略预留接口。 5. 检索后处理标准化:`threshold 过滤 -> 可选 reranker -> 统一降级`,当重排器异常时保留向量原始排序并打告警日志。 6. 多维隔离语义:统一采用 `user_id + agent_id + run_id` 三维过滤;在本项目映射为 `user_id + assistant_id + conversation_id`。 ### 3.5 本项目明确不做(本轮) 1. 不做图记忆(Graph Memory)落地实现,仅预留扩展点,避免 3 天范围失控。 2. 不做多 Provider 工厂体系,只保留单 Provider 可替换接口,后续再扩展。 3. 不做独立 server 化记忆服务,先在单体内完成闭环与指标验证。 ## 4. 3 天执行计划(可直接照着做) ## Day 1:把“可写入”打通(可靠入队 + 可追踪) ### 目标 1. 记忆任务能稳定从聊天主链路发出。 2. 能看到任务从 `pending` 到 `success/failed` 的状态流转。 3. 保证失败可重试、可追踪、可补偿。 ### 任务清单 1. 新增文档与目录占位: - `backend/memory/README.md`(模块说明) - `backend/memory/service/`(门面) - `backend/memory/model/`(DTO 与状态) - `backend/memory/repo/`(数据访问) - `backend/memory/orchestrator/`(编排) - `backend/memory/worker/`(异步执行) 2. 新增 MySQL 表(建议先手写 SQL + DAO): - `memory_items` - `memory_jobs` - `memory_audit_logs` - `memory_user_settings` 3. 新增配置对象(`memory config`): - 抽取 prompt、更新决策 prompt、阈值、是否启用 reranker、LLM 温度参数。 - 默认采用低随机参数(`temperature/top_p` 低值)提高可复现性。 4. 新增 Outbox 事件: - `memory.extract.requested`(v1) 5. 在聊天后置持久化环节发布事件: - 仅传轻量字段,避免超大 payload。 6. 新增消费处理器: - 只做任务入库,不做重型 LLM 调用。 7. 新增解析与标准化工具: - `extract_json()`:从模型输出中抽取 JSON(兼容代码块包裹)。 - `normalize_facts()`:去重、去空、长度校验、非法项过滤。 8. 新增决策状态机定义: - `ADD/UPDATE/DELETE/NONE` 的合法状态与动作映射。 9. 启动期接线: - 在 `backend/cmd/start.go` 注册记忆事件处理器。 ### Day 1 验收标准 1. 一次聊天后,Outbox 中能看到 `memory.extract.requested` 事件。 2. 事件消费后,`memory_jobs` 生成记录。 3. 人工触发 worker 可完成一次任务状态推进(哪怕先是 mock 抽取)。 ## Day 2:把“可读取可注入”打通(先 MySQL 后向量) ### 目标 1. 记忆可在回复前被检索并注入上下文。 2. 能避免明显的“尬提”与无关提及。 3. 提供最小用户可控能力(查看/删除/关闭)。 ### 任务清单 1. 实现 `MemoryReadService`: - 按用户与会话上下文读取记忆。 - 优先结构化硬约束(时间偏好、排程禁忌、显式偏好)。 2. 实现 `MemoryInjector`: - Top-K 记忆选择。 - token 预算截断。 - 注入模板统一化。 3. 实现门控逻辑: - 相关性阈值。 - 置信度阈值。 - 时间衰减权重。 - 敏感级别检查。 4. 增加“阈值 + 可选重排 + 降级”链路: - 阈值过滤作为第一道过滤。 - `reranker` 失败时自动降级为原排序并记录原因码。 5. 新增最小管理接口: - `GET /api/v1/memory/items` - `DELETE /api/v1/memory/items/:id` - `POST /api/v1/memory/settings`(开关) 6. 完成首版日志埋点: - 检索命中数、注入条数、门控丢弃原因。 - 决策分布(ADD/UPDATE/DELETE/NONE 占比)。 ### Day 2 验收标准 1. 给出偏好后,下一轮排程请求能利用该偏好。 2. 无关话题不会频繁硬提旧记忆。 3. 用户可删除指定记忆,删除后不再注入。 ## Day 3:把“可讲清楚”与“可评估”补齐(面试可答) ### 目标 1. 输出完整可讲架构,说明设计取舍。 2. 增加可量化指标,证明记忆“有用”而不是“看起来有”。 3. 可选接入 Milvus(若环境未就绪,先保留接口 + mock)。 ### 任务清单 1. 实现/预留向量接口: - `VectorStore.Upsert()` - `VectorStore.Search()` - `VectorStore.Delete()` - `VectorStore.Get()`(为 UPDATE/DELETE 决策回查旧值) 2. 对接 Milvus(可选): - collection 初始化。 - 向量 + 元数据过滤检索。 3. 指标体系落地: - 记忆命中率(retrieved/useful) - 错误提及率(wrong mention) - 用户纠正率(user correction) - 回复延迟影响(P50/P95) 4. 准备演示脚本与面试问答稿: - 5 分钟架构说明。 - 3 个典型失败案例及兜底策略。 - 未来迭代路线。 5. 输出“借鉴 Mem0 但本地化裁剪”的对比说明: - 借鉴了什么。 - 为什么暂时不做图记忆与多 Provider 工厂。 ### Day 3 验收标准 1. 能现场演示“记住偏好 -> 下轮生效 -> 删除后失效”。 2. 能答清楚“为什么不是纯同步/纯异步”。 3. 能答清楚“为什么 MySQL + Milvus 双存储”。 ## 5. 数据模型设计(首版) ## 5.1 `memory_items`(长期事实记忆) 用途:保存对业务有约束价值的可注入记忆。 关键字段建议: 1. `id` bigint PK 2. `user_id` bigint(必填) 3. `conversation_id` varchar(64)(可空,表示全局用户记忆) 4. `assistant_id` varchar(64)(可空,区分不同助手人格/技能域) 5. `run_id` varchar(64)(可空,会话级隔离) 6. `memory_type` varchar(32) - `preference`(偏好) - `constraint`(硬约束) - `fact`(事实) - `todo_hint`(近期提醒线索) 7. `title` varchar(128) 8. `content` text 9. `normalized_content` text(去噪后) 10. `content_hash` varchar(64)(幂等去重) 11. `confidence` decimal(5,4)(0~1) 12. `importance` decimal(5,4)(0~1) 13. `sensitivity_level` tinyint - 0 普通 - 1 中敏 - 2 高敏 14. `source_message_id` bigint 15. `source_event_id` varchar(64) 16. `is_explicit` tinyint(1)(是否用户明确要求记住) 17. `status` varchar(16) - `active` - `archived` - `deleted` 18. `ttl_at` datetime(到期时间) 19. `last_access_at` datetime 20. `created_at` datetime 21. `updated_at` datetime 22. `vector_status` varchar(16)(`pending/synced/failed`) 23. `vector_id` varchar(128)(向量库主键映射) 索引建议: 1. `(user_id, status, memory_type, updated_at desc)` 2. `(user_id, conversation_id, status, updated_at desc)` 3. `(source_message_id)`(排查链路) 4. `(ttl_at)`(过期清理) 5. `(user_id, assistant_id, run_id, status, updated_at desc)` 6. `(user_id, memory_type, content_hash)`(幂等去重) ## 5.2 `memory_jobs`(异步任务队列表) 用途:承接 Outbox 消费后的待处理任务,解耦重计算。 关键字段建议: 1. `id` bigint PK 2. `user_id` bigint 3. `conversation_id` varchar(64) 4. `source_message_id` bigint 5. `source_event_id` varchar(64) 6. `job_type` varchar(32) - `extract` - `embed` - `reconcile` 7. `idempotency_key` varchar(128) 8. `payload_json` longtext 9. `status` varchar(16) - `pending` - `processing` - `success` - `failed` - `dead` 10. `retry_count` int 11. `max_retry` int 12. `next_retry_at` datetime 13. `last_error` varchar(2000) 14. `created_at` datetime 15. `updated_at` datetime 索引建议: 1. `(status, next_retry_at, id)` 2. `(user_id, created_at desc)` 3. `(source_event_id)`(幂等与追踪) 4. `(idempotency_key)`(消费防重) ## 5.3 `memory_audit_logs`(审计日志) 用途:回答“这条记忆是谁在什么条件下写的/改的/删的”。 关键字段建议: 1. `id` bigint PK 2. `memory_id` bigint 3. `user_id` bigint 4. `operation` varchar(32) - `create` - `update` - `archive` - `delete` - `restore` 5. `operator_type` varchar(16) - `system` - `user` 6. `reason` varchar(255) 7. `before_json` longtext 8. `after_json` longtext 9. `created_at` datetime ## 5.4 `memory_user_settings`(用户记忆开关) 用途:实现用户可控能力。 关键字段建议: 1. `user_id` bigint PK 2. `memory_enabled` tinyint(1) 3. `implicit_memory_enabled` tinyint(1) 4. `sensitive_memory_enabled` tinyint(1) 5. `updated_at` datetime ## 6. 事件与协议设计 ## 6.1 事件类型 1. `memory.extract.requested`(v1) 2. 预留: - `memory.embed.requested` - `memory.cleanup.requested` ## 6.2 载荷字段(v1) 1. `user_id` 2. `conversation_id` 3. `assistant_id` 4. `run_id` 5. `source_message_id` 6. `source_role` 7. `source_text` 8. `occurred_at` 9. `trace_id` 10. `idempotency_key` 设计约束: 1. Payload 只放执行需要的最小字段。 2. 大文本允许截断并保留摘要,防止消息膨胀。 3. 必须包含幂等标识(如 `source_message_id + user_id`)。 4. 过滤维度必须完整(`user_id + assistant_id + run_id`),避免跨会话串记忆。 ## 7. 写入流程详细设计 ## 7.1 主流程 1. 聊天主链路完成并落历史消息。 2. 发布 `memory.extract.requested` 到 Outbox。 3. Outbox 消费处理器验证 payload。 4. 处理器创建或幂等更新 `memory_jobs`(仅任务入库)。 5. `memory/worker` 扫描 `pending` 任务并抢占为 `processing`。 6. Worker 调用 LLM 执行“候选事实抽取”(JSON 输出)。 7. 执行 `extract_json -> normalize_facts` 容错标准化链路。 8. 对每条候选事实做向量检索,召回 Top-K 旧记忆候选。 9. 对召回结果执行“临时整数 ID 映射”,再交给 LLM 决策 `ADD/UPDATE/DELETE/NONE`。 10. 根据决策执行写入动作: - `ADD`:新增 `memory_items` + 审计日志。 - `UPDATE`:更新记录并保留历史旧值。 - `DELETE`:软删除并记录删除原因。 - `NONE`:不写入,仅记调试日志。 11. 按决策动作触发向量同步(支持 `vector_pending`)。 12. 成功后任务标记 `success`,失败按重试策略推进。 ## 7.2 失败处理策略 1. Payload 非法:直接标记 dead,不重试。 2. LLM 短时失败:指数退避重试。 3. DB 写失败:重试,超过上限 dead。 4. 向量写失败: - MVP 策略:不阻塞事实写入,记录 `vector_pending` 状态。 - 后续策略:补偿任务重建向量索引。 ## 7.3 幂等策略 1. 幂等键:`user_id + source_message_id + memory_type + normalized_content_hash` 2. 同幂等键重复写入:更新 `updated_at`、提升访问热度,不新增重复条目。 3. 由 Outbox 重试导致的重复消费必须无副作用。 4. 对 UPDATE/DELETE 必须先校验目标 `memory_id` 是否存在且属于当前过滤域。 ## 8. 读取流程详细设计 ## 8.1 主流程 1. 接收用户新问题,先做意图分类(排程/闲聊/混合)。 2. 从 `memory_items` 拉取硬约束记忆(高优先级)。 3. 若 Milvus 可用,执行语义召回补充记忆候选。 4. 对候选执行重排: - 相关性分 - 置信度分 - 时间衰减分 - 显式记忆加权 5. 执行门控: - 低相关丢弃 - 高敏过滤 - 过期过滤 6. 执行阈值过滤后可选 reranker;若 reranker 异常则自动降级使用原排序。 7. 按 token budget 选择最终注入条目。 8. 组装统一注入上下文,传给主模型生成回复。 ## 8.2 重排评分(建议公式) `final_score = 0.45 * relevance + 0.25 * confidence + 0.20 * recency + 0.10 * explicit_bonus` 说明: 1. 排程类场景可增加硬约束权重。 2. 闲聊类场景可提高语义相关权重。 3. 该公式为 MVP 默认值,后续可通过线上数据调参。 ## 8.3 门控规则(MVP) 1. `final_score < 0.55` 不注入。 2. `sensitivity_level >= 2` 且用户未开启敏感记忆时不注入。 3. `ttl_at < now` 不注入。 4. 同主题最多注入 1~2 条,防止重复轰炸。 ## 9. 对外接口(MVP) ## 9.1 用户接口 1. `GET /api/v1/memory/items` - 支持按类型、时间、状态过滤。 2. `DELETE /api/v1/memory/items/:id` - 软删除并写审计日志。 3. `POST /api/v1/memory/settings` - 修改记忆总开关、隐式记忆开关。 ## 9.2 内部接口 1. `MemoryService.EnqueueExtractJob(ctx, payload)` 2. `MemoryService.RetrieveForPrompt(ctx, req)` 3. `MemoryService.UpsertMemoryItems(ctx, items)` 4. `MemoryService.DeleteMemory(ctx, userID, memoryID)` ## 10. 可观测性与指标 ## 10.1 指标定义 1. `memory_job_success_rate` 2. `memory_job_retry_rate` 3. `memory_retrieval_hit_rate` 4. `memory_injection_count_avg` 5. `memory_wrong_mention_rate` 6. `memory_user_correction_rate` 7. `chat_p95_latency_delta_with_memory` 8. `memory_json_parse_fail_rate` 9. `memory_decision_distribution`(ADD/UPDATE/DELETE/NONE) 10. `reranker_fallback_rate` ## 10.2 日志与追踪 1. 每个任务写 `trace_id`,贯穿聊天请求 -> outbox -> memory_job -> memory_item。 2. 对门控丢弃记录原因码: - `LOW_SCORE` - `EXPIRED` - `SENSITIVE_BLOCKED` - `DUP_TOPIC` 3. 保证可以反查“为什么这次没有提某条记忆”。 ## 11. 安全与隐私约束 1. 敏感信息默认不做隐式记忆(如健康、财务、证件等)。 2. 用户必须可删除历史记忆,删除后不再用于注入。 3. 记忆开关关闭后,仅保留必要系统数据,不再新增记忆条目。 4. 审计日志保留系统写入行为,便于风控与合规排查。 ## 12. 测试策略 ## 12.1 单元测试范围(实现阶段) 1. 候选抽取结果解析函数。 2. 冲突消解函数。 3. 重排评分函数。 4. 门控函数。 5. 幂等去重函数。 6. `extract_json` 容错解析函数。 7. `normalize_facts` 标准化函数。 8. UUID 映射与反查函数。 9. `ADD/UPDATE/DELETE/NONE` 决策结果校验函数。 ## 12.2 集成测试范围(实现阶段) 1. 聊天后事件成功入 outbox。 2. Outbox 消费后任务成功入 `memory_jobs`。 3. Worker 成功写 `memory_items`。 4. 读取链路能在回复中注入预期记忆。 ## 12.3 注意事项(遵循项目约束) 1. 若编写 Go 测试文件(`*_test.go`)做验证,任务完成后按项目约定移除测试文件。 2. 每次执行本地 `go test` 后清理项目根目录 `.gocache`。 ## 13. 风险与回滚 ## 13.1 主要风险 1. 记忆误提影响体验。 2. LLM 抽取不稳定导致脏记忆。 3. 向量检索误召回导致不相关注入。 4. 任务积压影响时效。 ## 13.2 应对策略 1. 先严门控,宁可少提,不要乱提。 2. 保留“用户纠正”入口,纠正后提高冲突更新优先级。 3. 对召回做 metadata 过滤(近 30 天、类型限定)。 4. 监控任务积压长度,超阈值降级(停向量,仅结构化记忆)。 ## 13.3 回滚方案 1. 配置开关 `memory.enabled=false` 可一键关闭记忆注入。 2. 保留写入链路但停读取链路,避免历史数据丢失。 3. 极端情况下停 worker,仅保留主链路聊天功能。 ## 14. 面试表达模板(可直接复述) 1. “我们做的是同步快路径 + 异步慢路径。同步保证下轮可用,异步负责治理和质量。” 2. “结构化事实放 MySQL 保证可控可审计,语义联想放 Milvus 提高召回覆盖。” 3. “Outbox 保证事件可靠入队,Worker 解耦重计算,避免阻塞主链路。” 4. “借鉴 Mem0 的双阶段策略:先向量召回旧记忆,再让 LLM 决策 ADD/UPDATE/DELETE/NONE,兼顾召回率与准确率。” 5. “我们用 UUID 映射防止模型伪造 ID,并且用 JSON 容错链保证抽取稳定性。” 6. “我们用命中率、误提率、纠正率和 reranker 降级率验证记忆是否真的有价值。” ## 15. DoD(完成定义) 1. 代码层: - 记忆事件可发布、可消费、可重试。 - 记忆可检索、可注入、可删除、可关闭。 2. 质量层: - 有基础指标与日志,支持问题排查。 - 有失败兜底与降级路径。 3. 叙事层: - 3 分钟能讲清架构。 - 5 分钟能演示端到端效果。 - 能回答核心取舍与后续演进。 ## 16. 本轮执行顺序建议 1. 先做 Day 1 的表结构与事件接线,不进入复杂抽取细节。 2. 再做 Day 2 的读取注入,优先 MySQL 结构化记忆。 3. 最后补 Day 3 的 Milvus 与指标,确保面试讲述闭环。 ## 17. Mem0 借鉴清单与取舍结论(本轮新增) ### 17.1 直接借鉴 1. `ADD/UPDATE/DELETE/NONE` 统一决策状态机。 2. `threshold -> reranker(可选) -> fallback` 的检索后处理套路。 3. 三维过滤隔离(`user_id/agent_id/run_id`)的语义边界设计。 4. 历史追踪思路(本项目落在 `memory_audit_logs`)。 5. 低随机参数 + JSON 输出约束,提升可复现性。 ### 17.2 延后借鉴 1. 图记忆(关系三元组与软删除)延后到 V2/V3。 2. 多 Provider 工厂体系延后到“需要跨云/跨模型”时再上。 3. 托管 API 平台化能力延后到单体稳定后再拆。 ### 17.3 不照搬的原因 1. 当前目标是 3 天可演示 MVP,优先“稳定可讲”而非“能力最全”。 2. 项目已有 Outbox 可靠链路,先最大化复用,避免架构重复。 3. 日程助手是强约束场景,结构化事实主库优先级高于图谱表达能力。 --- 本文件定位为“落地执行蓝图”。后续每完成一块能力,建议在本文件追加“已落地清单 + 待办差距”,持续收敛为真实实施记录。