Files
group_fqcd_jr/docs/接手文档-NL_develop-给架构师.md
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

253 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 接手文档 · `NL_develop` 这条线(给架构师)
> **交接对象**:`qyqy_develop` 维护者
> **交接时点**:2026-09-11 晚 **分支状态**:`origin/NL_develop` = `c6a52a3`
> **与你的关系**:本分支**已包含你 `qyqy_develop` 的全部提交**(`3f7c5ca`,落后 0)
> **本文用途**:你要接着维护/合并这条线时,**先读这一份就够**——环境口径、当前状态、
> 必须知道的 5 个坑、待你裁决的 3 件事。与另外两份文档的分工见 §9。
>
> 📌 配套文档:
> - 要 review 我的改动 → `docs/交付说明-NL_develop-给架构师.md`
> - 要对照你的评审意见 → `docs/评审意见回复-NL_develop.md`
> - 给下一位开发者(接手继续写代码)→ `docs/superpowers/handoff/2026-09-11-交接文档-客服Agent与RAG收尾.md`
---
## 1. 三条最要紧的结论(先看这个)
### ① 你这台和我这台**不是同一套环境**(已核实,不是配置没对齐)
| | 我这台 | 你那台 |
|---|---|---|
| `config_release` | **总共 4 条**,最高 id=216(我发的) | 最高 201 + 9 条 `agent_tools` |
| Milvus 标识字段 | `knowledge_id` / `snippet` | `doc_id` / `content` |
| Milvus 可见性字段 | **无** | `visibility`(过滤正常工作) |
| Milvus 行数 | 106 / 177 / 73 | 125 / 297 / 214 |
**⇒ 由此推出的两条硬纪律(已写进 `AGENTS.md`)**:
1. **"配置已发布""数据是某 schema"这类结论必须带环境限定**——`config_release` 与 Milvus 数据
**都是环境数据,不随代码合并**。
2. **禁止硬编码 Milvus 字段名**——检索层已改为运行时探测(`app/core/knowledge_schema.py`),
候选表在 `FIELD_CANDIDATES`。任何硬编码都会打挂另一套环境。
### ② 本库表数已从 **51 张变成 68 张业务表**,而文档没更新 ⚠️
同事(袁聪)那条线带进 **11 个 alembic 迁移**,新建 **17 张表**(我方已执行 `alembic upgrade heads`):
```
offsite_* (10) offsite_fund_mail / offsite_fund_attachment / offsite_fund_document /
offsite_execution_plan_task / offsite_rule_result / offsite_query_record /
offsite_notification / offsite_mail_cursor / offsite_recognition_attempt /
offsite_field_correction
promotion_*( 7) promotion_attachment / promotion_compliance_check / promotion_delivery_record /
promotion_input_snapshot / promotion_material_task / promotion_material_version /
promotion_review_record
```
- `tools/audit_schema.py` 现在报 **68 business tables**(原来 51)
- **但 `docs/00` 基线与 `docs/02` 建表设计都还是 51 张** —— 你第二轮的裁决是:
**不动 `docs/00`**(它冻结的是场内交易域,把独立域塞进去会让"基线"概念失效),
改为**另立登记文档**。我方已按此落地:新增 **`docs/28-场外与推广域数据表登记.md`**
(17 张表逐表登记来源迁移/归属域/跨域引用核对),并同步 `docs/08` 的审计口径为
「场内 51 + 场外/推广 17 = 68」。
- ⚠️ **表数跳变不是"有人偷偷建表"**:`audit_schema.py` 的期望集合是**动态推导**的
(读 `baseline_generated.sql` + 扫描 `alembic/versions/*.py` 的 `CREATE TABLE`),
**随迁移自动增长、没人手工改过**。代价是只增不减:将来若有迁移 DROP 表,
期望集合仍含它、会报 `missing` 假失败(详见 `docs/28` §5)。
- 你的仓库若也是"`alembic_version` 指向同事 revision、但表没建"的状态,**合并后需补跑迁移**。
### ③ 测试基线不是"全绿",但 3 个失败都**不是代码缺陷**
```
pytest -q → 3 failed, 1218 passed, 2 skipped
1. tests/unit/repository/test_fund_readonly_contract.py
→ 既有的"空集缺陷"(历史遗留,双方都不要修也不要报)
2. tests/unit/service/test_offsite_document_recognition_adapter.py(2 个用例)
→ 环境相关:断言要求请求体里是**中文原文**,而本机 httpx 序列化成 \uXXXX
→ 证据①该文件与 origin/qyqy_develop 逐字节相同(非我方改动)
证据②本机 .pytest_cache 的 lastfailed 早已记录这两个用例
→ 建议改为断言 json.loads(body) 后的字段值,比字节级断言稳
```
---
## 2. 环境口径(照抄,别创新)
| 项 | 值 / 说明 |
|---|---|
| 解释器 | 你这台用 `D:\conda\envs\jr_py313\python.exe`;我这台用 `.\.venv\Scripts\python.exe`。**`.venv` 被 .gitignore 忽略、不进仓库**,不存在"需要统一"的仓库状态 |
| MySQL | `127.0.0.1:3306` / 库 `jr_agent`(**两台机器各自的库**) |
| Redis | `127.0.0.1:6379`。我这台容器以 `--requirepass 123456` 启动而 `.env` 无密码 ⇒ 每个请求打一条 `AuthenticationError` 堆栈、限流降级放行(**不阻断业务**,但日志噪声大) |
| Milvus | `http://localhost:19530`(只监听 IPv6,写 `127.0.0.1` 可能连不上);容器 `milvus-standalone` |
| ⚠️ Docker Desktop | **不常驻**:它没运行时 Milvus 不可用(`docker` CLI 报连不上守护进程)。跑真机验证前先确认它在运行 —— 我这次就被它挡过一次 |
| 迁移 | `python -m alembic upgrade heads`;当前我方=head `20260911_merge_risk_heads` |
| mypy | ✅ **已收敛到与你同量级:mypy 1.20.2 + SQLAlchemy 2.0.52 → 3 个错(180 文件)**。此前 184 的真因是**我的环境版本旧**,不是"缺类型存根"——你在第二轮答复里纠正过:SQLAlchemy 2.0 **自带 `py.typed`**,不需要 `sqlalchemy2-stubs`(那是 1.4 用的)。教训与实测矩阵见 §10 |
### 环境版本教训(第二轮答复纠正后的复现矩阵)
**症状**:同一份代码,`mypy app` 在我这台报 184 个错、架构师那台 0 错。
**我最初的归因是错的**("缺 SQLAlchemy 2.0 类型信息")。实测矩阵:
| 组合 | 报错数 |
|---|---|
| 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):旧补丁版的类型标注不完整,
`BIGINT`/`DATETIME` 被判成未类型化函数,于是 `app/model/*.py` 每个列定义都报一条。
mypy 版本是次因(6 → 3)。
**两个反面教训(写在这里免得别人重走)**:
1. **`sqlalchemy2-stubs` 不能装** —— 它是给 SQLAlchemy **1.4** 用的。装上后 181→43
看着"变好了",实际是换了一批按 1.4 API 核对产生的错(`mapped_column`/`DeclarativeBase`
不存在)。我当时把它当成"缺存根"的证据,**方向对了一半、结论反了**。
2. **`pyproject.toml` 的 `sqlalchemy>=2.0,<3` 允许的范围内,补丁版差异会造成量级差异**。
若希望门禁数字稳定,需要把 SQLAlchemy 钉到具体补丁版(属公共约定,**我没擅自改**)。
---
## 3. 分支与提交
```
origin/NL_develop = c6a52a3(本地一致,工作区干净)
c6a52a3 docs: 新增《评审意见回复》
f2ac8a4 docs: 交付说明第二次修订;修同事带入的文档重号
8cff6b3 fix(tests): tmp_path 落点改到仓库内(33 个 setup ERROR → 0)
636dcbb chore: 修我文件里的 mypy 类型错误(8 处)
7677aea merge: 并入同事的场外申购/推广/行情/NL2SQL 线(11 提交、334 文件)
928d0bc chore: 按评审恢复 5 份文档、审计补 agent_type、修订环境口径
5f82ac5 feat(knowledge): 检索字段名改为运行时探测(两套 schema 都能跑)
e342670 docs: 交付说明计数修正
…
e4c4099 wip: 客服Agent + RAG + 画像收尾(代码主体,82 文件 / +13902 行)
```
**本地备份分支**(可回退到"按评审整改之前"的状态):
`NL-backup-20260911` = `e342670`
**另一条更早的工作分支**(上一轮"第二次跟进合并"的中间态,已被取代):
`nl-merge-latest` = `928d0bc`(可删)
---
## 4. 这条线交付了什么(一句话清单)
| 交付 | 位置 | 状态 |
|---|---|---|
**画像问答出口**(你原实现里没有) | `customer_profile_service.py` + `customer_service.py` 的"出口零" | 真机通过 |
**知识库文档管理三端点**(老师验收第 7 条) | `api/controllers/knowledge_management.py` | 真机 403/201/200/404 全绿 |
**合规语境豁免**(恢复政策问答) | `app/core/compliance_context.py` | 17 个单测 |
**知识向量链路**(入库→同步→检索) | `knowledge_ingest_service` / `knowledge_vector_worker` / `milvus_knowledge_writer` | 真机通过 |
**检索字段运行时探测**(按你 §1.3) | `app/core/knowledge_schema.py` | 双 schema 参数化测试 |
**治理审计留痕**(按你 §3.1) | `interaction_audit.detail` 加 `agent_type` + `governance_rewrite` | 真机验证 |
**同 key 覆盖修复**(发布脚本) | `tools/publish_customer_service_config.py` | 你会用到 |
**测试临时目录修复** | `tests/conftest.py` 覆盖 `tmp_path` | 33 错→0 |
---
## 5. 必须知道的 5 个坑(我踩过的)
1. **Milvus 删除/写入有读一致性延迟** —— 删除后的断言**必须轮询**。我早期只查一次,
得到过假的"删除失败"结论。
2. **Milvus VARCHAR 上限是 UTF-8 字节**(中文 3 字节/字)—— 写入前必须按字节截断,
否则一条超长 `title` 会让整批失败(`knowledge_vector_worker` 里已修)。
3. **`config_release` 是"整版本替换"语义** —— 发新版会清空旧版所有配置项;
且**同 key 的继承项必须被本次定义覆盖**,否则旧值会被 admin 端子集校验 422 拦下整次发布,
报错只说"配置超出 Agent 工具上限",看不出是继承造成的。
4. **两个 outbox 不能混** —— `domain_event_outbox`(领域事件)与 `memory_sync_outbox`(画像同步)
语义与消费者都不同。
5. **`interaction_audit` 没有 `agent_type` 列** —— 我用 `detail` JSON 键承载,
**没有改表结构**(遵守规则 4)。若你希望有独立列,那是一次迁移。
---
## 6. 待你裁决的 3 件事
### 6.1 画像工具白名单补发 —— **由你来做**(你已提议出脚本)
你那台 201 的 `customer_service:faq` 只有 `["search_knowledge"]`,合并后画像出口会
`AGENT_PERMISSION_DENIED`。需补发一版:
```
customer_service:faq = [search_knowledge, query_customer_profile] ← 唯一变化
其余 8 条原样继承(整版本替换语义,漏带会清空别人的白名单)
```
配置属环境数据,谁的环境谁发布 —— 我这边**不再重发**,避免两边各改一版。
### 6.2 `memory_sync_outbox` / `GraphProjectionWorker` 的接线归属 —— **我先不动手**
| | 你那台 | 我这台(实测) |
|---|---|---|
| `MemorySyncOutbox` 生产者 | 无 | **有**:`profile_repository.py:142` |
| 表内数据 | 空 | **2 行待处理**(MILVUS/NEO4J 各 1) |
| 消费端 | 无 | 有服务定义 `ProjectionReconciliationService`,**但全仓无实例化点** |
| `GraphProjectionWorker` | **无实例化点**(你发现) | **无**(我复核一致) |
**同一类问题:组件写好了、线没接。** 涉及 `app/worker/` 与 `app/service/profile_*`,
跨你我两条线 —— **请先定归属**,否则两边各接一根线会更乱。
### 6.3 两处文档让号是否认可
| 让号对象 | 原号 | 新号 | 依据 |
|---|---|---|---|
| JWT 密钥管理与轮换 | `21` | **`26`** | `21` 已被你的《风控业务第二版迁移清单》占用 |
| 金融NL2SQL工具接入说明 | `15` | **`27`** | `15` 已被《Agent组员详细开发与使用手册》占用,而手册被 `docs/16`/`17`/`AGENTS.md` 三处引用 |
若你认为该由另一方让号,我改(守卫已通过:34 份无重号)。
---
## 7. 还有两件建议你拍板的事
1. **文档体系的两套口径**:`AGENTS.md` 的「接手先读」是按"**清理后只读正确的**"设计的
(`docs/04`/`06`/`10`/`13`/`99` 我按你要求保留了,但列进了 D 类"不要用来判断当前进度")。
若团队更希望"全部保留、都读",这套索引需要相应改写。
2. **`docs/00` 基线要不要补那 17 张新表**(见 §1②)—— 涉及不可变基线,我只提示、不动手。
---
## 8. 快速自检(接手后先跑这几条)
```powershell
# 1) 测试(期望 3 failed / 1218 passed;那 3 个见 §1③,都不是代码缺陷)
.\.venv\Scripts\python.exe -m pytest -q
# 2) 结构审计(期望 68 business tables —— 与文档里的 51 张不一致,见 §1②)
.\.venv\Scripts\python.exe tools\audit_schema.py
# 3) 文档守卫(期望 34 documents, no number collision)
.\.venv\Scripts\python.exe tools\check_authoritative_docs.py
# 4) 迁移状态(期望 head = 20260911_merge_risk_heads)
.\.venv\Scripts\python.exe -m alembic current
# 5) Milvus 是否在(Docker Desktop 必须先启动)
docker ps --filter name=milvus
```
> 注:上面命令用的是**我这边**的解释器路径。你那台把 `.\.venv\Scripts\python.exe`
> 换成 `D:\conda\envs\jr_py313\python.exe` 即可,其余完全一致。
---
## 9. 三份文档的分工(别读串了)
| 文档 | 读者 | 回答什么问题 |
|---|---|---|
**本文** | **你(接手维护)** | 起点在哪、有哪些坑、哪些待裁决 |
`docs/交付说明-NL_develop-给架构师.md`(443 行) | 你(review 用) | 我动了你什么、为什么、证据 |
`docs/评审意见回复-NL_develop.md`(251 行) | 你(对照评审意见) | 你提的每条我是怎么处理的 |
`docs/superpowers/handoff/2026-09-11-交接文档-客服Agent与RAG收尾.md` | 下一位开发者 | 怎么继续写代码(环境、命令、模块) |
---
*本文所有数字均为 2026-09-11 实测。若与代码不一致,**以代码与 `docs/05` 接口文档为准**。*