Files
rag/docs/向量库边界风险分析.md
lacerate551 d589c27bce docs: 清理过时文档 + 更新关键文档
删除(10个):
- API与后端对接规范.md(已被后端对接规范.md 替代)
- image_processing_flow.md(已合入 RAG数据流程.md)
- 状态码功能更新说明.md(已合入后端对接规范.md)
- 题目模板.md(字段名与实际 API 不一致,以对接指南为准)
- 出题批卷系统设计.md(旧版出题设计,已被对接指南+变更说明替代)
- 测试指南.md(出题 API 格式过时,模型名过时)
- 企业文档更新管理方案.md(设计方案,已实施完成)
- 版本管理实施完成报告.md(里程碑报告,已完成归档)
- 生产路径优化计划.md(行号已偏移,阶段状态过时)

更新(7个):
- 出题批题后端对接指南.md:添加 generate-smart 端点、question_content→content
- 开发与系统模块说明.md:更新 reranker 和 LLM 模型名称
- 多源信息融合指南.md:标注生产路径 vs 备用路径
- 架构与部署方案.md:更新数据归属对照表(4个 SQLite DB)
- 认证与权限配置指南.md:出题 API 更新为 /exam/generate
- 向量库边界风险分析.md:标注 P1 旧切片残留和 P2 文件更新已修复
- 风险边界问题修复注意事项.md:标注所有场景已修复
2026-06-21 20:53:33 +08:00

