Files
rag/docs/后端对接规范.md
lacerate551 183a57e7f1 refactor(api): 统一响应格式迁移 + 异步任务系统 + 状态码体系完善
- 将全部路由文件(12个)的 jsonify 响应迁移至 success_response/error_response 统一格式
- 修复 sync_routes.py error_response 参数错误(P0)
- 新增异步任务系统:task_registry + task_routes
- 新增状态码:TASK_NOT_FOUND(4014)、TASK_CONFLICT(4015)、REINDEX_ERROR(5040)
- 修正 task_routes/exam_pkg 中语义不匹配的状态码
- 更新 curl 测试手册、后端对接规范文档
- 添加缓存性能报告和 Redis 迁移计划
2026-06-05 22:56:00 +08:00

83 KiB
Raw Blame History

RAG 服务 API 接口规范


📋 变更记录2026-06-05 更新)

本次更新内容:新增 AI 智能出题端口、更新生产环境测试结果、长操作改为异步任务

2026-06-05 更新:出题批卷接口格式优化与输入校验增强

2026-06-05 异步任务变更:同步、上传向量化、出题、批阅等长耗时操作改为异步任务模式,立即返回 task_id,通过 GET /tasks/<task_id> 轮询结果

异步任务变更(⚠️ 重要2026-06-05

端点 变更说明
POST /sync 改为异步任务,返回 {"task_id": "xxx"} 而非同步结果
POST /documents/sync 改为异步任务,返回 {"task_id": "xxx"}
POST /collections/<kb>/reindex 改为异步任务,返回 {"task_id": "xxx"}
POST /documents/upload 新增 task_id 字段(向量化后台执行)
POST /documents/batch-upload 新增 task_id 字段(批量向量化后台执行)
POST /exam/generate 改为异步任务,返回 {"task_id": "xxx"}
POST /exam/generate-smart 改为异步任务,返回 {"task_id": "xxx"}
POST /exam/grade 改为异步任务,返回 {"task_id": "xxx"}

新增任务查询接口

端点 方法 功能 说明
/tasks GET 任务列表 支持按 status/type 过滤
/tasks/<task_id> GET 任务状态JSON 后端组推荐轮询接口,建议 1-2 秒间隔
/tasks/<task_id>/progress GET 任务进度SSE dev-ui 前端推荐使用
/tasks/stats GET 任务统计 按状态和类型分组统计

新增端口

端点 方法 功能 说明
/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 已合并到 /ragSSE 流式返回)
/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 正常(已修复)

出题批卷接口变更2026-06-05

变更项 旧值 新值 影响
批卷请求字段名 question_content content ⚠️ 破坏性变更,后端需同步修改
出题总题数上限 无限制 20 道 超过返回 400
批卷 question_type 无校验 必须属于 5 种合法题型 无效值返回 400
批卷结果格式 各题型格式不统一 统一 grading_status + details 后端需适配新格式
跨调用去重 exclude_stems 参数 追加出题时传入已有题干避免重复
出题结果 无短缺提示 新增 warnings 字段 可选消费

一、服务概述

RAG服务负责

  • 向量检索ChromaDB向量数据库 + BM25关键词检索
  • 文档解析PDF/Word/Excel解析与分块
  • 知识库问答Agentic RAG问答引擎
  • 出题批阅本地LLM实现可选
  • 反馈系统用户反馈与FAQ管理

后端服务负责:

  • 用户认证与权限控制
  • 会话管理与对话历史
  • 业务数据存储

二、API接口清单

接口分类说明

  • 生产接口:后端组对接时需调用的接口,文档中详细说明请求/响应格式
  • 开发自用接口(标记为 🛠️ DEV):仅供 RAG 服务内部开发调试使用(如 dev-ui 前端),后端组无需调用

引用溯源职责边界RAG 服务的 /rag 接口确保返回的 citations 数据包含足够的位置信息(chunk_indexsectionpagebbox 等),使后端组和 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 生成题目 异步任务,返回 task_id
/exam/generate-smart POST AI 智能出题 异步任务,返回 task_id
/exam/grade POST 批阅答案 异步任务,返回 task_id

2.5 异步任务查询

所有异步操作(同步、重建索引、上传向量化、出题、批阅)返回的 task_id 均可通过以下接口查询进度。

端点 方法 功能 说明
/tasks GET 任务列表 支持按 status/type 过滤
/tasks/<task_id> GET 任务状态JSON 后端组推荐轮询接口,建议 1-2 秒间隔
/tasks/<task_id>/progress GET 任务进度SSE dev-ui 前端推荐使用
/tasks/stats GET 任务统计 按状态和类型分组统计

