286 lines
24 KiB
Markdown
286 lines
24 KiB
Markdown
# 设计文档:客服知识检索基础设施(子项目 A)
|
||||
|
|
|
|||
|
|
> 状态:待用户审阅
|
|||
|
|
> 日期: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) |
|
|||
|
|
|