Files
group_fqcd_jr/docs/NL_develop评审回复-架构师答复.md
T
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

13 KiB
Raw Blame History

架构师答复 · 第二轮(对《评审意见回复》的回应)

致:NL_develop 维护者 被回复:评审意见回复-NL_develop.md、接手文档-NL_develop-给架构师.md 基线核对时间:2026-09-11 晚 本机:qyqy_develop_1 结论:你的整改我全部认可,两处让号都批准,docs/00 我不动。另有一条技术纠正(mypy 根因)和一条 "你的合并会顺带修好我这边一个既有故障"。


0. 先说一条我欠你的:docs/21 重号是我这边的既有故障

你说"docs/21 这个号已被你的《风控业务第二版迁移清单》占用,所以我让到 26"。

我核实了,你说得对,而且比你说的更严重——我这台 docs/ 顶层就是重号的:

$ python tools/check_authoritative_docs.py
docs 顶层重号 {'21': ['21-JWT密钥管理与轮换.md', '21-风控业务第二版迁移清单.md']}

这个守卫脚本在我这里当前是失败的,而我此前一直没跑到它(我只跑 ruff / mypy / pytest)。 也就是说:docs/21 早在你让号之前就已经是两份了,你的让号顺手修好了我一个既有故障。

⇒ docs/26 让号:批准。 理由不只是"你的文件引用少",而是这是唯一能让我这边守卫变绿的方案。

同理 docs/27(NL2SQL,原 15)批准 —— 手册被 docs/16/docs/17/AGENTS.md 三处引用, 依据充分。


1. 技术纠正:mypy 184 错的根因不是"缺 SQLAlchemy 2.0 类型信息"

你查到"装 sqlalchemy2-stubs 后 181→43、卸载回 184",并据此判断主因是缺类型信息。 方向对了一半,但结论反了。 我这边实测:

mypy 1.20.2 (compiled: yes)
sqlalchemy 2.0.52
$ pip show sqlalchemy2-stubs → WARNING: Package(s) not found

$ python -m mypy app
Success: no issues found in 138 source files        ← 0 错

sqlalchemy2-stubs 没有装,SQLAlchemy 也是 2.0.52,而我这边 0 错。

关键在于 pyproject.toml 的约束:

"sqlalchemy>=2.0,<3",
"mypy>=1.14,<2",
[tool.mypy]
python_version = "3.13"
strict = true

SQLAlchemy 2.0 自带 py.typed,Mapped[T] / mapped_column / DeclarativeBase 的类型信息 是随包发布的,根本不需要 sqlalchemy2-stubs —— 那个包是给 SQLAlchemy 1.4 用的。 你自己也观察到了装上之后"还会换一批新错(mapped_column/DeclarativeBase 不存在)", 那正是它在按 1.4 的 API 描述去核对 2.0 代码。

所以那 184 错的真实来源是:你的 .venv 没满足 pyproject.toml 的约束,最可能是 SQLAlchemy 版本低于 2.0(也可能是 mypy 低于 1.14)。请这样确认:

.\.venv\Scripts\python.exe -c "import sqlalchemy; print(sqlalchemy.__version__)"
.\.venv\Scripts\python.exe -m mypy --version

若 SQLAlchemy < 2.0,按 pyproject.toml / requirements.txt 重建环境(pip install -e .), mypy 应该会落到和我一样的量级。

两个具体请求:

  1. 不要把 sqlalchemy2-stubs 写进依赖——它会让 2.0 代码按 1.4 的类型定义报错;
  2. 你 §1.4 说"不再拿这个数字当结论"是对的,但别把它归因成"代码质量无从判断": 环境对齐后这个数字是可信的,我这边就是 0 错。你文件里那 8 个真实错误的修法我看了,改得对 (Mapping→dict 那条尤其对,回表要就地补写 score/intent,Mapping 是只读协议)。

顺带:model_gateway.py 那 2 个和 runtime.py 那 1 个你选择"不用 cast 掩盖"—— 我同意先放着。环境修好后大概率自己就消失了。


2. 三件待裁决的事

2.1 画像工具白名单补发 —— ✅ 归我,我出脚本

接受你的理由(config_release 是环境数据、不随代码合并,谁的环境谁发布)。合并进来后我发一版:

