Files
group_fqcd_jr/docs/evidence/20260909-memory-chain-acceptance.md
T
张胜宇 e239eb778b docs: 品牌全量口径统一为「南方基金」+ 作废文档清理
1) 客服 Agent 四份交付文档 + 构建脚手架:品牌由包装占位 XX科技 / 旧名 南方财富
   统一为南方基金(热线 400-889-8899 / 官网 nffund.com),系统名改为「智能服务系统」;
   同步追加 §0.4 修订记录行,工程记录行保留原占位字面以支撑硬编码扫描验收。
2) 开发文档:清理 28 份已作废/残留文档(14 份移出归档 + 14 份仓库副本),
   新增《文档规整方案与开发前待决事项-2026-09-17》。
3) 客服agent 四份交付文档首次纳入本分支。
2026-09-17 15:15:22 +08:00

6.6 KiB
Raw Blame History

记忆链路端到端验收(2026-09-09)

对照 docs/evidence/20260909-memory-baseline-before.md 定义的判据,验证记忆链路是否真正连通。 本文件在 P1 验收后更新,已纳入 P2 的抽取环节。

一、验收方式

tools/memory_chain_probe.py——真实 MySQL、真实 WorkerRuntime 租约与治理链、真实 OutboxWorker 消费、生产公开接口。为不依赖尚未配置的模型端点,探针通过 WorkerRuntime(model_service=..., endpoint_resolver=...) 注入两个确定性替身, 使「抽取」这一段同样被端到端覆盖,而不是靠单元测试代替。

python tools/memory_chain_probe.py

二、验收结果:全部通过(含抽取)

环节 断言 结果
生产装配 模型服务、工具执行器、意图分类器均已注入工厂 PASS
受理 AgentRunApplicationService.accept 返回 run_id PASS
执行 Worker 领取并执行完成,agent_run.status = succeeded PASS
事件契约 complete_run 同事务写入 memory.extraction_requested PASS
事件载荷 payload 不含正文,只有 run_id / message_id / customer_id PASS
消费 Worker 轮询 3 轮清空该 run 的事件队列 PASS
抽取 抽取模型被真实调用、端点经解析器解析 PASS
记忆写入 memory_unit 由 0 变为 1 行 PASS
抽取结果 键为受控词表 preference:risk_level、内容为结构化值「稳健型」而非用户原文、类型 preference 与键前缀同构 PASS
归属 customer_id 为发起运行的客户 PASS
幂等 同一 event_id 重复消费被拦截,记忆行数 1 → 1 PASS
清理 探针数据清理后残留 0 行 PASS

三、断点与修复对照

修复前 修复后 证据
WorkerRuntime 只注册 agent.run_requested,记忆事件永不消费 注册 4 个 handler(含 memory.extraction_requested) 探针第 5 步 PASS
消费者读 payload["content"],而事件按设计不含正文 按 message_id/run_id 回查 conversation_message 探针内容断言 PASS
无幂等边界,重复消费重复写 event_id 作为 memory_evidence.idempotency_key 探针第 6 步 PASS
记忆内容是用户原文整句、键为 conversation.{session_id} 模型严格 JSON 抽取 + 受控语义键 探针抽取结果断言 PASS
MemoryConflict left/right 自引用、ORM 列名与库不符 已修复并与库列名对齐 tools/audit_constraints.py PASSED
memory_unit 缺 active_memory_key 生成列与唯一键 迁移 20260909_memory_active_key 空库重建与既有库审计均 PASSED
P2 引入的回归:同一 Session 先查询触发 autobegin,complete_run 抛 "A transaction is already begun" 只读查询改用独立 Session,并补失败堆栈日志 探针由 FAILED 转为 PASSED
信号识别重叠误命中与漏命中 重写模式表 + 11 个回归测试 tests/unit/service/test_memory_taxonomy_signals.py
P3 三个服务零调用点(召回 / episode / 级联都是死代码) 分别接入治理链、Worker 轮询与事件 handler 探针 PASSED;episode persisted_rows=1;级联 memory_status=invalidated + 审计 1 行
EpisodeWorker 只 flush 不提交,接线方漏 commit 接线层显式 session.begin() 并写明契约 由 inserted=1 但 rows=0 暴露,修正后 rows=1
召回缓存命中后降级状态丢失(写入带 degraded、读取硬编码 False) 缓存读写都携带 degraded_reasons test_cached_degraded_result_keeps_degraded_flag(原为 strict-xfail,已转正)
向量后端直接抛异常会中断整条召回(与"只降级不阻塞"承诺不符) _vector 增加异常兜底 test_vector_backend_exception_is_degraded_not_raised(原为 strict-xfail,已转正)
recall_with_decay 中 Decimal * float 抛 TypeError 排序键显式转 float 接线后首个真实数据集成测试触发并修复

四、必须记录的环境事实

  1. 零配置环境无法端到端运行 Agent。model_endpoint_config 为 0 行,IntentClassifier 在端点 为空时按设计失败关闭(RecoverableAgentError),run 无法进入 complete_run。记忆抽取同样 失败关闭(无端点时不落任何记忆)。业务接入前必须至少配置一个模型端点与一个受控备用端点; 本探针用替身绕开这一环境依赖,只验证链路本身。
  2. Outbox 消费吞吐。dispatch_one 每轮只领取一条事件,jr 库现有 262 条 pending agent.run_requested,Worker 常驻后需约 262 轮(默认轮询 1 秒)清空。不是缺陷,但积压深度 需要纳入运维观测。
  3. 记忆键已语义化(P1 遗留问题已由 P2 解决)。键来自受控词表,同一会话的不同事实落到不同键, 不再互相覆盖;同一客户同一键的并发写入由 active_memory_key 唯一键保证只有一条有效记录。
  4. 向量召回通道已启用,只差环境配置。MemoryRecallService 已接入主链路,组装层 build_memory_recall_service 现同时传入 vector=VectorMemoryAdapter(...)(Milvus 可达时) 与 embed=_embed_text。_embed_text 走发布配置解析 task_type=embedding 端点;当前 model_endpoint_config 为 0 行,因此带查询文本的召回会按设计降级为 embedding_failed 并保留结构化结果(实测:items/degraded_reasons 符合预期, 向量化失败不抛异常、不中断召回)。配好 embedding 端点后语义召回无需改代码即可生效。 注意:无查询文本时 _vector 直接跳过,不产生降级标记——这也是实测中 degraded=False 的原因,属设计行为而非漏检。
  5. 结构化召回是字面匹配,语义匹配依赖向量通道。实测查询「风险偏好」在只有结构化通道时 返回 0 条:recall_with_decay 的关键词过滤按空白切分,中文查询整体作为一个 term, 只有字面出现在记忆内容或键里才命中。语义等价的查询(记忆内容是「稳健型」)需要向量召回, 这正是通道 4 的价值所在——不应通过放宽关键词过滤去"修"它。

五、边界

本验收证明"受理 → 执行 → 事件 → 消费 → 抽取 → 记忆 → 幂等"链路连通;不证明抽取质量本身 (由 test_memory_extraction_service.py 的契约测试覆盖)、不证明向量召回的语义效果 (Milvus 未接真实实例,降级路径由 P3 单元测试覆盖)、不证明 episode 聚合的业务价值。