多库检索与存储修复: - RRF 融合去重改用 (collection, chunk_id) 复合键,修复同名文件结果被吞 - DocStore 存储路径加 collection 前缀,修复跨库同名切片数据覆盖 - search_multiple 去重改用复合键 - chunk_id 解析改用 rsplit 兼容下划线文件名 上传与版本管理修复: - 同名文件上传改为覆盖模式,自动清理旧切片 - 修复首次上传不创建版本记录 - 修复覆盖上传版本号回退到 v1 - sync ADDED 分支改用动态版本号生成 - _generate_version_id 改为基于全部版本递增 - 废止/恢复操作同步 SQLite 版本记录 - mark_document_as_superseded 改为仅更新 SQLite 删除清理修复: - 删除文档时同步清理 SQLite 版本记录和变更日志 - 删除向量库时同步清理该库所有版本记录 - cleanup 改为清理 SQLite 记录而非 ChromaDB 测试: - test_version_management.py: 27 条版本管理单元测试 - test_edge_cases.py: 28 条边界用例测试 - test_upload_dedup.py: 5 条上传去重测试 - e2e_risk_test.py: 27 条端到端风险测试 文档: - 新增风险边界问题修复注意事项.md(面向后端的对接文档) - 新增向量库边界风险分析.md - 更新多篇现有文档
120 lines
4.5 KiB
Markdown
120 lines
4.5 KiB
Markdown
## 风险边界问题修复注意事项
|
||
|
||
### 一、修复了哪些会出问题的情况
|
||
|
||
以下场景之前会报错或数据异常,现在已修复,不会再出问题:
|
||
|
||
| 场景 | 之前的问题 | 修复后 |
|
||
|------|-----------|--------|
|
||
| 同名文件覆盖上传 | 旧版本历史丢失,版本记录缺失 | 旧版本自动标记为 `superseded`,新版本正确记录,历史完整保留 |
|
||
| 首次上传文档 | 不创建版本记录,后续覆盖时无法追溯旧版本 | 首次上传也会创建 v1 版本记录 |
|
||
| 多次覆盖上传同一文件 | 版本号始终停留在 v1,新记录覆盖旧记录 | 版本号正确递增(v1 → v2 → v3...) |
|
||
| 废止文档后查询版本历史 | SQLite 版本状态没同步,仍显示 active | 废止和恢复操作同步更新 ChromaDB 和 SQLite |
|
||
| 废止后再废止 | 可能报错 | 不报错,正常返回成功 |
|
||
| 恢复未废止的文档 | 可能报错 | 不报错,返回错误提示 |
|
||
| 废止不存在的文档 | 可能崩溃 | 不崩溃,返回错误提示 |
|
||
| 上传空文件 | 可能崩溃 | 不崩溃,正常处理 |
|
||
| 废止的文档出现在搜索结果中 | deprecated/superseded 切片未被过滤 | 搜索引擎自动过滤,不再返回 |
|
||
| 删除文档后版本历史残留 | SQLite 版本记录不清理,查询返回幽灵数据 | 删除文档时同步清理版本记录和变更日志 |
|
||
| 删除向量库后版本历史残留 | 整个库的版本记录不清理,重建同名库后版本号错乱 | 删除向量库时同步清理所有版本记录和变更日志 |
|
||
|
||
### 二、端口和接口
|
||
|
||
端口不变,仍然是 `5001`。所有接口路径不变,无新增接口。
|
||
|
||
版本历史查询接口(已有,无变化):
|
||
|
||
```
|
||
GET /collections/{kb_name}/documents/{filename}/versions
|
||
```
|
||
|
||
### 三、返回数据字段变化
|
||
|
||
#### 上传接口 `POST /documents/upload`
|
||
|
||
响应 `data.file` 中新增 `replaced` 字段:
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"file": {
|
||
"filename": "制度.pdf",
|
||
"collection": "public_kb",
|
||
"path": "public_kb/制度.pdf",
|
||
"size": 1024,
|
||
"replaced": true
|
||
},
|
||
"sync_status": "已保存并添加到向量库"
|
||
}
|
||
}
|
||
```
|
||
|
||
`replaced` 为 `true` 表示本次上传覆盖了同名旧文件。首次上传时为 `false`。后端可据此判断是否需要更新自己的版本记录。
|
||
|
||
#### 版本历史接口返回格式
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"document_id": "制度.pdf",
|
||
"collection": "public_kb",
|
||
"versions": [
|
||
{
|
||
"version": "v2",
|
||
"status": "active",
|
||
"created_at": "2026-06-04T15:30:00",
|
||
"change_summary": "新增文档",
|
||
"chunk_count": 12
|
||
},
|
||
{
|
||
"version": "v1",
|
||
"status": "superseded",
|
||
"deprecated_date": "2026-06-04T15:30:00",
|
||
"deprecated_reason": "重新上传覆盖"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
`status` 可能的值:`active`(使用中)、`deprecated`(手动废止)、`superseded`(被新版本替代)。
|
||
|
||
#### 废止/恢复接口返回格式(无变化)
|
||
|
||
废止:
|
||
```json
|
||
{
|
||
"success": true,
|
||
"deprecated_chunks": 5,
|
||
"document_id": "制度.pdf",
|
||
"collection": "public_kb",
|
||
"deprecated_date": "2026-06-04T15:00:00"
|
||
}
|
||
```
|
||
|
||
恢复:
|
||
```json
|
||
{
|
||
"success": true,
|
||
"restored_chunks": 5,
|
||
"document_id": "制度.pdf",
|
||
"collection": "public_kb"
|
||
}
|
||
```
|
||
|
||
### 四、后端需要注意的
|
||
|
||
1. **chunk_id 必须配合 collection 使用** — chunk_id 格式是 `{文件名}_{序号}`,不同向量库中同名文件的 chunk_id 相同,存数据库时必须用 `(collection, chunk_id)` 做联合键。
|
||
2. **citation 的 content 截断至 300 字** — 需要完整内容请调切片查询接口。
|
||
3. **同名文件上传是覆盖模式** — 不再加时间戳重命名,直接覆盖。
|
||
|
||
### 五、已知限制(后端需规避)
|
||
|
||
以下场景当前未做特殊处理,后端在业务层需要注意规避:
|
||
|
||
1. **不要并发上传同名文件** — 两个请求同时上传同名文件到同一知识库可能产生竞态,导致版本记录或切片数据不一致。如有并发场景,请在业务层加锁或排队。
|
||
|
||
2. **superseded 文档不可恢复** — 被覆盖上传替代的旧版本(状态 `superseded`),其切片已从向量库中物理删除,无法通过恢复接口还原。恢复接口仅支持恢复手动废止(`deprecated`)的文档。对 superseded 文档调恢复接口会返回 `"未找到已废止的文档"`。
|
||
|
||
3. **批量上传中不要包含同名文件** — 如果一次批量上传请求中包含两个同名文件,第一个会被第二个无意义地覆盖。请确保单次批量上传中文件名不重复。
|