169 lines
8.9 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.
## 向量库边界风险分析
基于对 `knowledge/``core/engine.py``api/chat_routes.py``api/document_routes.py` 全链路代码的审查,覆盖 chunk_id 生成、多库检索合并、RRF 融合、引用构建、文档预览、DocStore 存储、上传/删除等环节。
---
### P0多库 RRF 融合按 chunk_id 去重 — 同名文件结果被吞
**位置**`core/engine.py` 第 1841 行 `reciprocal_rank_fusion()`
```python
if doc_id not in doc_scores:
doc_scores[doc_id] = {'score': 0.0, 'doc': doc, 'meta': meta}
doc_scores[doc_id]['score'] += rrf_score
```
**场景**`public_kb``dept_1_kb` 都上传了 `规章制度.pdf`,两个库中的 chunk_id 完全相同(`规章制度.pdf_0``规章制度.pdf_1` ...)。多库并行检索后进入 RRF 融合,`doc_scores` 以 chunk_id 为 key来自不同 collection 的同名 chunk 被视为同一文档——分数累加,但只保留先入的那条 `doc``meta`,另一个 collection 的切片被静默丢弃。
**影响**
- 用户查询可能只看到 `public_kb` 的版本,`dept_1_kb` 的同名文件完全不出现在结果中
- 引用溯源的 `_collection` 字段指向错误的库
**修复方向**:将 RRF 的 key 从 `doc_id` 改为 `f"{coll_name}/{doc_id}"`(复合键),或者在 chunk_id 前拼接 collection 名称。需要同步修改下游 MMR、Rerank、上下文扩展等环节的 ID 处理。
---
### P0DocStore 文件碰撞 — 跨库同名切片的表格/图片数据互相覆盖
**位置**`knowledge/processing.py` 第 345、374 行
```python
doc_path = docstore_dir / f"{doc_id}.json" # doc_id = chunk_id = "filename_N"
```
**场景**`public_kb``规章制度.pdf` 第 3 个切片chunk_id = `规章制度.pdf_2`)包含一个表格,写入 `.data/docstore/规章制度.pdf_2.json`。之后 `dept_1_kb` 也上传了同名文件,其第 3 个切片也写入 `.data/docstore/规章制度.pdf_2.json`,直接覆盖了前者。
**影响**:表格原始 Markdown 或图片引用路径被覆盖,引用跳转时展示的是错误 collection 的表格内容。
**修复方向**DocStore 路径改为 `{collection}_{doc_id}.json`,或在 JSON 内增加 collection 字段并检测冲突。
---
### P1同名文件重复上传 — 旧切片残留 + 搜索结果重复
> **✅ 已修复**2026-06-04上传接口现在自动替换同名文件旧切片自动标记为 `superseded`。详见 [风险边界问题修复注意事项.md](风险边界问题修复注意事项.md)。
**位置**`api/document_routes.py` 第 246-250 行
```python
if os.path.exists(filepath):
timestamp = datetime.now().strftime('_%Y%m%d_%H%M%S')
filename = f"{name}{timestamp}{ext_part}"
```
**场景**:用户向 `public_kb` 上传 `制度.pdf`v1后来更新为 v2 再次上传同名文件。系统自动重命名为 `制度_20260604_120000.pdf`v1 的旧切片(`制度.pdf_0` ... `制度.pdf_N`)仍然存在于向量库中。搜索结果同时返回 v1 和 v2 的内容,用户无法区分。
**影响**
- 搜索结果中出现重复内容(不同文件名但内容相近)
- MMR 去重可能保留 v1 的切片而排除 v2 的
- 文档列表 `list_documents()` 显示两个文件,用户难以判断哪个是最新版
**修复方向**:上传时检测是否存在同名文件,若有则先调用 `delete_document()` 清除旧切片,再写入新版本(或增加版本管理字段自动关联)。
---
### P1`_collection` 字段回退逻辑 — 多库时始终指向第一个 collection
**位置**`api/chat_routes.py` 第 1529-1536 行
```python
if not meta.get('_collection'):
if collections and len(collections) == 1:
meta['_collection'] = collections[0]
elif collections:
meta['_collection'] = collections[0] # 多知识库时回退到第一个
else:
meta['_collection'] = 'public_kb'
```
**场景**:在单库检索路径下(非 `_search_multi_kb`ChromaDB 原生返回的 metadata 不含 `_collection` 字段。当 `collections` 列表包含多个库名时,所有结果的 `_collection` 都被设为 `collections[0]`——即使某些结果实际来自其他库。
**影响**:引用跳转时 `preview API` 用错误的 collection 查询,返回 404 或其他文档的切片。
**现状缓解**:生产环境走 `_search_multi_kb` 路径时,每个 collection 的 metadata 都在查询时被正确注入了 `_collection`engine.py 第 1406 行),所以这个回退逻辑实际上很少被触发。但作为防御性代码,它的行为是错误的。
**修复方向**:回退逻辑应使用 `meta.get('collection')`(入库时写入的字段),而不是硬编码 `collections[0]`
---
### P1`search_multiple()` 去重仅按 ID — 低层 API 的同名文件问题
**位置**`knowledge/search.py` 第 289-292 行 `_merge_multiple_results()`
```python
seen = set()
for item in all_items:
if item['id'] not in seen:
seen.add(item['id'])
unique_items.append(item)
```
**场景**:与 P0 RRF 问题类似,但在 `SearchMixin.search_multiple()` 这个独立的检索 API 中。虽然生产 RAG 路径不走这个方法(走 engine 的 `_search_multi_kb`),但任何直接调用 `kb_manager.search_multiple()` 的代码都会遇到同样的去重问题。
**修复方向**:去重键改为 `(collection, id)` 复合键。
---
### P2引用构建的字段名不一致 — `collection` vs `_collection`
**位置**`knowledge/manager.py` 第 281 行写入 `"collection": kb_name``api/chat_routes.py` 第 590 行读取 `meta.get('_collection', '')`
**场景**:入库时 metadata 存的是 `collection`(无下划线),但 citation 构建时读的是 `_collection`(有下划线)。两个字段名不一致,如果 metadata 中没有被后续流程注入 `_collection`citation 的 `collection` 字段会返回空字符串。
**影响**:前端引用跳转 URL 缺少 collection 部分preview API 路径不完整。
**现状缓解**:生产 RAG 路径在检索阶段会注入 `_collection`engine.py 第 1406 行),所以正常流程下不会触发。但其他非标准路径(如直接查询 ChromaDB会暴露此问题。
**修复方向**`_build_citation` 同时读取两个字段名:`meta.get('_collection') or meta.get('collection', '')`
---
### P2文件无原地更新机制
> **✅ 已修复**2026-06-04上传接口新增自动替换机制`replaced=true`),同名文件自动替换旧版本。
**场景**:用户上传 `制度.pdf` v1 后发现内容有误,修改后想替换。当前系统没有 "更新文件" 接口,只能删除后重新上传。如果用户不知道要先删除,就会触发 P1 的重复问题。
**修复方向**upload 接口增加 "如果同名文件已存在则替换" 选项(先 delete_document 再 add_file_to_kb或提供独立的 "更新文档" API。
---
### P2元数据文件与磁盘状态不同步
**位置**`knowledge/collection.py` 第 362 行 `list_collections()` 依赖 `self._metadata` JSON 文件
**场景**metadata JSON 文件存储在 `knowledge/vector_store/kb_metadata.json`。如果该文件损坏或被手动修改,`list_collections()` 返回的列表与磁盘上实际的 ChromaDB 目录不一致。虽然有自动补充逻辑(扫描磁盘目录自动注册),但仅限于回退场景。
**影响**:向量库可能在列表中不可见,或列表中出现已不存在的向量库。
---
### P3文件名含下划线
**位置**`api/chat_routes.py` 第 578 行
```python
chunk_index = int(str(chunk_id_raw).rsplit('_', 1)[-1])
```
**场景**:文件名为 `2026_年度_制度.pdf`chunk_id 为 `2026_年度_制度.pdf_0`
**现状**:使用 `rsplit('_', 1)` 从右边分割,最后一个 `_` 后的数字能正确解析。此场景当前不会出错。但如果文件名为 `test_.pdf`尾部下划线chunk_id 为 `test_.pdf_0``rsplit` 仍正常工作。**无需修复**,记录备查。
---
### 风险总结
| 等级 | 风险 | 核心原因 | 触发条件 | 状态 |
|------|------|----------|----------|------|
| **P0** | RRF 融合吞结果 | 去重 key 缺少 collection | 多库有同名文件 | 未修复 |
| **P0** | DocStore 覆盖 | 存储路径缺少 collection | 多库有同名含表格/图片文件 | 未修复 |
| **P1** | 旧切片残留 | 重复上传只改名不替换 | 同名文件二次上传 | ✅ 已修复 |
| **P1** | _collection 回退错误 | 硬编码 collections[0] | 单库路径 + 多 collection | 未修复 |
| **P1** | search_multiple 去重 | 去重 key 缺少 collection | 直接调用低层 API | 未修复 |
| **P2** | citation 字段名不一致 | `collection` vs `_collection` | 非标准查询路径 | 未修复 |
| **P2** | 无文件更新机制 | 设计缺失 | 用户需要替换文档 | ✅ 已修复 |
| **P2** | 元数据不同步 | JSON 文件可能损坏 | 手动操作或异常退出 | 未修复 |
| **P3** | 文件名含下划线 | 无问题rsplit 兼容) | — | 无需修复 |