三、调用方式

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 tokenfalse=生产模式直接放行)。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-06-05

本文档供后端开发人员参考,用于对接 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
}

注意tablessections 字段当前始终为空数组,功能待实现。

sources 字段说明Phase 2.1 更新):

字段 类型 说明
source string 来源文件名
page int 起始页码
page_end int|null 结束页码(跨页切片时有值)
page_range string 页码范围显示文本,如 "5""5-8"
section string 所属章节路径
chunk_type string 切片类型:texttableimage
doc_type string 文档类型:pdfwordexcel
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 文档类型:pdfwordexcel
section string 所属章节路径(已清洗,过滤掉正文内容)
preview string 内容摘要(用于搜索定位)
content string 切片内容(截断至 300 字)
chunk_type string 切片类型:texttableimage
page int 起始页码(仅 PDF
page_end int 结束页码(仅 PDF
bbox array 边界框坐标 [x0,y0,x1,y1](仅 PDF
bbox_mode string 坐标模式:normalized0-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. 替换标记为编号,构建最终展示文本
// 后端处理示例
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

关键点:

  1. 首次对话不传 session_idRAG 服务自动创建新会话
  2. 后续对话传入 session_idRAG 服务自动加载历史(无需前端传 history
  3. 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,
  "status_code": 2002,
  "message": "文件上传成功,已保存,向量化任务已启动",
  "data": {
    "file": {
      "filename": "document.pdf",
      "collection": "public_kb",
      "path": "public_kb/document.pdf",
      "size": 1024000,
      "replaced": false
    },
    "sync_status": "已保存,向量化任务已启动",
    "task_id": "a1b2c3d4e5f6"
  }
}

异步说明:文件保存为同步操作,向量化在后台线程异步执行。响应中的 task_id 可用于轮询向量化进度(GET /tasks/<task_id>)。当同步服务不可用时,task_idnullsync_status"已保存,等待手动同步"

同名文件处理:上传同名文件时,旧版本的切片会被自动清理后覆盖(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_indexpagebbox 等字段自行实现文档定位功能。

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_code": 2010,
  "message": "同步任务已启动",
  "data": {
    "task_id": "c3d4e5f6a1b2",
    "message": "同步任务已启动,通过 GET /tasks/c3d4e5f6a1b2 查询进度"
  }
}

⚠️ 异步变更:此接口已从同步改为异步。不再直接返回同步结果,而是返回 task_id。后端需通过 GET /tasks/<task_id> 轮询任务状态,直到 statuscompletedfailed。任务完成后,result 字段包含完整的同步结果(含 documents_processeddocuments_added 等)。

冲突检测:如果已有同步任务正在运行,返回 HTTP 409{"error": "TASK_RUNNING", "message": "同步任务正在执行中 (task_id: xxx),请等待完成"}

后端轮询示例

import time
import requests

def trigger_sync_and_wait():
    """触发同步并等待完成"""
    # 1. 触发同步任务
    resp = requests.post('http://rag-service:5001/sync')
    task_id = resp.json()['data']['task_id']

    # 2. 轮询任务状态(每 2 秒)
    while True:
        time.sleep(2)
        status_resp = requests.get(f'http://rag-service:5001/tasks/{task_id}')
        task_data = status_resp.json()['data']

        if task_data['status'] == 'completed':
            print(f"同步完成: {task_data['result']}")
            return task_data['result']
        elif task_data['status'] == 'failed':
            raise Exception(f"同步失败: {task_data['error']}")
        else:
            print(f"同步中: {task_data['progress']}% - {task_data['message']}")

### 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

响应:

{
  "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": 2010,
  "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 返回缓存结果(幂等性)
exclude_stems string[] 已有题目题干列表,用于跨调用去重(最多 100 条,传空或不传则不去重)

collection 参数说明:

后端应根据用户权限传入可访问的向量库列表:

  • 单个向量库:"dept_a_kb"
  • 多个向量库:["dept_a_kb", "public_kb"](按优先级排序,优先在第一个库检索)

入参校验规则2026-06-05 新增)

校验项 规则 失败返回
question_types 题型 必须属于: single_choice, multiple_choice, true_false, fill_blank, subjective HTTP 400 INVALID_PARAMS
question_types 数量 每种题型数量必须为非负整数 HTTP 400 INVALID_PARAMS
difficulty 必须为 1-5 的整数 HTTP 400 INVALID_PARAMS
总题数上限 所有题型数量之和不能超过 20 HTTP 400 INVALID_PARAMS

响应(异步任务):

{
  "success": true,
  "status_code": 2020,
  "message": "出题任务已启动",
  "data": {
    "task_id": "d4e5f6a1b2c3",
    "message": "出题任务已启动 (10题),通过 GET /tasks/d4e5f6a1b2c3 查询结果"
  }
}

⚠️ 异步变更:此接口已从同步改为异步。响应仅返回 task_id,后端需通过 GET /tasks/<task_id> 轮询任务状态。任务完成后,result 字段包含完整出题结果(格式见下方说明)。

轮询结果GET /tasks/<task_id> 完成后的 result 字段)

{
  "success": true,
  "request_id": "xxx",
  "total": 10,
  "source_chunks_used": 15,
  "requested_types": {"single_choice": 3, "true_false": 2, "fill_blank": 2},
  "actual_types": {"single_choice": 3, "true_false": 2, "fill_blank": 2},
  "warnings": [],
  "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": "原文片段..."
          }
        ]
      }
    }
  ]
}

