Commit Graph
89 Commits
Author SHA1 Message Date
wangjianlong_0626 57f56bbcbe fix(profile): 修 profile_snapshots 重复定义(会打挂 Worker);消费端兜底 memory_sources
## 1. 独立缺陷:`profile_snapshots` 被两个 ORM 类重复映射

排查 `memory_sync_outbox` 中 `target_store='neo4j'` 那行 `last_error='InvalidRequestError'`
时发现,根因不在图库,而在模型层:

- `app/model/profile.py` → `ProfileSnapshot` 映射 `profile_snapshots`
- `app/model/risk_questionnaire.py` → **另一个** `ProfileSnapshot` 也映射 `profile_snapshots`

SQLAlchemy 不允许两个类映射同一张表。实测:

| 场景 | 结果 |
|---|---|
| 单独导入 `app.main` / `app.worker.runtime` | 正常 |
| 单独导入 `profile_assembly_service` / `risk_questionnaire_service` | 正常 |
| **两者同时导入** | `InvalidRequestError: Table 'profile_snapshots' is already defined` |

Worker 在同一进程里既要处理 `profile.rebuild_requested`(走 `app.model.profile`),
又要处理投顾风险问卷(走 `risk_questionnaire.py`)——所以这是**会打挂 Worker 的缺陷**,
不是理论风险。

修法:`app/model/risk_questionnaire.py` 不再重复定义,改为从 `app.model.profile`
转出(re-export),既有 4 处 `from app.model.risk_questionnaire import ProfileSnapshot`
无需改动。原定义多映射的 `current_customer_id` 经全仓核查无人使用,故不保留
(`app.model.profile` 明确注明该列由数据库维护、故意不映射)。

## 2. 消费端兜底 `memory_sources`

`memory_sources` 是本线新增的投影入参,而投顾线两处生产者的 payload
(`{customer_id, profile_uuid, version, profile}`)没有这个键,原样会导致它们每次画像
变更都 `memory_sources is invalid` → 重试至死信。

新增 `WorkerRuntime._with_memory_sources()`:**键缺失或为 None** 时回退查询该客户
`memory_unit` 中 `status='active'` 的记忆,并记 warning(使"谁没提供"保持可见)。
语义成立:长期记忆是**客户级**而非画像版本级的,每条记忆自带 `version`,
适配器按 `memory_uuid + version` 幂等,故"用的是哪一版"仍确定。

**兜底不掩盖真错误**:键**存在但格式不对**时**不兜底**,原样交给适配器失败关闭。
实现上用键存在性判断而非 `isinstance`——后者会把"缺失"与"格式错"混为一谈,
那是初版实现里的一个真 bug,被新测试抓出后修正。

## 3. 补上此前欠缺的消费端全路径验证

此前"整合验证"是直接调适配器,跳过了 outbox 的领取→分派→状态更新。

- **失败分支**(Milvus 断开时实测):行被领取、按 target_store 分派、异常被捕获、
  `status`/`retry_count`/`last_error`/`next_retry_at` 正确落库。
- **成功分支**(注入替身向量客户端):outbox 行 → `processed`、`processed_at` 已写;
  不可投影的 `constraint:` 被跳过(只写 1 行);维度 1024;字符串客户号转 int;
  **手机号脱敏生效**(`稳健型投资者,手机号 [手机号已隐藏] 请勿外泄`)。
- **兜底实证**:历史行 `id=5`(payload 无 `memory_sources`)经兜底后成功投递为 `processed`。
- **修复实证**:`id=6` 的 `last_error` 从 `InvalidRequestError` 变为
  `RecoverableAgentError`(图库不可用)——证明重复定义缺陷确已消除,剩下的是环境问题。

## 4. 测试与验证

- 新增 `tests/unit/worker/test_runtime_profile_projection.py`(5 用例:已提供原样透传、
  缺失兜底、空记忆给空列表而非删键、无客户号不兜底、格式错不兜底)
- 全量:`2 failed, 1312 passed, 2 skipped`(2 个失败为既有环境项,非本次引入)
- mypy:`Success: no issues found in 227 source files`
- 表结构审计:89 张业务表无缺失/意外(未改动任何表结构)
- 文档守卫:41 份文档无编号冲突

## 5. 文档

`docs/32-记忆投影链路实现说明.md` 增补 §6.1(兜底)、§6.2(重复定义缺陷)、
§7.1(消费端全路径验证)并更新验证表与文件清单;
`AGENTS.md` 新增"一张表只能有一个 ORM 类"易错点、校正测试基线数字。

## 未做

未改 `docs/00` 基线、未动数据库迁移、未改投顾线生产者代码、未启动常驻 Worker。
遗留:投顾线两处生产者的 payload 仍缺 `memory_sources`(已有兜底,不再死信,
但根治应由投顾线确认);Milvus/Neo4j 容器本轮不可用(Docker Desktop 崩溃),
`id=6` 停在 failed 属环境不可用、非代码缺陷。
2026-09-12 11:24:50 +08:00
wangjianlong_0626 cf70ac49e9 docs: docs/29 让号给架构师线(改号 33);补实施后续说明
架构师线 2026-09-12 推送的 `docs/29-Agent组员登录接口使用说明.md` 占用了 29,
与本线的《架构对齐 · 记忆→画像→图》重号。按"以架构师为主线"的口径,本线让号。

- `git mv docs/29-架构对齐-记忆与画像投影链路.md docs/33-架构对齐-记忆与画像投影链路.md`
  (用 mv 而非增删,保留文件历史)