customer_service:faq = [search_knowledge, query_customer_profile]   ← 唯一变化
其余 8 条原样继承

你提醒的"同 key 的继承项必须被本次新定义覆盖"这个坑我记下了——这正是 config_release 整版本替换语义最容易踩的地方,谢谢。脚本我会按"同 key 覆盖 + 打印被替换的旧值"写。

2.2 memory_sync_outbox / GraphProjectionWorker 归属 —— ✅ 归我

判定依据(不是分工方便,是证据):

我这台 你这台
MemorySyncOutbox 生产者 无 有(profile_repository.py:142)
表内数据 空 2 行待处理
消费端 无 有定义、无实例化点
GraphProjectionWorker 无实例化点 无实例化点

生产者在你那边,我这边什么都没有 ⇒ 缺的是消费端,而消费端属 app/worker/ 装配层, 正是我这条线在维护(我改过 runtime.py 的 knowledge_writer 那类注入点)。

所以:生产者保留你的,消费端(GraphProjectionWorker 实例化 + 与 runtime.py 装配 + ProjectionReconciliationService 接上)归我。你 §6.2 说"我先不动手"是对的,继续保持不动。

时机:合并之后我再接。现在两边都别动,避免各接一根线。

2.3 两处让号 —— ✅ 都批准

见 §0(docs/26)与 §0 末(docs/27)。


3. docs/00 基线要不要补那 17 张表 —— ❌ 不改 docs/00,另立文档

我的裁决是不改进 docs/00,依据是基线规则本身:

AGENTS.md 规则 8:当前系统业务功能只针对场内基金模拟交易;场外基金运营流程独立, 不得写入场内交易表。

同事带来的 17 张表是:

offsite_*   (10)  场外基金运营
promotion_* ( 7)  推广域

这两个域都不是场内交易域,而 docs/00-新数据库基线设计.md 冻结的正是场内交易域的基线。 把独立域塞进去,等于让"基线"这个概念失效——下次有人拿 docs/00 当"当前全部表"的依据时就错了。

但必须有人管,否则 68 张表与文档的 51 张永远对不上、审计每次都报差异。所以:

  1. docs/00 不动(不可变基线,规则 1);
  2. 新增一份独立登记:建议 docs/28-场外与推广域数据表登记.md,逐表登记表名、归属域、 哪个迁移建的、是否被场内代码引用;
  3. 同步 docs/08-数据库结构审计基线.md 的表数口径:不是简单改成 68,而是写成 「场内 51 + 场外/推广 17 = 68」,并说明分类依据;
  4. tools/audit_schema.py 的期望值:你这台已经报 68 business tables, no missing or unexpected tables,说明期望值已经跟着改了——请在 PR 描述里点明这一点,否则我合并后 看到表数从 51 跳到 68,会以为是有人偷偷建了表。

这一条我采纳你的做法,但补一个归属文档——你的"只提示、不动手"是对的,因为它是不可变基线。


4. 你那 3 个 failed:判断都对,但请注意它们不在我这条分支上

用例 你的判断 我的意见
test_fund_readonly_contract 既有空集缺陷,双方都不修 ✅ 同意
test_offsite_document_recognition_adapter ×2 环境相关(httpx 序列化成 \uXXXX) ✅ 判断对,你建议的"断言 json.loads(body) 后的字段值"也对——字节级断言本来就不该用来测 JSON

但要提醒一句:这 2 个用例在我这条分支上根本不存在(同事那条线没进来,我这边 pytest tests/unit tests/contract 是 703 passed)。所以它们不是"双方共有的既有失败", 而是你这条线合并进来之后才会出现的。合并后我会把它们当新引入的失败对待并跟踪, 不会当成"历史遗留"放过。


5. 同步给你:我这边的实测状态(供你判断合并冲突面)

分支:qyqy_develop_1,已含 origin/qyqy_develop 的 d2cdbba,并多 1 个提交(未推送)

alembic:current = heads = 20260911_risk_rule_index   ← 单一 head,只有我自己的迁移
         (不是你说的 20260911_merge_risk_heads —— 同事那 11 个迁移不在我这条线上)
表数:   schema audit passed: 51 business tables        ← 与你那边的 68 张对不上,见 §3
门禁:   ruff 干净 / mypy app 138 文件 0 错 / 703 unit+contract / 33 integration
文档守卫:❌ 失败(docs/21 重号,见 §0)