warnings 字段说明:当某题型实际生成数量少于请求数量时,warnings 数组会返回短缺提示(如 ["single_choice: 请求 5 道,实际生成 3 道"])。后端可据此判断是否需要重新出题。正常情况该数组为空。

题型与 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 文件内容为空,无法出题
INVALID_PARAMS 400 入参校验失败题型不合法、数量超限、difficulty 范围错误等)
LLM_ERROR 500 LLM 调用失败
PARSE_ERROR 500 解析 LLM 响应失败

幂等性说明:

  • 传入 request_id 时,相同 ID 会返回缓存结果
  • 缓存有效期24 小时
  • 建议后端生成 UUID 作为 request_id,便于追踪和去重

7.2 批改答案

⚠️ 重要变更2026-06-05:批卷请求中的 question_content 字段已更名为 content,与出题接口返回的题目 content 字段保持一致。后端可直接将出题结果的 content 透传到批卷接口,无需额外转换。

POST /exam/grade

请求体

{
  "request_id": "可选,用于幂等性追踪",
  "answers": [
    {
      "question_id": "uuid-001",
      "question_type": "single_choice",
      "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",
      "content": {
        "stem": "多选题题干",
        "data": {"options": [...]},
        "answer": ["A", "C"]
      },
      "student_answer": ["A", "B"],
      "max_score": 4.0
    },
    {
      "question_id": "uuid-003",
      "question_type": "true_false",
      "content": {
        "stem": "判断题题干",
        "answer": "T"
      },
      "student_answer": "F",
      "max_score": 2.0
    },
    {
      "question_id": "uuid-004",
      "question_type": "fill_blank",
      "content": {
        "stem": "填空题有___个空",
        "answer": [["答案1", "同义词1"], ["答案2"]]
      },
      "student_answer": ["学生答案1", "学生答案2"],
      "max_score": 4.0
    },
    {
      "question_id": "uuid-005",
      "question_type": "subjective",
      "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无效值返回 HTTP 400
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_code": 2021,
  "message": "批阅任务已启动",
  "data": {
    "task_id": "f6a1b2c3d4e5",
    "message": "批阅任务已启动 (5题),通过 GET /tasks/f6a1b2c3d4e5 查询结果"
  }
}

⚠️ 异步变更:此接口已从同步改为异步。响应仅返回 task_id,后端需通过 GET /tasks/<task_id> 轮询任务状态。任务完成后,result 字段包含完整批阅结果(格式见下方说明)。

轮询结果GET /tasks/<task_id> 完成后的 result 字段)

{
  "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,
      "grading_status": "success",
      "details": {
        "correct": false,
        "student_answer": "A",
        "correct_answer": "B",
        "feedback": "正确答案: B"
      }
    },
    {
      "question_id": "uuid-004",
      "score": 2.0,
      "max_score": 4.0,
      "grading_status": "success",
      "details": {
        "total_blanks": 2,
        "correct_blanks": 1,
        "blank_scores": [2.0, 0],
        "feedback": "2 个空中答对 1 个"
      }
    },
    {
      "question_id": "uuid-005",
      "score": 7.5,
      "max_score": 10.0,
      "grading_status": "success",
      "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": "整体回答良好",
        "warnings": ["缺少评分标准(scoring_points),评分结果仅供参考"]
      }
    },
    {
      "question_id": "uuid-006",
      "score": 0,
      "max_score": 10.0,
      "grading_status": "failed",
      "details": {
        "error": "评分结果解析失败"
      }
    }
  ]
}

grading_status 状态说明2026-06-05 新增)

状态 说明
success 评分成功score 和 details 有效
failed 评分失败LLM 解析失败或超时score 为 0details 包含 error 描述

批卷逻辑说明

题型 批卷方式 说明
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: 接收学生答案