- 该文档开头补"⚠️ 后续(2026-09-12)"提示:结论**已落地实施**,指向
  `docs/32-记忆投影链路实现说明.md`;避免接手人按原文的"本文只做核对与建议,
  不改任何代码"误判为尚未实施。同时记录让号原因,避免编号来历不明
- 文档内部无自引用编号、全仓无 `docs/29` 引用(已全量搜索确认),故无引用需改

验证
- `tools/check_authoritative_docs.py` → checked 41 documents, no number collision
- docs 编号现为 30(投顾迁移TODO) / 31(投顾灰度) / 32(记忆投影链路) / 33(架构对齐)

说明:架构师分支上 `docs/21`、`docs/22` 各自有两份(投顾线带入的重号),
本线已在 c82e989 改号为 30/31,合并后该重号自动消除,无需额外处理。
2026-09-12 10:52:25 +08:00
wangjianlong_0626 8a0cbab636 fix(memory-projection): 订正 outbox 取值口径并接通画像投影链路
背景:memory_sync_outbox 这条链此前**完全没有消费者**,且生产端照 docs/00 §6.4.6
写成大写 MILVUS/NEO4J + 中文「待处理」,而消费端按 target_store 的**值**分派 handler、
且只领 status in {pending, failed} —— 两个条件都不满足,事件任何消费者都领不到、
永久滞留且不报错(唯一键 (event_uuid, target_store) 对大小写无约束,MySQL 也不报错)。
根因是代码与测试都硬编码字面量,所以测试跟着一起错、谁也没拦住。

订正
- profile_generation_service:取值改为全仓一致的小写(milvus/neo4j/upsert/pending)
- 测试改为引用常量并断言消费端契约,不再硬编码(硬编码是本次跑偏的直接原因)
- 新增契约回归测试:断言大写值分派不到 handler、会进死信,谁改回大写立刻红
- 新增 tools/normalize_memory_sync_outbox.py:订正历史脏行(默认 dry-run、幂等)

接通投影链路(此前零消费者)
- 新增 Milvus 集合 user_long_term_memory_v1 及建集合工具(幂等、不覆盖已有集合)
- 新增 MilvusProfileProjection / MilvusProfileVectorClient,并修掉移植带来的两处必炸点:
  customer_id 由「必须 int」放宽为接受数字字符串(本仓所有生产者都写 str,
  不放宽则每个事件必然失败);不可投影的 memory_key 由「整批 raise」改为跳过留痕
  (否则一条 constraint: 记忆毒死该客户整批,而受控词表 13 个键里有 7 个不满足前缀)
- 新增 MemorySyncOutboxWorker(领取/指数退避/死信骨架保留原样)并接入 WorkerRuntime
- milvus → 向量投影;neo4j → 复用主干 ProfileGraphProjectionService(方案 A,
  不引入第二套投影,避免同一事实在图中两种说法、违反主干既有的只投影已确认事实的不变式)
- 生产端从 memory_unit(status=active) 组装 memory_sources,随事件带上确定快照
- 前置移植 conversation_privacy:写外部存储前脱敏手机号/证件号/银行卡等

验证
- 新增 17 个单测;全量 2 failed, 1307 passed, 2 skipped
  (2 个失败为既有环境项:断言请求体中文原文而 httpx 序列化成 \uXXXX,非本次引入)
- mypy app → 0 错(227 文件);audit_schema → 89 张业务表无缺失/意外,未改动表结构
- 真机:真实 embedding(1024 维) + 真实 Milvus 写入并回读通过
- 整合链路(测试记忆 → 生产端组装 → outbox → 消费端投递 → Milvus 回读)通过,
  且 MySQL 已回滚、Milvus 无残留

文档
- 新增 docs/32-记忆投影链路实现说明.md:真实口径、根因、契约与验证证据(供接手)
- AGENTS.md:新增该易错点;新增 Windows 中文输出乱码的正确命令(-X utf8);
  校正测试基线与 mypy 文件数

未做:未改 docs/00 基线、未动数据库迁移、未改投顾线代码、未启动常驻 Worker。
遗留:投顾线两处生产者的 payload 缺 memory_sources,会被消费至死信,待架构师确认是否投影。
2026-09-12 10:45:40 +08:00
qyqy c82e9890e4 docs: 修投顾线带入的文档重号(21/22 → 30/31);新增 docs/29 架构对齐
投顾线(bbf623a)带进两份文档,与既有的撞号:
  21-投顾Agent迁移TODO.md          → 30-(原 21 已被《风控业务第二版迁移清单》占用)
  22-投顾Agent灰度与回滚操作手册.md  → 31-(原 22 已被《四大Agent流程实现拆解》占用)
处理原则与 docs/27 一致:**让新来的那份让号**(既有那份被更多地方引用)。
同时修了 30 号文档里两处指向旧号的自引用。守卫恢复通过(40 份无重号)。

另含 docs/29-架构对齐-记忆与画像投影链路.md(合并 ZSY 分支前必须做的对齐,
结论见该文档:主干那条链**其实已经接好**,走的是 domain_event_outbox + profile.rebuild_requested;
ZSY 走的是另一条 memory_sync_outbox,两条平行不冲突,但需要先明确分工)。
2026-09-12 09:37:00 +08:00
qyqy af6f689b3f docs(29): 新增《架构对齐 · 记忆→画像→图 这条链》(ZSY_develop vs 主干)
合并 ZSY 分支前必须先做的对齐。核对结论与"线没接"的旧说法**不一致**:

**主干那条链其实已经接好了**——架构师在 09-10 21:52 的 d7f6ef7 就做完了"记忆→画像→图全自动触发",
走的是 `domain_event_outbox` + `profile.rebuild_requested`,**已注册进 runtime.py 的 handler 白名单**:
  agent.run_completed → memory.extraction_requested → MemoryExtractionService(提升 user_facts)
  → profile.rebuild_requested → ProfileAssemblyService 重建画像 + ProfileGraphProjectionService 投影 Neo4j

而 ZSY 走的是**另一条 outbox**(`memory_sync_outbox` + 自建 MemorySyncOutboxWorker)。
⇒ 两条平行链做同一件事,但 outbox、画像生成方式、消费装配都不同。

顺带修正一处旧结论:`graph_projection_worker.py` 确实**零实例化**(架构师说的对),
但由此推论"整条链没接"不对——接的是 handler 字典,不是那个类;那个类是死代码。

文档内容:
- §1 主干链的完整链路与引入者、现场数据(user_facts 0 行 / memory_sync_outbox 2 行是**我的**生产者写的)
- §2 ZSY 的**真正增量**逐文件列表(只有 3 个是主干完全没有的:Milvus 画像投影适配器、
  memory_sync_outbox worker、画像候选复核流程)
- §3 四个必须对齐的分歧(两条 outbox 的分工 / 直接快照 vs 候选复核 / 两套客服 Agent / ZSY 落后 144 提交)
- §4 三步收口方案(先只移植投影适配器,再定 outbox 分工,其余等裁决)
- §5 给架构师与张胜宇的四个问题
- §6 明确不做的事(不整体合并、不改已验链路)

文档守卫通过;本文只做核对,未改任何代码。
2026-09-12 09:36:27 +08:00
Windows bbf623a464 merge: integrate advisor capabilities on latest qyqy base 2026-09-11 21:03:58 +08:00
Windows 4393355c3a docs: record legacy database migration rejection 2026-09-11 20:30:25 +08:00
Windows 5a69cc266c docs: record advisor rollout preparation 2026-09-11 20:28:49 +08:00
Windows f5dd5b8ba2 feat: add advisor rollout gate and rollback playbook 2026-09-11 20:28:21 +08:00
lzf_0626 3029d0ca9f 更新 release-state 证据:画像工具白名单已补发(release 254 active) 2026-09-11 20:24:07 +08:00
lzf_0626 f09ea9e988 Merge origin/NL_develop:客服画像出口、知识管理三端点、合规语境与知识向量链路
NL 线(含其并入的袁聪场外/推广域)。唯一冲突是 .gitignore —— 双方都往同一区域加了
.workdir/,取对方版本(他的更完整,含 .tmp/ 与说明),顺带修掉我之前用
Add-Content -Encoding utf8 造成的编码混合(read 工具当时报 invalid UTF-8)。

合并后修的问题 —— 都不是"改别人业务逻辑",是让门禁能绿:

1. 缺运行依赖 python-docx。document_parser.py 解析 .docx 用它,但 requirements.txt 与
   pyproject.toml 都没声明 —— 别人环境跑知识入库会直接
   ModuleNotFoundError: No module named 'docx'。已补声明。
2. ruff 7 项:其中 tests/conftest.py 的 F821 Undefined name 'Path'(他的 tmp_path 修复
   写了字符串注解 "Path" 却漏 import,运行时不求值所以没炸,但 mypy/ruff 会抓)、
   tools/publish_customer_service_config.py 的 F841 inherited_keys 死变量(他改同 key
   覆盖、换成 inherited_only 后忘删旧的)、3 处 E501,另 2 项 ruff --fix 自动修复。
3. 合规基线种子未跑:integration 的 test_compliance_seed_mysql 4 个用例要求
   agent_negative_word 有 7 条 active 且已复核、agent_reply_template 覆盖 6 场景。
   跑 tools/seed_compliance_baseline.py(11 条 active 规则 / 6 个场景模板)后 80 passed。

验证:ruff 干净 / mypy 180 文件 0 错 / unit+contract 1140 passed /
integration 80 passed / 表数 68(alembic 已在 20260911_merge_risk_heads)。

唯一失败 tests/unit/repository/test_fund_readonly_contract.py 是双方一致的既有缺陷:
它断言 Base.metadata 里的 fin_* 表集合,而实测为空集 —— 即该测试依赖别的测试先导入模型的
副作用,单独跑必失败。NL 方也明确"不修不报",此处照办,仅记录。
2026-09-11 20:22:59 +08:00
qyqy 58849addcc docs: 交付说明补第二轮批复(含 mypy 归因纠正);修正残留过期数字
上一轮我只更新了接手文档,**漏了交付说明**——它的末次更新(19:15)早于架构师 19:43 的
第二轮答复,导致它还写着已被推翻的结论。本次补齐:

- 新增 §0-B:架构师第二轮的 9 条批复逐条落地对照(含"mypy 归因纠正""docs/00 不改另立登记"
  "两件事归他""那 2 个失败按新引入对待")
- §5.2 整体重写:**认错并给出四组对照实验**(SQLAlchemy 2.0.34+1.14→173、2.0.52+1.14→6、
  2.0.52+1.20→3),说明真因是 SQLAlchemy **补丁版**而非"缺类型存根";环境已升级到
  2.0.52+1.20.2,mypy 184→3;并列出剩余 3 个错全在同事那条线的文件里
- §5.1 接受架构师口径:那 2 个失败**不是"双方共有的既有失败"**,应作为合并后**新引入的失败**跟踪
- §2 净差异分两段重算:本线增量 16 模块/17 测试/5 工具 vs 同事那条线 334 文件/11 迁移
- §6/§7 更新为第二轮后的状态(哪些已批准、哪些归他、哪些已落地)
- 修正残留过期数字:结构审计 51→68 张表、去掉"缺 SQLAlchemy 2.0 类型信息"的错误论断
- 章节号整理:两个同名 "## 0" → "## 0-A / ## 0-B"

