Files
group_fqcd_jr/docs/superpowers/specs/2026-09-10-knowledge-retrieval-infra-design.md
T
lzf_0626 36c7a9d8d2 文档:审查报告入库 + 全量校对补注
## 新入库(`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 个文件**,只抽样确认了改动性质与规模。若其中有需要复核的段落,
请指明文件,我逐处核对。
2026-09-14 20:36:00 +08:00

292 lines
24 KiB
Markdown
Raw 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.
# 设计文档:客服知识检索基础设施(子项目 A)
> 🗂 **过程产物 · 结论已归档**(2026-09-14 批注)
> 本设计**已实现并闭环**(检索在 Phase 1 验收中实测命中 score 0.7837)。
> ⚠️ 实现期有一处偏离本文:Milvus 集合字段名**改为运行时探测**(`app/core/knowledge_schema.py`),
> 不再硬编码 —— 因为两套环境的 schema 不同。详见 `docs/18-知识检索接入方案.md` §4.2 的 ⚠️ 块。
> **判断当前进度请看 `docs/验收与审计/phase1-acceptance-report.md`**。
> 状态:待用户审阅
> 日期:2026-09-10
> 依赖关系:本文档是子项目 B(客服 Agent 本体)的前置依赖,须先完成本文档再启动 B。
> 权威来源优先级:本文档在"检索路径"上完全采纳桌面《15-知识检索接入方案.md》(架构负责人已批准的方案,不重新设计);在"知识内容与安全边界"上采纳桌面《胜宇前期开发资料》文件夹内的
> 《客服Agent一期运行边界基线_v1.md》《客服Agent一期向量化前流程文档_v1.md》
> 《客服Agent知识库_QA问答对_v5_RAG发布候选版.txt》(v5.8,105 条)。
> `智能财富管家系统-业务与技术全景设计说明书.md`、`智能财富管家系统-完整项目流程文档.md`
> 两份文档确认为更早期的整体项目背景资料(不同技术栈),仅作背景参考,不作为本设计依据。
## 1. 背景与目标
客服 Agent 需要一条"检索知识库回答问题"的链路。现状:
- `fin_knowledge_meta`/`agent_faq_synonym` 两张表存在于数据库基线,但代码里没有任何 ORM、Repository 或 Service 使用它们。
- `KnowledgeReferenceService.resolve()` 是无条件抛异常的占位代码。
- `ModelGateway`/`ModelGenerationService` 只有文本生成能力,没有向量生成(embedding)能力。
- Milvus 服务本身可连通(健康检查通过),但没有任何集合,也没有任何写入或检索它的代码。
- 客服知识内容已经由业务方(胜宇)审核完毕,固化在 `客服Agent知识库_QA问答对_v5_RAG发布候选版.txt`(v5.8,105 条),文件本身声明状态为 `approved_candidate`,可直接作为向量化语料,**不需要在系统里另外实现一套"草稿→审核→发布"的在线知识库管理后台**——这是本轮讨论中被推翻的早期方向,纠正记录见第 8 节。
**目标**:把这 105 条已审核 QA 结构化导入数据库,建立"生成向量并写入 Milvus"和"检索"两条链路,产出一个公共只读工具 `query_knowledge`,供客服 Agent(子项目 B)调用。
## 2. 范围
**做什么**:
- `fin_knowledge_meta` 新增 ORM 映射(`milvus_collection` 已存在于基线,**不新增字段、不新增迁移**,见 §3.1)。
- `agent_faq_synonym` 新增只读 ORM 映射。
- 一次性结构化导入脚本:解析 QA 源文件 → 校验 → 写入两张表。
- `ModelGateway`/`ModelGenerationService` 新增 `embed()` 方法(阿里云 DashScope,`text-embedding-v3`,1024 维)。
- 只读 Milvus 检索适配器 + 检索服务 `KnowledgeRetrievalService`(三集合路由、配置驱动、MySQL 二次校验、降级兜底)。
- 写路径 Milvus 适配器(`upsert`/`delete`,与只读适配器代码隔离)+ 复用现有 `domain_event_outbox`/`OutboxWorker` 机制的向量同步 Worker。
- 公共工具 `query_knowledge` 注册进 `bootstrap.py`。
- 修复 `KnowledgeReferenceService.resolve()`(HMAC 签名 token)。
**不做什么(明确排除)**:
- 知识内容的在线创建/审核管理页面或接口(本轮只做一次性导入,未来如需运营团队自主维护知识库,是独立后续任务)。
- `fin_product_collection`、`fin_policy_collection` 两个集合的实际内容——105 条 QA 目前没有可靠依据区分"产品咨询"和"政策解释"两类,第一版**全部导入 `fin_faq_collection`**,另外两个集合建好但暂不populate,等有明确分类依据的内容时再拆分。
- `agent_faq_synonym` 的审核后台——本轮直接把 QA 文件里的"相似问法"写成 `status='approved'`(因为源文件本身已审核),不做同义词的独立审核流程。
## 3. 数据模型变更
### 3.1 `fin_knowledge_meta`
`docs/02` §6.2 已明确要新增 4 个字段:`content_text`、`tags`、`reviewer_id`、`review_status`。
**`milvus_collection` 的字段状态已经核实完毕:它已存在于不可变基线中,本设计不新增该字段,也不需要新增迁移。**
核实链路(可复现,不依赖任何一方的"没提到"这类间接证据):
1. `docs/00-新数据库基线设计.md` §6.2 `fin_knowledge_meta` 字段表(第 871 行):
`| milvus_collection | VARCHAR(64) | 非空 | Milvus 集合名 |`——**未标 🆕**,而同表其它 7 个字段
(`effective_date`/`expire_date`/`content_text`/`tags`/`reviewer_id`/`review_status` 等)都带 🆕 标记。
这个对照本身就是"它是基线自带、不是新增字段"的直接证据。
2. `docs/02-数据库建表设计.md` 的 ALTER TABLE 清单没提它,**不构成矛盾**——该清单只列"需要在基线上补充的字段",
基线已有字段本就不该出现在里面。此前把"没提到"当成矛盾是误读,此处更正。
3. `tools/generate_baseline_sql.py` 从 `docs/00` 的字段表生成 `alembic/baseline_generated.sql`,
生成的 DDL 中该列为 `milvus_collection VARCHAR(64) NOT NULL`,并经 `tools/audit_schema.py`
作为期望 schema 的一部分参与审计。
**对实施的影响(与原稿的关键差异)**:
- 不写新增字段的 Alembic 迁移;只写 ORM 映射。
- 该列是 **`NOT NULL` 且无默认值**(不是原稿假设的"可空"),所以 §4 的导入脚本**必须为每一行显式赋值**
`milvus_collection`,不能依赖默认值——这一点原本就是设计意图,现在从"设计选择"变为"数据库强约束"。
- 实施时仍需对现库做一次 Inspector/`audit_schema.py` 复核,确认现库确实是按这份基线建的表;
但这属于**常规环境一致性校验**,不再是"设计上悬而未决的未知项"。
同表 `version`、`effective_date`、`expire_date` 三个字段的状态沿用同一条核实链:`docs/00` 中
`version` 无 🆕 标记(基线自带),`effective_date`/`expire_date` 带 🆕 标记(由后续迁移补充),
`alembic/baseline_generated.sql` 中三者齐全,因此本设计同样**不新增这三个字段**。
**另外提醒**:桌面另有一份《智能客服Agent新增表详细设计.md》(2026-01-08,v1.0),是 `docs/00` 引用但已过期的草稿,
其 `agent_negative_word`/`svc_handover_ticket`/`agent_faq_synonym`/`conversation_feedback` 字段定义与代码实际实现
(`docs/02` + 现有 ORM/governance.py 查询)不一致,本设计文档不采用,实施时也不应参考该文件。
### 3.2 ORM 映射(新增文件 `app/model/knowledge.py`)
```python
class KnowledgeMeta(Base):
__tablename__ = "fin_knowledge_meta"
# 字段对齐 docs/02 §6.2 + 本文档 §3.1,只读字段沿用既有定义,不重复列出全部历史字段
class FaqSynonym(Base):
__tablename__ = "agent_faq_synonym"
# 只读映射,字段对齐 docs/02 §7.3
```
具体字段清单在实现阶段对照 `docs/00-新数据库基线设计.md` 和 `docs/02-数据库建表设计.md` 逐字段核对,不在本设计文档重复抄写 DDL。
## 4. 一次性结构化导入
### 4.1 源文件字段映射
按《客服Agent一期向量化前流程文档_v1.md》§4 定义:
| 源文件字段 | 目标 | 说明 |
|---|---|---|
| `qa_id`(如 `RAG-PER-001`) | 存入 `KnowledgeMeta.tags` JSON(如 `{"qa_id": "RAG-PER-001"}`),不新增字段 | 保持原始编号,不重新编号 |
| `问题` | `KnowledgeMeta.title` | |
| `相似问法`(`\|` 分隔) | `FaqSynonym.phrase`(每个变体一行) | 去空、去重 |
| `回答` | `KnowledgeMeta.content_text` | 原文照抄,不改写 |
| `phase`固定 `phase_1`、`source_version`固定 `v5.8` | `KnowledgeMeta.tags`(JSON) | |
| 状态 | `KnowledgeMeta.review_status = 'published'`、`FaqSynonym.status = 'approved'`、`FaqSynonym.source_type = 'import'` | 因为源文件本身已是 `approved_candidate`;`source_type='import'` 是 `docs/02` §7.3 DDL 里已经定义好的合法枚举值 |
| 分类 | `KnowledgeMeta.milvus_collection = 'fin_faq_collection'`(全部,本轮不拆分) | 见第 2 节 |
| (源文件无对应字段) | `KnowledgeMeta.knowledge_type`、`KnowledgeMeta.status`、`KnowledgeMeta.created_at`、`KnowledgeMeta.updated_at` | **这些列在 DDL 里都是 `NOT NULL` 且无默认值,导入脚本必须显式赋值**,不能依赖数据库默认 |
**`FaqSynonym` 侧还有三个非空列必须显式赋值,原稿遗漏了**:`docs/02` §7.3 的 `agent_faq_synonym` DDL 里
`created_by BIGINT UNSIGNED NOT NULL`,另有可空的 `reviewer_id`/`reviewed_at`。由于本轮是脚本导入而非人工操作,
`created_by` 需要一个**明确的"系统导入"账号 ID**,不能凭空填 0 或 NULL:
- **该列带外键约束**:`CONSTRAINT fk_synonym_created_by FOREIGN KEY (created_by) REFERENCES sys_user(id)`
(`reviewer_id` 同样有 `fk_synonym_reviewer`)。所以填的必须是一个**真实存在的 `sys_user.id`**,
否则导入会在数据库层直接失败。实施时使用一个已存在的运维/管理员账号 ID,或按项目既有做法新增一个专用系统导入账号;
具体取值在实施计划里作为显式前置步骤确认,不在本文档臆断。
- `reviewer_id`/`reviewed_at` 建议一并填入同一账号与导入时间——它们可空,但填上可以让"这批同义词是谁在什么时候
导入的"可追溯,符合源文件本身已经过审核的事实。
- `normalized_phrase` 与 `phrase_hash` 按 `docs/02` §7.3 已有明文约定实现:**`phrase_hash` 由应用对完整
`normalized_phrase` 计算 SHA-256**(目的是避免索引仅用 191 前缀时把不同长文本误判为重复),
不自行发明算法。去重依据是唯一索引 `uk_faq_synonym (knowledge_id, phrase_hash)`,
所以 §4.1 里"相似问法去重"应落在 `normalized_phrase` 归一化之后做。
- `status` 有 CHECK 约束 `IN ('pending','approved','disabled','archived')`,本设计用的 `'approved'` 合法。
### 4.2 导入校验规则(不通过则整体不导入)
- 记录数必须等于源文件声明的 105 条;
- `qa_id` 不重复;
- 每条必须有问题、至少一个相似问法、完整答案;
- 答案文本与源文件逐字一致(防止转录出错)。
### 4.3 实现形式
一次性管理脚本(例如 `tools/import_knowledge_seed.py`),而非常驻的 HTTP 写接口——因为本轮明确不做知识管理后台。脚本可重复执行(先清空本次导入批次再重新写入,或按 `qa_id` upsert),便于源文件升级到 v5.9 等后续版本时重新运行。
### 4.4 导入后的向量同步
复用第 5 节的写路径:导入脚本对每条新写入/更新的记录,在同一批次结束后,写入 `domain_event_outbox`(`event_type="knowledge.vector_sync_requested"`),交给 Worker 异步生成向量并写入 Milvus,导入脚本本身不直接调用 Milvus 写入。
## 5. 写路径(生成向量并同步 Milvus)
### 5.0 建立 Milvus 集合(两份来源文档都未覆盖的空白,本节补齐)
《15-知识检索接入方案.md》和本文档早期版本都只把"Milvus 三集合已建"当作前置条件,没有说明谁建、怎么建。
本节补上这一步,作为写路径里实际最先执行的动作。
**集合结构**(`fin_faq_collection`/`fin_product_collection`/`fin_policy_collection` 三个集合结构相同,仅名字不同):
| 字段 | 类型 | 说明 |
|---|---|---|
| `knowledge_id` | VARCHAR(64),主键 | 对应 `KnowledgeMeta.id` 的字符串形式,与读路径 `KnowledgeHit.knowledge_id: str` 类型一致 |
| `title` | VARCHAR(256) | 对应问题标题 |
| `snippet` | VARCHAR(4000) | 对应标准答案全文(这批 QA 答案长度都在几百字以内,4000 字节留足余量) |
| `tags` | VARCHAR(512) | 逗号拼接的标签字符串(不用 Milvus JSON 字段类型,避免不同 pymilvus 版本对 JSON 字段支持不一致的风险) |
| `version` | VARCHAR(16) | 对应源版本号,如 `v5.8` |
| `embedding` | FLOAT_VECTOR(1024) | text-embedding-v3 输出向量 |
索引:向量字段使用 `AUTOINDEX`,`metric_type="COSINE"`——必须和读路径适配器 `search()` 时用的 `metric_type` 一致,
否则检索结果不可信。数据量小(105 条起步),不需要额外调优索引参数。
**建立方式**:新增一次性脚本 `tools/setup_milvus_knowledge_collections.py`,对三个集合分别执行"若不存在则创建
(`has_collection` 判断,幂等)+ 创建向量索引 + `load()` 载入内存"。此脚本与 `alembic upgrade head` 是并列的
独立环境初始化步骤,不通过 Alembic 管理(Milvus 不是关系型数据库,没有对应的迁移框架)。每个环境(本地/测试/生产)
部署时都需要单独执行一次。
执行顺序建议:`tools/audit_schema.py`(确认现库与基线一致,不再需要为 `milvus_collection` 做字段核实)→
`python tools/setup_milvus_knowledge_collections.py` → `python tools/import_knowledge_seed.py`(第 4 节的一次性导入,
导入后触发第 5.3 节的向量同步)。
### 5.1 Embedding 出口
采纳《15-知识检索接入方案.md》步骤 1 的设计:给 `ModelGateway` 协议新增 `embed()` 方法,`ModelGenerationService` 新增对应封装方法。模型端点为阿里云 DashScope `text-embedding-v3`(1024 维),通过 `ModelEndpointConfig` 新增一条 `capabilities=["embedding"]` 的记录,密钥走 `secret_ref`。
**这条 `ModelEndpointConfig` 记录不需要新写代码创建**:平台已有通用管理端资源接口
(`app/api/controllers/admin.py` 已注册 `model-endpoints` 资源类型),用管理员账号调用现有接口新增即可。
新增时除了 `base_url`/`model_name`/`secret_ref`/`capabilities` 外,`EndpointPayload` 还要求
`allowed_data_levels`(数据级别清单,需要和现有其他端点记录的取值约定保持一致,不要凭空发明新值)和
`context_window`(正整数,DashScope `text-embedding-v3` 的单次输入 Token 上限,需查官方文档确认具体数值)两个必填字段,
实施时不要漏填。
DashScope OpenAI 兼容接口地址为 `https://dashscope.aliyuncs.com/compatible-mode/v1`,`secret_ref` 格式固定为
`env:XXX`(大写字母/数字/下划线),对应 `.env` 里新增一行真实 Key(不提交 git),`.env.example` 只放变量名占位。
本次确认可以复用团队已有的 DashScope 账号 Key,不需要新申请,但需要确认该账号的额度/计费方式能覆盖本项目的调用量。
**补充一处读路径文档未展开的实现细节**:`ModelGenerationService.embed()` 委托给 `self.dispatch.embed(...)`,
意味着 `ModelDispatchService`(`app/service/model_gateway.py` 现有类)也需要新增 `embed()` 方法,
镜像其 `generate()` 方法已有的"按顺序尝试多个已批准端点、全部失败才报错"的重试逻辑,读路径文档的代码片段里没有
单独给出这个方法体,实施时需要照 `generate()` 的模式补上,而不是漏掉。
**从现有代码读出的三处实现约束(原稿未覆盖,实施时容易踩)**:
1. **路由必须按能力筛选,否则会把 embedding 请求打到聊天端点上**。`ModelRouterService.select_endpoint()`/
`select_with_fallback()` 支持 `required_capability` 参数(`app/service/model_router_service.py` 第 19/34 行),
现有 `generate()` 链路依赖它做能力筛选。新增的 embedding 链路解析端点时必须传
`required_capability="embedding"`,与新端点记录 `capabilities=["embedding"]` 对应。
2. **HTTP 路径是 `/embeddings`,不是复用 `/chat/completions`**。`OpenAICompatibleGateway.generate()` 内部写死
`endpoint.base_url.rstrip("/") + "/chat/completions"`(`model_gateway.py` 第 56 行);embedding 需要新增一条
请求路径为 `/embeddings`、请求体为 `{"model": ..., "input": ...}`、响应从 `body["data"][0]["embedding"]`
取向量的实现。DashScope 的 `compatible-mode/v1` 同时提供这两条路径,所以 `base_url` 可以沿用同一套约定。
3. **`DatabaseModelGateway` 也需要一个 `embed()` 镜像**:它现在从 `ModelEndpointConfig` 动态取端点、再构造
`OpenAICompatibleGateway`(`model_gateway.py` 第 83-99 行),embedding 要走同一条"从库取配置"的路径,
不能另起一套端点解析逻辑。
`secret_ref` 校验沿用 `EnvironmentSecretResolver.resolve()` 的既有约定:**必须以 `env:` 开头**
(`model_gateway.py` 第 27-28 行硬校验),否则直接抛 `RecoverableAgentError`;变量名从环境读取,缺失同样报错。
### 5.2 写路径 Milvus 适配器(与读路径隔离)
新文件 `app/infrastructure/milvus_knowledge_writer.py`,只提供:
```python
class MilvusKnowledgeWriter:
async def upsert(self, *, collection: str, knowledge_id: str,
vector: list[float], fields: dict[str, Any]) -> None: ...
async def delete(self, *, collection: str, knowledge_id: str) -> None: ...
```
与《15-知识检索接入方案.md》里"只读"的 `MilvusKnowledgeClient` 是两个不同的类、不同的文件,检索路径永远不持有写权限的客户端实例。
### 5.3 Outbox Worker Handler
复用已有的通用 `domain_event_outbox` + `OutboxWorker`(`app/worker/outbox_worker.py`),不新建专用表。新增两个事件类型的 handler:
- `knowledge.vector_sync_requested`:按 `knowledge_id` 重新从 MySQL 读取最新已发布内容 → 调用 `embed()` → 校验向量维度等于 1024(不等则失败,交给 Outbox 重试机制,不静默降级)→ `MilvusKnowledgeWriter.upsert()`。
- `knowledge.vector_delete_requested`:知识下线(`review_status` 转为 `archived`)时触发,调用 `MilvusKnowledgeWriter.delete()`。即使删除失败也不影响安全性——读路径的 MySQL 二次校验(见第 6 节)会挡住已下线内容,这是纵深防御,不是唯一防线。
失败重试完全复用 `OutboxWorker` 已有的指数退避、5 次上限、死信标记机制,不新写重试逻辑。
## 6. 读路径(检索)
完全采纳《15-知识检索接入方案.md》第 3-8 步的设计,不重新设计,仅摘要关键约束供本文档自成一体地被理解:
- 三集合按意图路由:`faq→fin_faq_collection(top_k=3)`、`product_inquiry→fin_product_collection(top_k=5)`、`policy_explain→fin_policy_collection(top_k=5)`(本轮只有第一个集合有数据)。
- 集合白名单强校验,非白名单集合名一律拒绝。
- Milvus 命中后必须回查 `fin_knowledge_meta`,确认 `review_status='published' AND status='active'` 且在有效期内,防止已下线知识被检索到。
- Milvus 不可用时降级为 MySQL `content_text LIKE` 关键词检索,降级结果标记 `degraded=True`,同样执行"已发布+有效期"过滤。
- Embedding 维度与集合定义不一致时**失败关闭**,不得静默返回空结果或错误结果。
- 公共只读工具 `query_knowledge` 注册进 `bootstrap.py`,`allowed_roles` 含 `customer`(区别于 `query_financial_data` 不含 customer)。
- `KnowledgeReferenceService.resolve()` 改为 HMAC 签名 token 校验 + 真实查询 `fin_knowledge_meta`,返回脱敏元数据、不返回全文。
## 7. 测试计划
在《15-知识检索接入方案.md》第 8 步既有测试计划基础上,补充导入相关测试:
| 类型 | 覆盖 |
|---|---|
| 单元 | 导入校验:105 条数量、编号唯一、字段完整、答案与源文件一致;破坏其中一项应导致整体导入失败 |
| 单元 | `embed()` 返回维度不等于 1024 时失败关闭 |
| 集成(真实 MySQL) | 导入后能查到 105 条 `review_status='published'` 记录;`agent_faq_synonym` 条数等于所有相似问法之和 |
| 集成 | Outbox handler:向量生成失败时重试,达到上限进入死信,不阻塞已发布记录本身的可读性 |
| (沿用读路径文档)| 意图路由集合与 TopK 正确;非法集合名拒绝;降级仍执行有效期过滤;跨已下线内容不可检索 |
## 8. 本轮讨论中的方向修正记录
为避免后续协作者困惑,记录一次被推翻的设计方向:讨论初期曾计划新建完整的知识库在线审核后台(草稿→审核→发布状态机、管理端 HTTP 接口、创建人不得审核自己等),对齐 `config_release_service` 的模式。读到胜宇提供的《客服Agent一期向量化前流程文档_v1.md》后确认:一期知识内容已经是审核完毕的静态文件(v5.8,105 条),系统只需一次性结构化导入,不需要在线管理后台。该方向已放弃,本文档第 4 节的"一次性导入脚本"取代了原计划。
## 9. 开工前必须确认(沿用读路径文档 + 本轮新增)
1. 可用的 `text-embedding-v3` 端点(`base_url`/`model_name`/`secret_ref`),维度确认为 1024。
2. Milvus 三集合的建立已在第 5.0 节纳入本方案范围(`tools/setup_milvus_knowledge_collections.py`),不再是外部前置条件;
但需要和胜宇那边确认他是否已经自行建过同名集合,避免重复建或字段结构对不上(例如他本地测试环境可能已建了一份不同字段的
`fin_faq_collection`,需要先核实、必要时统一)。
3. `pymilvus` 异步客户端可用性确认(决定读写/建集合三类操作的具体实现方式,AsyncMilvusClient 不可用则需用
`anyio.to_thread.run_sync` 包装同步客户端)。
**已核实(2026-09-10,见第 10 节):目标环境 `pymilvus 2.6.17` 提供 `AsyncMilvusClient`,且
`has_collection`/`create_collection`/`create_index`/`load_collection`/`upsert`/`delete`/`search` 全部具备。
因此三类操作直接用原生异步客户端实现,`anyio.to_thread` 包装方案**不需要**。**
4. 源文件后续升级(如 v5.9)时,重新运行导入脚本的责任人和时机需要约定。
5. 导入脚本写入 `agent_faq_synonym.created_by` 所使用的那一个真实 `sys_user.id`(外键约束要求存在),
由实施计划显式确认,见 §4.1。
## 10. 交接后的核实记录(2026-09-10,接手方补充)
本轮接手时对照代码与文档逐条核实了第 9 节的外部事实,结论如下(**只记录已复核的事实,未复核的保持"待确认"**):
| # | 事项 | 结论 | 依据 |
|---|---|---|---|
| 1 | `fin_knowledge_meta.milvus_collection` 是否已存在 | **已存在于基线,无需新增字段与迁移**;且为 `NOT NULL` 无默认值 | `docs/00` §6.2 第 871 行无 🆕 标记(同表其它 7 字段均有);`alembic/baseline_generated.sql` 生成的 DDL 含该列并通过 `tools/audit_schema.py` 审计 |
| 2 | `fin_knowledge_meta.version`/`effective_date`/`expire_date` | 均已存在于生成 DDL,无需新增 | 同上 |
| 3 | `pymilvus` 异步客户端 | **可用**,原生异步实现,无需线程包装 | 目标环境 `pymilvus 2.6.17` 实测 `AsyncMilvusClient` 及其 7 个所需方法 |
| 4 | `agent_faq_synonym` 导入所需的非空列 | `created_by` 非空**且有外键指向 `sys_user(id)`**;`phrase_hash` = SHA-256(`normalized_phrase`);`status` 有 CHECK 约束 | `docs/02` §7.3 DDL 与第 355 行说明 |
| 5 | 现库是否与基线一致 | **待确认**——需环境(MySQL)可用后跑 `tools/audit_schema.py`,属常规环境一致性校验 | — |
| 6 | Milvus 三个集合是否已被胜宇建过 | **待确认**——需 Milvus 可用后查现有集合名与字段结构 | — |
| 7 | DashScope Key 额度/计费覆盖本项目调用量 | **待确认**(用户提供 Key 与额度信息后核实) | — |
| 8 | `text-embedding-v3` 端点的 `context_window` 具体取值 | **待确认**——创建 `ModelEndpointConfig` 记录前须查阿里云官方文档确定 | [阿里云 text-embedding 同步接口文档](https://www.alibabacloud.com/help/tc/model-studio/text-embedding-synchronous-api#2) |