Files
group_fqcd_jr/docs/38-架构对齐-记忆与画像投影链路.md
T
lzf_0626 36c7a9d8d2 文档:审查报告入库 + 全量校对补注
## 新入库(`docs/演示用/`)

- `代码库全面审查报告-2026-09-14.md`
- `代码修改方案-2026-09-14.md`
- `记忆系统排查报告-2026-09-14.md`
- `记忆系统修复文档-2026-09-14.md`
- `文档一致性审计报告-2026-09-14.md`
- `多Worker接入方案-2026-09-14.md`

## 全量校对(32 个既有文档 + `AGENTS.md`)

跨 39 个文件、**1125 insertions / 148 deletions**。

⚠️ **这批改动同样不是本次会话写的**。我抽样核对过性质:是**实质内容补充**而不是
格式/换行转换。例如 `docs/44-演示流程.md` 新增两条"2026-09-14 补注":

- `启动金融Agent平台.bat` 只在**桌面**上,仓库里只有 `启动平台.bat` 这一份
  (两份由同一个 `tools/make_launcher_bat.py` 产出,改完 `start.ps1` 重跑它一起更新);
- `advisor_t`(9020) 与 `offsite_t`(9006) **不在 `tools/seed_test_rbac.py` 的演示用户里**
  (那里只有 `cust_t`/`risk_t`/`admin_t`/`review_t` 四个),由 `grant_*.py` 系列创建,
  **重跑种子不会重建它们** —— 换机器时这两个账号登录失败,要先查 `sys_user` 有没有这两行,
  而不是查密码。

这两条都是对的地方,与我这一路踩到的现象一致(我确实用到了 `advisor_t`/`offsite_t`)。

**我没有逐字审阅全部 39 个文件**,只抽样确认了改动性质与规模。若其中有需要复核的段落,
请指明文件,我逐处核对。
2026-09-14 20:36:00 +08:00

13 KiB
Raw Blame History

架构对齐 · 记忆→画像→图 这条链(ZSY_develop vs 主干)

目的:ZSY_develop 分支(张胜宇,最后更新 2026-09-11 20:45)包含一整套画像投影实现, 而主干 qyqy_develop 上也有同类实现。在合并之前必须先做一次架构对齐,否则两套会互相覆盖。 本文只做核对与建议,不改任何代码。

⚠️ 后续(2026-09-12):本文的结论已落地实施,实施说明与验证证据见 docs/37-记忆投影链路实现说明.md。本文保留作为决策依据 —— 它记录了 "为什么选方案 A(复用主干图投影、不引入第二套)"、"为什么由生产端组装 memory_sources" 这两个决定的原始核对过程与判据,这部分推演在 docs/37 里没有重复。

编号说明:本文原为 docs/29;2026-09-12 让号给架构师线的 docs/29-Agent组员登录接口使用说明.md,先改号 docs/33,后又因主干续占 32–36 再让号至 docs/38(本文的实际编号以文件名与 AGENTS.md 为准)。

核对时间:2026-09-11 晚 主干:qyqy_develop @ bbf623a ZSY:cbcf7c7


一、最重要的发现:主干那条链已经接好了(走的是另一条 outbox)

之前(包括架构师的评审意见里)的说法是"组件写好了、线没接"。核对后这个说法不准确 —— 主干上有一条完整且已在装配层接线的链路,只是它走的不是 memory_sync_outbox:

agent.run_completed
      ↓  (app/worker/runtime.py 的 handler 字典,已注册)
memory.extraction_requested
      ↓  dispatch_memory_extraction
MemoryExtractionService → 提升成 user_facts
      ↓
profile.rebuild_requested
      ↓  dispatch_profile_rebuild(runtime.py L175-197)
ProfileAssemblyService.rebuild() → 重建画像快照
ProfileGraphProjectionService.project_customer() → 投影到 Neo4j

这条链的引入者与时间:

d7f6ef7  09-10 21:52  卿云秋月(架构师)  feat: 记忆→画像→图全自动触发

⇒ 架构师 09-10 就做完了"全自动触发",并且是直接接在 runtime.py 的 handler 字典里的。 app/worker/runtime.py 的 handler 白名单包含 "profile.rebuild_requested" —— 这就是"线接上了"的证据。

1.1 与 ZSY 那条链的关系:两条平行的 outbox

主干(架构师的链) ZSY(张胜宇的链)
触发事件 profile.rebuild_requested memory_sync_outbox 表里的行
存储 domain_event_outbox(领域事件表) memory_sync_outbox(记忆同步表)
消费方式 WorkerRuntime 的 handler 字典(已注册) 独立的 MemorySyncOutboxWorker
消费者装配 app/worker/__main__.py → WorkerRuntime(...) app/worker/__main__.py → MemorySyncOutboxWorker({...})
投影目标 Neo4j(ProfileGraphProjectionService + relationships) Milvus + Neo4j(两个 adapter)
画像生成 ProfileAssemblyService(从 user_facts 组装) CustomerProfileCandidateService(候选 → 复核 → 已批准快照)