文档守卫 35 份无重号;mypy 3 错;测试 1218 passed。
2026-09-11 20:06:23 +08:00
qyqy f7a666fb5a fix(env)+docs: 按架构师第二轮纠正 mypy 根因(环境版本);补场外/推广域表登记
架构师第二轮答复里有一条技术纠正是对的,我原归因错了:
- 我原判断"184 个 mypy 错是因为缺 SQLAlchemy 2.0 类型信息"——**方向对了一半、结论反了**。
  SQLAlchemy 2.0 自带 py.typed,根本不需要 sqlalchemy2-stubs(那是 1.4 用的)。
- 实测复现矩阵(决定性证据):
    SQLAlchemy 2.0.34 + mypy 1.14.1 → 173 错(我原来的环境)
    SQLAlchemy 2.0.34 + mypy 1.20.2 → 173 错
    SQLAlchemy 2.0.52 + mypy 1.14.1 →   6 错
    SQLAlchemy 2.0.52 + mypy 1.20.2 →   3 错
  ⇒ 主因是 SQLAlchemy 的**补丁版本**(173→6),mypy 版本是次因(6→3)。
- 已把环境升到 SQLAlchemy 2.0.52 + mypy 1.20.2(均在 pyproject 约束内),
  mypy 从 184 降到 **3 个错**(剩下的 3 个全在同事的 offsite 文件里,非本线代码)。
- 没有把 sqlalchemy2-stubs 写进依赖(采纳架构师明确要求)。
- 交接文档已重写该条,并留下"两个反面教训":不要装 1.4 的存根包;补丁版差异会造成量级差异,
  若要门禁稳定需把 SQLAlchemy 钉到具体补丁版(属公共约定,未擅自改)。

按架构师裁决补文档(他裁定:不动 docs/00,另立登记):
- 新增 docs/28-场外与推广域数据表登记.md:17 张表逐表登记(表名/来源迁移/归属域/当前行数),
  并做规则 8 的**两向边界核对**——场内代码零引用这 17 张表(config.py 里的 offsite_ 只是配置项名)、
  场外代码零写场内交易表
- docs/08 审计口径更新为「场内 51 + 场外/推广 17 = 68」,并新增"第四种坏状态"
  (alembic_version 已指向新 revision 但表没建)的处置说明
- 澄清一个易误读点:audit_schema.py 的期望集合是**动态推导**的(读 baseline_generated.sql
  + 扫描 alembic/versions/*.py),表数 51→68 是自动结果,**没有人手工改期望值**;
  代价是只增不减(DROP 表会报 missing 假失败)

测试:3 failed(1 既有 + 2 环境相关)/ 1218 passed;文档守卫 35 份无重号;
mypy 3 错(180 文件);结构审计 68 张表通过。
2026-09-11 20:00:56 +08:00
lzf_0626 9957ddc191 Merge pull request 'NL_develop 评审答复与画像白名单补发脚本' (#6) from qyqy_develop_1 into qyqy_develop
合并 qyqy_develop_1 第四轮工作:NL_develop 评审答复、画像工具白名单补发脚本、docs/08 §8。
2026-09-11 19:58:29 +08:00
lzf_0626 8268646b0c 答复文档补充 §8:两个既有发布脚本会丢提示词(写补发脚本时发现)
publish_customer_service_config.py 与 publish_risk_agent_config.py 的
active_config_items() 都自己写 SQL、只查 platform_config_item,而
ConfigReleaseService.effective_snapshot() 要读全部三张受管表。用它们发版会把生效版本里的
提示词一起清掉 —— 且没有任何声音:_warn_dropped_items() 只告警不阻断激活,Agent 侧
_chitchat_prompt 有兜底会回落代码默认值,功能看着正常(与 release 174 那次事故同型)。

已在答复文档里建议改用 effective_snapshot(),并列出三个细节:同 key 覆盖(对方已修)、
JSON 列必须归一化(SELECT * 读出来可能是字符串,不解析会静默走进"没有该 key"分支)、
提示词搬家要重分配 version 且按 PromptPayload 字段挑(漏 input_schema/output_schema 即丢)。

另提醒时序:admin 端校验「配置 ⊆ 代码 allowed_tools」,画像工具那一版必须等代码合并后才能发,
否则 422 且报错只有"配置超出 Agent 工具上限"。
2026-09-11 19:52:08 +08:00
Windows fbb1171a45 feat: add quote sync command 2026-09-11 19:51:51 +08:00
Windows 5de5c6c6b5 docs: record dual-source quote acceptance 2026-09-11 19:49:31 +08:00
Windows 2376585898 feat: add dual-source quote health orchestration 2026-09-11 19:47:14 +08:00
lzf_0626 d28ccdd88c 答复 NL_develop 第二轮:批准整改,纠正 mypy 归因,裁定 docs/00 不动
产出 docs/NL_develop评审回复-架构师答复.md(可直接转给对方)。三条核实结论:

1. docs/21 在我这边本来就重号(21-JWT密钥管理与轮换.md + 21-风控业务第二版迁移
   清单.md),tools/check_authoritative_docs.py 当前就是失败的 —— 我此前只跑
   ruff/mypy/pytest,没跑到文档守卫。对方的让号顺手修好了这个既有故障,
   故批准 docs/26 与 docs/27 两处让号。

2. 对方的 mypy 归因需要纠正。他说主因是缺 SQLAlchemy 2.0 类型信息;我这边
   mypy 1.20.2 + SQLAlchemy 2.0.52 + 未装 sqlalchemy2-stubs → 0 错,而
   pyproject.toml 约束是 sqlalchemy>=2.0,<3 / mypy>=1.14,<2。SQLAlchemy 2.0 自带
   py.typed,sqlalchemy2-stubs 是给 1.4 用的,装了反而按 1.4 的 API 报错(他自己也
   观察到"还会换一批新错")。真实根因是他的 .venv 没满足 pyproject 约束。

3. 他的迁移警告对我不适用。我这边 alembic current = heads = 20260911_risk_rule_index
   (单一 head,只有我自己的迁移),schema audit 报 51 business tables —— 即"表没建、
   版本号也没跑",与他那边"版本号跑了、表没建"是两种不同的坏状态。合并后我必须补跑
   alembic upgrade heads。

裁定:
- 补发配置(customer_service:faq 加 query_customer_profile)归我做;
- memory_sync_outbox / GraphProjectionWorker 消费端归我(生产者在他那边);
- 两处让号批准;
- docs/00 不动,另立文档登记场外/推广域 17 张表,并更新 docs/08 审计基线口径
  (依据 AGENTS.md 规则 8:场外基金运营流程独立,不得写入场内交易表)。
2026-09-11 19:40:17 +08:00
qyqy f1b8cb859a docs: 新增《接手文档 · NL_develop 这条线(给架构师)》
面向"接手维护"而非"review"的第三份文档,与另两份分工明确:
- 交付说明 → review 用(我动了你什么、为什么)
- 评审意见回复 → 对照你的评审意见
- **本文** → 接手维护用(起点在哪、有哪些坑、哪些待裁决)
- 给下一位开发者 → docs/superpowers/handoff/…

内容要点:
- §1 三条最要紧的结论:① 两台机器不是同一套环境(附三处实测证据)并由此立下两条硬纪律
  (环境相关结论必须带环境限定、禁止硬编码 Milvus 字段名);② **表数已从 51 变 68**
  (同事 11 个迁移新建 17 张 offsite_*/promotion_* 表),而 docs/00 基线与 docs/02 未更新
  —— 已提示、未擅自改不可变基线;③ 测试 3 failed 均非代码缺陷(附证据)