# 学生提交的答案
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,
            "content": question.content,  # 含正确答案,与出题接口 content 结构一致
            "student_answer": ans['answer'],
            "max_score": question.score
        })

    return {"answers": grade_answers}

Step 4: 调用 RAG 批卷接口(异步任务)

import time

def call_rag_grade(grade_request):
    """调用 RAG 批卷接口并轮询等待结果"""
    # 1. 提交批阅任务
    response = requests.post(
        'http://rag-service:5001/exam/grade',
        json=grade_request
    )
    task_id = response.json()['data']['task_id']

    # 2. 轮询任务状态(每 2 秒)
    while True:
        time.sleep(2)
        status_resp = requests.get(f'http://rag-service:5001/tasks/{task_id}')
        task_data = status_resp.json()['data']

        if task_data['status'] == 'completed':
            return task_data['result']
        elif task_data['status'] == 'failed':
            raise Exception(f"批阅失败: {task_data['error']}")

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 后端数据库 题型,决定批卷方式
content.answer 后端数据库 正确答案(客观题直接比对,主观题作为参考)
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 - 技术部知识库

十二、部署注意事项

  1. 环境变量

    • DEV_MODE=false - 生产环境必须关闭开发模式
    • DOCUMENTS_PATH - 文档存储路径
  2. 网络配置

    • RAG 服务端口5001默认
    • 后端网关需要配置反向代理
  3. 文件存储

    • 文档存储在 documents/ 目录
    • 向量库存储在 vector_store/chroma/ 目录
  4. 资源要求

    • 内存:建议 4GB+(向量检索占用)
    • 磁盘:根据文档数量评估
  5. 数据库要求(新增)

    • 需要 SQLite 或其他数据库支持文档版本管理
    • 数据库路径:data/knowledge.db(默认)
    • 首次启动会自动创建 document_versionsversion_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 触发同步(异步任务,返回 task_id
/sync/status GET 同步状态
/sync/history GET 同步历史
/sync/changes GET 变更日志
/sync/start POST 启动文件监控
/sync/stop POST 停止文件监控
/documents/sync POST 触发文档同步(异步任务,返回 task_id
/collections/<kb_name>/reindex POST 重建索引(异步任务,返回 task_id

13.7 出题系统

端点 方法 说明
/exam/generate POST 生成题目(异步任务,返回 task_id
/exam/generate-smart POST AI 智能出题(异步任务,返回 task_id
/exam/grade POST 批改答案(异步任务,返回 task_id
/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.13 异步任务查询

所有异步操作(同步、重建索引、上传向量化、出题、批阅)返回的 task_id 均可通过以下接口查询进度。

端点 方法 说明
/tasks GET 任务列表(支持 status/type 过滤)
/tasks/<task_id> GET 任务状态JSON 轮询,后端组推荐使用
/tasks/<task_id>/progress GET 任务进度SSE 流式dev-ui 前端使用)
/tasks/stats GET 任务统计

任务状态字段说明

字段 类型 说明
task_id string 任务唯一标识
type string 类型sync / reindex / upload / batch_upload / exam_generate / exam_grade
status string 状态pending / running / completed / failed
progress float 进度百分比0-100
current int 当前处理项数
total int 总项数
stage string 当前阶段
message string 当前步骤描述
result any 完成后的结果数据(仅 status=completed 时存在)
error string 失败错误信息(仅 status=failed 时存在)
duration_ms int 执行耗时毫秒(仅已完成时存在)

后端对接轮询模式

import time
import requests

def async_task_poll(task_id, base_url='http://rag-service:5001', interval=2, timeout=300):
    """
    通用异步任务轮询函数

    Args:
        task_id: 任务 ID
        base_url: RAG 服务地址
        interval: 轮询间隔(秒)
        timeout: 超时时间(秒)

    Returns:
        任务结果result 字段)

    Raises:
        TimeoutError: 超时
        Exception: 任务失败
    """
    elapsed = 0
    while elapsed < timeout:
        time.sleep(interval)
        elapsed += interval

        resp = requests.get(f'{base_url}/tasks/{task_id}')
        if resp.status_code == 404:
            raise Exception(f"任务不存在: {task_id}")

        task = resp.json()['data']

        if task['status'] == 'completed':
            return task.get('result')
        elif task['status'] == 'failed':
            raise Exception(f"任务失败: {task.get('error', '未知错误')}")
        # 可选:记录进度日志
        # logger.info(f"任务 {task_id}: {task['progress']}% - {task['message']}")

    raise TimeoutError(f"任务超时: {task_id} (已等待 {timeout}s)")

十四、文件管理服务(后端负责)

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 服务需要同时:

  1. 删除物理文件
  2. 删除 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 服务扩展此关键词列表。