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