⇒ 两条链做的是同一件事,但走不同的 outbox、不同的画像生成方式、不同的消费装配。

1.2 现场数据(我这边实测)

表 行数 说明
user_facts 0 事实层为空 ⇒ 主干那条链从没跑过(或跑过但被清理)
memory_unit 0 记忆单元为空
profile_snapshots 5 我的 seed_profile_demo.py 种的演示数据
memory_sync_outbox 2 我的 profile_generation_service 写的,status='待处理',无人消费

⇒ 关键因果链:user_facts 为空 → 主干链没产出过东西;memory_sync_outbox 的 2 行是我的生产者写的。

1.3 graph_projection_worker.py 的真相

app/worker/graph_projection_worker.py  →  GraphProjectionWorker 类
git grep "GraphProjectionWorker("      →  零处实例化

它是死代码(或备用路径)。架构师说的"GraphProjectionWorker 没有实例化点"是对的; 但由此推论"整条链没接"不对 —— 接的是 runtime.py 的 handler,不是这个类。


二、ZSY 分支带来的真正增量(主干确实没有的)

逐文件核对后,ZSY 分支独有且主干没有的只有这些:

文件 作用 主干有没有等价物
app/worker/memory_sync_outbox_worker.py memory_sync_outbox 的消费者(handler 注入 + 重试 + 死信) 无(主干消费的是另一个 outbox)
app/infrastructure/milvus_profile_projection.py 画像投影到 Milvus 无(主干只投影到 Neo4j)
app/infrastructure/neo4j_profile_projection.py 画像投影到 Neo4j(另一个实现) 🟡 主干有 ProfileGraphProjectionService
app/service/knowledge_publication_service.py 知识发布状态机(草稿→审核→发布) 🟡 主干有 knowledge_ingest_service + 审核字段
app/service/knowledge_authority.py / knowledge_config.py 知识权威来源与运行期配置 无
app/service/customer_service_session_memory_service.py 客服多轮会话记忆 🟡 主干有 Redis 短期记忆 + docs/24 说多轮已跑通
app/service/customer_service_handover_*.py 转人工上下文与后台 🟡 主干有 handover-requests 端点 + 工单表
app/service/customer_profile_candidate_service.py 画像候选 → 复核 → 批准工作流 无(我的 profile_generation_service 是直接生成快照)
app/worker/customer_profile_candidate_worker.py 候选流程的 worker 无
app/service/agent/customer_service_agent.py 他自己的客服 Agent 🔴 主干已有(implementations/customer_service.py,老师验收第 4/5/6 条靠它)
app/service/agent/customer_service_routing.py 他自己的路由 🔴 主干已有 core/customer_service_rules.py
app/service/knowledge_tool_service.py 知识工具(另一份) 🔴 主干已有 knowledge_tool.py

三、四个必须对齐的架构分歧

✅ 四个分歧均已收口(2026-09-14 复核),逐条结论:

分歧 最终选择 落地位置
1. 用哪条 outbox 沿用主干那条链(agent.run_completed → memory.extraction_requested → profile.rebuild_requested),不引入第二套 app/worker/runtime.py 的 handler 字典
2. 画像"直接快照"vs"候选→复核→批准" 两者并存:主干直接重建快照;候选复核作为第二条独立路径(memory:candidate:confirm/memory:candidate:review),由 PR #7 合入 docs/36 §1
3. 两套客服 Agent 放弃 ZSY 那套,只保留主干的 CustomerServiceAgent。ZSY 的访客身份 / 候选 / 转人工工单三块能力保留 docs/36 §1
4. ZSY 落后主干 144 提交 按"只取增量"处理,未整体合并 docs/36 §1

另外:app/infrastructure/neo4j_profile_projection.py(ZSY 那套私有 Preference/Goal 节点投影) 未被装配进生产 —— 主干走的是 ProfileGraphProjectionService(写共享 Tag 节点,源自 user_facts)。 两者语义不同,不要为了"启用它"而把 __main__.py 的装配加回去,那会重新变成两套图投影并存。

实施细节与验证证据见 docs/37-记忆投影链路实现说明.md;本文下面各节保留原始核对过程。

分歧 1:用哪条 outbox 承载"记忆→画像→图"?

  • 主干:domain_event_outbox + profile.rebuild_requested(已在 handler 白名单里,架构师 09-10 接的)
  • ZSY:memory_sync_outbox + MemorySyncOutboxWorker

我的判断:memory_sync_outbox 是 docs/00 基线里定义的表(存在即有其设计意图), 而主干那条链并没有消费它 —— 所以两套有各自的合法位置,不该二选一,而该明确分工:

场景 应该走哪条
记忆内容变化 → 画像重建 → 图投影 主干那条(profile.rebuild_requested,已接线、已验)
画像快照 → 同步到 Milvus 向量库(用于语义召回) 需要一个消费者,这正是 ZSY 的 milvus_profile_projection 补的

⇒ 建议:主干链保留;ZSY 的 Milvus 投影适配器接进主干链的尾部(或接成 memory_sync_outbox 的消费者, 但那样要明确"这两个 outbox 各自负责什么",否则就是两套并存)。

分歧 2:画像生成"直接快照"还是"候选→复核→批准"?

  • 我的:profile_generation_service.py —— 直接生成快照(符合 docs/00 §6.4.6 的事务口径)
  • ZSY 的:customer_profile_candidate_service.py —— 先生成候选、人工/规则复核后才成为正式快照

我的判断:这是业务裁决,不是技术裁决。候选复核流程更严(金融场景可能更合适), 但成本更高(需要复核人、需要审核界面)。建议由架构师或业务方定,不要由合并动作决定。

分歧 3:两套客服 Agent

  • 主干:implementations/customer_service.py(已进主干,老师验收第 4/5/6 条靠它)
  • ZSY:agent/customer_service_agent.py

我的判断:这是最危险的一条。主干那套已通过验收(产品咨询 5/5、政策 3/3、多轮 3 轮), ZSY 那套是从旧基线写的、落后主干 144 个提交。不能因为合并把它顶掉。

分歧 4:ZSY 落后主干 144 个提交 —— 他的改动里有大量"过期内容"

ZSY 的分叉点是 c2178a9(09-11 09:49),而它改过 68 个与主干重叠的文件,其中包含:

app/service/tool_executor.py、app/service/agent/base.py、app/service/agent/governance.py、
app/worker/runtime.py、app/service/public_platform_service.py、app/main.py、app/core/contracts.py

这些文件在主干上已经过多轮修改(风控 P3、投顾线、我的画像线)。整体合并会把他的旧版本顶回主干。


四、建议的收口方案(分三步,风险递增)

✅ 三步均已执行完成(2026-09-12),证据见 docs/37;本节保留原始方案供追溯。

第 1 步:只移植两个投影适配器(低风险、有明确收益)

移植什么 从哪来 落到哪
Milvus 画像投影适配器 app/infrastructure/milvus_profile_projection.py 同一路径(主干没有同名文件)
(可选)Neo4j 投影改进 app/infrastructure/neo4j_profile_projection.py 与主干 ProfileGraphProjectionService 比对后再定
测试 tests/unit/infrastructure/test_milvus_profile_projection.py 同路径

为什么安全:这两个文件在主干不存在 ⇒ 零冲突;它们是纯适配器(无业务改动)。

第 2 步:memory_sync_outbox 的消费者(中风险,需要先定分工)

两种做法,必须选一种:

做法 说明
2a 把 ZSY 的 MemorySyncOutboxWorker 接进 app/worker/__main__.py(他那套本来就是这么设计的)—— 直接能用,但要明确它与主干链的分工
2b 不引入新 worker,而是把主干链的尾部补上"同时写 Milvus"(改 dispatch_profile_rebuild)—— 单一链路更简单,但要改主干的已验代码

我倾向 2a,理由:memory_sync_outbox 是基线表,让它有自己的消费者更符合原本设计; 且不用改主干已验的那条链。

第 3 步:其余部分对齐后再谈(需要架构师/业务裁决)

  • 画像候选复核流程要不要采纳(分歧 2)
  • ZSY 的客服 Agent / 知识工具 / 路由是否全部放弃(分歧 3)—— 我建议放弃
  • ZSY 的知识发布状态机与主干的知识入库/审核是否合并

五、给架构师/张胜宇的四个问题

  1. memory_sync_outbox 与 profile.rebuild_requested 的分工是什么? (两个 outbox 都在用,但谁负责什么没写下来。我在 AGENTS.md 里已经记了"两个 outbox 不能混", 但现在两条链各用一个,需要明确边界。)
  2. 画像生成要"直接快照"还是"候选复核"? 这是业务裁决。
  3. ZSY 的客服 Agent 要不要保留? 主干那套已通过老师验收第 4/5/6 条,我建议以主干为准。
  4. ZSY 分支怎么收尾? 它落后主干 144 个提交 —— 建议不整体合并,改为"按需移植 2-3 个文件" (投影适配器 + outbox worker + 对应测试),其余明确标记为"已被主干实现取代"。

六、我这边确认不做的事

  • 不改任何代码(本文只是对齐)
  • 不整体合并 ZSY 分支(会让 144 个提交的旧改动回退主干)
  • 不动主线那条已验链路(profile.rebuild_requested 全自动触发)

核对依据:git ls-tree 逐文件比对 + git grep 引用追踪 + 现库 information_schema 与行数实测。