2026-09-11 19:32:19 +08:00
|
|
|
|
# 接手文档 · `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)
|
2026-09-11 20:00:56 +08:00
|
|
|
|
- **但 `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)。
|
2026-09-11 19:32:19 +08:00
|
|
|
|
- 你的仓库若也是"`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` |
|
2026-09-11 20:00:56 +08:00
|
|
|
|
| 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 钉到具体补丁版(属公共约定,**我没擅自改**)。
|
2026-09-11 19:32:19 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 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` 接口文档为准**。*
|