- §2 环境口径(含 Docker Desktop 不常驻、mypy 不可比的实测根因)
- §3 分支与提交 + 两个可回退的备份分支
- §4 交付清单(8 项,含状态与位置)
- §5 必须知道的 5 个坑(Milvus 读一致性与字节截断、config_release 整版本替换、
  两个 outbox 不能混、审计表无 agent_type 列)
- §6 待你裁决的 3 件事(白名单补发归属、接线归属、两处文档让号)
- §7 建议你拍板的两件事(文档体系口径、docs/00 是否补 17 张新表)
- §8 接手后先跑的 5 条自检命令
- §9 四份文档的分工表(防止读串)
2026-09-11 19:32:19 +08:00
Windows 5c60036dd6 docs: record product comparison acceptance 2026-09-11 19:21:26 +08:00
Windows b6429e0e9c feat: add advisory product comparison tool 2026-09-11 19:20:32 +08:00
qyqy c6a52a36a8 docs: 新增《评审意见回复》,逐条对照架构师的评审意见
按评审意见结构逐条回复,每条都给实测证据与落地位置:
- §1 环境不是同一套(三处独立证据:config_release 总 4 条 vs 最高 201、
  Milvus 字段/行数三项全不同、代理表命名不同),并据此提出团队纪律
  "环境相关结论必须带环境限定"
- §1.3 承认我原先的 A/B/C 三分法**前提就错了**(以为是"选哪套 schema"的决策问题,
  实际是"两套环境"的兼容问题),已按架构师方案改为运行时探测并用双 schema 测试锁住
- §1.4 给出 mypy 差异的根因(本机缺 SQLAlchemy 2.0 类型信息,装 sqlalchemy2-stubs
  181→43、卸载回 184),明确不再拿该数字当结论,也不擅自改 mypy 公共配置
- §2 澄清 docs/26 是重命名而非并存(21 已被风控迁移清单占用)
- §3.1 审计留痕已补并有真机证据(含改动前 false 的对照行)
- §4 给出 memory_sync_outbox / GraphProjectionWorker 的**双环境对照表**,
  证明与架构师的发现同源(组件写好、线没接),且我方更进一步(事件已写进表里)
- §3 告知同事那条线已并入、11 个 alembic 迁移已执行(此前"版本号跑了表没建"),
  并说明我方修的两处重号与测试临时目录问题
- §5 列出需要架构师确认的三件事(配置补发归属、接线归属、两处让号)
2026-09-11 19:15:50 +08:00
qyqy f2ac8a4460 docs: 交付说明按评审意见第二次修订;修同事带入的文档重号
评审意见逐条落地(详见文档新增的 §0 对照表):
- §1.1/§1.2/§4:确认两台机器连的**不是同一套 MySQL/Milvus**(我方 config_release 总共 4 条、
  最高 216;架构师侧 201 + 9 条白名单),据此把"216 已在共享库生效"整体改写为
  "**我方环境**已发布,你那台需补发";补发由架构师做(配置属环境数据,不随代码合并)
- §1.3:字段映射已改为运行时探测(见上一提交),文档里的 A/B/C 三选一整体替换为探测方案说明
- §1.4:mypy 那条改写——查清主因是**本机缺 SQLAlchemy 2.0 类型信息**(装 sqlalchemy2-stubs
  181→43,卸载回 184),明确"本机数字不可作为质量结论、两边不可比";我文件里的 8 个真实错误已修
