Files
rag/docs/后端对接规范.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

2788 lines
73 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 接口规范
---
## 📋 变更记录2026-05-28 更新)
> **本次更新内容**:新增 AI 智能出题端口、更新生产环境测试结果
### 新增端口
| 端点 | 方法 | 功能 | 说明 |
|-----|------|------|------|
| `/exam/generate-smart` | POST | AI 智能出题 | 自动分析文档决定题型和数量 |
| `/images/<image_id>` | GET | 获取图片 | 返回图片文件 |
| `/images/list` | GET | 图片列表 | 返回所有图片列表 |
| `/images/stats` | GET | 图片统计 | 返回图片数量和大小统计 |
| `/feedback/stats` | GET | 反馈统计 | 返回反馈统计数据 |
| `/reports/weekly` | GET | 周报告 | 返回周度反馈报告 |
| `/reports/monthly` | GET | 月报告 | 返回月度反馈报告 |
| `/feedback/bad-cases` | GET | 差评案例 | 返回差评反馈列表 |
| `/faq/suggestions` | GET | FAQ 建议 | 返回 FAQ 建议列表 |
| `/kb/route` | POST | 路由测试 | 测试知识库路由逻辑(调试用) |
| `/collections/<kb_name>/update-image-descriptions` | POST | 更新图片描述 | 重新生成图片切片描述 |
### 删除的端口(已废弃)
| 端点 | 原因 |
|------|------|
| `/rag/stream` | 已合并到 `/rag`SSE 流式返回) |
| `/graph/*` | Graph RAG 功能未启用,已删除 |
| `/outline/*` | 纲要生成功能未启用,已删除 |
| `/questions/*` | 题库维护功能未启用,已删除 |
### 修复的 Bug
| 文件 | 问题 | 修复 |
|------|------|------|
| `api/kb_routes.py` | `/documents/sync` 端口报错 `NameError: name 'current_app' is not defined` | 添加 `current_app` 导入 |
### 生产环境测试结果
所有核心端口在 `DEV_MODE=false` 生产模式下测试通过:
| 端点 | 状态码 | 说明 |
|-----|--------|------|
| `/health` | 200 | ✅ 正常 |
| `/rag` | 200 | ✅ 正常SSE 流式) |
| `/search` | 200 | ✅ 正常 |
| `/collections` | 200 | ✅ 正常 |
| `/documents/list` | 200 | ✅ 正常 |
| `/sync` | 200 | ✅ 正常 |
| `/sync/status` | 200 | ✅ 正常 |
| `/feedback` | 200 | ✅ 正常 |
| `/faq` | 200 | ✅ 正常 |
| `/images/list` | 200 | ✅ 正常 |
| `/documents/sync` | 200 | ✅ 正常(已修复) |
---
## 一、服务概述
RAG服务负责
- **向量检索**ChromaDB向量数据库 + BM25关键词检索
- **文档解析**PDF/Word/Excel解析与分块
- **知识库问答**Agentic RAG问答引擎
- **出题批阅**本地LLM实现可选
- **反馈系统**用户反馈与FAQ管理
后端服务负责:
- 用户认证与权限控制
- 会话管理与对话历史
- 业务数据存储
---
## 二、API接口清单
> **接口分类说明**
> - **生产接口**:后端组对接时需调用的接口,文档中详细说明请求/响应格式
> - **开发自用接口**(标记为 `🛠️ DEV`):仅供 RAG 服务内部开发调试使用(如 dev-ui 前端),**后端组无需调用**
>
> **引用溯源职责边界**RAG 服务的 `/rag` 接口确保返回的 `citations` 数据包含足够的位置信息(`chunk_index`、`section`、`page`、`bbox` 等),使后端组和 dev-ui 能够各自实现文件跳转功能。具体的跳转实现细节由各团队自行负责。
### 2.1 核心接口(必需对接)
| 端点 | 方法 | 功能 | 说明 |
|-----|------|------|------|
| `/health` | GET | 健康检查 | 服务状态监控 |
| `/rag` | POST | RAG问答SSE | 流式返回,推荐使用 |
| `/search` | POST | 混合检索 | 知识库检索 |
### 2.2 知识库管理(可选)
| 端点 | 方法 | 功能 |
|-----|------|------|
| `/collections` | GET | 向量库列表 |
| `/collections` | POST | 创建向量库 |
| `/collections/<name>` | DELETE | 删除向量库 |
| `/documents/upload` | POST | 上传文档 |
| `/documents/list` | GET | 文档列表 |
| `/documents/<path>` | DELETE | 删除文档 |
### 2.3 反馈系统(可选)
| 端点 | 方法 | 功能 |
|-----|------|------|
| `/feedback` | POST | 提交反馈 |
| `/feedback/list` | GET | 反馈列表 |
| `/faq` | GET/POST | FAQ管理 |
### 2.4 出题系统(可选)
| 端点 | 方法 | 功能 |
|-----|------|------|
| `/exam/generate` | POST | 生成题目 |
| `/exam/grade` | POST | 批阅答案 |
---
## 三、调用方式
### 3.1 RAG问答核心接口
**请求**
```json
POST /rag
Content-Type: application/json
{
"message": "用户问题",
"collections": ["kb1", "kb2"],
"chat_history": [
{"role": "user", "content": "之前的问题"},
{"role": "assistant", "content": "之前的回答"}
]
}
```
**参数说明**
| 参数 | 必需 | 说明 |
|-----|------|------|
| `message` | ✅ | 用户问题 |
| `collections` | ⚠️ | 用户可访问的知识库列表(权限控制),不传时默认 `["public_kb"]` |
| `chat_history` | ⚠️ | 对话历史(**生产环境必需**,首次对话传 `[]` |
| `history` | ❌ | 对话历史(旧参数名,与 `chat_history` 等效) |
| `session_id` | ❌ | 会话标识(可选,用于日志追踪) |
> **注意**:生产环境(`APP_ENV=prod`)必须传递 `chat_history` 参数,即使是首次对话也要传空数组 `[]`。开发环境可省略。
**响应SSE流式**
```
data: {"type": "start", "message": "正在检索知识库..."}
data: {"type": "sources", "sources": [...]}
data: {"type": "chunk", "content": "回答"}
data: {"type": "chunk", "content": "内容"}
...
data: {"type": "finish", "answer": "完整答案", "session_id": "xxx", "sources": [...], "images": [...], "duration_ms": 1500}
```
**SSE 事件类型**
| 事件类型 | 说明 |
|---------|------|
| `start` | 开始处理,可显示加载状态 |
| `sources` | 检索到的来源,可提前展示溯源 |
| `chunk` | 每个 token用于打字机效果 |
| `finish` | **完整响应对象**,包含完整答案用于存储 |
| `error` | 错误事件,显示错误提示 |
### 3.2 权限控制
**通过 `collections` 参数控制**
- 后端根据用户权限生成可访问的知识库列表
- RAG服务只检索用户有权访问的知识库
- 无需传递用户Header
---
## 四、环境配置
### 4.1 必需配置
```env
DASHSCOPE_API_KEY=your-api-key
```
### 4.2 环境模式配置
```env
# 生产环境:关闭开发模式(认证由后端控制,通过 collections 传参)
DEV_MODE=false
# 应用环境标识(控制会话存储、审计日志等功能开关)
APP_ENV=prod
```
> **说明**`DEV_MODE` 控制认证行为(`true`=开发模式支持 mock token`false`=生产模式直接放行)。`APP_ENV` 控制功能模块开关(`dev`=启用会话存储和审计日志,`prod`=无状态模式)。两者独立配置。
### 4.3 可选配置
```env
ENABLE_WEB_SEARCH=false
ENABLE_GRAPH_RAG=false
ENABLE_DIFY_WORKFLOW=false
```
---
## 五、部署要求
| 项目 | 要求 |
|-----|------|
| 端口 | 5001 |
| Python | 3.10+ |
| 内存 | 建议8GB+ |
| 部署方式 | Docker / Gunicorn |
---
## 六、数据流
```
前端 → Java后端(8080) → RAG服务(5001)
├── 生成 collections 列表(权限控制)
├── 传入 chat_history对话历史
└── 调用 RAG API
```
---
**最后更新**: 2026-04-29
> 本文档供后端开发人员参考,用于对接 RAG 知识库服务。
---
## 一、职责边界
### 1.1 后端负责
| 职责 | 说明 |
|------|------|
| **用户认证** | JWT/Session 验证,确保用户身份合法 |
| **权限判断** | 判断用户可访问哪些知识库,生成 `collections` 列表 |
| **会话管理** | 创建/删除会话,存储会话元数据 |
| **消息存储** | 存储用户问题和 AI 回答的完整原文 |
| **知识库权限表** | 维护用户与知识库的权限关系 |
### 1.2 RAG 服务负责
| 职责 | 说明 |
|------|------|
| **知识库问答** | 在指定知识库中检索,生成回答 |
| **向量检索** | 向量相似度检索 + BM25 关键词检索 |
| **返回溯源** | 返回答案来源chunks供前端展示 |
| **文档处理** | 文档上传、切片、向量化 |
### 1.3 数据流
```
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ 前端 │───▶│ 后端 │───▶│ RAG │───▶│ 后端 │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│ 权限判断 │ │ 存储消息 │
│ 生成collections │ 更新会话 │
└──────────┘ └──────────┘
```
---
## 二、认证方式
### 2.1 模式说明
RAG 服务支持两种模式,通过环境变量 `DEV_MODE` 控制:
| 模式 | DEV_MODE | Header | 适用场景 |
|------|----------|--------|----------|
| **开发模式** | `true` | 可选(支持模拟用户) | 前端测试、开发调试 |
| **生产模式** | `false` | 不需要 | 后端直接调用 |
### 2.2 生产模式调用(推荐)
生产环境下,后端直接调用,**不需要传 Header**
```http
POST http://rag-service:5001/rag
Content-Type: application/json
{
"message": "",
"collections": ["public_kb", "dept_finance"],
"chat_history": [
{"role": "user", "content": ""},
{"role": "assistant", "content": ""}
]
}
```
**说明**
- 权限由后端控制,通过 `collections` 参数指定可访问的知识库
- 会话由后端管理,通过 `chat_history` 参数传入对话历史
- RAG 服务完全无状态,只负责问答检索
### 2.3 开发模式调用
开发环境下(`DEV_MODE=true`),支持模拟用户测试:
**方式 1使用模拟 Token**
```http
POST http://rag-service:5001/rag
Authorization: Bearer mock-token-admin
Content-Type: application/json
{
"message": "",
"collections": ["public_kb"],
"chat_history": []
}
```
**方式 2不传 Header自动使用开发用户**
```http
POST http://rag-service:5001/rag
Content-Type: application/json
{
"message": "",
"collections": ["public_kb"],
"chat_history": []
}
```
**模拟用户列表**
| Token | user_id | role | department |
|-------|---------|------|------------|
| `mock-token-admin` | admin001 | admin | 管理部 |
| `mock-token-manager` | manager001 | manager | 财务部 |
| `mock-token-user` | user001 | user | 技术部 |
### 2.4 环境配置
```bash
# 开发环境(默认)
DEV_MODE=true
# 生产环境
DEV_MODE=false
```
---
## 三、问答接口
### 3.1 普通聊天
```
POST /chat
```
**请求体:**
```json
{
"message": "用户消息",
"history": [
{"role": "user", "content": "历史问题"},
{"role": "assistant", "content": "历史回答"}
]
}
```
> **注意**`/chat` 接口中 `history` 为可选参数,也可使用 `chat_history` 作为参数名(两者等效)。该接口不走知识库检索,直接由 LLM 回答。
**响应:**
```json
{
"answer": "AI 回复内容",
"mode": "chat",
"sources": [],
"web_searched": false
}
```
### 3.2 知识库问答(核心接口 - SSE 流式返回)
```
POST /rag
```
> **重要变更**`/rag` 接口已升级为 **SSE 流式返回**,不再返回阻塞 JSON。
> 原独立的 `/rag/stream` 端点已合并至此端点。
**请求体:**
```json
{
"message": "用户问题",
"collections": ["public_kb", "dept_finance"],
"session_id": "可选,用于后端日志追踪",
"chat_history": [
{"role": "user", "content": "历史问题"},
{"role": "assistant", "content": "历史回答"}
]
}
```
**参数说明:**
| 参数 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `message` | string | ✅ | 用户问题 |
| `collections` | string[] | ⚠️ | 用户有权限的知识库列表,由后端判断后传入(不传时默认 `["public_kb"]` |
| `session_id` | string | ❌ | 会话标识(首次对话不传,后续对话传入以加载历史) |
| `chat_history` | array | ⚠️ | 对话历史(**生产环境必需**,首次对话传 `[]` |
**响应SSE 流式事件**
```
Content-Type: text/event-stream
data: {"type": "start", "message": "正在检索知识库..."}
data: {"type": "sources", "sources": [{"source": "doc.pdf", "page": 1, "page_range": "1", "section": "", "chunk_type": "text", "score": 0.95}]}
data: {"type": "chunk", "content": "根"}
data: {"type": "chunk", "content": "据"}
data: {"type": "chunk", "content": "公司"}
...
data: {"type": "finish", "answer": "完整答案...", "mode": "rag", "session_id": "xxx", "sources": [...], "images": [], "tables": [], "sections": [], "duration_ms": 1500}
```
**SSE 事件类型:**
| 事件类型 | 字段 | 说明 |
|---------|------|------|
| `start` | `message` | 开始处理,前端可显示加载状态 |
| `sources` | `sources` | 检索到的来源,可提前展示溯源 |
| `chunk` | `content` | 每个 token用于打字机效果 |
| `finish` | 完整响应对象 | **必须消费**,包含完整答案用于存储 |
| `error` | `message`, `traceback` | 错误事件,前端显示错误提示 |
**finish 事件完整结构:**
```json
{
"type": "finish",
"answer": "完整答案文本(含 [ref:chunk_id] 引用标记)",
"mode": "rag",
"session_id": "会话ID首次对话自动创建",
"sources": [
{
"source": "文档名.pdf",
"page": 5,
"page_end": 8,
"page_range": "5-8",
"section": "1. Introduction",
"chunk_type": "text",
"doc_type": "pdf",
"section_chunk_id": 3,
"score": 0.856
}
],
"citations": [
{
"chunk_id": "文档名.pdf_text_12",
"chunk_index": 12,
"source": "文档名.pdf",
"collection": "public_kb",
"doc_type": "pdf",
"page": 5,
"page_end": 5,
"bbox": [62, 480, 946, 904],
"bbox_mode": "normalized",
"section": "1. Introduction",
"preview": "内容摘要..."
}
],
"images": [
{
"id": "img_001",
"caption": "图片描述",
"url": "/images/img_001",
"source": "文档名.pdf",
"page": 2
}
],
"tables": [],
"sections": [],
"duration_ms": 1500
}
```
> **注意**`tables` 和 `sections` 字段当前始终为空数组,功能待实现。
**sources 字段说明Phase 2.1 更新):**
| 字段 | 类型 | 说明 |
|------|------|------|
| `source` | string | 来源文件名 |
| `page` | int | 起始页码 |
| `page_end` | int\|null | 结束页码(跨页切片时有值) |
| `page_range` | string | 页码范围显示文本,如 `"5"``"5-8"` |
| `section` | string | 所属章节路径 |
| `chunk_type` | string | 切片类型:`text``table``image` |
| `doc_type` | string | 文档类型:`pdf``word``excel` |
| `section_chunk_id` | int | 章节内段落序号Word文档语义定位 |
| `score` | float | 相关性分数0-1 |
**citations 字段说明Phase 3.0 新增 - 引用溯源):**
> `citations` 是结构化的引用列表,用于实现精确的引用溯源功能。
> `answer` 中的 `[ref:chunk_id]` 标记需要后端根据 `citations` 替换为用户可见的编号。
| 字段 | 类型 | 说明 |
|------|------|------|
| `chunk_id` | string | 切片唯一标识,格式 `{文件名}_{序号}` |
| `chunk_index` | int | 全局切片序号(从 chunk_id 提取),用于文档预览跳转 |
| `source` | string | 来源文件名 |
| `collection` | string | 所属向量库名称,用于定位文档所在知识库 |
| `doc_type` | string | 文档类型:`pdf``word``excel` |
| `section` | string | 所属章节路径(已清洗,过滤掉正文内容) |
| `preview` | string | 内容摘要(用于搜索定位) |
| `content` | string | 切片内容(截断至 300 字) |
| `chunk_type` | string | 切片类型:`text``table``image` |
| `page` | int | 起始页码(仅 PDF |
| `page_end` | int | 结束页码(仅 PDF |
| `bbox` | array | 边界框坐标 [x0,y0,x1,y1](仅 PDF |
| `bbox_mode` | string | 坐标模式:`normalized`0-1000 |
| `section_chunk_id` | int | 章节内段落序号(仅 Word |
**按文档类型差异化定位:**
| 文档类型 | 定位字段 | 定位方式 |
|---------|---------|--------|
| **PDF** | `page` + `bbox` | 坐标定位(跳转到页码 + 高亮区域) |
| **Word** | `chunk_index` + `section` + `preview` | 切片序号定位(跳转到具体切片,复用 `/documents/<path>/preview` 接口) |
| **Excel** | `page`(工作表序号)+ `preview` | 表格定位(工作表 + 搜索) |
**answer 引用标记格式:**
```
三峡船闸2022年运行10400闸次[ref:三峡公报.pdf_text_12]过闸货运量达1.56亿吨[ref:三峡公报.pdf_text_15]。
```
**后端处理建议:**
1. 解析 `answer` 中的 `[ref:chunk_id]` 标记
2. 根据 `citations` 数组顺序生成用户可见编号1, 2, 3...
3. 替换标记为编号,构建最终展示文本
```javascript
// 后端处理示例
function processAnswerWithCitations(answer, citations) {
const citationMap = {};
citations.forEach((c, i) => {
citationMap[c.chunk_id] = i + 1;
});
return {
text: answer.replace(/\[ref:([^\]]+)\]/g, (match, chunkId) => {
const num = citationMap[chunkId];
return num ? `[${num}]` : '';
}),
citationMap: citationMap
};
}
```
**前端显示建议:**
- 单页:`第5页`
- 跨页:`第5-8页`
- 带章节:`第5页 / 1. Introduction`
**后端消费示例JavaScript**
```javascript
const response = await fetch('http://rag-service:5001/rag', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: '出差补助标准是什么?',
collections: ['public_kb'],
chat_history: []
})
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
let fullAnswer = '';
let sources = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
const text = decoder.decode(value);
const lines = text.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const event = JSON.parse(line.slice(6));
switch (event.type) {
case 'start':
console.log('开始处理:', event.message);
break;
case 'sources':
sources = event.sources;
break;
case 'chunk':
fullAnswer += event.content;
// 可实时推送给前端显示打字机效果
break;
case 'finish':
// 使用完整答案存储到数据库
fullAnswer = event.answer; // 以 finish 中的为准
sources = event.sources;
break;
case 'error':
console.error('RAG 错误:', event.message);
break;
}
}
}
}
// 存储 fullAnswer 和 sources 到数据库
```
**后端消费示例Python**
```python
import requests
import json
def call_rag_stream(message, collections, history=None):
response = requests.post(
'http://rag-service:5001/rag',
json={
'message': message,
'collections': collections,
'chat_history': history or []
},
stream=True # 启用流式读取
)
full_answer = ''
sources = []
for line in response.iter_lines():
if not line:
continue
line = line.decode('utf-8')
if line.startswith('data: '):
event = json.loads(line[6:])
if event['type'] == 'start':
print(f"开始: {event['message']}")
elif event['type'] == 'sources':
sources = event['sources']
elif event['type'] == 'chunk':
full_answer += event['content']
elif event['type'] == 'finish':
full_answer = event['answer'] # 以 finish 为准
sources = event['sources']
break
elif event['type'] == 'error':
raise Exception(event['message'])
return full_answer, sources
# 使用
answer, sources = call_rag_stream('出差补助标准是什么?', ['public_kb'])
# 存储 answer 和 sources 到数据库
```
### 3.3 会话管理(多轮对话)
> **开发环境特性**RAG 服务内置 SQLite 会话存储,支持完整的多轮对话测试。
**会话管理流程:**
```
首次对话:
POST /rag { "message": "出差补助标准", "collections": ["public_kb"] }
finish 事件返回 session_id
前端保存 session_id
后续对话:
POST /rag { "message": "它有什么限制", "session_id": "xxx", "collections": ["public_kb"] }
RAG 服务自动加载历史 → Query Rewriting 消歧 → 生成回答
finish 事件返回相同 session_id
```
**关键点:**
1. 首次对话不传 `session_id`RAG 服务自动创建新会话
2. 后续对话传入 `session_id`RAG 服务自动加载历史(无需前端传 `history`
3. Query Rewriting 会利用历史上下文进行消歧和实体补全
**会话相关 API**
| 接口 | 说明 |
|------|------|
| `GET /sessions` | 获取用户会话列表 |
| `GET /history/<session_id>` | 获取会话历史 |
| `DELETE /session/<session_id>` | 删除会话 |
**后端实现参考(数据库表结构):**
```sql
CREATE TABLE sessions (
session_id VARCHAR(64) PRIMARY KEY,
user_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_active TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
metadata JSON
);
CREATE TABLE messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id VARCHAR(64) NOT NULL,
role VARCHAR(20) NOT NULL, -- 'user' 或 'assistant'
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (session_id) REFERENCES sessions(session_id)
);
CREATE INDEX idx_messages_session ON messages(session_id);
CREATE INDEX idx_sessions_user ON sessions(user_id);
```
### 3.4 混合检索
```
POST /search
```
**请求体:**
```json
{
"query": "检索关键词",
"top_k": 5,
"collections": ["public_kb", "dept_finance"]
}
```
**响应:**
```json
{
"contexts": ["文档片段1", "文档片段2"],
"metadatas": [
{"source": "doc1.pdf", "page": 1},
{"source": "doc2.pdf", "page": 5}
],
"scores": [0.95, 0.87]
}
```
**scores 说明**:相似度分数,范围 0-1越高越相关。
---
## 四、知识库管理接口
### 4.1 获取向量库列表
```
GET /collections
```
**响应:**
```json
{
"collections": [
{
"name": "public_kb",
"display_name": "公开知识库",
"document_count": 150,
"department": null,
"description": "全员可访问"
}
],
"total": 1
}
```
### 4.2 创建向量库
```
POST /collections
```
**请求体:**
```json
{
"name": "dept_finance",
"display_name": "财务部知识库",
"department": "财务部",
"description": "财务部专用知识库"
}
```
### 4.3 修改向量库
```
PUT /collections/<name>
```
### 4.4 删除向量库
```
DELETE /collections/<name>
```
**查询参数:**
| 参数 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `delete_documents` | boolean | ❌ | 是否删除文档源文件,默认 false |
**响应:**
```json
{
"success": true,
"message": "向量库 'dept_finance' 已删除",
"deleted_documents": false
}
```
**说明:**
- 默认只删除向量数据ChromaDB 集合、BM25 索引)
- 设置 `delete_documents=true` 会同时删除文档源文件
- 公开知识库 (`public_kb`) 不允许删除
### 4.5 获取向量库文档列表
```
GET /collections/<kb_name>/documents
```
**响应:**
```json
{
"collection": "public_kb",
"documents": [
{
"chunks": 105,
"source": "1.docx"
},
{
"chunks": 39,
"source": "三峡公报_1-15页.pdf"
}
],
"total": 9
}
```
### 4.6 获取向量库切片列表
```
GET /collections/<kb_name>/chunks
```
**查询参数:**
| 参数 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `document_id` | string | ❌ | 过滤指定文档的切片 |
| `limit` | int | ❌ | 返回数量,默认 100 |
| `offset` | int | ❌ | 偏移量,默认 0 |
**响应:**
```json
{
"collection": "public_kb",
"chunks": [
{
"id": "chunk_001",
"document": "考勤制度.pdf",
"content": "切片内容...",
"metadata": {
"page": 1,
"section": "第一章",
"status": "active"
},
"score": null
}
],
"total": 150
}
```
---
## 四点五、文档版本管理接口
> **功能说明**:支持文档的废止(软删除)、恢复和版本历史查询。
### 4.5.1 废止文档
```
POST /collections/<kb_name>/documents/<filename>/deprecate
```
**请求体:**
```json
{
"reason": "新版本已发布,旧版本废止"
}
```
**响应:**
```json
{
"success": true,
"deprecated_chunks": 15,
"document_id": "报销制度.pdf",
"collection": "public_kb",
"deprecated_date": "2026-04-20T15:00:00"
}
```
**说明:**
- 废止是软删除,切片标记为 `status: "deprecated"`,不物理删除
- 废止后的文档不会出现在检索结果中
- 可通过恢复接口恢复
### 4.5.2 恢复已废止文档
```
POST /collections/<kb_name>/documents/<filename>/restore
```
**响应:**
```json
{
"success": true,
"restored_chunks": 15,
"document_id": "报销制度.pdf",
"collection": "public_kb"
}
```
### 4.5.3 获取文档版本历史
```
GET /collections/<kb_name>/documents/<filename>/versions
```
**查询参数:**
| 参数 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `limit` | int | ❌ | 返回数量,默认 10 |
**响应:**
```json
{
"success": true,
"document_id": "报销制度.pdf",
"collection": "public_kb",
"versions": [
{
"document_id": "报销制度.pdf",
"collection": "public_kb",
"version": "v2",
"status": "active",
"effective_date": "2026-01-15T10:00:00",
"deprecated_date": null,
"deprecated_reason": null,
"change_summary": "更新报销标准",
"supersedes": "v1",
"created_at": "2026-01-15T10:00:00",
"created_by": "admin001",
"chunk_count": 20
},
{
"document_id": "报销制度.pdf",
"collection": "public_kb",
"version": "v1",
"status": "superseded",
"effective_date": "2025-01-01T10:00:00",
"deprecated_date": "2026-01-15T10:00:00",
"deprecated_reason": "被 v2 替代",
"change_summary": null,
"supersedes": null,
"created_at": "2025-01-01T10:00:00",
"created_by": "admin001",
"chunk_count": 15
}
],
"total": 2
}
```
**文档状态说明:**
| 状态 | 说明 |
|------|------|
| `draft` | 草稿,未生效 |
| `active` | 生效中,参与检索 |
| `deprecated` | 已废止,不参与检索 |
| `superseded` | 被新版本替代 |
---
## 四点六、知识库路由接口(调试)
> **功能说明**:测试知识库路由逻辑,用于调试和验证路由策略。
### 4.6.1 测试路由
```
POST /kb/route
```
**请求体:**
```json
{
"query": "财务部的报销流程是什么"
}
```
**响应:**
```json
{
"query": "财务部的报销流程是什么",
"user_role": "user",
"user_department": "tech",
"target_collections": ["dept_finance", "public_kb"],
"intent": {
"is_general": false,
"department": "finance",
"confidence": 0.8,
"keywords": ["财务", "报销"],
"reason": "匹配到部门关键词: 财务, 报销"
}
}
```
**说明:**
- 根据查询内容和用户权限,返回应查询的目标向量库列表
- `intent` 字段展示意图分析结果
- 可用于调试路由策略和关键词匹配
---
## 五、文档管理接口
### 5.1 上传单个文件
```
POST /documents/upload
Content-Type: multipart/form-data
```
**表单参数:**
- `file`: 文件(必需)
- `collection`: 目标向量库名称(必需,也可用 `kb_name`
**响应:**
```json
{
"success": true,
"message": "文件上传成功,已保存并添加到向量库",
"file": {
"filename": "document.pdf",
"collection": "public_kb",
"path": "public_kb/document.pdf",
"size": 1024000,
"replaced": false
}
}
```
**同名文件处理**:上传同名文件时,旧版本的切片会被自动清理后覆盖(`replaced: true`),不会生成时间戳后缀文件。这确保了向量库中不会出现同一文档的新旧切片共存的情况。
### 5.2 批量上传
```
POST /documents/batch-upload
Content-Type: multipart/form-data
```
**表单参数:**
- `files`: 文件列表(必需)
- `collection`: 目标向量库名称(必需,也可用 `kb_name`
### 5.3 文档列表
```
GET /documents/list?collection=public_kb
```
### 5.4 获取文档状态
```
GET /documents/<path>/status
```
**响应:**
```json
{
"success": true,
"status": "active",
"chunk_count": 105,
"last_processed": null
}
```
### 5.5 更新/替换文档
```
PUT /documents/<path>
Content-Type: multipart/form-data
```
**表单参数:**
- `file`: 替换的文件(必需)
**响应:**
```json
{
"success": true,
"message": "文件已更新"
}
```
**说明:**
- 文件必须已存在,否则返回 404
- 更新后自动触发重新向量化
### 5.6 删除文档
```
DELETE /documents/<path>
```
### 5.7 查看文件切片
```
GET /documents/<path>/chunks
```
**响应:**
```json
{
"success": true,
"document_id": "public/document.pdf",
"collection": "public_kb",
"chunks": [
{
"id": "chunk_001",
"content": "切片内容...",
"metadata": {"page": 1, "section": "第一章"}
}
],
"total": 25
}
```
---
### 5.8 🛠️ DEV 文档预览(引用溯源跳转)
> **⚠️ 开发环境自用接口,后端组无需调用。**
> 此接口仅供 dev-ui 内部前端实现引用跳转使用。后端组可根据 `citations` 中的 `chunk_index`、`page`、`bbox` 等字段自行实现文档定位功能。
```
GET /documents/<path>/preview?chunk_index=<N>&context=<M>
```
> **用途**:前端点击引用标签后,调用此接口跳转到文档中的具体切片位置。
> 复用现有切片查询逻辑,不新增存储。
**查询参数:**
| 参数 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `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}
]
}
```
**字段说明:**
- `target_index`:目标切片的全局序号
- `chunks`:包含目标切片及其上下文的切片列表
- `is_target: true`:标记目标切片,前端可高亮显示并滚动到该位置
- 不传 `chunk_index` 时返回前 5 个切片作为文档概览
**前端调用示例:**
```javascript
// 用户点击引用标签时跳转
async function jumpToCitation(source, chunkIndex, collection) {
const path = `${collection}/${source}`;
const resp = await fetch(`/documents/${encodeURIComponent(path)}/preview?chunk_index=${chunkIndex}`);
const data = await resp.json();
// 打开文档预览模态框,高亮 is_target=true 的切片
showPreviewModal(data);
}
```
---
## 六、同步服务接口
> **注意**:同步服务用于检测文档变更并自动更新向量库。订阅通知功能由后端负责。
### 6.1 触发同步
```
POST /sync
```
**请求体:** 无需传递参数(同步所有知识库)
**响应:**
```json
{
"success": true,
"status": "success",
"status_code": 2010,
"message": "同步完成",
"data": {
"result": {
"documents_added": 1,
"documents_deleted": 1,
"documents_modified": 0,
"documents_processed": 2,
"errors": [],
"status": "completed"
}
}
}
```
### 6.2 同步状态
```
GET /sync/status
```
**响应:**
```json
{
"enabled": true,
"monitoring": true,
"last_sync": "2026-04-19T18:30:00",
"documents_tracked": 150
}
```
### 6.3 同步历史
```
GET /sync/history?limit=20
```
**参数:**
| 参数 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `limit` | int | ❌ | 返回记录数,默认 20 |
**响应:**
```json
{
"history": [
{
"sync_time": "2026-04-19T18:30:00",
"collection": "public_kb",
"added": 3,
"updated": 2,
"deleted": 1,
"status": "success"
}
]
}
```
### 6.4 变更日志
```
GET /sync/changes?limit=50&collection=public_kb
```
**参数:**
| 参数 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `limit` | int | ❌ | 返回记录数,默认 50 |
| `collection` | string | ❌ | 过滤指定向量库 |
**响应:**
```json
{
"changes": [
{
"change_time": "2026-04-19T18:25:00",
"document": "规章制度/考勤制度.docx",
"change_type": "modified",
"collection": "public_kb"
}
]
}
```
### 6.5 启动/停止文件监控
```
POST /sync/start
POST /sync/stop
```
**响应:**
```json
{
"status": "success",
"status_code": 3001,
"message": "文件监控已启动"
}
```
---
## 七、出题接口
### 7.1 生成题目
```
POST /exam/generate
```
**请求体:**
```json
{
"file_path": "public/考勤制度.docx",
"collection": "dept_a_kb",
"question_types": {
"single_choice": 3,
"multiple_choice": 2,
"true_false": 2,
"fill_blank": 2,
"subjective": 1
},
"difficulty": 3,
"request_id": "可选,幂等性支持"
}
```
**参数说明:**
| 参数 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `file_path` | string | ✅ | 文件路径(相对于 documents 目录) |
| `collection` | string 或 string[] | ✅ | 向量库名称,支持数组(按优先级排序) |
| `question_types` | object | ✅ | 题型及数量,键为题型名,值为数量 |
| `difficulty` | int | ❌ | 难度等级 1-5默认 3 |
| `request_id` | string | ❌ | 请求 ID相同 ID 返回缓存结果(幂等性) |
**collection 参数说明:**
后端应根据用户权限传入可访问的向量库列表:
- 单个向量库:`"dept_a_kb"`
- 多个向量库:`["dept_a_kb", "public_kb"]`(按优先级排序,优先在第一个库检索)
**响应:**
**完整响应格式**(包含外层包装):
```json
{
"success": true,
"status": "success",
"status_code": 2011,
"message": "出题完成",
"data": {
"success": true,
"request_id": "xxx",
"total": 10,
"source_chunks_used": 15,
"questions": [...]
}
}
```
**data 内部结构**
```json
{
"success": true,
"request_id": "xxx",
"total": 10,
"source_chunks_used": 15,
"questions": [
{
"question_type": "single_choice",
"difficulty": 3,
"content": {
"stem": "题干内容",
"data": {
"options": [
{"key": "A", "content": "选项A"},
{"key": "B", "content": "选项B"},
{"key": "C", "content": "选项C"},
{"key": "D", "content": "选项D"}
]
},
"answer": "B",
"explanation": "答案解析"
},
"source_trace": {
"document_name": "考勤制度.docx",
"chunk_ids": ["chunk_001"],
"page_numbers": [5],
"sources": [
{
"chunk_id": "chunk_001",
"page": 5,
"section": "请假制度",
"snippet": "原文片段..."
}
]
}
}
]
}
```
**题型与 answer 格式对照:**
| 题型 | question_type | answer 格式 | data 字段 |
|------|---------------|-------------|-----------|
| 单选题 | single_choice | `"B"` | `options[]` |
| 多选题 | multiple_choice | `["A", "C"]` | `options[]` |
| 判断题 | true_false | `"T"``"F"` | 无 |
| 填空题 | fill_blank | `[["答案1"], ["答案2", "同义词"]]` | `blank_count` |
| 简答题 | subjective | `"参考范文..."` | `scoring_points[]` |
**后端职责:**
| 操作 | 说明 |
|------|------|
| 权限校验 | 判断用户是否有出题权限(通常为管理员) |
| 生成 question_id | 入库时生成 UUID |
| 设置 score | 根据题型或配置设定满分 |
| 添加 tags | 根据业务需求添加标签 |
| 审核入库 | 人工或自动审核后存入题库 |
**错误响应格式:**
```json
{
"success": false,
"error": "错误描述",
"error_code": "ERROR_CODE"
}
```
**常见错误码:**
| 错误码 | HTTP 状态码 | 说明 |
|--------|-------------|------|
| `FILE_NOT_FOUND` | 404 | 指定文件不存在 |
| `COLLECTION_NOT_FOUND` | 404 | 指定向量库不存在 |
| `NO_CONTENT` | 400 | 文件内容为空,无法出题 |
| `LLM_ERROR` | 500 | LLM 调用失败 |
| `PARSE_ERROR` | 500 | 解析 LLM 响应失败 |
**幂等性说明:**
- 传入 `request_id` 时,相同 ID 会返回缓存结果
- 缓存有效期24 小时
- 建议后端生成 UUID 作为 `request_id`,便于追踪和去重
### 7.2 批改答案
```
POST /exam/grade
```
#### 请求体
```json
{
"request_id": "可选,用于幂等性追踪",
"answers": [
{
"question_id": "uuid-001",
"question_type": "single_choice",
"question_content": {
"stem": "题干内容",
"data": {"options": [{"key": "A", "content": "选项A"}, {"key": "B", "content": "选项B"}]},
"answer": "B"
},
"student_answer": "A",
"max_score": 2.0
},
{
"question_id": "uuid-002",
"question_type": "multiple_choice",
"question_content": {
"stem": "多选题题干",
"data": {"options": [...]},
"answer": ["A", "C"]
},
"student_answer": ["A", "B"],
"max_score": 4.0
},
{
"question_id": "uuid-003",
"question_type": "true_false",
"question_content": {
"stem": "判断题题干",
"answer": "T"
},
"student_answer": "F",
"max_score": 2.0
},
{
"question_id": "uuid-004",
"question_type": "fill_blank",
"question_content": {
"stem": "填空题有___个空",
"answer": [["答案1", "同义词1"], ["答案2"]]
},
"student_answer": ["学生答案1", "学生答案2"],
"max_score": 4.0
},
{
"question_id": "uuid-005",
"question_type": "subjective",
"question_content": {
"stem": "简答题题干",
"data": {
"scoring_points": [
{"point": "要点1", "weight": 0.4},
{"point": "要点2", "weight": 0.6}
]
},
"answer": "参考答案..."
},
"student_answer": "学生作答内容...",
"max_score": 10.0
}
]
}
```
#### 请求参数说明
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `request_id` | string | 否 | 请求 ID用于追踪和幂等性 |
| `answers` | array | 是 | 答案列表 |
**answers 数组中每个对象的字段:**
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `question_id` | string | **是** | 题目 ID后端生成用于匹配结果 |
| `question_type` | string | 是 | 题型single_choice/multiple_choice/true_false/fill_blank/subjective |
| `question_content` | object | 是 | 题目内容(从出题结果中获取) |
| `student_answer` | any | 是 | 学生答案(格式见下表) |
| `max_score` | number | 是 | 该题满分 |
**各题型 student_answer 格式:**
| 题型 | 格式 | 示例 |
|------|------|------|
| single_choice | string | `"B"` |
| multiple_choice | array | `["A", "C"]` |
| true_false | string | `"T"``"F"` |
| fill_blank | array | `["答案1", "答案2"]` |
| subjective | string | `"学生作答的长文本..."` |
#### 响应
**完整响应格式**(包含外层包装):
```json
{
"success": true,
"status": "success",
"status_code": 2021,
"message": "批阅完成",
"data": {
"request_id": "可选,原样返回",
"success": true,
"total_score": 12.5,
"total_max_score": 22.0,
"score_rate": 56.8,
"results": [...]
}
}
```
**data 内部结构**
```json
{
"success": true,
"request_id": "可选,原样返回",
"total_score": 12.5,
"total_max_score": 22.0,
"score_rate": 56.8,
"results": [
{
"question_id": "uuid-001",
"score": 0,
"max_score": 2.0,
"correct": false,
"feedback": "正确答案: B"
},
{
"question_id": "uuid-002",
"score": 0,
"max_score": 4.0,
"correct": false,
"feedback": "正确答案: ['A', 'C']"
},
{
"question_id": "uuid-003",
"score": 0,
"max_score": 2.0,
"correct": false,
"feedback": "正确答案: T"
},
{
"question_id": "uuid-004",
"score": 2.0,
"max_score": 4.0,
"details": {
"blank_scores": [2.0, 0],
"correct_answers": [["答案1", "同义词1"], ["答案2"]]
}
},
{
"question_id": "uuid-005",
"score": 7.5,
"max_score": 10.0,
"details": {
"scoring_breakdown": [
{"point": "要点1", "weight": 0.4, "achieved": 0.35, "comment": "部分掌握"},
{"point": "要点2", "weight": 0.6, "achieved": 0.55, "comment": "基本掌握"}
],
"highlights": ["思路清晰"],
"shortcomings": ["细节不够完整"],
"overall_feedback": "整体回答良好,建议补充细节。"
}
}
]
}
```
#### 批卷逻辑说明
| 题型 | 批卷方式 | 说明 |
|------|----------|------|
| single_choice | 本地判断 | 学生答案 == 正确答案 |
| multiple_choice | 本地判断 | set(学生答案) == set(正确答案),顺序无关 |
| true_false | 本地判断 | 学生答案 == 正确答案 |
| fill_blank | 本地模糊匹配 | 按空给分,支持同义词匹配(忽略大小写和空格) |
| subjective | LLM 评分 | 并发调用 LLM带重试和限流机制 |
#### 注意事项
1. **question_id 必填**:用于匹配批卷结果,后端需在调用时传入
2. **顺序保证**results 数组顺序与 answers 数组顺序一致
3. **主观题超时**:主观题批阅有 15 秒超时,失败时返回 score=0
4. **填空题同义词**answer 字段支持同义词数组,如 `[["北京", "Beijing"]]`
#### 后端对接流程
**重要RAG 服务是无状态的,不存储题库数据。所有题目信息需由后端传入。**
```
完整批卷流程:
┌─────────────┐ ┌─────────────┐
│ 后端 │ │ RAG 服务 │
├─────────────┤ ├─────────────┤
│ 1. 接收学生答案 │ │
│ 2. 查询数据库获取题目 │ │
│ 3. 组装请求 ────────────────────▶ │ 4. 批卷处理 │
│ │ - 客观题:本地比对
│ │ - 主观题LLM评分
│ 6. 更新学生成绩 ◀──────────────── │ 5. 返回结果 │
│ (根据 question_id 匹配) │ │
└─────────────┘ └─────────────┘
```
**Step 1: 接收学生答案**
```python
# 学生提交的答案
student_answers = {
"exam_id": "exam-uuid",
"student_id": "student-uuid",
"answers": [
{"question_id": "q-001", "answer": "B"},
{"question_id": "q-002", "answer": ["A", "C"]},
{"question_id": "q-003", "answer": "三峡水库主要用于防洪..."}
]
}
```
**Step 2: 从数据库查询题目信息**
```python
def get_questions_for_grading(exam_id, answer_list):
"""根据 question_id 批量查询题目信息"""
question_ids = [a['question_id'] for a in answer_list]
questions = db.query("""
SELECT question_id, question_type, content, score
FROM questions
WHERE question_id IN (?)
""", question_ids)
# 转为字典方便查找
return {q.question_id: q for q in questions}
```
**Step 3: 组装批卷请求**
```python
def build_grade_request(answer_list, questions_map):
"""组装 RAG 批卷接口所需的请求格式"""
grade_answers = []
for ans in answer_list:
qid = ans['question_id']
question = questions_map.get(qid)
if not question:
continue
grade_answers.append({
"question_id": qid,
"question_type": question.question_type,
"question_content": question.content, # 含正确答案
"student_answer": ans['answer'],
"max_score": question.score
})
return {"answers": grade_answers}
```
**Step 4: 调用 RAG 批卷接口**
```python
def call_rag_grade(grade_request):
"""调用 RAG 批卷接口"""
response = requests.post(
'http://rag-service:5001/exam/grade',
json=grade_request
)
return response.json()
```
**Step 5: 更新学生成绩**
```python
def update_student_scores(student_id, exam_id, grade_result):
"""根据批卷结果更新学生成绩"""
for result in grade_result['results']:
qid = result['question_id']
db.execute("""
INSERT INTO student_answers (
student_id, exam_id, question_id,
score, max_score, details
) VALUES (?, ?, ?, ?, ?, ?)
""",
student_id, exam_id, qid,
result['score'], result['max_score'],
json.dumps(result.get('details', {}))
)
# 更新总分
db.execute("""
UPDATE student_exams
SET total_score = ?, score_rate = ?, graded_at = NOW()
WHERE student_id = ? AND exam_id = ?
""",
grade_result['total_score'],
grade_result['score_rate'],
student_id, exam_id
)
```
**完整调用示例**
```python
def grade_student_exam(student_answers):
"""批阅学生试卷完整流程"""
# 1. 查询题目信息
questions_map = get_questions_for_grading(
student_answers['exam_id'],
student_answers['answers']
)
# 2. 组装请求
grade_request = build_grade_request(
student_answers['answers'],
questions_map
)
# 3. 调用 RAG 批卷
grade_result = call_rag_grade(grade_request)
# 4. 更新成绩
update_student_scores(
student_answers['student_id'],
student_answers['exam_id'],
grade_result
)
return grade_result
```
#### 数据来源说明
| 字段 | 来源 | 说明 |
|------|------|------|
| `question_id` | 后端数据库 | 用于匹配返回结果,更新成绩 |
| `question_type` | 后端数据库 | 题型,决定批卷方式 |
| `question_content.answer` | 后端数据库 | 正确答案(客观题直接比对,主观题作为参考) |
| `question_content.data` | 后端数据库 | 题目附加数据(选项、评分标准等) |
| `student_answer` | 学生提交 | 学生作答内容 |
| `max_score` | 后端数据库 | 该题满分 |
---
## 七点五、出题接口 - 后端对接指南
### 7.5.1 后端需要做的事情
**Step 1: 权限校验**
```python
def check_exam_permission(user_id):
"""检查用户是否有出题权限"""
user = get_user(user_id)
# 通常只有管理员和部门管理员有出题权限
return user.role in ['admin', 'manager']
```
**Step 2: 获取用户可访问的向量库**
```python
def get_user_collections(user_id):
"""获取用户有权限的向量库列表(按优先级排序)"""
permissions = db.query("""
SELECT kb_name, permission
FROM kb_permissions
WHERE user_id = ?
ORDER BY
CASE permission
WHEN 'admin' THEN 1
WHEN 'write' THEN 2
WHEN 'read' THEN 3
END
""", user_id)
return [p.kb_name for p in permissions]
```
**Step 3: 调用 RAG 出题接口**
```python
def generate_exam(user_id, file_path, question_types, difficulty=3):
# 1. 权限校验
if not check_exam_permission(user_id):
raise PermissionError("无出题权限")
# 2. 获取向量库列表
collections = get_user_collections(user_id)
# 3. 调用 RAG 服务
response = requests.post(
'http://rag-service:5001/exam/generate',
json={
'file_path': file_path,
'collection': collections, # 传入数组
'question_types': question_types,
'difficulty': difficulty
}
)
result = response.json()
if not result.get('success'):
raise Exception(result.get('error'))
return result['questions']
```
**Step 4: 入库存储**
```python
def save_questions_to_db(questions, exam_id, creator_id):
"""将题目存入数据库"""
for q in questions:
# 生成 question_id
question_id = str(uuid.uuid4())
# 根据题型设置满分
score = get_default_score(q['question_type'])
# 存入数据库
db.execute("""
INSERT INTO questions (
question_id, exam_id, question_type, difficulty,
content, source_trace, score, tags, creator_id, status
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending')
""",
question_id, exam_id, q['question_type'], q['difficulty'],
json.dumps(q['content']), json.dumps(q['source_trace']),
score, json.dumps([]), creator_id
)
```
**Step 5: 题型默认分值参考**
```python
def get_default_score(question_type):
"""根据题型获取默认满分"""
score_map = {
'single_choice': 2.0,
'multiple_choice': 4.0,
'true_false': 2.0,
'fill_blank': 3.0,
'subjective': 10.0
}
return score_map.get(question_type, 2.0)
```
### 7.5.2 数据库设计建议
**题目表 (questions)**
```sql
CREATE TABLE questions (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
question_id VARCHAR(64) UNIQUE NOT NULL, -- 后端生成的 UUID
exam_id VARCHAR(64), -- 所属试卷
question_type VARCHAR(32) NOT NULL, -- 题型
difficulty INT DEFAULT 3, -- 难度
content JSON NOT NULL, -- 题目内容
source_trace JSON, -- 溯源信息
score DECIMAL(4,1) DEFAULT 2.0, -- 满分(后端设置)
tags JSON, -- 标签(后端设置)
creator_id VARCHAR(64), -- 创建人
status ENUM('pending', 'approved', 'rejected') DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_exam_id (exam_id),
INDEX idx_question_type (question_type),
INDEX idx_status (status)
);
```
**试卷表 (exams)**
```sql
CREATE TABLE exams (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
exam_id VARCHAR(64) UNIQUE NOT NULL,
title VARCHAR(255),
total_score DECIMAL(6,1),
question_count INT,
creator_id VARCHAR(64),
status ENUM('draft', 'published', 'archived') DEFAULT 'draft',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
---
## 八、后端数据库设计建议
### 8.1 会话表 (sessions)
```sql
CREATE TABLE sessions (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
session_id VARCHAR(64) UNIQUE NOT NULL,
user_id VARCHAR(64) NOT NULL,
title VARCHAR(255), -- 会话标题(可从首条消息生成)
last_message TEXT, -- 最后一条消息摘要
message_count INT DEFAULT 0, -- 消息数量
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_user_id (user_id),
INDEX idx_updated_at (updated_at)
);
```
### 8.2 消息表 (messages)
```sql
CREATE TABLE messages (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
session_id VARCHAR(64) NOT NULL,
role ENUM('user', 'assistant') NOT NULL,
content TEXT NOT NULL, -- 完整消息内容
sources JSON, -- AI 回答的来源(仅 assistant
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_session_id (session_id),
FOREIGN KEY (session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
);
```
### 8.3 知识库权限表 (kb_permissions)
```sql
CREATE TABLE kb_permissions (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id VARCHAR(64) NOT NULL,
kb_name VARCHAR(64) NOT NULL, -- 知识库名称,如 'public_kb', 'dept_finance'
permission ENUM('read', 'write', 'admin') DEFAULT 'read',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_user_kb (user_id, kb_name),
INDEX idx_user_id (user_id)
);
```
**权限判断逻辑:**
```python
def get_user_collections(user_id):
# 查询用户有权限的知识库
permissions = db.query(
"SELECT kb_name FROM kb_permissions WHERE user_id = ?",
user_id
)
return [p.kb_name for p in permissions]
```
### 8.4 文档版本表 (document_versions) - 新增
> **功能**:记录文档的版本信息,支持版本追溯和废止管理。
```sql
CREATE TABLE document_versions (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
-- 文档标识
document_id VARCHAR(512) NOT NULL, -- 文档ID文件名或路径
collection VARCHAR(64) NOT NULL, -- 所属向量库
-- 版本信息
version VARCHAR(32) NOT NULL, -- 版本号,如 'v1', 'v2'
status ENUM('draft', 'active', 'deprecated', 'superseded') DEFAULT 'active',
-- 时间信息
effective_date TIMESTAMP, -- 生效日期
deprecated_date TIMESTAMP, -- 废止日期
-- 变更信息
deprecated_reason TEXT, -- 废止原因
change_summary TEXT, -- 变更摘要
supersedes VARCHAR(32), -- 替代的旧版本号
-- 元数据
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by VARCHAR(64), -- 创建者用户ID
chunk_count INT DEFAULT 0, -- 切片数量
INDEX idx_collection_doc (collection, document_id),
INDEX idx_status (status),
UNIQUE KEY uk_doc_version (collection, document_id, version)
);
```
**字段说明:**
| 字段 | 类型 | 说明 |
|------|------|------|
| `document_id` | VARCHAR(512) | 文档标识,通常为文件名 |
| `collection` | VARCHAR(64) | 所属向量库名称 |
| `version` | VARCHAR(32) | 版本号 |
| `status` | ENUM | 文档状态draft/active/deprecated/superseded |
| `effective_date` | TIMESTAMP | 生效日期 |
| `deprecated_date` | TIMESTAMP | 废止日期(废止时设置) |
| `deprecated_reason` | TEXT | 废止原因 |
| `change_summary` | TEXT | 版本变更摘要 |
| `supersedes` | VARCHAR(32) | 被替代的旧版本号 |
| `chunk_count` | INT | 该版本的切片数量 |
### 8.5 版本变更日志表 (version_change_logs) - 新增
> **功能**:记录文档版本的变更历史,便于审计追踪。
```sql
CREATE TABLE version_change_logs (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
-- 文档标识
document_id VARCHAR(512) NOT NULL,
collection VARCHAR(64) NOT NULL,
-- 变更信息
old_version VARCHAR(32), -- 旧版本号
new_version VARCHAR(32), -- 新版本号
old_status VARCHAR(32), -- 旧状态
new_status VARCHAR(32), -- 新状态
change_type VARCHAR(32) NOT NULL, -- 变更类型update/deprecate/restore
-- 变更原因
reason TEXT,
changed_by VARCHAR(64), -- 操作者用户ID
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_collection_doc (collection, document_id),
INDEX idx_change_type (change_type),
INDEX idx_created_at (created_at)
);
```
**变更类型说明:**
| change_type | 说明 |
|-------------|------|
| `update` | 更新文档(上传新版本) |
| `deprecate` | 废止文档 |
| `restore` | 恢复已废止文档 |
### 8.6 向量库切片元数据扩展
> **说明**:在 ChromaDB 的 metadata 中增加 `status` 字段,支持软删除。
**切片 metadata 字段:**
```json
{
"source": "报销制度.pdf",
"page": 5,
"section": "第一章",
"chunk_type": "text",
"status": "active", // 新增active / deprecated
"version": "v2", // 新增:文档版本号
"deprecated_date": null // 新增:废止时间(如有)
}
```
**检索时过滤:**
- 默认只返回 `status: "active"` 的切片
- 废止的切片不参与检索,但保留在向量库中
- 可通过恢复接口将状态改回 `active`
---
## 九、完整调用流程示例
### 9.1 用户发起问答
```
1. 前端发送请求到后端
POST /api/chat
{
"session_id": "xxx",
"message": "出差补助标准是什么?"
}
2. 后端处理
a. 验证用户身份JWT
b. 查询用户知识库权限,生成 collections 列表
c. 查询会话历史(最近 5-10 条)
d. 调用 RAG 服务
3. 后端调用 RAG
POST http://rag-service:5001/rag
{
"message": "出差补助标准是什么?",
"collections": ["public_kb", "dept_finance"],
"chat_history": [
{"role": "user", "content": "之前的问题"},
{"role": "assistant", "content": "之前的回答"}
]
}
4. RAG 返回结果
{
"answer": "根据公司规定...",
"sources": [...],
"duration_ms": 1500
}
5. 后端存储
a. 保存用户问题到 messages 表
b. 保存 AI 回答到 messages 表
c. 更新 sessions 表的 last_message、updated_at
6. 后端返回前端
{
"answer": "根据公司规定...",
"sources": [...]
}
```
### 9.2 用户上传文档
```
1. 前端发送文件到后端
POST /api/documents/upload
Content-Type: multipart/form-data
file: document.pdf
collection: dept_finance
2. 后端验证权限
- 检查用户是否有 dept_finance 的 write 权限
3. 后端调用 RAG
POST http://rag-service:5001/documents/upload
Content-Type: multipart/form-data
file: document.pdf
collection: dept_finance
4. RAG 返回结果
{
"success": true,
"file": {"filename": "document.pdf", "path": "finance/document.pdf"}
}
5. 后端返回前端
```
---
## 十、错误响应格式
错误响应存在两种格式,后端需同时兼容:
**格式一:统一错误格式**(大部分接口使用)
```json
{
"success": false,
"status": "failed",
"error_code": "MISSING_PARAMS",
"status_code": 4000,
"message": "缺少 file_path 或 collection 参数"
}
```
**格式二:简单错误格式**(部分接口的异常处理使用)
```json
{
"error": "错误描述信息"
}
```
**常见 HTTP 状态码:**
- `400` - 请求参数错误
- `401` - 未认证
- `403` - 权限不足
- `404` - 资源不存在
- `500` - 服务器内部错误
- `503` - 服务不可用
---
## 十一、向量库命名规范
ChromaDB 集合名称限制:
- 只能包含 `[a-zA-Z0-9._-]`
- 长度 3-63 字符
- 必须以字母或数字开头和结尾
**推荐命名:**
- `public_kb` - 公开知识库
- `dept_finance` - 财务部知识库
- `dept_hr` - 人事部知识库
- `dept_tech` - 技术部知识库
---
## 十二、部署注意事项
1. **环境变量**
- `DEV_MODE=false` - 生产环境必须关闭开发模式
- `DOCUMENTS_PATH` - 文档存储路径
2. **网络配置**
- RAG 服务端口5001默认
- 后端网关需要配置反向代理
3. **文件存储**
- 文档存储在 `documents/` 目录
- 向量库存储在 `vector_store/chroma/` 目录
4. **资源要求**
- 内存:建议 4GB+(向量检索占用)
- 磁盘:根据文档数量评估
5. **数据库要求(新增)**
- 需要 SQLite 或其他数据库支持文档版本管理
- 数据库路径:`data/knowledge.db`(默认)
- 首次启动会自动创建 `document_versions``version_change_logs`
---
## 十三、API 端点汇总
### 13.1 核心接口
| 端点 | 方法 | 说明 |
|------|------|------|
| `/chat` | POST | 普通聊天 |
| `/rag` | POST | 知识库问答SSE 流式) |
| `/search` | POST | 混合检索 |
### 13.2 向量库管理
| 端点 | 方法 | 说明 |
|------|------|------|
| `/collections` | GET | 获取向量库列表 |
| `/collections` | POST | 创建向量库 |
| `/collections/<name>` | PUT | 修改向量库 |
| `/collections/<name>` | DELETE | 删除向量库 |
| `/collections/<name>/documents` | GET | 获取向量库文档列表 |
| `/collections/<name>/chunks` | GET | 获取向量库切片列表 |
| `/collections/<name>/update-image-descriptions` | POST | 更新图片描述 |
### 13.3 文档版本管理(新增)
| 端点 | 方法 | 说明 |
|------|------|------|
| `/collections/<kb_name>/documents/<filename>/deprecate` | POST | 废止文档 |
| `/collections/<kb_name>/documents/<filename>/restore` | POST | 恢复文档 |
| `/collections/<kb_name>/documents/<filename>/versions` | GET | 获取版本历史 |
### 13.4 文档管理
| 端点 | 方法 | 说明 |
|------|------|------|
| `/documents/upload` | POST | 上传文件 |
| `/documents/batch-upload` | POST | 批量上传 |
| `/documents/list` | GET | 文档列表 |
| `/documents/<path>/status` | GET | 获取文档处理状态 |
| `/documents/<path>` | PUT | 更新/替换文档 |
| `/documents/<path>` | DELETE | 删除文档 |
### 13.5 切片管理
| 端点 | 方法 | 说明 |
|------|------|------|
| `/documents/<path>/chunks` | GET | 查看文件切片 |
| `/documents/<path>/preview` 🛠️ | GET | 文档预览dev-ui 自用,按 `chunk_index` 跳转) |
| `/chunks` | POST | 新增切片 |
| `/chunks/<id>` | PUT | 修改切片 |
| `/chunks/<id>` | DELETE | 删除切片 |
### 13.6 同步服务
| 端点 | 方法 | 说明 |
|------|------|------|
| `/sync` | POST | 触发同步 |
| `/sync/status` | GET | 同步状态 |
| `/sync/history` | GET | 同步历史 |
| `/sync/changes` | GET | 变更日志 |
| `/sync/start` | POST | 启动文件监控 |
| `/sync/stop` | POST | 停止文件监控 |
### 13.7 出题系统
| 端点 | 方法 | 说明 |
|------|------|------|
| `/exam/generate` | POST | 生成题目 |
| `/exam/grade` | POST | 批改答案 |
| `/exam/health` | GET | 出题服务健康检查 |
### 13.8 反馈与 FAQ 管理
| 端点 | 方法 | 说明 |
|------|------|------|
| `/feedback` | POST | 提交反馈 |
| `/feedback/list` | GET | 反馈列表 |
| `/feedback/stats` | GET | 反馈统计 |
| `/feedback/bad-cases` | GET | 差评案例 |
| `/feedback/blacklist` | GET | 切片黑名单 |
| `/faq` | GET | FAQ 列表 |
| `/faq` | POST | 创建 FAQ |
| `/faq/<id>/approve` | POST | 审批 FAQ |
| `/faq/<id>` | PUT | 修改 FAQ |
| `/faq/<id>` | DELETE | 删除 FAQ |
| `/faq/suggestions` | GET | FAQ 建议列表 |
| `/faq/suggestions/<id>/approve` | POST | 批准 FAQ 建议 |
| `/faq/suggestions/<id>/reject` | POST | 拒绝 FAQ 建议 |
### 13.9 图片服务
| 端点 | 方法 | 说明 |
|------|------|------|
| `/images/list` | GET | 图片列表 |
| `/images/<id>` | GET | 获取图片 |
| `/images/<id>/info` | GET | 图片元数据 |
| `/images/stats` | GET | 图片统计 |
### 13.10 报告服务
| 端点 | 方法 | 说明 |
|------|------|------|
| `/reports/weekly` | GET | 周报告 |
| `/reports/monthly` | GET | 月报告 |
### 13.11 调试与管理接口
| 端点 | 方法 | 说明 |
|------|------|------|
| `/kb/route` | POST | 测试知识库路由 |
| `/health` | GET | 健康检查 |
| `/stats` | GET | 系统统计admin |
| `/auth/login` | POST | 模拟登录(开发模式) |
| `/auth/me` | GET | 当前用户信息 |
### 13.12 🛠️ 开发环境自用接口
> 以下接口仅供 RAG 服务内部开发调试使用dev-ui 前端、脚本测试等),**后端组无需对接**。
| 端点 | 方法 | 说明 |
|------|------|------|
| `/documents/<path>/preview` | GET | 文档预览,按 `chunk_index` 跳转到具体切片dev-ui 引用溯源用) |
---
## 十四、文件管理服务(后端负责)
### 13.1 功能概述
用户询问"我能访问哪些文件"、"我的权限能查看什么文档"等**元问题**时,需要后端提供完整的文件列表服务。
**职责划分:**
| 层面 | 负责 | 说明 |
|------|------|------|
| **文件元数据管理** | 后端 | 维护文件索引表,记录文件路径、大小、上传时间、权限等 |
| **文件目录展示** | 后端 | 根据用户权限返回可访问的文件列表 |
| **文件内容检索** | RAG | 向量检索、关键词检索、内容问答 |
**为什么不由 RAG 负责?**
```
RAG 向量库存储的是"文档切片"chunks不是"文件列表"
向量库 metadata 只记录 source文件名不记录
- 文件层级结构(目录/子目录)
- 上传时间、文件大小
- 用户权限关系
用户问"有哪些文件"时RAG 只能遍历 chunks 提取 source
这种方式无法展示完整的文件目录结构
```
### 13.2 后端数据库设计
**文件索引表 (file_index)**
```sql
CREATE TABLE file_index (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
-- 文件信息
file_path VARCHAR(512) NOT NULL, -- 相对路径,如 "public/规章制度/考勤制度.docx"
file_name VARCHAR(255) NOT NULL, -- 文件名,如 "考勤制度.docx"
file_type VARCHAR(50), -- 文件类型pdf, docx, xlsx 等
file_size BIGINT, -- 文件大小(字节)
-- 所属知识库
kb_name VARCHAR(64) NOT NULL, -- 知识库名称,如 "public_kb", "dept_finance"
-- 层级结构
parent_path VARCHAR(512), -- 父目录路径,如 "public/规章制度"
level INT DEFAULT 1, -- 层级深度
-- 状态
status ENUM('active', 'deleted', 'processing') DEFAULT 'active',
-- 向量化状态
vectorized BOOLEAN DEFAULT FALSE, -- 是否已向量化
chunk_count INT DEFAULT 0, -- 切片数量
-- 时间戳
uploaded_by VARCHAR(64), -- 上传者用户ID
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_kb_name (kb_name),
INDEX idx_parent_path (parent_path),
INDEX idx_file_path (file_path),
UNIQUE KEY uk_file_path (file_path, kb_name)
);
```
**文件权限表 (file_permissions)**
```sql
-- 方案 A继承知识库权限推荐
-- 用户对知识库有权限 = 对知识库内所有文件有权限
-- 无需单独的文件权限表
-- 方案 B细粒度文件权限可选
CREATE TABLE file_permissions (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
file_id BIGINT NOT NULL,
user_id VARCHAR(64) NOT NULL,
permission ENUM('read', 'write', 'admin') DEFAULT 'read',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (file_id) REFERENCES file_index(id) ON DELETE CASCADE,
UNIQUE KEY uk_user_file (user_id, file_id)
);
```
### 13.3 后端 API 设计
**获取文件列表:**
```http
GET /api/files?kb_name=public_kb&parent_path=public
```
**响应:**
```json
{
"success": true,
"current_path": "public",
"files": [
{
"name": "规章制度",
"type": "folder",
"path": "public/规章制度"
},
{
"name": "考勤制度.docx",
"type": "file",
"path": "public/考勤制度.docx",
"size": 102400,
"uploaded_at": "2026-04-19T10:00:00",
"chunk_count": 15
}
],
"breadcrumb": [
{"name": "根目录", "path": ""},
{"name": "public", "path": "public"}
]
}
```
**获取用户可访问的文件列表:**
```http
GET /api/files/accessible
Authorization: Bearer <jwt_token>
```
**响应:**
```json
{
"success": true,
"knowledge_bases": [
{
"kb_name": "public_kb",
"display_name": "公开知识库",
"file_count": 25,
"total_size": 52428800,
"files": [
{
"path": "public/考勤制度.docx",
"name": "考勤制度.docx",
"size": 102400,
"uploaded_at": "2026-04-19T10:00:00"
}
]
}
],
"total_files": 50,
"total_size": 104857600
}
```
### 13.4 与 RAG 服务配合
**文件上传流程:**
```
┌─────────┐ ┌─────────┐ ┌─────────┐
│ 前端 │───▶│ 后端 │───▶│ RAG │
└─────────┘ └─────────┘ └─────────┘
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ 1. 验证权限 │ │ 4. 解析文档 │
│ 2. 存储文件 │ │ 5. 切片向量化 │
│ 3. 写 file_index │ 6. 写入 ChromaDB │
└──────────────┘ └──────────────┘
┌──────────────┐
│ 7. 返回 chunk_count │
└──────────────┘
┌──────────────┐
│ 8. 后端更新 │
│ file_index. │
│ vectorized=true │
└──────────────┘
```
**后端调用 RAG 上传文件后:**
```python
# 1. 调用 RAG 上传接口
response = requests.post(
'http://rag-service:5001/documents/upload',
files={'file': file},
data={'collection': kb_name}
)
# 2. 写入文件索引表
db.execute("""
INSERT INTO file_index (file_path, file_name, file_type, file_size, kb_name, parent_path, uploaded_by)
VALUES (?, ?, ?, ?, ?, ?, ?)
""", (file_path, file_name, file_type, file_size, kb_name, parent_path, user_id))
# 3. RAG 向量化完成后,更新状态
# 可以通过回调或轮询实现
db.execute("""
UPDATE file_index
SET vectorized = TRUE, chunk_count = ?
WHERE file_path = ?
""", (chunk_count, file_path))
```
### 13.5 RAG 服务配合要求
**文档上传接口响应增强:**
当 RAG 服务处理完文件后,应返回切片数量:
```json
{
"success": true,
"message": "文件上传成功,已添加到向量库",
"file": {
"filename": "考勤制度.docx",
"collection": "public_kb",
"path": "public/考勤制度.docx",
"size": 102400
},
"vectorization": {
"status": "completed",
"chunk_count": 15
}
}
```
**文档删除接口:**
删除文件时RAG 服务需要同时:
1. 删除物理文件
2. 删除 ChromaDB 中的所有相关切片
```http
DELETE /documents/<path>
```
**响应:**
```json
{
"success": true,
"message": "文件已删除",
"deleted_chunks": 15
}
```
### 13.6 元问题处理流程
当用户询问"我能访问哪些文件"时:
```
1. RAG 服务识别为"元问题"
2. RAG 服务检查是否有后端文件管理服务
有 → 调用后端 API 获取文件列表
无 → 从 ChromaDB metadata 中提取 source 列表(降级方案)
3. 返回文件列表给用户
```
**后端提供的文件列表 API**
```http
GET /api/files/list-for-rag?user_id=xxx&kb_names=public_kb,dept_finance
Authorization: Bearer <service_token>
```
**响应:**
```json
{
"success": true,
"files": [
{
"name": "考勤制度.docx",
"path": "public/考勤制度.docx",
"kb_name": "public_kb",
"size": 102400,
"uploaded_at": "2026-04-19T10:00:00"
}
],
"grouped_by_kb": {
"public_kb": {"count": 25, "files": [...]},
"dept_finance": {"count": 10, "files": [...]}
}
}
```
### 13.7 RAG 服务配置
`config.py` 中添加后端文件服务配置:
```python
# 后端文件管理服务(可选)
BACKEND_FILE_SERVICE_URL = os.getenv('BACKEND_FILE_SERVICE_URL', '')
BACKEND_SERVICE_TOKEN = os.getenv('BACKEND_SERVICE_TOKEN', '')
```
如果配置了后端文件服务RAG 在回答元问题时会调用后端 API 获取完整文件列表。
---
## 附录:元问题识别关键词
RAG 服务会自动识别以下关键词,判断为"元问题"并返回文件列表:
**权限相关:**
- "我的权限"、"用户权限"、"查看权限"、"访问权限"
- "权限能"、"权限可以"、"有什么权限"、"有哪些权限"
**文件列表相关:**
- "有哪些文件"、"什么文件"、"哪些文件"、"文件列表"
- "能查看"、"可以查看"、"有权限查看"
- "能访问"、"可以访问"、"有权限访问"
- "我能看"、"我可以看"、"我能查"、"我可以查"
- "能看到什么"、"能查到什么"、"可以看什么"、"可以查什么"
**知识库相关:**
- "知识库有哪些"、"库里有"、"文档有哪些"
- "有什么文档"、"有什么文件"、"包含什么"
后端可根据业务需求,要求 RAG 服务扩展此关键词列表。