多库检索与存储修复: - 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 - 更新多篇现有文档
73 KiB
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问答(核心接口)
请求:
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 必需配置
DASHSCOPE_API_KEY=your-api-key
4.2 环境模式配置
# 生产环境:关闭开发模式(认证由后端控制,通过 collections 传参)
DEV_MODE=false
# 应用环境标识(控制会话存储、审计日志等功能开关)
APP_ENV=prod
说明:
DEV_MODE控制认证行为(true=开发模式支持 mock token,false=生产模式直接放行)。APP_ENV控制功能模块开关(dev=启用会话存储和审计日志,prod=无状态模式)。两者独立配置。
4.3 可选配置
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:
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
POST http://rag-service:5001/rag
Authorization: Bearer mock-token-admin
Content-Type: application/json
{
"message": "问题",
"collections": ["public_kb"],
"chat_history": []
}
方式 2:不传 Header(自动使用开发用户)
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 环境配置
# 开发环境(默认)
DEV_MODE=true
# 生产环境
DEV_MODE=false
三、问答接口
3.1 普通聊天
POST /chat
请求体:
{
"message": "用户消息",
"history": [
{"role": "user", "content": "历史问题"},
{"role": "assistant", "content": "历史回答"}
]
}
注意:
/chat接口中history为可选参数,也可使用chat_history作为参数名(两者等效)。该接口不走知识库检索,直接由 LLM 回答。
响应:
{
"answer": "AI 回复内容",
"mode": "chat",
"sources": [],
"web_searched": false
}
3.2 知识库问答(核心接口 - SSE 流式返回)
POST /rag
重要变更:
/rag接口已升级为 SSE 流式返回,不再返回阻塞 JSON。 原独立的/rag/stream端点已合并至此端点。
请求体:
{
"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 事件完整结构:
{
"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) |
按文档类型差异化定位:
| 文档类型 | 定位字段 | 定位方式 |
|---|---|---|
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]。
后端处理建议:
- 解析
answer中的[ref:chunk_id]标记 - 根据
citations数组顺序生成用户可见编号(1, 2, 3...) - 替换标记为编号,构建最终展示文本
// 后端处理示例
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):
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):
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
关键点:
- 首次对话不传
session_id,RAG 服务自动创建新会话 - 后续对话传入
session_id,RAG 服务自动加载历史(无需前端传history) - Query Rewriting 会利用历史上下文进行消歧和实体补全
会话相关 API:
| 接口 | 说明 |
|---|---|
GET /sessions |
获取用户会话列表 |
GET /history/<session_id> |
获取会话历史 |
DELETE /session/<session_id> |
删除会话 |
后端实现参考(数据库表结构):
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
请求体:
{
"query": "检索关键词",
"top_k": 5,
"collections": ["public_kb", "dept_finance"]
}
响应:
{
"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
响应:
{
"collections": [
{
"name": "public_kb",
"display_name": "公开知识库",
"document_count": 150,
"department": null,
"description": "全员可访问"
}
],
"total": 1
}
4.2 创建向量库
POST /collections
请求体:
{
"name": "dept_finance",
"display_name": "财务部知识库",
"department": "财务部",
"description": "财务部专用知识库"
}
4.3 修改向量库
PUT /collections/<name>
4.4 删除向量库
DELETE /collections/<name>
查询参数:
| 参数 | 类型 | 必需 | 说明 |
|---|---|---|---|
delete_documents |
boolean | ❌ | 是否删除文档源文件,默认 false |
响应:
{
"success": true,
"message": "向量库 'dept_finance' 已删除",
"deleted_documents": false
}
说明:
- 默认只删除向量数据(ChromaDB 集合、BM25 索引)
- 设置
delete_documents=true会同时删除文档源文件 - 公开知识库 (
public_kb) 不允许删除
4.5 获取向量库文档列表
GET /collections/<kb_name>/documents
响应:
{
"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 |
响应:
{
"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
请求体:
{
"reason": "新版本已发布,旧版本废止"
}
响应:
{
"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
响应:
{
"success": true,
"restored_chunks": 15,
"document_id": "报销制度.pdf",
"collection": "public_kb"
}
4.5.3 获取文档版本历史
GET /collections/<kb_name>/documents/<filename>/versions
查询参数:
| 参数 | 类型 | 必需 | 说明 |
|---|---|---|---|
limit |
int | ❌ | 返回数量,默认 10 |
响应:
{
"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
请求体:
{
"query": "财务部的报销流程是什么"
}
响应:
{
"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)
响应:
{
"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
响应:
{
"success": true,
"status": "active",
"chunk_count": 105,
"last_processed": null
}
5.5 更新/替换文档
PUT /documents/<path>
Content-Type: multipart/form-data
表单参数:
file: 替换的文件(必需)
响应:
{
"success": true,
"message": "文件已更新"
}
说明:
- 文件必须已存在,否则返回 404
- 更新后自动触发重新向量化
5.6 删除文档
DELETE /documents/<path>
5.7 查看文件切片
GET /documents/<path>/chunks
响应:
{
"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 个) |
响应:
{
"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 个切片作为文档概览
前端调用示例:
// 用户点击引用标签时跳转
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
请求体: 无需传递参数(同步所有知识库)
响应:
{
"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
响应:
{
"enabled": true,
"monitoring": true,
"last_sync": "2026-04-19T18:30:00",
"documents_tracked": 150
}
6.3 同步历史
GET /sync/history?limit=20
参数:
| 参数 | 类型 | 必需 | 说明 |
|---|---|---|---|
limit |
int | ❌ | 返回记录数,默认 20 |
响应:
{
"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 | ❌ | 过滤指定向量库 |
响应:
{
"changes": [
{
"change_time": "2026-04-19T18:25:00",
"document": "规章制度/考勤制度.docx",
"change_type": "modified",
"collection": "public_kb"
}
]
}
6.5 启动/停止文件监控
POST /sync/start
POST /sync/stop
响应:
{
"status": "success",
"status_code": 3001,
"message": "文件监控已启动"
}
七、出题接口
7.1 生成题目
POST /exam/generate
请求体:
{
"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"](按优先级排序,优先在第一个库检索)
响应:
完整响应格式(包含外层包装):
{
"success": true,
"status": "success",
"status_code": 2011,
"message": "出题完成",
"data": {
"success": true,
"request_id": "xxx",
"total": 10,
"source_chunks_used": 15,
"questions": [...]
}
}
data 内部结构:
{
"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 | 根据业务需求添加标签 |
| 审核入库 | 人工或自动审核后存入题库 |
错误响应格式:
{
"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
请求体
{
"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 | "学生作答的长文本..." |
响应
完整响应格式(包含外层包装):
{
"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 内部结构:
{
"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,带重试和限流机制 |
注意事项
- question_id 必填:用于匹配批卷结果,后端需在调用时传入
- 顺序保证:results 数组顺序与 answers 数组顺序一致
- 主观题超时:主观题批阅有 15 秒超时,失败时返回 score=0
- 填空题同义词:answer 字段支持同义词数组,如
[["北京", "Beijing"]]
后端对接流程
重要:RAG 服务是无状态的,不存储题库数据。所有题目信息需由后端传入。
完整批卷流程:
┌─────────────┐ ┌─────────────┐
│ 后端 │ │ RAG 服务 │
├─────────────┤ ├─────────────┤
│ 1. 接收学生答案 │ │
│ 2. 查询数据库获取题目 │ │
│ 3. 组装请求 ────────────────────▶ │ 4. 批卷处理 │
│ │ - 客观题:本地比对
│ │ - 主观题:LLM评分
│ 6. 更新学生成绩 ◀──────────────── │ 5. 返回结果 │
│ (根据 question_id 匹配) │ │
└─────────────┘ └─────────────┘
Step 1: 接收学生答案
# 学生提交的答案
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: 从数据库查询题目信息
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: 组装批卷请求
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 批卷接口
def call_rag_grade(grade_request):
"""调用 RAG 批卷接口"""
response = requests.post(
'http://rag-service:5001/exam/grade',
json=grade_request
)
return response.json()
Step 5: 更新学生成绩
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
)
完整调用示例
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: 权限校验
def check_exam_permission(user_id):
"""检查用户是否有出题权限"""
user = get_user(user_id)
# 通常只有管理员和部门管理员有出题权限
return user.role in ['admin', 'manager']
Step 2: 获取用户可访问的向量库
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 出题接口
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: 入库存储
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: 题型默认分值参考
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)
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)
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)
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)
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)
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)
);
权限判断逻辑:
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) - 新增
功能:记录文档的版本信息,支持版本追溯和废止管理。
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) - 新增
功能:记录文档版本的变更历史,便于审计追踪。
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 字段:
{
"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. 后端返回前端
十、错误响应格式
错误响应存在两种格式,后端需同时兼容:
格式一:统一错误格式(大部分接口使用)
{
"success": false,
"status": "failed",
"error_code": "MISSING_PARAMS",
"status_code": 4000,
"message": "缺少 file_path 或 collection 参数"
}
格式二:简单错误格式(部分接口的异常处理使用)
{
"error": "错误描述信息"
}
常见 HTTP 状态码:
400- 请求参数错误401- 未认证403- 权限不足404- 资源不存在500- 服务器内部错误503- 服务不可用
十一、向量库命名规范
ChromaDB 集合名称限制:
- 只能包含
[a-zA-Z0-9._-] - 长度 3-63 字符
- 必须以字母或数字开头和结尾
推荐命名:
public_kb- 公开知识库dept_finance- 财务部知识库dept_hr- 人事部知识库dept_tech- 技术部知识库
十二、部署注意事项
-
环境变量:
DEV_MODE=false- 生产环境必须关闭开发模式DOCUMENTS_PATH- 文档存储路径
-
网络配置:
- RAG 服务端口:5001(默认)
- 后端网关需要配置反向代理
-
文件存储:
- 文档存储在
documents/目录 - 向量库存储在
vector_store/chroma/目录
- 文档存储在
-
资源要求:
- 内存:建议 4GB+(向量检索占用)
- 磁盘:根据文档数量评估
-
数据库要求(新增):
- 需要 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):
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):
-- 方案 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 设计
获取文件列表:
GET /api/files?kb_name=public_kb&parent_path=public
响应:
{
"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"}
]
}
获取用户可访问的文件列表:
GET /api/files/accessible
Authorization: Bearer <jwt_token>
响应:
{
"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 上传文件后:
# 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 服务处理完文件后,应返回切片数量:
{
"success": true,
"message": "文件上传成功,已添加到向量库",
"file": {
"filename": "考勤制度.docx",
"collection": "public_kb",
"path": "public/考勤制度.docx",
"size": 102400
},
"vectorization": {
"status": "completed",
"chunk_count": 15
}
}
文档删除接口:
删除文件时,RAG 服务需要同时:
- 删除物理文件
- 删除 ChromaDB 中的所有相关切片
DELETE /documents/<path>
响应:
{
"success": true,
"message": "文件已删除",
"deleted_chunks": 15
}
13.6 元问题处理流程
当用户询问"我能访问哪些文件"时:
1. RAG 服务识别为"元问题"
↓
2. RAG 服务检查是否有后端文件管理服务
↓
有 → 调用后端 API 获取文件列表
无 → 从 ChromaDB metadata 中提取 source 列表(降级方案)
↓
3. 返回文件列表给用户
后端提供的文件列表 API:
GET /api/files/list-for-rag?user_id=xxx&kb_names=public_kb,dept_finance
Authorization: Bearer <service_token>
响应:
{
"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 中添加后端文件服务配置:
# 后端文件管理服务(可选)
BACKEND_FILE_SERVICE_URL = os.getenv('BACKEND_FILE_SERVICE_URL', '')
BACKEND_SERVICE_TOKEN = os.getenv('BACKEND_SERVICE_TOKEN', '')
如果配置了后端文件服务,RAG 在回答元问题时会调用后端 API 获取完整文件列表。
附录:元问题识别关键词
RAG 服务会自动识别以下关键词,判断为"元问题"并返回文件列表:
权限相关:
- "我的权限"、"用户权限"、"查看权限"、"访问权限"
- "权限能"、"权限可以"、"有什么权限"、"有哪些权限"
文件列表相关:
- "有哪些文件"、"什么文件"、"哪些文件"、"文件列表"
- "能查看"、"可以查看"、"有权限查看"
- "能访问"、"可以访问"、"有权限访问"
- "我能看"、"我可以看"、"我能查"、"我可以查"
- "能看到什么"、"能查到什么"、"可以看什么"、"可以查什么"
知识库相关:
- "知识库有哪些"、"库里有"、"文档有哪些"
- "有什么文档"、"有什么文件"、"包含什么"
后端可根据业务需求,要求 RAG 服务扩展此关键词列表。