- §2:明确 docs/26 是**重命名**(原 21,因 21 已被风控迁移清单占用),不是新增、不会并存
- §3.1:审计留痕已补(agent_type + governance_rewrite)
- §3.3:5 份文档删除**已撤回**(上一提交)
- §5/§7:更新验证数字(1218 passed)、两个环境相关失败的证据、memory_sync_outbox 双环境对照表

另修一处**同事那条线带入的重号**:docs/15-金融NL2SQL工具接入说明.md 与既有
docs/15-Agent组员详细开发与使用手册.md 撞号 → 新那份让号到 docs/27
(依据:手册被 docs/16、docs/17、AGENTS.md 三处引用,改名代价更大)。守卫恢复通过(32 份)。
2026-09-11 19:15:23 +08:00
qyqy 7677aeaee1 merge: 并入同事的场外申购/推广/行情/NL2SQL 线(11 提交、334 文件)
冲突仅 3 个文件,全部取并集(双方都没有需要丢弃的改动):
- app/main.py:import 双方路由(我方 knowledge_management + 同事的 offsite_fund/
  promotion_material);include_router 段本已自动合并
- app/service/agent/bootstrap.py:import 与工具注册均取并集
  (query_customer_profile + query_financial_data 都注册)
- tests/integration/test_config_release_mysql.py:outbox 清理同时保留
  架构师的 event_type 限定(防误删其它域 outbox 行)与同事新增的 peer_release_id

同事这轮带入:11 个 alembic 迁移(建 offsite_* / promotion_* 等表)、
场外申购与推广素材 Agent、financial NL2SQL 工具。
注意:本库尚无 offsite_*/promotion_* 表,跑相关测试前需要执行 alembic upgrade。

边界核对:同事的场外代码未写入场内交易表(fin_sim_order/fin_capital_flow/fin_cash_ledger),
符合 AGENTS.md 规则 8。
2026-09-11 18:56:26 +08:00
Windows 6757ca047d docs: record market data pipeline acceptance 2026-09-11 18:46:36 +08:00
qyqy 928d0bcea3 chore: 按评审恢复 5 份文档、审计补 agent_type、修订环境口径
评审意见落地(§3.3 驳回删除 / §3.1 审计补充 / §1.4 解释器 / §2 编号):
- docs/04、06、10、13、99 全部恢复(评审:删除收益为零、保留成本同样为零);
  AGENTS.md 改为"保留但仅作历史参考"并列入 D 类,ARCHIVE 归档说明加作废声明
- 治理审计补留痕:interaction_audit.detail 增加 agent_type 与 governance_rewrite
  (治理层会改写对外输出,事后必须能追溯到是哪个 Agent 触发的;不改表结构,detail 是 JSON 列)
  新增 tests/unit/service/test_agent_persistence_audit.py 锁住该契约
- AGENTS.md 修订环境口径:解释器各用本机可用的那个(.venv 被 gitignore、不进仓库);
  config_release 与 Milvus schema 均属环境数据、不随代码合并,相关结论必须带环境限定;
  测试基线 1034;mypy 数字双方不可比(本机未装 sqlalchemy2-stubs,报错集中在模型层)
- docs/26 JWT 文档:因 21 已被风控迁移清单占用而改名,PR 描述里会单独说明
2026-09-11 18:42:46 +08:00
Windows 930dd59d67 feat: complete advisory market data refresh pipeline 2026-09-11 18:39:57 +08:00
Windows 95633188f1 test: extend advisor e2e fixtures 2026-09-11 17:59:47 +08:00
yuancong_0626 3f7c5ca74f merge: 同步 origin/qyqy_develop(风控扫描、知识检索、客服 Agent、协商等)
冲突处理:均为双方各自新增,按并集保留——
- .gitignore:本地 data/logs 忽略项 + 远端 .dsh-drop/
- app/core/config.py:offsite/promotion 与 risk_scan 配置项并存
- app/service/agent/bootstrap.py:场外/推介/风控/NL2SQL/知识检索 工具与 Agent 全部注册
- app/worker/__main__.py:场外邮件 Worker 接线 + runtime 关系服务/投影清理注入并存

收尾:新增 20260911_merge_risk_heads 收敛迁移双 head;按 docs/21 生成 config/jwt/dev 开发密钥。
2026-09-11 17:32:47 +08:00
yuancong_0626 61861cdef6 袁聪merge:合并分支 2026-09-11 17:26:18 +08:00
Windows 3e4f21388c test: complete advisor full-stack acceptance record 2026-09-11 17:19:40 +08:00
yuancong_0626 fd9598efdf 袁聪的第二次提交,项目已完整 2026-09-11 16:57:47 +08:00
lzf_0626 927a6fecc0 评审 NL_develop 交付说明:三个环境前提必须先对齐
产出 docs/NL_develop交付说明-评审意见.md(可直接转给组员),以及一个只读探查工具
tools/probe_knowledge_collections.py(Milvus 集合 schema 与行数)。

核查中发现的硬矛盾,都有可复核的证据:

1. 配置库不是同一个。对方说 active=216、合并前生效版本是 186;我这边实测 active=201,
   最近 5 条 id 就是 201/198/197/196/195 —— release.id 自增,同库不可能一边 216 一边 201。
   所以他以为已发布的 customer_service:faq=[search_knowledge, query_customer_profile]
   在这边并不存在,而他的画像出口复用的正是这个 key ⇒ 合并后被 ToolExecutor 失败关闭。
   同理他说的 fund_query_demo:fund_quote 缺失,我这边是存在的。

