主干 `qyqy_develop` 已占用 29–36(29 登录接口 / 30 投顾迁移TODO / 31 投顾灰度 / 32 平台侧交接与联调准备 / 33–35 ZSY 底座扩展确认往来 / 36 PR7 合并记录), 本线原用的 32、33 与之重号。按"以架构师为主线"继续让号。 - `docs/32-记忆投影链路实现说明.md` → `docs/37-记忆投影链路实现说明.md` - `docs/33-架构对齐-记忆与画像投影链路.md` → `docs/38-架构对齐-记忆与画像投影链路.md` (两次 `git mv`,保留文件历史) - 同步更新 `AGENTS.md` 内 2 处引用、`docs/38` 内 2 处交叉引用 - 文档内部无自引用编号;全仓已无其它 `docs/32`、`docs/33` 引用(全量搜索确认) 验证:`tools/check_authoritative_docs.py` → checked 41 documents, no number collision
12 KiB
架构对齐 · 记忆→画像→图 这条链(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@bbf623aZSY: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 |
三、四个必须对齐的架构分歧
分歧 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、投顾线、我的画像线)。整体合并会把他的旧版本顶回主干。
四、建议的收口方案(分三步,风险递增)
第 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 的知识发布状态机与主干的知识入库/审核是否合并
五、给架构师/张胜宇的四个问题
memory_sync_outbox与profile.rebuild_requested的分工是什么? (两个 outbox 都在用,但谁负责什么没写下来。我在AGENTS.md里已经记了"两个 outbox 不能混", 但现在两条链各用一个,需要明确边界。)- 画像生成要"直接快照"还是"候选复核"? 这是业务裁决。
- ZSY 的客服 Agent 要不要保留? 主干那套已通过老师验收第 4/5/6 条,我建议以主干为准。
- ZSY 分支怎么收尾? 它落后主干 144 个提交 —— 建议不整体合并,改为"按需移植 2-3 个文件" (投影适配器 + outbox worker + 对应测试),其余明确标记为"已被主干实现取代"。
六、我这边确认不做的事
- 不改任何代码(本文只是对齐)
- 不整体合并 ZSY 分支(会让 144 个提交的旧改动回退主干)
- 不动主线那条已验链路(
profile.rebuild_requested全自动触发)
核对依据:git ls-tree 逐文件比对 + git grep 引用追踪 + 现库 information_schema 与行数实测。