## 新入库(`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 个文件**,只抽样确认了改动性质与规模。若其中有需要复核的段落,
请指明文件,我逐处核对。
13 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 |
三、四个必须对齐的架构分歧
✅ 四个分歧均已收口(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§13. 两套客服 Agent 放弃 ZSY 那套,只保留主干的 CustomerServiceAgent。ZSY 的访客身份 / 候选 / 转人工工单三块能力保留docs/36§14. 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 的知识发布状态机与主干的知识入库/审核是否合并
五、给架构师/张胜宇的四个问题
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 与行数实测。