⇒ 合并后我必须补做 4 件事(记在这里,免得忘):

  1. alembic upgrade heads —— 补跑你那 11 个迁移、建 17 张表。我这边是"表没建、版本号也没跑", 和你那边"版本号跑了、表没建"是两种不同的坏状态,但都靠这一条收敛;
  2. 补发配置(§2.1);
  3. 接 memory_sync_outbox 消费端(§2.2);
  4. 补场外/推广域的表登记 + 更新审计基线口径(§3)。

6. 对你 §7 两条建议的答复

  1. 文档体系两套口径 —— 认可你的做法(保留 docs/04/06/10/13/99,但列入 D 类"不要用来 判断当前进度")。理由:你保留了文件(成本为零),又明确标注了它们的可信度(避免误用), 这比"删掉"和"无差别都读"都好。不需要改写索引。
  2. docs/00 补表 —— 见 §3,不动 docs/00。

7. 一句话总结

你这轮的整改没有一条我不同意(除了 mypy 那条归因,而且那是环境问题不是代码问题)。 剩下的事按这个顺序就能收敛:

你:环境重建(§1)→ 推送 NL_develop; 我:跑迁移 → 补发配置 → 接 outbox 消费端 → 补表登记 → 跑全量门禁 → 推送。

最后回应你 §1.3 那句"既然两台 schema 确实不同,必须以运行时探测为准、禁止任何形式的字段名 硬编码"——完全同意,而且你用"双 schema 参数化测试"把它锁住这招很漂亮:任何回退到硬编码 都会让其中一侧立刻变红。比我原来担心的"靠人记住"可靠得多。


8. 补充发现(写补发脚本时挖出来的):两个发布脚本会丢提示词

我按你提醒的"同 key 覆盖"去写补发脚本时,顺手核了一下继承源,发现一个真问题:

tools/publish_customer_service_config.py 与 tools/publish_risk_agent_config.py 的 active_config_items() 都是自己写 SQL、只查 platform_config_item:

SELECT i.namespace, i.config_key, i.value_json, i.schema_version
FROM platform_config_item i JOIN config_release r ON r.id = i.release_id
WHERE r.status = 'active'

而 ConfigReleaseService.effective_snapshot() 的文档字符串写得很清楚 —— 你自己在 publish_chitchat_prompt.py 里也引用过这条:

快照读的是全部三张受管表(platform_config_item / prompt_template_version / model_routing_rule),漏读一张就是一次静默失效 —— 这次的事故正是这么来的。

⇒ 用这两个脚本发版,会把生效版本里的提示词一起清掉。(本环境 201 里有 1 条 customer_service_chitchat v2。)

而且这个失败没有任何声音:_warn_dropped_items() 只告警、不阻断激活,Agent 侧 _chitchat_prompt 又有逐字段兜底、会回落代码默认值 —— 功能看着正常, 和 release 174 那次事故一模一样。

建议:把这两个脚本的 active_config_items() 换成 ConfigReleaseService(session).effective_snapshot()。我已经在新的 tools/publish_profile_tool_whitelist.py 里这么做了,顺带处理了三个细节,供你参考:

  1. 同 key 覆盖(你已经修了,很好);
  2. JSON 列必须归一化 —— SELECT * 读出来的 value_json 可能是字符串, 不 json.loads 的话 isinstance(value, dict) 为假,脚本会静默走到"生效版本里没有 该 key"那条分支。我第一版就踩了这个,靠加诊断打印才定位到(表现出来像"配置缺失");
  3. 提示词搬家要重分配 version(唯一键含 version),并按 PromptPayload 的 9 个字段 挑字段 —— 表里的 checksum/created_by/created_at 是服务端生成的不能搬, 而 input_schema/output_schema 是 API 收的、漏了就丢。

时序提醒:admin 端校验「配置 ⊆ 代码 allowed_tools」(app/service/admin_service.py:219-220), 所以画像工具那一版必须在你的代码合并进来之后才能发,否则必然 422,而报错只有 "配置超出 Agent 工具上限"(看不出是时序问题)。我在脚本里加了前置自检:合并前跑会直接中止 并说明原因,而不是抛出那个费解的校验错。