2. Milvus 也不是同一个。对方说现库字段是 knowledge_id/snippet、无 visibility、行数
   106/177/73;我这边实测是 doc_id/content/chapter/section/.../visibility 共 15 个字段、
   行数 125/297/214,且与 knowledge_search_service.py 的 OUTPUT_FIELDS 逐字一致。
   他这次的字段映射改动(doc_id→knowledge_id)在这边会直接报 field knowledge_id not
   exist,把客服知识检索整条打挂 —— 比他自述的"关闭 visibility 隔离"严重得多。
   建议不是 A/B/C 三选一,而是第四种:运行时探测字段名,两套 schema 都能跑。

3. 解释器不同。他用 .venv(项目里不存在,那是他机器上的 gitignore 目录),约定是
   D:\conda\envs\jr_py313。所以"mypy 151→181"跑不到本基线 —— 这边是 138 文件 0 错。

另指出:docs/26-JWT密钥管理与轮换.md 会与已存在的 docs/21-JWT密钥管理与轮换.md 重复,
建议并入 21;驳回删除 docs/04/06/10/13/99。

四项待裁决的答复:同意 agent_type 方案(要求补审计);字段映射改为运行时探测;
驳回删除编号文档;26 并入 21。
2026-09-11 16:51:52 +08:00
Windows 852bafb374 feat: add profile drift governance workflow 2026-09-11 16:42:31 +08:00
lzf_0626 fcd3eaadd6 Merge origin/qyqy_develop:风控时间口径补齐 + Agent 截断提示
组员的 4 个提交(相对 c11e56c):

