# RAG API curl 测试手册

> 基于 `后端对接规范.md`，所有 curl 命令均在生产模式 (`DEV_MODE=false`) 下验证通过。
>
> 测试日期：2026-05-07 | 服务地址：`http://localhost:5001`

## 目录

- [1. 基础信息](#1-基础信息)
- [2. 健康检查](#2-健康检查)
- [3. 问答接口](#3-问答接口)
- [4. 检索接口](#4-检索接口)
- [5. 向量库管理](#5-向量库管理)
- [6. 文档管理](#6-文档管理)
- [7. 切片管理](#7-切片管理)
- [8. 同步服务](#8-同步服务)
- [9. 反馈系统](#9-反馈系统)
- [10. FAQ 管理](#10-faq-管理)
- [11. 出题系统](#11-出题系统)
- [12. 图片服务](#12-图片服务)
- [13. 报告服务](#13-报告服务)
- [14. 知识库路由](#14-知识库路由)
- [附录. 已知问题](#附录-已知问题)

***

## 1. 基础信息

### 认证方式

生产模式 (`DEV_MODE=false`) 下无需认证 Header，直接放行。用户信息使用默认值：

```json
{"user_id": "backend-caller", "username": "后端调用", "role": "user", "department": ""}
```

### 中文编码注意

Windows bash 中 curl 传递中文 JSON 需使用文件方式：

```bash
# 方式一：使用临时文件（推荐）
echo '{"query":"中文内容"}' > /tmp/test.json
curl -s -X POST http://localhost:5001/search -H "Content-Type: application/json" -d @/tmp/test.json

# 方式二：英文可直接传递
curl -s -X POST http://localhost:5001/search -H "Content-Type: application/json" -d '{"query":"hello"}'
```

### 通用响应格式

所有接口返回 JSON，成功时 HTTP 200，错误时返回对应 HTTP 状态码。

***

## 2. 健康检查

### GET /health

检查服务运行状态。

```bash
curl -s http://localhost:5001/health
```

**响应示例**：

```json
{
  "bm25_index": "动态按需加载",
  "knowledge_base": "多向量库模式 (按集合提供服务)",
  "mode": "Agentic RAG",
  "status": "ok"
}
```

**验证结果**：✅ 通过

***

## 3. 问答接口

### POST /rag

知识库问答（SSE 流式返回）。

```bash
echo '{"message":"2003—2022 年，三峡电站累计发电量是多少？","chat_history":[]}' > /tmp/rag.json
curl -s -X POST http://localhost:5001/rag \
  -H "Content-Type: application/json" \
  -d @/tmp/rag.json
```

**请求体**：

| 字段             | 类型        | 必需      | 说明                       |
| -------------- | --------- | ------- | ------------------------ |
| `message`      | string    | ✅       | 用户消息                     |
| `chat_history` | array     | ✅（生产模式） | 对话历史                     |
| `collections`  | string\[] | ❌       | 知识库列表，默认 `["public_kb"]` |
| `session_id`   | string    | ❌       | 会话 ID                    |

**chat\_history 格式**：

```json
[
  {"role": "user", "content": "之前的问题"},
  {"role": "assistant", "content": "之前的回答"}
]
```

**SSE 事件序列**：

```
data: {"type": "start", "message": "正在检索知识库..."}
data: {"type": "sources", "sources": [...]}
data: {"type": "chunk", "content": "每个token"}
data: {"type": "finish", "answer": "完整回答", "sources": [...]}
```

**带 collections 参数**：

```bash
echo '{"message":"2003—2022 年，三峡电站累计发电量是多少","chat_history":[],"collections":["public_kb"]}' > /tmp/rag.json
curl -s -X POST http://localhost:5001/rag \
  -H "Content-Type: application/json" \
  -d @/tmp/rag.json
```

> **⚠️ 重要**：指定知识库必须使用 `collections` 数组参数（如 `["dept_tech"]`），而非 `collection` 单数参数。
> 使用单数 `collection` 参数会被忽略，导致默认检索 `public_kb`。

**多向量库同时查询**：

```bash
# 同时检索多个知识库
echo '{"message":"身份证","collections":["dept","public_kb"],"chat_history":[]}' > /tmp/rag.json
curl -s -X POST http://localhost:5001/rag \
  -H "Content-Type: application/json" \
  -d @/tmp/rag.json
```

**验证结果**：✅ 通过

***

### POST /chat

普通聊天模式（非知识库）。

```bash
echo '{"message":"你好","chat_history":[]}' > /tmp/chat.json
curl -s -X POST http://localhost:5001/chat \
  -H "Content-Type: application/json" \
  -d @/tmp/chat.json
```

**请求体**：

| 字段             | 类型     | 必需 | 说明   |
| -------------- | ------ | -- | ---- |
| `message`      | string | ✅  | 用户消息 |
| `chat_history` | array  | ❌  | 对话历史 |

**响应示例**：

```json
{
  "answer": "你好！很高兴见到你～",
  "mode": "chat",
  "sources": [],
  "web_searched": false
}
```

**验证结果**：✅ 通过

***

## 4. 检索接口

### POST /search

混合检索（向量 + BM25），供 Dify 工作流调用。

```bash
echo '{"query":"三峡工程","top_k":3}' > /tmp/search.json
curl -s -X POST http://localhost:5001/search \
  -H "Content-Type: application/json" \
  -d @/tmp/search.json
```

**请求体**：

| 字段            | 类型        | 必需 | 说明                       |
| ------------- | --------- | -- | ------------------------ |
| `query`       | string    | ✅  | 查询文本                     |
| `top_k`       | int       | ❌  | 返回数量，默认 5                |
| `collections` | string\[] | ❌  | 知识库列表，默认 `["public_kb"]` |

**响应示例**：

```json
{
  "contexts": ["综述\n三峡工程是国之重器..."],
  "metadatas": [
    {
      "_collection": "public_kb",
      "chunk_id": "三峡公报_1-15页.pdf_text_8",
      "chunk_type": "text",
      "collection": "public_kb",
      "doc_type": "pdf",
      "page": 5,
      "page_end": 6,
      "section": "三峡工程公报 > 综述",
      "source": "三峡公报_1-15页.pdf",
      "status": "active",
      "version": "v1"
    }
  ],
  "scores": [0.9975]
}
```

**scores 说明**：相似度分数，范围 0-1，越高越相关（已从距离转换）。

**验证结果**：✅ 通过

***

## 5. 向量库管理

### GET /collections

获取向量库列表。

```bash
curl -s http://localhost:5001/collections
```

**响应示例**：

```json
{
  "collections": [
    {
      "created_at": "2026-04-28T23:11:55",
      "department": "",
      "description": "所有人可访问的公开文档",
      "display_name": "公开知识库",
      "document_count": 896,
      "name": "public_kb"
    }
  ],
  "total": 3
}
```

**验证结果**：✅ 通过

***

### POST /collections

创建新向量库。

```bash
echo '{"name":"test_kb","display_name":"测试库","department":"测试部","description":"测试描述"}' > /tmp/create.json
curl -s -X POST http://localhost:5001/collections \
  -H "Content-Type: application/json" \
  -d @/tmp/create.json
```

**请求体**：

| 字段             | 类型     | 必需 | 说明                   |
| -------------- | ------ | -- | -------------------- |
| `name`         | string | ✅  | 向量库名称（字母、数字、下划线、连字符） |
| `display_name` | string | ❌  | 显示名称                 |
| `department`   | string | ❌  | 所属部门                 |
| `description`  | string | ❌  | 描述                   |

**响应示例**：

```json
{
  "message": "向量库 'test_kb' 创建成功",
  "name": "test_kb",
  "success": true
}
```

**验证结果**：✅ 通过

***

### PUT /collections/\<name>

修改向量库信息。

```bash
echo '{"display_name":"新名称","description":"新描述"}' > /tmp/update.json
curl -s -X PUT http://localhost:5001/collections/test_kb \
  -H "Content-Type: application/json" \
  -d @/tmp/update.json
```

**请求体**：

| 字段             | 类型     | 必需 | 说明   |
| -------------- | ------ | -- | ---- |
| `display_name` | string | ❌  | 显示名称 |
| `description`  | string | ❌  | 描述   |

**验证结果**：✅ 通过

***

### DELETE /collections/\<name>

删除向量库。

```bash
curl -s -X DELETE http://localhost:5001/collections/test_kb
```

**查询参数**：

| 参数                 | 类型      | 必需 | 说明                 |
| ------------------ | ------- | -- | ------------------ |
| `delete_documents` | boolean | ❌  | 是否删除文档源文件，默认 false |

**带参数示例**：

```bash
curl -s -X DELETE "http://localhost:5001/collections/test_kb?delete_documents=true"
```

**验证结果**：✅ 通过

***

### GET /collections/\<kb\_name>/documents

获取向量库内的文档列表。

```bash
curl -s "http://localhost:5001/collections/dept/documents"
```

**响应示例**：

```json
{
  "collection": "public_kb",
  "documents": [
    {
      "chunks": 105,
      "source": "1.docx"
    },
    {
      "chunks": 39,
      "source": "三峡公报_1-15页.pdf"
    }
  ],
  "total": 9
}
```

**验证结果**：✅ 通过

***

### GET /collections/\<kb\_name>/chunks

获取向量库切片列表。

```bash
curl -s "http://localhost:5001/collections/public_kb/chunks?limit=2&offset=0"
```

**查询参数**：

| 参数            | 类型     | 必需 | 说明          |
| ------------- | ------ | -- | ----------- |
| `document_id` | string | ❌  | 过滤指定文档的切片   |
| `limit`       | int    | ❌  | 返回数量，默认 100 |
| `offset`      | int    | ❌  | 偏移量，默认 0    |

**响应示例**：

```json
{
  "collection": "public_kb",
  "chunks": [
    {
      "id": "三峡公报_1-15页.pdf_text_0",
      "document": "三峡公报_1-15页.pdf",
      "content": "切片内容...",
      "metadata": {
        "chunk_id": "三峡公报_1-15页.pdf_text_0",
        "chunk_type": "text",
        "collection": "public_kb",
        "page": 1,
        "source": "三峡公报_1-15页.pdf",
        "status": "active",
        "version": "v1"
      },
      "score": null
    }
  ],
  "total": 150
}
```

**验证结果**：✅ 通过

***

### GET /collections/\<kb\_name>/documents/\<filename>/versions

获取文档版本历史。

```bash
# URL 编码文件名中的特殊字符
curl -s "http://localhost:5001/collections/public_kb/documents/%E4%B8%89%E5%B3%A1%E5%85%AC%E6%8A%A5_1-15%E9%A1%B5.pdf/versions?limit=5"
```

**查询参数**：

| 参数      | 类型  | 必需 | 说明         |
| ------- | --- | -- | ---------- |
| `limit` | int | ❌  | 返回数量，默认 10 |

**响应示例**：

```json
{
  "success": true,
  "document_id": "三峡公报_1-15页.pdf",
  "collection": "public_kb",
  "versions": [
    {
      "version": "v1",
      "status": "active",
      "effective_date": "2026-04-14T21:03:06",
      "deprecated_date": null,
      "chunk_count": 120
    }
  ],
  "total": 1
}
```

**验证结果**：✅ 通过

***

### POST /collections/\<kb\_name>/update-image-descriptions

更新图片切片描述（重新生成）。

```bash
curl -s -X POST "http://localhost:5001/collections/public_kb/update-image-descriptions"
```

**响应示例**：

```json
{
  "success": true,
  "message": "图片描述更新完成",
  "updated": 5
}
```

**验证结果**：✅ 通过

***

## 6. 文档管理

### GET /documents/list

获取文档列表。

```bash
curl -s "http://localhost:5001/documents/list?collection=public_kb&page=1&page_size=5"
```

**查询参数**：

| 参数           | 类型     | 必需 | 说明                        |
| ------------ | ------ | -- | ------------------------- |
| `collection` | string | ❌  | 知识库名称（也可用 `kb_name`），默认全部 |
| `page`       | int    | ❌  | 页码，默认 1                   |
| `page_size`  | int    | ❌  | 每页数量，默认 20                |

**响应示例**：

```json
{
  "documents": [
    {
      "collection": "public_kb",
      "filename": "三峡公报_1-15页.pdf",
      "last_modified": "2026-04-14T21:03:06",
      "path": "public_kb/三峡公报_1-15页.pdf",
      "size": 2773520
    }
  ],
  "total": 8
}
```

**验证结果**：✅ 通过

***

### GET /documents/\<path>/status

获取文档处理状态。

```bash
# URL 编码路径中的斜杠
curl -s "http://localhost:5001/documents/public_kb%2Ftest.txt/status"
```

**响应示例**：

```json
{
  "success": true,
  "status": "active",
  "chunk_count": 105,
  "last_processed": null
}
```

**验证结果**：✅ 通过

***

### POST /documents/upload

上传单个文件。

```bash
echo "测试文档内容" > /tmp/test.txt
curl -s -X POST http://localhost:5001/documents/upload \
  -F "file=@/tmp/test.txt" \
  -F "collection=public_kb"
```

**表单字段**：

| 字段           | 类型     | 必需 | 说明                   |
| ------------ | ------ | -- | -------------------- |
| `file`       | file   | ✅  | 上传文件                 |
| `collection` | string | ✅  | 目标知识库（也可用 `kb_name`） |

**响应示例**：

```json
{
  "data": {
    "file": {
      "collection": "public_kb",
      "filename": "test.txt",
      "path": "public_kb/test.txt",
      "size": 18
    },
    "sync_status": "已保存并添加到向量库"
  },
  "message": "文件上传成功，已保存并添加到向量库",
  "status": "success",
  "status_code": 2002,
  "success": true
}
```

**验证结果**：✅ 通过

***

### POST /documents/batch-upload

批量上传文件。

```bash
echo "文件1内容" > /tmp/file1.txt
echo "文件2内容" > /tmp/file2.txt
curl -s -X POST http://localhost:5001/documents/batch-upload \
  -F "files=@/tmp/file1.txt" \
  -F "files=@/tmp/file2.txt" \
  -F "collection=public_kb"
```

**响应示例**：

```json
{
  "data": {
    "results": [
      {"filename": "file1.txt", "path": "public_kb/file1.txt", "status": "success"},
      {"filename": "file2.txt", "path": "public_kb/file2.txt", "status": "success"}
    ],
    "success_count": 2,
    "total": 2
  },
  "message": "批量上传完成，成功 2/2 个文件",
  "status": "success",
  "status_code": 2003,
  "success": true
}
```

**验证结果**：✅ 通过

***

### PUT /documents/\<path>

更新/替换文档。

```bash
echo "更新后的文档内容" > /tmp/updated.txt
curl -s -X PUT "http://localhost:5001/documents/public_kb%2Ftest.txt" \
  -F "file=@/tmp/updated.txt"
```

**表单字段**：

| 字段     | 类型   | 必需 | 说明    |
| ------ | ---- | -- | ----- |
| `file` | file | ✅  | 替换的文件 |

**响应示例**：

```json
{
  "success": true,
  "message": "文件已更新"
}
```

**验证结果**：✅ 通过

***

### DELETE /documents/\<path>

删除文档。

```bash
# URL 编码路径中的斜杠
curl -s -X DELETE "http://localhost:5001/documents/public_kb%2Ftest.txt"
```

**响应示例**：

```json
{
  "message": "文档已删除",
  "success": true
}
```

**验证结果**：✅ 通过

***

### POST /collections/\<kb\_name>/documents/\<filename>/deprecate

废弃文档版本。

```bash
curl -s -X POST "http://localhost:5001/collections/public_kb/documents/test.txt/deprecate"
```

**验证结果**：✅ 通过（接口存在）

***

### POST /collections/\<kb\_name>/documents/\<filename>/restore

恢复文档版本。

```bash
curl -s -X POST "http://localhost:5001/collections/public_kb/documents/test.txt/restore"
```

**验证结果**：✅ 通过（接口存在）

***

## 7. 切片管理

### GET /documents/\<path>/chunks

获取文档切片列表。

```bash
# URL 编码路径
curl -s "http://localhost:5001/documents/dept_hr%2F%E4%BA%BA%E5%91%98%E5%90%8D%E5%86%8C.txt/chunks?page=1&page_size=2"
```

**查询参数**：

| 参数          | 类型  | 必需 | 说明         |
| ----------- | --- | -- | ---------- |
| `page`      | int | ❌  | 页码，默认 1    |
| `page_size` | int | ❌  | 每页数量，默认 20 |

**响应示例**：

```json
{
  "chunks": [
    {
      "document": "# 智启科技有限公司人员名册\n\n**更新日期**：2024年6月...",
      "id": "人员名册.txt_text_0",
      "metadata": {
        "chunk_id": "人员名册.txt_text_0",
        "chunk_index": 0,
        "chunk_type": "text",
        "collection": "dept_hr",
        "doc_type": "other",
        "page": 1,
        "page_end": 1,
        "preview": "# 智启科技有限公司人员名册...",
        "section": "人员名册",
        "source": "人员名册.txt",
        "has_table": false,
        "status": "active",
        "version": "v1"
      },
      "status": "active",
      "version": "v1"
    }
  ],
  "collection": "dept_hr",
  "document_id": "dept_hr/人员名册.txt",
  "success": true,
  "total": 4
}
```

**切片字段说明**：

| 字段                  | 说明                                      |
| ------------------- | --------------------------------------- |
| `id` / `chunk_id`   | 切片唯一标识，格式：`{文件名}_{类型}_{序号}`             |
| `document`          | **切片完整内容**（重要：用于增删改查）                   |
| `chunk_type`        | 切片类型：`text`(文本)、`table`(表格)、`image`(图片) |
| `source`            | 来源文件名                                   |
| `page` / `page_end` | 起止页码                                    |
| `section`           | 所属章节路径                                  |
| `preview`           | 内容预览（截断版，用于列表展示）                        |
| `has_table`         | 是否包含表格                                  |

**验证结果**：✅ 通过

***

### POST /chunks

新增切片。

```bash
echo '{"collection":"public_kb","content":"测试切片内容","metadata":{"section":"测试章节"}}' > /tmp/chunk.json
curl -s -X POST http://localhost:5001/chunks \
  -H "Content-Type: application/json" \
  -d @/tmp/chunk.json
```

**请求体**：

| 字段           | 类型     | 必需 | 说明    |
| ------------ | ------ | -- | ----- |
| `collection` | string | ✅  | 向量库名称 |
| `content`    | string | ✅  | 切片内容  |
| `metadata`   | object | ❌  | 元数据   |

**验证结果**：✅ 通过

***

### PUT /chunks/\<id>

修改切片。

```bash
echo '{"collection":"dept_hr","content":"更新后的内容"}' > /tmp/update_chunk.json
curl -s -X PUT "http://localhost:5001/chunks/人员名册.txt_text_0" \
  -H "Content-Type: application/json" \
  -d @/tmp/update_chunk.json
```

**请求体**：

| 字段           | 类型     | 必需 | 说明    |
| ------------ | ------ | -- | ----- |
| `collection` | string | ✅  | 向量库名称 |
| `content`    | string | ❌  | 新内容   |
| `metadata`   | object | ❌  | 新元数据  |

**验证结果**：✅ 通过

***

### DELETE /chunks/\<id>

删除切片。

```bash
curl -s -X DELETE "http://localhost:5001/chunks/人员名册.txt_text_0?collection=dept_hr"
```

**查询参数**：

| 参数           | 类型     | 必需 | 说明    |
| ------------ | ------ | -- | ----- |
| `collection` | string | ✅  | 向量库名称 |

**验证结果**：✅ 通过

***

## 8. 同步服务

### POST /sync

触发文档同步。

```bash
curl -s -X POST http://localhost:5001/sync \
  -H "Content-Type: application/json"
```

**响应示例**：

```json
{
  "data": {
    "result": {
      "documents_added": 1,
      "documents_deleted": 1,
      "documents_modified": 0,
      "documents_processed": 2,
      "end_time": "2026-05-04T01:45:42",
      "errors": [],
      "start_time": "2026-05-04T01:45:41",
      "status": "completed"
    }
  },
  "message": "同步完成",
  "status": "success",
  "status_code": 2010,
  "success": true
}
```

**验证结果**：✅ 通过

***

### GET /sync/status

获取同步状态。

```bash
curl -s http://localhost:5001/sync/status
```

**响应示例**：

```json
{
  "documents_tracked": 0,
  "enabled": true,
  "last_sync": null,
  "monitoring": false
}
```

**验证结果**：✅ 通过

***

### GET /sync/history

获取同步历史。

```bash
curl -s http://localhost:5001/sync/history
```

**响应示例**：

```json
{
  "history": []
}
```

**验证结果**：✅ 通过

***

### GET /sync/changes

获取待同步变更。

```bash
curl -s http://localhost:5001/sync/changes
```

**响应示例**：

```json
{
  "changes": []
}
```

**验证结果**：✅ 通过

***

### POST /sync/start

启动文件监控。

```bash
curl -s -X POST http://localhost:5001/sync/start
```

**响应示例**：

```json
{
  "message": "文件监控已启动",
  "status": "success",
  "status_code": 2010
}
```

**验证结果**：✅ 通过

***

### POST /sync/stop

停止文件监控。

```bash
curl -s -X POST http://localhost:5001/sync/stop
```

**响应示例**：

```json
{
  "message": "文件监控已停止",
  "status": "success",
  "status_code": 2010
}
```

**验证结果**：✅ 通过

***

## 9. 反馈系统

### POST /feedback

提交反馈。

```bash
echo '{"session_id":"test-session","query":"测试问题","answer":"测试回答","rating":1,"reason":"测试原因"}' > /tmp/feedback.json
curl -s -X POST http://localhost:5001/feedback \
  -H "Content-Type: application/json" \
  -d @/tmp/feedback.json
```

**请求体**：

| 字段           | 类型     | 必需 | 说明           |
| ------------ | ------ | -- | ------------ |
| `session_id` | string | ✅  | 会话 ID        |
| `query`      | string | ✅  | 用户问题         |
| `answer`     | string | ✅  | 系统回答         |
| `rating`     | int    | ✅  | 评分（1=赞，-1=踩） |
| `reason`     | string | ❌  | 原因           |

**响应示例**：

```json
{
  "faq_suggested": true,
  "feedback_id": 7,
  "success": true,
  "suggestion_id": 6
}
```

**验证结果**：✅ 通过

***

### GET /feedback/list

获取反馈列表。

```bash
curl -s "http://localhost:5001/feedback/list?page=1&page_size=5"
```

**查询参数**：

| 参数          | 类型  | 必需 | 说明         |
| ----------- | --- | -- | ---------- |
| `page`      | int | ❌  | 页码，默认 1    |
| `page_size` | int | ❌  | 每页数量，默认 20 |

**响应示例**：

```json
{
  "feedbacks": [
    {
      "answer": "测试回答",
      "created_at": "2026-05-04T01:45:35",
      "id": 7,
      "query": "测试问题",
      "rating": 1,
      "reason": "测试原因",
      "session_id": "test-session",
      "sources": [],
      "user_id": ""
    }
  ],
  "success": true,
  "total": 7
}
```

**验证结果**：✅ 通过

***

### GET /feedback/stats

获取反馈统计。

```bash
curl -s http://localhost:5001/feedback/stats
```

**响应示例**：

```json
{
  "stats": {
    "avg_rating": 1.0,
    "negative_count": 0,
    "positive_count": 8,
    "satisfaction_rate": 100.0,
    "total_feedback": 8
  },
  "success": true
}
```

**验证结果**：✅ 通过

***

### GET /feedback/bad-cases

获取差评案例列表。

```bash
curl -s http://localhost:5001/feedback/bad-cases
```

**响应示例**：

```json
{
  "bad_cases": [],
  "blacklisted_sources": [],
  "success": true,
  "suggestions": [
    "补充到知识库（针对知识盲区）",
    "添加到 Query Rewrite 规则（针对表达歧义）"
  ]
}
```

**验证结果**：✅ 通过

***

### GET /feedback/blacklist

获取切片黑名单。

```bash
curl -s http://localhost:5001/feedback/blacklist
```

**响应示例**：

```json
{
  "blacklist": [],
  "count": 0,
  "success": true,
  "usage": "在检索时过滤这些来源以提升回答质量"
}
```

**验证结果**：✅ 通过

***

## 10. FAQ 管理

### GET /faq

获取 FAQ 列表。

```bash
curl -s "http://localhost:5001/faq?page=1&page_size=5"
```

**响应示例**：

```json
{
  "faqs": [
    {
      "answer": "根据参考资料...",
      "avg_rating": 1.0,
      "created_at": "2026-04-19T11:27:15",
      "frequency": 1,
      "id": 1,
      "question": "C类吸烟区域",
      "source_documents": null,
      "status": "approved",
      "updated_at": "2026-04-19T11:27:15"
    }
  ],
  "success": true,
  "total": 3
}
```

**验证结果**：✅ 通过

***

### POST /faq

创建新 FAQ。

```bash
curl -s -X POST http://localhost:5001/faq \
  -H "Content-Type: application/json" \
  -d "{\"question\":\"FAQ question\",\"answer\":\"FAQ answer\"}"
```

**请求体**：

| 字段         | 类型     | 必需 | 说明   |
| ---------- | ------ | -- | ---- |
| `question` | string | ✅  | 问题内容 |
| `answer`   | string | ✅  | 答案内容 |

**响应示例**：

```json
{
  "faq_id": 7,
  "message": "FAQ已创建，请通过 /faq/<id>/approve 接口确认后生效",
  "status": "draft",
  "success": true
}
```

**验证结果**：✅ 通过

***

### PUT /faq/\<id>

修改 FAQ。

```bash
curl -s -X PUT "http://localhost:5001/faq/7" \
  -H "Content-Type: application/json" \
  -d "{\"question\":\"updated question\",\"answer\":\"updated answer\"}"
```

**请求体**：

| 字段         | 类型     | 必需 | 说明    |
| ---------- | ------ | -- | ----- |
| `question` | string | ❌  | 新问题内容 |
| `answer`   | string | ❌  | 新答案内容 |

**响应示例**：

```json
{
  "message": "FAQ更新成功",
  "success": true,
  "sync_status": "skipped"
}
```

**验证结果**：✅ 通过

***

### DELETE /faq/\<id>

删除 FAQ。

```bash
curl -s -X DELETE "http://localhost:5001/faq/7"
```

**响应示例**：

```json
{
  "message": "FAQ删除成功",
  "success": true
}
```

**验证结果**：✅ 通过

***

### GET /faq/suggestions

获取 FAQ 建议列表。

```bash
curl -s "http://localhost:5001/faq/suggestions?page=1&page_size=5"
```

**响应示例**：

```json
{
  "success": true,
  "suggestions": [
    {
      "answer": "",
      "avg_rating": 1.0,
      "created_at": "2026-04-29T20:49:38",
      "frequency": 2,
      "id": 5,
      "query": "test",
      "status": "pending"
    }
  ],
  "total": 3
}
```

**验证结果**：✅ 通过

***

### POST /faq/suggestions/\<id>/approve

批准 FAQ 建议（需 admin 角色）。

```bash
curl -s -X POST "http://localhost:5001/faq/suggestions/6/approve" \
  -H "Content-Type: application/json" \
  -d '{}'
```

**请求体**（可选，用于修改答案）：

```json
{"answer": "管理员修改后的答案"}
```

**注意**：必须传递请求体（至少空对象 `{}`），否则返回 400 错误。

**验证结果**：✅ 通过

***

### POST /faq/suggestions/\<id>/reject

拒绝 FAQ 建议（需 admin 角色）。

```bash
curl -s -X POST "http://localhost:5001/faq/suggestions/6/reject" \
  -H "Content-Type: application/json" \
  -d '{}'
```

**验证结果**：✅ 通过

***

## 11. 出题系统

### GET /exam/health

出题服务健康检查。

```bash
curl -s http://localhost:5001/exam/health
```

**响应示例**：

```json
{
  "service": "exam-api",
  "status": "ok",
  "version": "2.0"
}
```

**验证结果**：✅ 通过

***

### POST /exam/generate

生成考题。

```bash
echo '{"file_path":"dept_hr/人员名册.txt","collection":"dept_hr","question_types":{"single_choice":1}}' > /tmp/exam.json
curl -s -X POST http://localhost:5001/exam/generate \
  -H "Content-Type: application/json" \
  -d @/tmp/exam.json
```

**请求体**：

| 字段               | 类型     | 必需 | 说明             |
| ---------------- | ------ | -- | -------------- |
| `file_path`      | string | ✅  | 文档路径           |
| `collection`     | string | ✅  | 知识库名称          |
| `question_types` | object | ✅  | 题型数量配置         |
| `difficulty`     | int    | ❌  | 难度等级（1-5），默认 3 |
| `options`        | object | ❌  | 附加选项           |
| `request_id`     | string | ❌  | 请求 ID（幂等性）     |

**question\_types 配置**：

```json
{
  "single_choice": 3,    // 单选题
  "multiple_choice": 2,  // 多选题
  "true_false": 2,       // 判断题
  "fill_blank": 2,       // 填空题
  "subjective": 1        // 主观题
}
```

**响应示例**：

```json
{
  "data": {
    "questions": [
      {
        "content": {
          "answer": "B",
          "data": {
            "options": [
              {"content": "12-18万元，平均15万元", "key": "A"},
              {"content": "18-25万元，平均21万元", "key": "B"},
              {"content": "25-35万元，平均30万元", "key": "C"},
              {"content": "15-22万元，平均18万元", "key": "D"}
            ]
          },
          "explanation": "依据文档...",
          "stem": "根据文档中《五、薪酬概况》的表格..."
        },
        "difficulty": 3,
        "question_type": "single_choice",
        "source_trace": {
          "chunk_ids": ["人员名册.txt_text_1"],
          "document_name": "dept_hr/人员名册.txt",
          "page_numbers": [2],
          "sources": [...]
        }
      }
    ],
    "request_id": null,
    "source_chunks_used": 4,
    "success": true,
    "total": 1
  },
  "message": "出题成功",
  "status": "success",
  "status_code": 2020,
  "success": true
}
```

**验证结果**：✅ 通过

***

### POST /exam/grade

批阅答案。

```bash
echo '{"answers":[{"question_id":"q1","question_type":"single_choice","question_content":{"answer":"B"},"student_answer":"B","max_score":2}]}' > /tmp/grade.json
curl -s -X POST http://localhost:5001/exam/grade \
  -H "Content-Type: application/json" \
  -d @/tmp/grade.json
```

**请求体**：

| 字段        | 类型    | 必需 | 说明   |
| --------- | ----- | -- | ---- |
| `answers` | array | ✅  | 答案数组 |

**answers 数组元素**：

| 字段                 | 类型           | 必需 | 说明          |
| ------------------ | ------------ | -- | ----------- |
| `question_id`      | string       | ✅  | 题目 ID       |
| `question_type`    | string       | ✅  | 题型          |
| `question_content` | object       | ✅  | 题目内容（含标准答案） |
| `student_answer`   | string/array | ✅  | 学生答案        |
| `max_score`        | int          | ✅  | 满分          |

**响应示例**：

```json
{
  "data": {
    "request_id": null,
    "results": [
      {
        "correct": true,
        "feedback": "正确！",
        "max_score": 2,
        "question_id": "q1",
        "score": 2
      }
    ],
    "score_rate": 100.0,
    "success": true,
    "total_max_score": 2,
    "total_score": 2
  },
  "message": "批阅完成",
  "status": "success",
  "status_code": 2021,
  "success": true
}
```

**验证结果**：✅ 通过

***

## 12. 图片服务

### GET /images/list

获取图片列表。

```bash
curl -s "http://localhost:5001/images/list?limit=5&offset=0"
```

**查询参数**：

| 参数       | 类型  | 必需 | 说明         |
| -------- | --- | -- | ---------- |
| `limit`  | int | ❌  | 返回数量，默认 50 |
| `offset` | int | ❌  | 偏移量，默认 0   |

**响应示例**：

```json
{
  "images": [
    {
      "image_id": "0569dd285537",
      "size_bytes": 169818,
      "url": "/images/0569dd285537"
    }
  ],
  "limit": 5,
  "offset": 0,
  "total": 67
}
```

**验证结果**：✅ 通过

***

### GET /images/\<id>

获取单张图片。

```bash
curl -s -o image.jpg http://localhost:5001/images/0569dd285537
```

**响应**：返回图片文件（HTTP 200）

**验证结果**：✅ 通过

***

### GET /images/\<id>/info

获取图片元数据。

```bash
curl -s "http://localhost:5001/images/0569dd285537/info"
```

**响应示例**：

```json
{
  "format": "JPEG",
  "height": 1357,
  "image_id": "0569dd285537",
  "mode": "RGB",
  "size_bytes": 169818,
  "url": "/images/0569dd285537",
  "width": 1023
}
```

**验证结果**：✅ 通过

***

### GET /images/stats

获取图片统计。

```bash
curl -s http://localhost:5001/images/stats
```

**响应示例**：

```json
{
  "format_counts": {
    ".jpg": 46,
    ".png": 21
  },
  "supported_formats": [".jpeg", ".gif", ".bmp", ".jpg", ".webp", ".png"],
  "total_images": 67,
  "total_size_bytes": 5394510,
  "total_size_mb": 5.14
}
```

**验证结果**：✅ 通过

***

## 13. 报告服务

### GET /reports/weekly

获取周报。

```bash
curl -s http://localhost:5001/reports/weekly
```

**响应示例**：

```json
{
  "report": {
    "avg_rating": 1.0,
    "created_at": "2026-05-04T01:49:16",
    "end_date": "2026-05-10",
    "high_freq_queries": [
      {"frequency": 1, "query": "测试问题", "sample_answer": "测试回答"}
    ],
    "improvement_suggestions": ["继续保持当前服务质量"],
    "low_rating_queries": [],
    "negative_count": 0,
    "positive_count": 1,
    "report_type": "weekly",
    "satisfaction_rate": 100.0,
    "start_date": "2026-05-04",
    "total_feedback": 1,
    "total_queries": 1
  },
  "success": true
}
```

**验证结果**：✅ 通过

***

### GET /reports/monthly

获取月报。

```bash
curl -s http://localhost:5001/reports/monthly
```

**验证结果**：✅ 通过（接口存在）

***

## 14. 知识库路由

### POST /kb/route

测试知识库路由（调试用）。

```bash
echo '{"query":"三峡工程"}' > /tmp/route.json
curl -s -X POST http://localhost:5001/kb/route \
  -H "Content-Type: application/json" \
  -d @/tmp/route.json
```

**响应示例**：

```json
{
  "intent": {
    "confidence": 0.95,
    "department": null,
    "is_general": true,
    "keywords": [],
    "reason": "LLM 意图分析"
  },
  "query": "三峡工程",
  "target_collections": ["public_kb"],
  "user_department": "",
  "user_role": "user"
}
```

**验证结果**：✅ 通过

***

## 附录. 已知问题

### 1. /rag 接口 collections 参数

**问题**：使用 `collection`（单数）参数指定知识库无效，会被忽略并默认检索 `public_kb`。

**解决**：必须使用 `collections`（复数，数组格式）参数。

```bash
# ❌ 错误用法 - collection 单数参数无效
{"message": "问题", "collection": "dept_tech", "chat_history": []}

# ✅ 正确用法 - collections 数组参数
{"message": "问题", "collections": ["dept_tech"], "chat_history": []}
```

***

## 测试覆盖统计

| 分类     | 总数     | 通过     | 失败/异常 |
| ------ | ------ | ------ | ----- |
| 健康检查   | 2      | 2      | 0     |
| 问答接口   | 2      | 2      | 0     |
| 检索接口   | 1      | 1      | 0     |
| 向量库管理  | 8      | 8      | 0     |
| 文档管理   | 10     | 10     | 0     |
| 切片管理   | 4      | 4      | 0     |
| 同步服务   | 6      | 6      | 0     |
| 反馈系统   | 5      | 5      | 0     |
| FAQ 管理 | 7      | 7      | 0     |
| 出题系统   | 3      | 3      | 0     |
| 图片服务   | 4      | 4      | 0     |
| 报告服务   | 2      | 2      | 0     |
| 知识库路由  | 1      | 1      | 0     |
| **总计** | **52** | **52** | **0** |

