Files
rag/docs/curl测试手册.md
lacerate551 cb75b9b274 fix(boundary): 修复多库边界问题、版本管理及删除清理
多库检索与存储修复:
- 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
- 更新多篇现有文档
2026-06-04 23:58:44 +08:00

1947 lines
43 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.
# RAG API curl 测试手册
> 基于 `后端对接规范.md`,所有 curl 命令均在生产模式 (`APP_ENV=prod`) 下验证通过。
>
> 测试日期2026-06-04 | 生产服务器:`47.116.16.222` | 服务地址:`http://127.0.0.1:5001`
>
> 当前部署模型:`qwen-plus`DashScope| 嵌入模型:`bge-base-zh-v1.5`(本地 CPU| Rerank`qwen3-rerank`DashScope 云端 API
## 目录
- [1. 基础信息](#1-基础信息)
- [2. 健康检查](#2-健康检查)
- [3. 问答接口](#3-问答接口)
- [4. 检索接口](#4-检索接口)
- [5. 向量库管理](#5-向量库管理)
- [6. 文档管理](#6-文档管理)
- [7. 切片管理](#7-切片管理)
- [8. 同步服务](#8-同步服务)
- [9. 反馈系统](#9-反馈系统)
- [10. FAQ 管理](#10-faq-管理)
- [11. 出题系统](#11-出题系统)
- [12. 图片服务](#12-图片服务)
- [13. 报告服务](#13-报告服务)
- [14. 知识库路由](#14-知识库路由)
- [附录. 已知问题](#附录-已知问题)
---
## 1. 基础信息
### 认证方式
生产模式 (`APP_ENV=prod`) 下无需认证 Header直接放行。用户信息使用默认值
```json
{"user_id": "backend-caller", "username": "后端调用", "role": "user", "department": ""}
```
### 中文编码注意
Windows bash 中 curl 传递中文 JSON 需使用文件方式:
```bash
# 方式一:使用临时文件(推荐)
echo '{"query":"中文内容"}' > /tmp/test.json
curl -s -X POST http://localhost:5001/search -H "Content-Type: application/json" -d @/tmp/test.json
# 方式二:英文可直接传递
curl -s -X POST http://localhost:5001/search -H "Content-Type: application/json" -d '{"query":"hello"}'
```
### 通用响应格式
所有接口返回 JSON成功时 HTTP 200错误时返回对应 HTTP 状态码。
---
## 2. 健康检查
### GET /health
检查服务运行状态。
```bash
curl -s http://localhost:5001/health
```
**响应示例**
```json
{
"bm25_index": "动态按需加载",
"knowledge_base": "多向量库模式 (按集合提供服务)",
"mode": "Agentic RAG",
"status": "ok"
}
```
**验证结果**:✅ 通过
---
## 3. 问答接口
### POST /rag
知识库问答SSE 流式返回)。
```bash
echo '{"message":"三峡工程","chat_history":[]}' > /tmp/rag.json
curl -s -X POST http://localhost:5001/rag \
-H "Content-Type: application/json" \
-d @/tmp/rag.json
```
**请求体**
| 字段 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `message` | string | ✅ | 用户消息 |
| `chat_history` | array | ✅(生产模式) | 对话历史 |
| `collections` | string[] | ❌ | 知识库列表,默认 `["public_kb"]` |
| `session_id` | string | ❌ | 会话 ID |
**chat_history 格式**
```json
[
{"role": "user", "content": "之前的问题"},
{"role": "assistant", "content": "之前的回答"}
]
```
**SSE 事件序列**
```
data: {"type": "start", "message": "正在检索知识库..."}
data: {"type": "sources", "sources": [...]}
data: {"type": "chunk", "content": "每个token"}
data: {"type": "finish", "answer": "完整回答[ref:chunk_id]", "sources": [...], "citations": [...]}
```
**finish 事件中 citations 结构**(引用溯源,用于前端跳转定位):
```json
{
"citations": [
{
"chunk_id": "3.docx_33",
"chunk_index": 33,
"source": "3.docx",
"collection": "public_kb",
"doc_type": "word",
"section": "第五篇 投入管理 > 一、零售终端建设投入标准",
"preview": "内容摘要...",
"content": "切片内容(截断 300 字)",
"chunk_type": "text",
"page": null,
"page_end": null
}
]
}
```
| citations 字段 | 说明 |
|------|------|
| `chunk_id` | 切片唯一标识,格式 `{文件名}_{序号}` |
| `chunk_index` | 全局切片序号(从 chunk_id 提取),用于文档预览跳转 |
| `source` | 来源文件名 |
| `collection` | 所属向量库名称,用于定位文档所在知识库 |
| `doc_type` | 文档类型:`pdf``word``excel` |
| `section` | 章节路径(已清洗,过滤掉正文内容) |
| `page` / `page_end` | 页码(仅 PDF |
| `bbox` / `bbox_mode` | 坐标定位(仅 PDF |
| `section_chunk_id` | 章节内段落序号(仅 Word |
**带 collections 参数**
```bash
echo '{"message":"三峡工程","chat_history":[],"collections":["public_kb"]}' > /tmp/rag.json
curl -s -X POST http://localhost:5001/rag \
-H "Content-Type: application/json" \
-d @/tmp/rag.json
```
> **⚠️ 重要**:指定知识库必须使用 `collections` 数组参数(如 `["dept_tech"]`),而非 `collection` 单数参数。
> 使用单数 `collection` 参数会被忽略,导致默认检索 `public_kb`。
**多向量库同时查询**
```bash
# 同时检索多个知识库
echo '{"message":"信息安全","collections":["dept_tech","public_kb"],"chat_history":[]}' > /tmp/rag.json
curl -s -X POST http://localhost:5001/rag \
-H "Content-Type: application/json" \
-d @/tmp/rag.json
```
**验证结果**:✅ 通过
---
### POST /chat
普通聊天模式(非知识库)。
```bash
echo '{"message":"你好","chat_history":[]}' > /tmp/chat.json
curl -s -X POST http://localhost:5001/chat \
-H "Content-Type: application/json" \
-d @/tmp/chat.json
```
**请求体**
| 字段 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `message` | string | ✅ | 用户消息 |
| `chat_history` | array | ❌ | 对话历史 |
**响应示例**
```json
{
"answer": "你好!很高兴见到你~",
"mode": "chat",
"sources": [],
"web_searched": false
}
```
**验证结果**:✅ 通过
---
## 4. 检索接口
### POST /search
混合检索(向量 + BM25供 Dify 工作流调用。
```bash
echo '{"query":"三峡工程","top_k":3}' > /tmp/search.json
curl -s -X POST http://localhost:5001/search \
-H "Content-Type: application/json" \
-d @/tmp/search.json
```
**请求体**
| 字段 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `query` | string | ✅ | 查询文本 |
| `top_k` | int | ❌ | 返回数量,默认 5 |
| `collections` | string[] | ❌ | 知识库列表,默认 `["public_kb"]` |
**响应示例**
```json
{
"contexts": ["综述\n三峡工程是国之重器..."],
"metadatas": [
{
"_collection": "public_kb",
"chunk_id": "三峡公报_1-15页.pdf_text_8",
"chunk_type": "text",
"collection": "public_kb",
"doc_type": "pdf",
"page": 5,
"page_end": 6,
"section": "三峡工程公报 > 综述",
"source": "三峡公报_1-15页.pdf",
"status": "active",
"version": "v1"
}
],
"scores": [0.9975]
}
```
**scores 说明**:相似度分数,范围 0-1越高越相关已从距离转换
**验证结果**:✅ 通过
---
## 5. 向量库管理
### GET /collections
获取向量库列表。
```bash
curl -s http://localhost:5001/collections
```
**响应示例**
```json
{
"collections": [
{
"created_at": "2026-06-02T21:35:43.839864",
"department": "",
"description": "所有人可访问的公开文档",
"display_name": "公开知识库",
"document_count": 824,
"name": "public_kb"
},
{
"created_at": "2026-06-04T06:15:52.556940",
"department": "",
"description": "",
"display_name": "dept_1_kb",
"document_count": 800,
"name": "dept_1_kb"
}
],
"total": 9
}
```
**验证结果**:✅ 通过
---
### POST /collections
创建新向量库。
```bash
echo '{"name":"test_kb","display_name":"测试库","department":"测试部","description":"测试描述"}' > /tmp/create.json
curl -s -X POST http://localhost:5001/collections \
-H "Content-Type: application/json" \
-d @/tmp/create.json
```
**请求体**
| 字段 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `name` | string | ✅ | 向量库名称(字母、数字、下划线、连字符) |
| `display_name` | string | ❌ | 显示名称 |
| `department` | string | ❌ | 所属部门 |
| `description` | string | ❌ | 描述 |
**响应示例**
```json
{
"message": "向量库 'test_kb' 创建成功",
"name": "test_kb",
"success": true
}
```
**验证结果**:✅ 通过
---
### PUT /collections/\<name\>
修改向量库信息。
```bash
echo '{"display_name":"新名称","description":"新描述"}' > /tmp/update.json
curl -s -X PUT http://localhost:5001/collections/test_kb \
-H "Content-Type: application/json" \
-d @/tmp/update.json
```
**请求体**
| 字段 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `display_name` | string | ❌ | 显示名称 |
| `description` | string | ❌ | 描述 |
**验证结果**:✅ 通过
---
### DELETE /collections/\<name\>
删除向量库。
```bash
curl -s -X DELETE http://localhost:5001/collections/test_kb
```
**查询参数**
| 参数 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `delete_documents` | boolean | ❌ | 是否删除文档源文件,默认 false |
**带参数示例**
```bash
curl -s -X DELETE "http://localhost:5001/collections/test_kb?delete_documents=true"
```
**验证结果**:✅ 通过
---
### GET /collections/\<kb_name\>/documents
获取向量库内的文档列表。
```bash
curl -s "http://localhost:5001/collections/public_kb/documents"
```
**响应示例**
```json
{
"collection": "public_kb",
"documents": [
{
"chunks": 105,
"source": "1.docx"
},
{
"chunks": 39,
"source": "三峡公报_1-15页.pdf"
}
],
"total": 9
}
```
**验证结果**:✅ 通过
---
### GET /collections/\<kb_name\>/chunks
获取向量库切片列表。
```bash
curl -s "http://localhost:5001/collections/public_kb/chunks?limit=2&offset=0"
```
**查询参数**
| 参数 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `document_id` | string | ❌ | 过滤指定文档的切片 |
| `limit` | int | ❌ | 返回数量,默认 100 |
| `offset` | int | ❌ | 偏移量,默认 0 |
**响应示例**
```json
{
"collection": "public_kb",
"chunks": [
{
"id": "三峡公报_1-15页.pdf_text_0",
"document": "三峡公报_1-15页.pdf",
"content": "切片内容...",
"metadata": {
"chunk_id": "三峡公报_1-15页.pdf_text_0",
"chunk_type": "text",
"collection": "public_kb",
"page": 1,
"source": "三峡公报_1-15页.pdf",
"status": "active",
"version": "v1"
},
"score": null
}
],
"total": 150
}
```
**验证结果**:✅ 通过
---
### GET /collections/\<kb_name\>/documents/\<filename\>/versions
获取文档版本历史。
```bash
# URL 编码文件名中的特殊字符
curl -s "http://localhost:5001/collections/public_kb/documents/%E4%B8%89%E5%B3%A1%E5%85%AC%E6%8A%A5_1-15%E9%A1%B5.pdf/versions?limit=5"
```
**查询参数**
| 参数 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `limit` | int | ❌ | 返回数量,默认 10 |
**响应示例**
```json
{
"success": true,
"document_id": "三峡公报_1-15页.pdf",
"collection": "public_kb",
"versions": [
{
"version": "v1",
"status": "active",
"effective_date": "2026-04-14T21:03:06",
"deprecated_date": null,
"chunk_count": 120
}
],
"total": 1
}
```
**验证结果**:✅ 通过
---
### POST /collections/\<kb_name\>/update-image-descriptions
更新图片切片描述(重新生成)。
```bash
curl -s -X POST "http://localhost:5001/collections/public_kb/update-image-descriptions"
```
**响应示例**
```json
{
"success": true,
"message": "图片描述更新完成",
"updated": 5
}
```
**验证结果**:✅ 通过
---
## 6. 文档管理
### GET /documents/list
获取文档列表。
```bash
curl -s "http://localhost:5001/documents/list?collection=public_kb"
```
**查询参数**
| 参数 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `collection` | string | ❌ | 知识库名称(也可用 `kb_name`),默认返回全部 |
> **注意**:该接口不支持分页,返回指定知识库的全部文档列表。
**响应示例**
```json
{
"documents": [
{
"collection": "public_kb",
"filename": "三峡公报_1-15页.pdf",
"last_modified": "2026-04-14T21:03:06",
"path": "public_kb/三峡公报_1-15页.pdf",
"size": 2773520
}
],
"total": 8
}
```
**验证结果**:✅ 通过
---
### GET /documents/\<path\>/status
获取文档处理状态。
```bash
# URL 编码路径中的斜杠
curl -s "http://localhost:5001/documents/public_kb%2Ftest.txt/status"
```
**响应示例**
```json
{
"success": true,
"status": "active",
"chunk_count": 105,
"last_processed": null
}
```
**验证结果**:✅ 通过
---
### POST /documents/upload
上传单个文件。
```bash
echo "测试文档内容" > /tmp/test.txt
curl -s -X POST http://localhost:5001/documents/upload \
-F "file=@/tmp/test.txt" \
-F "collection=public_kb"
```
**表单字段**
| 字段 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `file` | file | ✅ | 上传文件 |
| `collection` | string | ✅ | 目标知识库(也可用 `kb_name` |
**响应示例**
```json
{
"data": {
"file": {
"collection": "public_kb",
"filename": "test.txt",
"path": "public_kb/test.txt",
"size": 18,
"replaced": false
},
"sync_status": "已保存并添加到向量库"
},
"message": "文件上传成功,已保存并添加到向量库",
"status": "success",
"status_code": 2002,
"success": true
}
```
> **同名文件处理**:上传同名文件时,旧版本的切片会被自动清理后覆盖(`replaced: true`),不会生成时间戳后缀文件。
**验证结果**:✅ 通过
---
### POST /documents/batch-upload
批量上传文件。
```bash
echo "文件1内容" > /tmp/file1.txt
echo "文件2内容" > /tmp/file2.txt
curl -s -X POST http://localhost:5001/documents/batch-upload \
-F "files=@/tmp/file1.txt" \
-F "files=@/tmp/file2.txt" \
-F "collection=public_kb"
```
**响应示例**
```json
{
"data": {
"results": [
{"filename": "file1.txt", "path": "public_kb/file1.txt", "status": "success", "replaced": false},
{"filename": "file2.txt", "path": "public_kb/file2.txt", "status": "success", "replaced": false}
],
"success_count": 2,
"total": 2
},
"message": "批量上传完成,成功 2/2 个文件",
"status": "success",
"status_code": 2003,
"success": true
}
```
> **同名文件处理**:与单文件上传相同,批量上传中遇到同名文件也会自动覆盖旧版本(`replaced: true`)。
**验证结果**:✅ 通过
---
### PUT /documents/\<path\>
更新/替换文档。
```bash
echo "更新后的文档内容" > /tmp/updated.txt
curl -s -X PUT "http://localhost:5001/documents/public_kb%2Ftest.txt" \
-F "file=@/tmp/updated.txt"
```
**表单字段**
| 字段 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `file` | file | ✅ | 替换的文件 |
**响应示例**
```json
{
"success": true,
"message": "文件已更新"
}
```
**验证结果**:✅ 通过
---
### DELETE /documents/\<path\>
删除文档。
```bash
# URL 编码路径中的斜杠
curl -s -X DELETE "http://localhost:5001/documents/public_kb%2Ftest.txt"
```
**响应示例**
```json
{
"message": "文档已删除",
"success": true
}
```
**验证结果**:✅ 通过
---
### POST /collections/\<kb_name\>/documents/\<filename\>/deprecate
废弃文档版本。
```bash
curl -s -X POST "http://localhost:5001/collections/public_kb/documents/test.txt/deprecate"
```
**验证结果**:✅ 通过(接口存在)
---
### POST /collections/\<kb_name\>/documents/\<filename\>/restore
恢复文档版本。
```bash
curl -s -X POST "http://localhost:5001/collections/public_kb/documents/test.txt/restore"
```
**验证结果**:✅ 通过(接口存在)
---
### 🛠️ DEV GET /documents/\<path\>/preview
> **开发环境自用接口,后端组无需调用。**
> 仅供 dev-ui 内部前端实现引用溯源跳转使用。后端组可根据 `/rag` 返回的 `citations` 中的 `chunk_index`、`page`、`bbox` 等字段自行实现文档定位。
文档预览(支持按切片序号跳转),用于引用溯源点击后定位到文档中的具体位置。
```bash
# 跳转到指定切片(前后各取 2 个上下文切片)
curl -s "http://localhost:5001/documents/public_kb%2F1.docx/preview?chunk_index=33&context=2"
# 不指定 chunk_index返回前 5 个切片作为概览
curl -s "http://localhost:5001/documents/public_kb%2F1.docx/preview"
```
**查询参数**
| 参数 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `chunk_index` | int | ✅ | 目标切片序号(来自 citations 的 `chunk_index` 字段) |
| `context` | int | ✅ | 上下文切片数,默认 2前后各取 2 个) |
**响应示例**
```json
{
"success": true,
"collection": "public_kb",
"source": "1.docx",
"total_chunks": 105,
"target_index": 33,
"chunks": [
{"id": "1.docx_31", "content": "...", "metadata": {...}, "is_target": false},
{"id": "1.docx_32", "content": "...", "metadata": {...}, "is_target": false},
{"id": "1.docx_33", "content": "...", "metadata": {...}, "is_target": true},
{"id": "1.docx_34", "content": "...", "metadata": {...}, "is_target": false},
{"id": "1.docx_35", "content": "...", "metadata": {...}, "is_target": false}
]
}
```
> **说明**`is_target: true` 的切片即为引用定位目标,前端可高亮显示并滚动到该位置。
---
## 7. 切片管理
### GET /documents/\<path\>/chunks
获取文档切片列表。
```bash
# URL 编码路径
curl -s "http://localhost:5001/documents/dept_hr%2F%E4%BA%BA%E5%91%98%E5%90%8D%E5%86%8C.txt/chunks"
```
> **注意**:该接口不支持分页或过滤参数,返回指定文档的全部切片。
**响应示例**
```json
{
"chunks": [
{
"document": "# 智启科技有限公司人员名册\n\n**更新日期**2024年6月...",
"id": "人员名册.txt_text_0",
"metadata": {
"chunk_id": "人员名册.txt_text_0",
"chunk_index": 0,
"chunk_type": "text",
"collection": "dept_hr",
"doc_type": "other",
"page": 1,
"page_end": 1,
"preview": "# 智启科技有限公司人员名册...",
"section": "人员名册",
"source": "人员名册.txt",
"has_table": false,
"status": "active",
"version": "v1"
},
"status": "active",
"version": "v1"
}
],
"collection": "dept_hr",
"document_id": "dept_hr/人员名册.txt",
"success": true,
"total": 4
}
```
**切片字段说明**
| 字段 | 说明 |
|------|------|
| `id` / `chunk_id` | 切片唯一标识,格式:`{文件名}_{类型}_{序号}` |
| `document` | **切片完整内容**(重要:用于增删改查) |
| `chunk_type` | 切片类型:`text`(文本)、`table`(表格)、`image`(图片) |
| `source` | 来源文件名 |
| `page` / `page_end` | 起止页码 |
| `section` | 所属章节路径 |
| `preview` | 内容预览(截断版,用于列表展示) |
| `has_table` | 是否包含表格 |
**验证结果**:✅ 通过
---
### POST /chunks
新增切片。
```bash
echo '{"collection":"public_kb","content":"测试切片内容","metadata":{"section":"测试章节"}}' > /tmp/chunk.json
curl -s -X POST http://localhost:5001/chunks \
-H "Content-Type: application/json" \
-d @/tmp/chunk.json
```
**请求体**
| 字段 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `collection` | string | ✅ | 向量库名称 |
| `content` | string | ✅ | 切片内容 |
| `metadata` | object | ❌ | 元数据 |
**验证结果**:✅ 通过
---
### PUT /chunks/\<id\>
修改切片。
```bash
echo '{"collection":"dept_hr","content":"更新后的内容"}' > /tmp/update_chunk.json
curl -s -X PUT "http://localhost:5001/chunks/人员名册.txt_text_0" \
-H "Content-Type: application/json" \
-d @/tmp/update_chunk.json
```
**请求体**
| 字段 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `collection` | string | ✅ | 向量库名称 |
| `content` | string | ❌ | 新内容 |
| `metadata` | object | ❌ | 新元数据 |
**验证结果**:✅ 通过
---
### DELETE /chunks/\<id\>
删除切片。
```bash
curl -s -X DELETE "http://localhost:5001/chunks/人员名册.txt_text_0?collection=dept_hr"
```
**查询参数**
| 参数 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `collection` | string | ✅ | 向量库名称 |
**验证结果**:✅ 通过
---
## 8. 同步服务
### POST /sync
触发文档同步。
```bash
curl -s -X POST http://localhost:5001/sync \
-H "Content-Type: application/json"
```
**响应示例**
```json
{
"data": {
"result": {
"documents_added": 1,
"documents_deleted": 1,
"documents_modified": 0,
"documents_processed": 2,
"end_time": "2026-05-04T01:45:42",
"errors": [],
"start_time": "2026-05-04T01:45:41",
"status": "completed"
}
},
"message": "同步完成",
"status": "success",
"status_code": 2010,
"success": true
}
```
**验证结果**:✅ 通过
---
### GET /sync/status
获取同步状态。
```bash
curl -s http://localhost:5001/sync/status
```
**响应示例**
```json
{
"documents_tracked": 0,
"enabled": true,
"last_sync": null,
"monitoring": false
}
```
**验证结果**:✅ 通过
---
### GET /sync/history
获取同步历史。
```bash
curl -s http://localhost:5001/sync/history
```
**响应示例**
```json
{
"history": []
}
```
**验证结果**:✅ 通过
---
### GET /sync/changes
获取待同步变更。
```bash
curl -s http://localhost:5001/sync/changes
```
**响应示例**
```json
{
"changes": []
}
```
**验证结果**:✅ 通过
---
### POST /sync/start
启动文件监控。
```bash
curl -s -X POST http://localhost:5001/sync/start
```
**响应示例**
```json
{
"message": "文件监控已启动",
"status": "success",
"status_code": 2010
}
```
**验证结果**:✅ 通过
---
### POST /sync/stop
停止文件监控。
```bash
curl -s -X POST http://localhost:5001/sync/stop
```
**响应示例**
```json
{
"message": "文件监控已停止",
"status": "success",
"status_code": 2010
}
```
**验证结果**:✅ 通过
---
## 9. 反馈系统
### POST /feedback
提交反馈。
```bash
echo '{"session_id":"test-session","query":"测试问题","answer":"测试回答","rating":1,"reason":"测试原因"}' > /tmp/feedback.json
curl -s -X POST http://localhost:5001/feedback \
-H "Content-Type: application/json" \
-d @/tmp/feedback.json
```
**请求体**
| 字段 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `session_id` | string | ✅ | 会话 ID |
| `query` | string | ✅ | 用户问题 |
| `answer` | string | ❌ | 系统回答(可为空) |
| `rating` | int | ✅ | 评分1=赞,-1=踩) |
| `reason` | string | ❌ | 原因 |
**响应示例**
```json
{
"faq_suggested": true,
"feedback_id": 7,
"success": true,
"suggestion_id": 6
}
```
**验证结果**:✅ 通过
---
### GET /feedback/list
获取反馈列表。
```bash
curl -s "http://localhost:5001/feedback/list?limit=5"
```
**查询参数**
| 参数 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `limit` | int | ❌ | 返回数量,默认 100 |
| `rating` | int | ❌ | 按评分过滤1=赞,-1=踩) |
| `user_id` | string | ❌ | 按用户 ID 过滤 |
| `start_date` | string | ❌ | 起始日期 |
| `end_date` | string | ❌ | 结束日期 |
> **注意**:该接口不支持 `page`/`page_size` 分页,使用 `limit` 控制返回数量。
**响应示例**
```json
{
"feedbacks": [
{
"answer": "测试回答",
"created_at": "2026-05-04T01:45:35",
"id": 7,
"query": "测试问题",
"rating": 1,
"reason": "测试原因",
"session_id": "test-session",
"sources": [],
"user_id": ""
}
],
"success": true,
"total": 7
}
```
**验证结果**:✅ 通过
---
### GET /feedback/stats
获取反馈统计。
```bash
curl -s http://localhost:5001/feedback/stats
```
**响应示例**
```json
{
"stats": {
"avg_rating": 1.0,
"negative_count": 0,
"positive_count": 8,
"satisfaction_rate": 100.0,
"total_feedback": 8
},
"success": true
}
```
**验证结果**:✅ 通过
---
### GET /feedback/bad-cases
获取差评案例列表。
```bash
curl -s http://localhost:5001/feedback/bad-cases
```
**响应示例**
```json
{
"bad_cases": [],
"blacklisted_sources": [],
"success": true,
"suggestions": [
"补充到知识库(针对知识盲区)",
"添加到 Query Rewrite 规则(针对表达歧义)"
]
}
```
**验证结果**:✅ 通过
---
### GET /feedback/blacklist
获取切片黑名单。
```bash
curl -s http://localhost:5001/feedback/blacklist
```
**响应示例**
```json
{
"blacklist": [],
"count": 0,
"success": true,
"usage": "在检索时过滤这些来源以提升回答质量"
}
```
**验证结果**:✅ 通过
---
## 10. FAQ 管理
### GET /faq
获取 FAQ 列表。
```bash
curl -s "http://localhost:5001/faq?limit=5"
```
**查询参数**
| 参数 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `status` | string | ❌ | 按状态过滤(如 `approved``draft` |
| `limit` | int | ❌ | 返回数量,默认 50 |
**响应示例**
```json
{
"faqs": [
{
"answer": "根据参考资料...",
"avg_rating": 1.0,
"created_at": "2026-04-19T11:27:15",
"frequency": 1,
"id": 1,
"question": "C类吸烟区域",
"source_documents": null,
"status": "approved",
"updated_at": "2026-04-19T11:27:15"
}
],
"success": true,
"total": 3
}
```
**验证结果**:✅ 通过
---
### POST /faq
创建新 FAQ。
```bash
curl -s -X POST http://localhost:5001/faq \
-H "Content-Type: application/json" \
-d "{\"question\":\"FAQ question\",\"answer\":\"FAQ answer\"}"
```
**请求体**
| 字段 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `question` | string | ✅ | 问题内容 |
| `answer` | string | ✅ | 答案内容 |
**响应示例**
```json
{
"faq_id": 7,
"message": "FAQ已创建请通过 /faq/<id>/approve 接口确认后生效",
"status": "draft",
"success": true
}
```
**验证结果**:✅ 通过
---
### PUT /faq/\<id\>
修改 FAQ。
```bash
curl -s -X PUT "http://localhost:5001/faq/7" \
-H "Content-Type: application/json" \
-d "{\"question\":\"updated question\",\"answer\":\"updated answer\"}"
```
**请求体**
| 字段 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `question` | string | ❌ | 新问题内容 |
| `answer` | string | ❌ | 新答案内容 |
**响应示例**
```json
{
"message": "FAQ更新成功",
"success": true,
"sync_status": "skipped"
}
```
**验证结果**:✅ 通过
---
### DELETE /faq/\<id\>
删除 FAQ。
```bash
curl -s -X DELETE "http://localhost:5001/faq/7"
```
**响应示例**
```json
{
"message": "FAQ删除成功",
"success": true
}
```
**验证结果**:✅ 通过
---
### GET /faq/suggestions
获取 FAQ 建议列表。
```bash
curl -s "http://localhost:5001/faq/suggestions?status=pending&limit=5"
```
**查询参数**
| 参数 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `status` | string | ❌ | 按状态过滤,默认 `pending` |
| `limit` | int | ❌ | 返回数量,默认 50 |
**响应示例**
```json
{
"success": true,
"suggestions": [
{
"answer": "",
"avg_rating": 1.0,
"created_at": "2026-04-29T20:49:38",
"frequency": 2,
"id": 5,
"query": "test",
"status": "pending"
}
],
"total": 3
}
```
**验证结果**:✅ 通过
---
### POST /faq/suggestions/\<id\>/approve
批准 FAQ 建议(需 admin 角色)。
```bash
curl -s -X POST "http://localhost:5001/faq/suggestions/6/approve" \
-H "Content-Type: application/json" \
-d '{}'
```
**请求体**(可选,用于修改答案):
```json
{"answer": "管理员修改后的答案"}
```
**注意**:必须传递请求体(至少空对象 `{}`),否则返回 400 错误。
**验证结果**:✅ 通过
---
### POST /faq/suggestions/\<id\>/reject
拒绝 FAQ 建议(需 admin 角色)。
```bash
curl -s -X POST "http://localhost:5001/faq/suggestions/6/reject" \
-H "Content-Type: application/json" \
-d '{}'
```
**验证结果**:✅ 通过
---
## 11. 出题系统
### 出题架构说明v2
v2 重构后采用 **4 阶段出题架构**,确保题目质量和多样性:
| 阶段 | 说明 | 改进点 |
|------|------|--------|
| Phase 1 | 文档结构分析 | 按章节分组,统计内容量 |
| Phase 2 | 知识点规划 | 全局唯一,避免跨章节重复 |
| Phase 3 | 精准出题 | 每个知识点单独检索,确保上下文充足 |
| Phase 4 | 质量校验 | 去重 + 数量校正,零重复率 |
**支持的题型**
| 题型 key | 中文名 | answer 格式 |
|----------|--------|-------------|
| `single_choice` | 单选题 | 单个选项字母(如 "B" |
| `multiple_choice` | 多选题 | 选项字母列表(如 ["A","B"] |
| `true_false` | 判断题 | 布尔值或字符串(如 true/"true" |
| `fill_blank` | 填空题 | 嵌套列表(如 [["答案1"],["答案2"]] |
| `subjective` | 简答题 | 参考答案字符串 |
---
### GET /exam/health
出题服务健康检查。
```bash
curl -s http://localhost:5001/exam/health
```
**响应示例**
```json
{
"service": "exam-api",
"status": "ok",
"version": "2.0"
}
```
**验证结果**:✅ 通过
---
### POST /exam/generate
生成考题(指定题型和数量)。
**特点**
- 需要手动指定 `question_types`(每种题型的数量)
- 适合有明确需求的场景(如"出 5 道单选题"
```bash
curl -s -X POST http://localhost:5001/exam/generate \
-H "Content-Type: application/json" \
-d '{"file_path":"public_kb/1.docx","collection":"public_kb","question_types":{"single_choice":5,"true_false":3,"fill_blank":2}}'
```
**请求体**
| 字段 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `file_path` | string | ✅ | 文档路径(格式:`collection/filename` |
| `collection` | string 或 string[] | ✅ | 知识库名称,支持数组(按优先级排序) |
| `question_types` | object | ✅ | 题型数量配置 |
| `difficulty` | int | ❌ | 难度等级1-5默认 3 |
| `options` | object | ❌ | 附加选项(如 `max_source_chunks` |
| `request_id` | string | ❌ | 请求 ID幂等性 |
> **注意**:生产模式(`DEV_MODE=false`)下无需传 `Authorization` header直接放行。开发模式下可传 `Authorization: Bearer mock-token-admin` 模拟管理员身份。
**question_types 配置示例**
```json
{
"single_choice": 5,
"multiple_choice": 2,
"true_false": 3,
"fill_blank": 2,
"subjective": 1
}
```
**响应示例**
```json
{
"data": {
"questions": [
{
"content": {
"stem": "重购率的定义是下列哪一项?",
"answer": "B",
"data": {
"options": [
{"content": "选项A内容", "key": "A"},
{"content": "品规连续两周订货客户数/上周订货客户数*100%", "key": "B"}
]
},
"explanation": "重购率的定义是..."
},
"difficulty": 3,
"question_type": "single_choice",
"source_trace": {
"chunk_ids": ["1.docx_76"],
"document_name": "public_kb/1.docx",
"page_numbers": [1],
"sources": [...]
}
}
],
"request_id": null,
"source_chunks_used": 15,
"success": true,
"total": 10
},
"message": "出题成功",
"status": "success",
"status_code": 2020,
"success": true
}
```
**验证结果**:✅ 通过
---
### POST /exam/generate-smart
AI 智能出题 - 自动分析文件并决定题型和数量。
**特点**
- 不需要传 `question_types`AI 自动分析文档后决定
- 返回格式与旧端口完全兼容,额外包含 `ai_analysis` 字段
- 适合不想费脑子的场景
```bash
curl -s -X POST http://localhost:5001/exam/generate-smart \
-H "Content-Type: application/json" \
-d '{"file_path":"public_kb/1.docx","collection":"public_kb"}'
```
**请求体**
| 字段 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `file_path` | string | ✅ | 文档路径(格式:`collection/filename` |
| `collection` | string 或 string[] | ✅ | 知识库名称,支持数组(按优先级排序) |
| `difficulty` | int | ❌ | 难度等级1-5默认 3 |
| `options` | object | ❌ | 附加选项 |
**响应示例**
```json
{
"data": {
"ai_analysis": {
"total_knowledge_points": 21,
"suitable_types": ["single_choice", "multiple_choice", "true_false", "subjective"],
"question_types": {
"single_choice": 8,
"multiple_choice": 6,
"true_false": 4,
"fill_blank": 0,
"subjective": 3
},
"reason": "文档包含21个知识点涵盖术语定义、流程步骤、数值标准..."
},
"questions": [...],
"request_id": null,
"source_chunks_used": 15,
"success": true,
"total": 16
},
"message": "AI 智能出题成功",
"status": "success",
"status_code": 2020,
"success": true
}
```
**注意事项**
- 实际出题数量 ≤ min(文档知识点数, AI 推荐数量)
- 如果文档知识点较少,生成的题目数量会相应减少
- 生产模式下无需传 `Authorization` header
**支持的题型**
| 题型 key | 中文名 | 说明 |
|----------|--------|------|
| `single_choice` | 单选题 | content.answer 为单个选项字母 |
| `multiple_choice` | 多选题 | content.answer 为选项字母列表 |
| `true_false` | 判断题 | content.answer 为布尔值或字符串 |
| `fill_blank` | 填空题 | content.answer 为嵌套列表 |
| `subjective` | 简答题 | content.answer 为参考答案 |
**验证结果**:✅ 通过
---
### POST /exam/grade
批阅答案。
```bash
echo '{"answers":[{"question_id":"q1","question_type":"single_choice","question_content":{"answer":"B"},"student_answer":"B","max_score":2}]}' > /tmp/grade.json
curl -s -X POST http://localhost:5001/exam/grade \
-H "Content-Type: application/json" \
-d @/tmp/grade.json
```
**请求体**
| 字段 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `answers` | array | ✅ | 答案数组 |
**answers 数组元素**
| 字段 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `question_id` | string | ✅ | 题目 ID |
| `question_type` | string | ✅ | 题型 |
| `question_content` | object | ✅ | 题目内容(含标准答案) |
| `student_answer` | string/array | ✅ | 学生答案 |
| `max_score` | int | ✅ | 满分 |
**响应示例**
```json
{
"data": {
"request_id": null,
"results": [
{
"correct": true,
"feedback": "正确!",
"max_score": 2,
"question_id": "q1",
"score": 2
}
],
"score_rate": 100.0,
"success": true,
"total_max_score": 2,
"total_score": 2
},
"message": "批阅完成",
"status": "success",
"status_code": 2021,
"success": true
}
```
**验证结果**:✅ 通过
---
## 12. 图片服务
### GET /images/list
获取图片列表。
```bash
curl -s "http://localhost:5001/images/list?limit=5&offset=0"
```
**查询参数**
| 参数 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `limit` | int | ❌ | 返回数量,默认 50 |
| `offset` | int | ❌ | 偏移量,默认 0 |
**响应示例**
```json
{
"images": [
{
"image_id": "0569dd285537",
"size_bytes": 169818,
"url": "/images/0569dd285537"
}
],
"limit": 5,
"offset": 0,
"total": 67
}
```
**验证结果**:✅ 通过
---
### GET /images/\<id\>
获取单张图片。
```bash
curl -s -o image.jpg http://localhost:5001/images/0569dd285537
```
**响应**返回图片文件HTTP 200
**验证结果**:✅ 通过
---
### GET /images/\<id\>/info
获取图片元数据。
```bash
curl -s "http://localhost:5001/images/0569dd285537/info"
```
**响应示例**
```json
{
"format": "JPEG",
"height": 1357,
"image_id": "0569dd285537",
"mode": "RGB",
"size_bytes": 169818,
"url": "/images/0569dd285537",
"width": 1023
}
```
**验证结果**:✅ 通过
---
### GET /images/stats
获取图片统计。
```bash
curl -s http://localhost:5001/images/stats
```
**响应示例**
```json
{
"format_counts": {
".jpg": 46,
".png": 21
},
"supported_formats": [".jpeg", ".gif", ".bmp", ".jpg", ".webp", ".png"],
"total_images": 67,
"total_size_bytes": 5394510,
"total_size_mb": 5.14
}
```
**验证结果**:✅ 通过
---
## 13. 报告服务
### GET /reports/weekly
获取周报。
```bash
curl -s http://localhost:5001/reports/weekly
```
**响应示例**
```json
{
"report": {
"avg_rating": 1.0,
"created_at": "2026-05-04T01:49:16",
"end_date": "2026-05-10",
"high_freq_queries": [
{"frequency": 1, "query": "测试问题", "sample_answer": "测试回答"}
],
"improvement_suggestions": ["继续保持当前服务质量"],
"low_rating_queries": [],
"negative_count": 0,
"positive_count": 1,
"report_type": "weekly",
"satisfaction_rate": 100.0,
"start_date": "2026-05-04",
"total_feedback": 1,
"total_queries": 1
},
"success": true
}
```
**验证结果**:✅ 通过
---
### GET /reports/monthly
获取月报。
```bash
curl -s http://localhost:5001/reports/monthly
```
**验证结果**:✅ 通过(接口存在)
---
## 14. 知识库路由
### POST /kb/route
测试知识库路由(调试用)。
```bash
echo '{"query":"三峡工程"}' > /tmp/route.json
curl -s -X POST http://localhost:5001/kb/route \
-H "Content-Type: application/json" \
-d @/tmp/route.json
```
**响应示例**
```json
{
"intent": {
"confidence": 0.95,
"department": null,
"is_general": true,
"keywords": [],
"reason": "LLM 意图分析"
},
"query": "三峡工程",
"target_collections": ["public_kb"],
"user_department": "",
"user_role": "user"
}
```
**验证结果**:✅ 通过
---
## 附录. 已知问题
### 1. /rag 接口 collections 参数
**问题**:使用 `collection`(单数)参数指定知识库无效,会被忽略并默认检索 `public_kb`
**解决**:必须使用 `collections`(复数,数组格式)参数。
```bash
# ❌ 错误用法 - collection 单数参数无效
{"message": "问题", "collection": "dept_tech", "chat_history": []}
# ✅ 正确用法 - collections 数组参数
{"message": "问题", "collections": ["dept_tech"], "chat_history": []}
```
---
## 测试覆盖统计
> 2026-06-04 生产服务器测试结果47.116.16.222,云端 Reranker + MMR 文本相似度)
| 分类 | 总数 | 通过 | 超时/异常 | 备注 |
|------|------|------|-----------|------|
| 健康检查 | 1 | 1 | 0 | ✅ 0.002s |
| 问答接口 | 2 | 2 | 0 | ✅ /rag 流式 12-14s |
| 检索接口 | 1 | 1 | 0 | ✅ /search 0.58-1.53s |
| 向量库管理 | 3 | 3 | 0 | ✅ |
| 文档管理 | 1 | 1 | 0 | ✅ |
| 同步服务 | 3 | 3 | 0 | ✅ |
| 反馈系统 | 4 | 4 | 0 | ✅ |
| FAQ 管理 | 2 | 2 | 0 | ✅ |
| 出题系统 | 1 | 1 | 0 | ✅ |
| 图片服务 | 2 | 2 | 0 | ✅ |
| 报告服务 | 2 | 2 | 0 | ✅ |
| 知识库路由 | 1 | 1 | 0 | ✅ |
| **总计** | **23** | **23** | **0** | **全部通过** |
---
## 性能基准测试
> 2026-06-04 生产服务器实测47.116.16.222public_kb 824 切片)
### 检索性能
| 接口 | 参数 | 优化前 | 优化后 | 提升 |
|------|------|--------|--------|------|
| `/search` | top_k=10 | ~34s | **0.58s** | ~60x |
| `/search` | top_k=30 | ~87s | **1.53s** | ~57x |
### 完整 RAG 问答性能(流式)
| 查询 | 完整响应时间 | 搜索阶段 | LLM 生成 |
|------|-------------|----------|----------|
| 现代终端分类有哪些? | 13.8s | ~1.5s | ~12s |
| 加盟终端分公司审批流程 | 12.7s | ~1.5s | ~11s |
| 什么是金丝利零售 | 12.7s | ~1.5s | ~11s |
### 优化措施
| 优化项 | 配置 | 效果 |
|--------|------|------|
| Reranker 云端化 | `RERANK_BACKEND=cloud`qwen3-rerank | 搜索阶段从 33-48s 降至 ~0.3s |
| MMR 文本相似度 | `MMR_USE_EMBEDDING=false` | MMR 阶段从 ~36s 降至 ~0s |
### 耗时拆解(优化后 /rag 问答)
```
总耗时 ~13s
├── 搜索管线 ~1.5s
│ ├── Embedding (query) ~0.25s
│ ├── 向量检索 + BM25 ~0.3s
│ ├── RRF 融合 ~0.01s
│ ├── 上下文扩展 ~0.1s
│ ├── MMR 去重 (文本) ~0.01s
│ ├── Cloud Reranker ~0.3s
│ └── 后处理 ~0.5s
└── LLM 流式生成 ~11.5s
├── 网络建连 ~0.5s
└── 流式 token 生成 ~11s
```
> **注**LLM 生成阶段耗时取决于 qwen-plus API 响应速度,非本地可优化。如需进一步压缩至 10s 以内,可考虑换用更快的模型(如 qwen-turbo
---
## 附录 B. 生产部署说明
### 多向量库架构
生产环境采用**多向量库模式**,每个知识库独立存储在 `knowledge/vector_store/chroma/<kb_name>/` 目录下,各自维护独立的 ChromaDB SQLite 数据库和 BM25 索引。
当前生产服务器知识库列表:
| 知识库 | 切片数 | 说明 |
|--------|--------|------|
| public_kb | ~824 | 公开知识库,所有用户可访问 |
| dept_1_kb | ~800 | 部门知识库 1 |
| dept_2_kb | ~118 | 部门知识库 2 |
| dept_3_kb | ~132 | 部门知识库 3 |
| dept_4_kb | ~4 | 部门知识库 4 |
| dept_6_kb | ~29 | 部门知识库 6 |
| test1 | ~637 | 测试知识库 |
| faq_kb / test3 | 0 | 空(预留) |
### 切片元数据字段
新代码构建的向量库切片包含以下 metadata 字段:
| 字段 | 说明 | 新代码新增 |
|------|------|-----------|
| `chunk_id` | 切片唯一标识 `{文件名}_{序号}` | |
| `chunk_index` | 切片在文档中的序号(用于引用溯源跳转) | ✅ |
| `chunk_type` | text / table / image | |
| `source` | 来源文件名 | |
| `collection` | 所属知识库 | |
| `doc_type` | 文档类型 pdf/word/excel/other | ✅ |
| `page` / `page_end` | 页码范围 | |
| `section` | 所属章节 | |
| `status` | active / deprecated | |
| `version` | 版本号 v1 | |
### 向量库重构
当代码升级涉及元数据字段变更时(如新增 `chunk_index``doc_type`),需要重构向量库:
```bash
# 重构指定知识库(清除哈希记录 → 触发全量同步)
curl -s -X POST http://127.0.0.1:5001/collections/<kb_name>/reindex
```
> **⚠️ 注意**reindex 会调用 `sync_now()` 全局同步,期间 gunicorn worker 被阻塞,搜索和问答接口将暂时无响应。建议在低峰期执行。
### Reranker 配置
生产环境使用 DashScope 云端 Reranker 替代本地 CPU 推理,显著提升检索速度:
| 配置项 | 值 | 说明 |
|--------|-----|------|
| `RERANK_BACKEND` | `cloud` | 使用云端 API可选 `local` / `cloud` / `fallback` |
| `RERANK_CLOUD_MODEL` | `qwen3-rerank` | DashScope 云端排序模型 |
| `RERANK_CLOUD_API_KEY` | `sk-*` | DashScope 标准 API Key |
| `RERANK_CLOUD_BASE_URL` | `https://dashscope.aliyuncs.com/compatible-api/v1/reranks` | OpenAI 兼容端点 |
| `MMR_USE_EMBEDDING` | `false` | MMR 使用文本相似度(零额外计算) |
> **fallback 模式**`RERANK_BACKEND=fallback` 优先使用云端 API如果云端不可用则自动回退到本地模型适合生产环境高可用场景。