- 4b0c148 把我这边的风控 P3 收尾与适当性豁免额度合并进 qyqy_develop(PR #4)
- 2ebe438 docs: 增加风控 Agent 工具白名单与意图配置
- 5102eaa Merge origin/qyqy_develop into RM2_develop
- d2cdbba 修复风控时间口径与 Agent 截断提示

自动合并零冲突,两侧的改动是互补而非重叠:

- 时区:我在 list_alerts 做了 from_local,他补的是 list_evidence 与通知列表;
- 截断:我加了 data_truncated / evidence_truncated,他在 risk_agent 的 system prompt
  里加了第 10 条 —— 这些标记出现时不得按全量证据下结论。方向是一致的。

验证:ruff 干净 / mypy 138 文件 / 703 unit+contract(+6,组员新增的用例)/
33 integration,均在合并后的工作树上跑过。
2026-09-11 16:33:55 +08:00
lzf_0626 8b8883ccca Worker 失败原因不再只留类名:区分"可落库的固定文案"与"异常消息"
起因是查 Worker 运行状态时发现库里 373 条死信的 last_error 全是裸的 "ValueError"
(工具 tools/probe_worker_state.py,证据 docs/evidence/worker-state.json)。
dispatch(run not found)、dispatch_run_completed、dispatch_memory_extraction、
dispatch_profile_rebuild 抛的都是 ValueError,只记类名等于把"哪一处失败"也一起丢了。

但"直接存 str(exc)"是错的:tests/unit/worker/test_outbox_worker.py 那条
RuntimeError("credential=do-not-log") 断言异常消息不得落库 —— 它可能含凭据、SQL 或
客户标识。第一版改动就是这么写的,被这个测试当场拦下(这测试写得值)。

折中:

- 新增 OutboxHandlerError(继承 ValueError,这些失败本就是 ValueError 语义,保持
  继承关系才不会改动既有的 except ValueError 行为与断言)。它的 reason 由代码写死、
  不含任何请求数据,因此可以落库;
- safe_error_text:OutboxHandlerError → "类名: 固定文案"(截断 500 字符),
  其余异常 → 仍只记类名;
- runtime.py 的 5 处 handler 失败改抛 OutboxHandlerError。

测试:新增"固定文案落库"用例;并把既有用例的断言收紧为 last_error == "RuntimeError"
(原先只断言"不含 do-not-log",太松,漏掉的情况测不出来)。

顺带产出 tools/probe_worker_state.py(只读):outbox / agent_run 各状态计数、按事件
类型分组、死信原因聚合。当前环境实测 pending 347、dead 373、published 410、
agent_run 无 queued/running。

门禁:ruff 干净 / mypy 138 文件 / 697 unit+contract / 33 integration。
2026-09-11 16:28:19 +08:00
Windows 4088c634de feat: complete advisor conversation loop 2026-09-11 16:12:38 +08:00
qyqy e342670130 docs: 修正交付说明的净差异计数(新增本文档自身后 92→93、新增 50→51) 2026-09-11 16:09:16 +08:00
qyqy ac0b973006 docs: 新增给架构师的交付说明(PR 评审用)
- 与'接手 AI 交接文档'定位区分:这份讲'动了架构师什么、为什么、要不要他点头'
- 逐文件列出对其 7 个核心文件的改动与理由(base.py 协议变更、knowledge_search_service
  字段映射的三选一裁决、governance 免责声明范围、model_gateway 映射并入、bootstrap/runtime 并集)
- 明示 6 项待裁决/知晓事项(含 5 份文档删除、config_release 216 已生效、fund_query_demo 白名单缺失)
- 证据段附可复现命令与真机结果;mypy 181 的错数按'谁引入'拆分为 170/8/3
- 已知限制 4 条(memory_sync_outbox 无消费者、mypy 归属、Redis 降级、文档删除的连带说明)
2026-09-11 15:59:29 +08:00
qyqy 96a6e01634 docs: 交接文档与 AGENTS.md 同步到合并后状态
- 交接文档 §0/§5/§6 重写:分支改为 NL_develop、测试基线 1013、已知问题逐条标注当前状态、
  Git 段改为已推送 + PR 注意事项(5 份文档删除/26 号新增/字段映射三处跨分支决策)
- AGENTS.md 入口指向与基线数字同步:工具名改 search_knowledge、生效版本 id=216、
  测试 1013、mypy 181(含归属说明)、新增 Agent 与文档编号现状
2026-09-11 15:41:49 +08:00
qyqy fb7d2f7b6d merge: 跟进架构师最新 qyqy_develop(38 提交)
- model_gateway 冲突取并集:保留本线对 intent_classification→chat 的修正与空集回退,
  并入架构师补充的 5 个风控 task_type;未登记 task_type 的告警留痕一并保留
- docs/25 撞号(本人 JWT 文档 vs 架构师风控评审报告)→ 本人让号到 docs/26,同步 docs/19 引用
- 架构师恢复的 5 份编号文档(04/06/10/13/99)保留其版本(那 5 份已无引用,仅编号占位)
- bootstrap/model_gateway/docs/05/test_risk_agent_contract 自动合并成功

测试:1013 passed / 1 failed(既有空集缺陷)
真机:知识问答 succeeded 且声明仅 1 条;画像问答 succeeded;三端点 403/201/200/404 全绿
配置:release 216 仍为生效版本,工具白名单未被顶掉
2026-09-11 15:40:32 +08:00
qyqy cbd6de2721 Merge remote-tracking branch 'origin/qyqy_develop' into nl-merge-latest
# Conflicts:
#	app/service/model_gateway.py
2026-09-11 15:38:11 +08:00
Windows e8ec0af398 feat: add advisor recommendation review flow 2026-09-11 15:33:27 +08:00
zhangshy d2cdbbac01 修复风控时间口径与Agent截断提示 2026-09-11 15:25:45 +08:00
Windows 13bb4f257d feat: add allocation backtest evidence 2026-09-11 15:08:37 +08:00
qyqy 57c4add7d8 merge: 客服Agent+RAG+画像 与 架构师最新 qyqy_develop 合并
- customer_service.py 以架构师实现为骨架(三档置信/适当性/会话记忆/话题矩阵),嫁接本人画像出口
- 知识检索契约合并两条链路:架构师 search_knowledge(KnowledgeSearchInput) + 本线
  query_knowledge 链路所需常量(ALLOWED_CONSTANTS/VECTOR_DIM/intent_for_qa_id)
- bootstrap 保留架构师 6 工具/3 Agent,补回 query_customer_profile 与 get_milvus_knowledge_writer
- model_gateway 能力映射修正 intent_classification→chat,保留空集回退兜底
- governance 免责声明限定面向客户 Agent(agent_type 由定义透传),风控结构化输出不再被追加
- 修 JWT 密钥路径(config/jwt/dev)、文档 21 号撞号→25
- 测试基线 934 passed / 1 failed(既有空集缺陷)
2026-09-11 15:06:44 +08:00
zhangshy 5102eaac64 Merge remote-tracking branch 'origin/qyqy_develop' into RM2_develop 2026-09-11 15:06:42 +08:00
zhangshy 2ebe438ba5 docs: 增加风控Agent工具白名单与意图配置 2026-09-11 15:06:06 +08:00
lzf_0626 575b4c2baa 客服适当性改按第十二条匹配矩阵(更正我上一轮的复核结论)
你说得对:C ≥ R 是风控的口径,客服要走政策原文的矩阵。

上一轮我复核后说"两边一致、无需改动"是错的 —— 我只对比了政策文本与
SuitabilityService,漏了第三个东西:客服自己的知识库。POL-AST-012 就是第十二条矩阵
原文,而客服为 C1-C5 补的「能买什么产品」问答答案直接取自该矩阵(docs/24 第二节)。
于是同一个客服 Agent 对同一个问题给出相反答案:

  问"C1 能买什么产品"   → 知识检索答 R1、R2 可买(矩阵口径)
  问"C1 能买这只 R2 吗" → 适当性出口答不能购买(严格 C ≥ R)

政策冲突的精确位置也不是"矩阵 vs 硬匹配",而是第十四条**第 1 款与它自己的第 2、3 款**
矛盾:第 2 款说"低于一个等级以上"才拒绝(C1→R2 只低 1 级,够不上"以上"),第 3 款禁止
C1 买 R3+、C2 买 R4+(R2 不在禁止列表)。矩阵与第 2、3 款三方一致,孤立的是第 1 款。

改动:

- suitability_service.py 新增 MATRIX_ALLOWED / MATRIX_NEEDS_DISCLOSURE,_decide 由
  "C < R 即拒绝"改为按矩阵:C1→R2、C2→R3 直接可买;C3→R4、C4→R5 走第十五条豁免档
  (reason_code=SUITABLE_WITH_DISCLOSURE,强制揭示 + 确认 + 录音);低两级及以上仍拒绝。
- 客服话术分档:越级档不再说"在您的风险承受能力范围内" —— 那句只对 C ≥ R 成立,
  用在 C1 买 R2 上会让客户以为自己的测评本来就覆盖这只产品。
- 风控侧不动,保留 C ≥ R:它要发现的是"越级成交且留痕不全",这个差异是有意保留的。

测试:新增逐格对照政策原文的 25 格矩阵用例、豁免档用例、话术分档用例(+29)。
docs/25 第七节 #1 与 docs/24 第七节同步更正,包括写明我上一轮那个结论错在哪。

门禁:ruff 干净 / mypy 137 文件 / 693 unit+contract / 33 integration。
2026-09-11 14:47:08 +08:00