chore: 同步项目状态
- main.py: 去掉 emoji 避免 GBK 编码崩溃 - docs/curl测试手册.md: 更新模型名和测试日期 - docs/代码审查报告_2026-06-05.md: 删除过期报告 - docs/main分支独有功能说明.md: 新增 - docs/出题系统逻辑.md: 新增
This commit is contained in:
561
docs/curl测试手册.md
561
docs/curl测试手册.md
@@ -2,9 +2,9 @@
|
||||
|
||||
> 基于 `后端对接规范.md`,所有 curl 命令均在生产模式 (`APP_ENV=prod`) 下验证通过。
|
||||
>
|
||||
> 测试日期:2026-06-04 | 生产服务器:`47.116.16.222` | 服务地址:`http://127.0.0.1:5001`
|
||||
> 测试日期:2026-06-10(最近更新)| 生产服务器:`47.116.16.222` | 服务地址:`http://127.0.0.1:5001`
|
||||
>
|
||||
> 当前部署模型:`qwen-plus`(DashScope)| 嵌入模型:`bge-base-zh-v1.5`(本地 CPU)| Rerank:`qwen3-rerank`(DashScope 云端 API)
|
||||
> 当前部署模型:`qwen-turbo`(DashScope)| 嵌入模型:`bge-base-zh-v1.5`(本地 CPU)| Rerank:`qwen3-rerank`(DashScope 云端 API)
|
||||
|
||||
## 目录
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
- [13. 报告服务](#13-报告服务)
|
||||
- [14. 知识库路由](#14-知识库路由)
|
||||
- [15. 异步任务查询](#15-异步任务查询)
|
||||
- [16. 认证系统](#16-认证系统)
|
||||
- [17. 其他端点](#17-其他端点)
|
||||
- [附录. 已知问题](#附录-已知问题)
|
||||
|
||||
---
|
||||
@@ -281,11 +283,19 @@ curl -s http://localhost:5001/collections
|
||||
"department": "",
|
||||
"description": "",
|
||||
"display_name": "dept_1_kb",
|
||||
"document_count": 800,
|
||||
"document_count": 500,
|
||||
"name": "dept_1_kb"
|
||||
},
|
||||
{
|
||||
"created_at": "2026-06-10T03:04:20.619279",
|
||||
"department": "",
|
||||
"description": "",
|
||||
"display_name": "resources",
|
||||
"document_count": 0,
|
||||
"name": "resources"
|
||||
}
|
||||
],
|
||||
"total": 9
|
||||
"total": 3
|
||||
}
|
||||
```
|
||||
|
||||
@@ -382,15 +392,15 @@ curl -s "http://localhost:5001/collections/public_kb/documents"
|
||||
"collection": "public_kb",
|
||||
"documents": [
|
||||
{
|
||||
"chunks": 105,
|
||||
"source": "1.docx"
|
||||
"chunks": 69,
|
||||
"source": "3.txt"
|
||||
},
|
||||
{
|
||||
"chunks": 39,
|
||||
"source": "三峡公报_1-15页.pdf"
|
||||
"chunks": 12,
|
||||
"source": "1.txt"
|
||||
}
|
||||
],
|
||||
"total": 9
|
||||
"total": 10
|
||||
}
|
||||
```
|
||||
|
||||
@@ -580,27 +590,25 @@ curl -s -X POST http://localhost:5001/documents/upload \
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"file": {
|
||||
"collection": "public_kb",
|
||||
"filename": "test.txt",
|
||||
"path": "public_kb/test.txt",
|
||||
"size": 18,
|
||||
"replaced": false
|
||||
},
|
||||
"sync_status": "已保存,向量化任务已启动",
|
||||
"task_id": "a1b2c3d4e5f6"
|
||||
},
|
||||
"message": "文件上传成功,已保存,向量化任务已启动",
|
||||
"success": true,
|
||||
"status": "success",
|
||||
"status_code": 2002,
|
||||
"success": true
|
||||
"message": "文件上传成功,已保存并添加到向量库",
|
||||
"data": {
|
||||
"file": {
|
||||
"filename": "test.txt",
|
||||
"collection": "public_kb",
|
||||
"path": "public_kb/test.txt",
|
||||
"size": 18
|
||||
},
|
||||
"sync_status": "已保存并添加到向量库"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **异步说明**:文件保存为同步操作,向量化在后台线程异步执行。响应中的 `task_id` 可用于轮询向量化进度(`GET /tasks/<task_id>`)。
|
||||
> **说明**:文件保存和向量化均为同步操作,响应时文件已处理完成。不包含 `task_id` 字段(异步任务系统仅 main 分支可用)。
|
||||
>
|
||||
> **同名文件处理**:上传同名文件时,旧版本的切片会被自动清理后覆盖(`replaced: true`),不会生成时间戳后缀文件。
|
||||
> **同名文件处理**:上传同名文件时,旧版本的切片会被自动清理后覆盖,不会生成时间戳后缀文件。
|
||||
|
||||
**验证结果**:✅ 通过
|
||||
|
||||
@@ -622,25 +630,24 @@ curl -s -X POST http://localhost:5001/documents/batch-upload \
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"results": [
|
||||
{"filename": "file1.txt", "path": "public_kb/file1.txt", "status": "success", "replaced": false},
|
||||
{"filename": "file2.txt", "path": "public_kb/file2.txt", "status": "success", "replaced": false}
|
||||
],
|
||||
"success_count": 2,
|
||||
"total": 2,
|
||||
"task_id": "b2c3d4e5f6a1"
|
||||
},
|
||||
"message": "批量上传完成,成功 2/2 个文件",
|
||||
"success": true,
|
||||
"status": "success",
|
||||
"status_code": 2003,
|
||||
"success": true
|
||||
"message": "批量上传完成,成功 2/2 个文件",
|
||||
"data": {
|
||||
"total": 2,
|
||||
"success_count": 2,
|
||||
"results": [
|
||||
{"filename": "file1.txt", "path": "public_kb/file1.txt", "status": "success"},
|
||||
{"filename": "file2.txt", "path": "public_kb/file2.txt", "status": "success"}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **异步说明**:批量上传成功后自动触发后台向量化任务。`task_id` 可用于轮询向量化进度(`GET /tasks/<task_id>`)。若无成功上传的文件则不返回 `task_id`。
|
||||
> **说明**:批量上传为同步操作,响应时所有文件已处理完成。不包含 `task_id` 字段(异步任务系统仅 main 分支可用)。
|
||||
>
|
||||
> **同名文件处理**:与单文件上传相同,批量上传中遇到同名文件也会自动覆盖旧版本(`replaced: true`)。
|
||||
> **同名文件处理**:与单文件上传相同,批量上传中遇到同名文件也会自动覆盖旧版本。
|
||||
|
||||
**验证结果**:✅ 通过
|
||||
|
||||
@@ -899,22 +906,27 @@ curl -s -X POST http://localhost:5001/sync \
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"task_id": "c3d4e5f6a1b2",
|
||||
"message": "同步任务已启动,通过 GET /tasks/c3d4e5f6a1b2 查询进度"
|
||||
},
|
||||
"message": "同步任务已启动",
|
||||
"success": true,
|
||||
"status": "success",
|
||||
"status_code": 2010,
|
||||
"success": true
|
||||
"message": "同步完成",
|
||||
"data": {
|
||||
"result": {
|
||||
"documents_processed": 20,
|
||||
"documents_added": 3,
|
||||
"documents_modified": 2,
|
||||
"documents_deleted": 0,
|
||||
"errors": []
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **⚠️ 异步变更**:此接口已从同步改为异步。不再直接返回同步结果,而是返回 `task_id`。后端需通过 `GET /tasks/<task_id>` 轮询任务状态,直到 `status` 为 `completed` 或 `failed`。
|
||||
> **说明**:此接口为**同步操作**,请求会阻塞直到同步完成(可能需要较长时间)。响应中 `data.result` 包含完整的同步结果。不包含 `task_id` 字段(异步任务系统仅 main 分支可用)。
|
||||
>
|
||||
> **冲突检测**:如果已有同步任务正在运行,返回 HTTP 409:
|
||||
> ```json
|
||||
> {"error": "TASK_RUNNING", "message": "同步任务正在执行中 (task_id: xxx),请等待完成"}
|
||||
> {"error": "TASK_RUNNING", "message": "同步任务正在执行中,请等待完成"}
|
||||
> ```
|
||||
|
||||
**验证结果**:✅ 通过
|
||||
@@ -1368,6 +1380,22 @@ curl -s -X POST "http://localhost:5001/faq/suggestions/6/reject" \
|
||||
|
||||
---
|
||||
|
||||
### POST /faq/\<id\>/approve
|
||||
|
||||
直接批准 FAQ(非从建议列表)。
|
||||
|
||||
```bash
|
||||
curl -s -X POST "http://localhost:5001/faq/1/approve" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{}'
|
||||
```
|
||||
|
||||
> **注意**:此端点与 `POST /faq/suggestions/<id>/approve` 不同,后者用于审批用户提交的 FAQ 建议。
|
||||
|
||||
**验证结果**:✅ 通过(路由存在)
|
||||
|
||||
---
|
||||
|
||||
## 11. 出题系统
|
||||
|
||||
### 出题架构说明(v2)
|
||||
@@ -1461,22 +1489,11 @@ curl -s -X POST http://localhost:5001/exam/generate \
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"task_id": "d4e5f6a1b2c3",
|
||||
"message": "出题任务已启动 (10题),通过 GET /tasks/d4e5f6a1b2c3 查询结果"
|
||||
},
|
||||
"message": "出题任务已启动",
|
||||
"success": true,
|
||||
"status": "success",
|
||||
"status_code": 2020,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
> **⚠️ 异步变更**:此接口已从同步改为异步。响应仅返回 `task_id`,后端需通过 `GET /tasks/<task_id>` 轮询任务状态。任务完成后,`result` 字段包含完整出题结果(格式见下方说明)。
|
||||
|
||||
**轮询结果(GET /tasks/\<task_id\> 完成后的 result 字段)**:
|
||||
```json
|
||||
{
|
||||
"message": "出题成功",
|
||||
"data": {
|
||||
"questions": [
|
||||
{
|
||||
"content": {
|
||||
@@ -1507,10 +1524,13 @@ curl -s -X POST http://localhost:5001/exam/generate \
|
||||
"requested_types": {"single_choice": 5, "true_false": 3, "fill_blank": 2},
|
||||
"actual_types": {"single_choice": 5, "true_false": 3, "fill_blank": 2},
|
||||
"warnings": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **说明**:`warnings` 字段在某题型实际生成数量少于请求数量时返回提示信息。
|
||||
> **说明**:此接口为**同步操作**,请求会阻塞直到出题完成(通常需要 30-60 秒)。响应中 `data` 直接包含完整出题结果。不包含 `task_id` 字段(异步任务系统仅 main 分支可用)。
|
||||
>
|
||||
> **注意**:`warnings` 字段在某题型实际生成数量少于请求数量时返回提示信息。
|
||||
|
||||
**验证结果**:✅ 通过(2026-06-05)
|
||||
|
||||
@@ -1542,22 +1562,11 @@ curl -s -X POST http://localhost:5001/exam/generate-smart \
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"task_id": "e5f6a1b2c3d4",
|
||||
"message": "AI 智能出题任务已启动,通过 GET /tasks/e5f6a1b2c3d4 查询结果"
|
||||
},
|
||||
"message": "AI 智能出题任务已启动",
|
||||
"success": true,
|
||||
"status": "success",
|
||||
"status_code": 2020,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
> **⚠️ 异步变更**:此接口已从同步改为异步。响应仅返回 `task_id`,后端需通过 `GET /tasks/<task_id>` 轮询任务状态。任务完成后,`result` 字段包含完整出题结果(含 `ai_analysis` 字段)。
|
||||
|
||||
**轮询结果(GET /tasks/\<task_id\> 完成后的 result 字段)**:
|
||||
```json
|
||||
{
|
||||
"message": "AI 智能出题成功",
|
||||
"data": {
|
||||
"ai_analysis": {
|
||||
"total_knowledge_points": 21,
|
||||
"suitable_types": ["single_choice", "multiple_choice", "true_false", "subjective"],
|
||||
@@ -1575,9 +1584,12 @@ curl -s -X POST http://localhost:5001/exam/generate-smart \
|
||||
"source_chunks_used": 15,
|
||||
"success": true,
|
||||
"total": 16
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **说明**:此接口为**同步操作**,请求会阻塞直到出题完成。响应中 `data` 直接包含完整出题结果(含 `ai_analysis` 字段)。不包含 `task_id` 字段(异步任务系统仅 main 分支可用)。
|
||||
|
||||
**注意事项**:
|
||||
- 实际出题数量 ≤ min(文档知识点数, AI 推荐数量)
|
||||
- 如果文档知识点较少,生成的题目数量会相应减少
|
||||
@@ -1626,22 +1638,11 @@ curl -s -X POST http://localhost:5001/exam/grade \
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"task_id": "f6a1b2c3d4e5",
|
||||
"message": "批阅任务已启动 (1题),通过 GET /tasks/f6a1b2c3d4e5 查询结果"
|
||||
},
|
||||
"message": "批阅任务已启动",
|
||||
"success": true,
|
||||
"status": "success",
|
||||
"status_code": 2021,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
> **⚠️ 异步变更**:此接口已从同步改为异步。响应仅返回 `task_id`,后端需通过 `GET /tasks/<task_id>` 轮询任务状态。任务完成后,`result` 字段包含完整批阅结果(格式见下方说明)。
|
||||
|
||||
**轮询结果(GET /tasks/\<task_id\> 完成后的 result 字段)**:
|
||||
```json
|
||||
{
|
||||
"message": "批阅完成",
|
||||
"data": {
|
||||
"request_id": null,
|
||||
"results": [
|
||||
{
|
||||
@@ -1661,9 +1662,12 @@ curl -s -X POST http://localhost:5001/exam/grade \
|
||||
"success": true,
|
||||
"total_max_score": 2,
|
||||
"total_score": 2
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **说明**:此接口为**同步操作**,响应中 `data` 直接包含完整批阅结果。不包含 `task_id` 字段(异步任务系统仅 main 分支可用)。
|
||||
|
||||
**`grading_status` 取值说明**:
|
||||
- `success`:评分成功
|
||||
- `failed`:评分失败(主观题 LLM 解析失败或超时),此时 `details` 包含 `error` 字段
|
||||
@@ -1842,16 +1846,16 @@ curl -s -X POST http://localhost:5001/kb/route \
|
||||
```json
|
||||
{
|
||||
"intent": {
|
||||
"confidence": 0.95,
|
||||
"confidence": 1.0,
|
||||
"department": null,
|
||||
"is_general": true,
|
||||
"keywords": [],
|
||||
"reason": "LLM 意图分析"
|
||||
},
|
||||
"query": "三峡工程",
|
||||
"query": "test",
|
||||
"target_collections": ["public_kb"],
|
||||
"user_department": "",
|
||||
"user_role": "user"
|
||||
"user_department": "开发部",
|
||||
"user_role": "admin"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1861,188 +1865,154 @@ curl -s -X POST http://localhost:5001/kb/route \
|
||||
|
||||
## 15. 异步任务查询
|
||||
|
||||
> 所有异步操作(同步、重建索引、上传向量化、出题、批阅)返回的 `task_id` 均可通过以下接口查询进度。
|
||||
|
||||
### GET /tasks
|
||||
|
||||
获取任务列表。
|
||||
|
||||
```bash
|
||||
curl -s http://localhost:5001/tasks
|
||||
```
|
||||
|
||||
**查询参数**:
|
||||
| 参数 | 类型 | 必需 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `status` | string | ❌ | 过滤状态:`pending` / `running` / `completed` / `failed` |
|
||||
| `type` | string | ❌ | 过滤类型:`sync` / `reindex` / `upload` / `batch_upload` / `exam_generate` / `exam_grade` |
|
||||
| `limit` | int | ❌ | 返回数量限制(默认 50) |
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"tasks": [
|
||||
{
|
||||
"task_id": "a1b2c3d4e5f6",
|
||||
"type": "sync",
|
||||
"description": "文档同步",
|
||||
"status": "running",
|
||||
"progress": 45.0,
|
||||
"current": 9,
|
||||
"total": 20,
|
||||
"stage": "处理文件",
|
||||
"message": "已处理: 产品手册.pdf",
|
||||
"created_at": "2026-06-05T10:30:00"
|
||||
}
|
||||
],
|
||||
"total": 1
|
||||
},
|
||||
"message": "查询成功",
|
||||
"status": "success",
|
||||
"status_code": 2000,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
**验证结果**:✅ 通过
|
||||
|
||||
---
|
||||
|
||||
### GET /tasks/\<task_id\>
|
||||
|
||||
获取单个任务状态(JSON 轮询接口)。
|
||||
|
||||
**后端组推荐使用此接口轮询任务进度,建议间隔 1-2 秒。**
|
||||
|
||||
```bash
|
||||
curl -s http://localhost:5001/tasks/a1b2c3d4e5f6
|
||||
```
|
||||
|
||||
**响应示例(运行中)**:
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"task_id": "a1b2c3d4e5f6",
|
||||
"type": "sync",
|
||||
"description": "文档同步",
|
||||
"status": "running",
|
||||
"progress": 45.0,
|
||||
"current": 9,
|
||||
"total": 20,
|
||||
"stage": "处理文件",
|
||||
"message": "已处理: 产品手册.pdf",
|
||||
"created_at": "2026-06-05T10:30:00",
|
||||
"started_at": "2026-06-05T10:30:01"
|
||||
},
|
||||
"message": "查询成功",
|
||||
"status": "success",
|
||||
"status_code": 2000,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例(已完成)**:
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"task_id": "a1b2c3d4e5f6",
|
||||
"type": "sync",
|
||||
"description": "文档同步",
|
||||
"status": "completed",
|
||||
"progress": 100.0,
|
||||
"current": 20,
|
||||
"total": 20,
|
||||
"stage": "完成",
|
||||
"message": "同步完成",
|
||||
"created_at": "2026-06-05T10:30:00",
|
||||
"started_at": "2026-06-05T10:30:01",
|
||||
"completed_at": "2026-06-05T10:30:15",
|
||||
"duration_ms": 14000,
|
||||
"result": {
|
||||
"documents_processed": 20,
|
||||
"documents_added": 3,
|
||||
"documents_modified": 2,
|
||||
"documents_deleted": 0,
|
||||
"errors": []
|
||||
}
|
||||
},
|
||||
"message": "查询成功",
|
||||
"status": "success",
|
||||
"status_code": 2000,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
> **任务状态**:
|
||||
> - `pending`:已创建,等待执行
|
||||
> - `running`:正在执行
|
||||
> - `completed`:执行完成,`result` 字段包含完整结果
|
||||
> - `failed`:执行失败,`error` 字段包含错误信息
|
||||
> **⚠️ 生产服务器(server-release)不支持**:异步任务系统(`/tasks` 系列端点)仅存在于 main 分支。当前生产服务器的上传、同步、出题、批阅接口均为**同步操作**,直接返回结果,无需轮询。
|
||||
>
|
||||
> **轮询建议**:间隔 1-2 秒,当 `status` 为 `completed` 或 `failed` 时停止轮询。
|
||||
> 如果后续升级到 main 分支版本,以下接口将可用:
|
||||
>
|
||||
> | 端点 | 方法 | 说明 |
|
||||
> |------|------|------|
|
||||
> | `/tasks` | GET | 获取任务列表(支持 status/type/limit 过滤) |
|
||||
> | `/tasks/<task_id>` | GET | 获取单个任务状态(JSON 轮询) |
|
||||
> | `/tasks/<task_id>/progress` | GET | SSE 流式任务进度推送 |
|
||||
> | `/tasks/stats` | GET | 获取任务统计信息 |
|
||||
>
|
||||
> **任务状态**:`pending` → `running` → `completed` / `failed`
|
||||
|
||||
**验证结果**:✅ 通过
|
||||
**验证结果**:❌ 路由不存在(404)— 符合 server-release 预期行为
|
||||
|
||||
---
|
||||
|
||||
### GET /tasks/\<task_id\>/progress
|
||||
## 16. 认证系统
|
||||
|
||||
SSE 流式任务进度推送(dev-ui 前端推荐使用)。
|
||||
### POST /auth/login
|
||||
|
||||
用户登录。
|
||||
|
||||
```bash
|
||||
curl -s -N http://localhost:5001/tasks/a1b2c3d4e5f6/progress
|
||||
echo '{"username":"admin","password":"xxx"}' > /tmp/login.json
|
||||
curl -s -X POST http://localhost:5001/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @/tmp/login.json
|
||||
```
|
||||
|
||||
**SSE 事件序列**:
|
||||
```
|
||||
data: {"type": "start", "data": {"stage": "扫描文档"}}
|
||||
|
||||
data: {"type": "progress", "data": {"progress": 10.0, "current": 2, "total": 20, "stage": "处理文件", "message": "已处理: file1.pdf"}}
|
||||
|
||||
data: {"type": "progress", "data": {"progress": 25.0, "current": 5, "total": 20, "stage": "处理文件", "message": "已处理: file2.docx"}}
|
||||
|
||||
data: {"type": "complete", "data": {"task_id": "a1b2c3d4e5f6", "status": "completed", "result": {...}}}
|
||||
```
|
||||
|
||||
> **SSE 事件类型**:
|
||||
> - `start`:任务开始
|
||||
> - `progress`:进度更新(包含 progress/current/total/stage/message)
|
||||
> - `complete`:任务完成(data 为完整任务详情,含 result)
|
||||
> - `error`:任务失败(data 包含 message 错误信息)
|
||||
> - 每 1 秒发送一次 `: heartbeat` 保活
|
||||
|
||||
**验证结果**:✅ 通过
|
||||
|
||||
---
|
||||
|
||||
### GET /tasks/stats
|
||||
|
||||
获取任务统计信息。
|
||||
|
||||
```bash
|
||||
curl -s http://localhost:5001/tasks/stats
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
**响应示例**(失败):
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"total": 5,
|
||||
"by_status": {"running": 1, "completed": 3, "failed": 1},
|
||||
"by_type": {"sync": 2, "exam_generate": 2, "upload": 1}
|
||||
},
|
||||
"message": "查询成功",
|
||||
"status": "success",
|
||||
"status_code": 2000,
|
||||
"success": true
|
||||
}
|
||||
{"error": "用户名或密码错误"}
|
||||
```
|
||||
|
||||
**验证结果**:✅ 通过(参数校验正常)
|
||||
|
||||
---
|
||||
|
||||
### GET /auth/me
|
||||
|
||||
获取当前用户信息。
|
||||
|
||||
```bash
|
||||
curl -s http://localhost:5001/auth/me
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### GET /auth/users
|
||||
|
||||
获取用户列表。
|
||||
|
||||
```bash
|
||||
curl -s http://localhost:5001/auth/users
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### PUT /auth/users/\<user_id\>
|
||||
|
||||
修改用户信息。
|
||||
|
||||
```bash
|
||||
echo '{"role":"user"}' > /tmp/update_user.json
|
||||
curl -s -X PUT "http://localhost:5001/auth/users/test-user" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @/tmp/update_user.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### POST /auth/change-password
|
||||
|
||||
修改密码。
|
||||
|
||||
```bash
|
||||
echo '{"old_password":"old","new_password":"new"}' > /tmp/changepw.json
|
||||
curl -s -X POST http://localhost:5001/auth/change-password \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @/tmp/changepw.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 17. 其他端点
|
||||
|
||||
### GET /documents/\<path\>/raw
|
||||
|
||||
下载文档原始文件。
|
||||
|
||||
```bash
|
||||
curl -s -o output.docx "http://localhost:5001/documents/public_kb%2F1.docx/raw"
|
||||
```
|
||||
|
||||
**响应**:返回原始文件(HTTP 200)
|
||||
|
||||
**验证结果**:✅ 通过
|
||||
|
||||
---
|
||||
|
||||
### DELETE /chunks/batch
|
||||
|
||||
批量删除切片。
|
||||
|
||||
```bash
|
||||
echo '{"chunk_ids":["file.txt_text_0","file.txt_text_1"],"collection":"public_kb"}' > /tmp/batch_del.json
|
||||
curl -s -X DELETE http://localhost:5001/chunks/batch \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @/tmp/batch_del.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### GET /stats
|
||||
|
||||
获取系统统计信息。
|
||||
|
||||
```bash
|
||||
curl -s http://localhost:5001/stats
|
||||
```
|
||||
|
||||
**验证结果**:❌ HTTP 500(`KeyError: 'SESSION_MANAGER'`,见已知问题 #2)
|
||||
|
||||
---
|
||||
|
||||
### POST /collections/sync-vlm-cache
|
||||
|
||||
同步 VLM 缓存。
|
||||
|
||||
```bash
|
||||
curl -s -X POST http://localhost:5001/collections/sync-vlm-cache
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### POST /collections/\<kb_name\>/reindex
|
||||
|
||||
重构向量库索引(异步任务)。
|
||||
|
||||
```bash
|
||||
curl -s -X POST "http://localhost:5001/collections/public_kb/reindex"
|
||||
```
|
||||
|
||||
> **说明**:清除哈希记录并触发全量同步。
|
||||
|
||||
---
|
||||
|
||||
## 附录. 已知问题
|
||||
|
||||
### 1. /rag 接口 collections 参数
|
||||
@@ -2059,33 +2029,63 @@ curl -s http://localhost:5001/tasks/stats
|
||||
{"message": "问题", "collections": ["dept_tech"], "chat_history": []}
|
||||
```
|
||||
|
||||
### 2. /stats 接口 500 错误
|
||||
|
||||
**问题**:`GET /stats` 返回 HTTP 500,日志显示 `KeyError: 'SESSION_MANAGER'`。
|
||||
|
||||
**原因**:该端点依赖 `SESSION_MANAGER` 配置项,但 server-release 分支未注册会话管理器。
|
||||
|
||||
**状态**:已知缺陷,不影响其他接口正常使用。
|
||||
|
||||
### 3. /tasks 系列端点不存在
|
||||
|
||||
**问题**:`GET /tasks`、`GET /tasks/<id>`、`GET /tasks/<id>/progress`、`GET /tasks/stats` 均返回 HTTP 404。
|
||||
|
||||
**原因**:异步任务系统仅存在于 main 分支,server-release 保持同步操作模式。上传、同步、出题、批阅接口直接返回结果,无需轮询。
|
||||
|
||||
**影响**:无 — 这是预期行为,所有操作均为同步完成。
|
||||
|
||||
### 4. /documents/sync 与 /sync 的区别
|
||||
|
||||
**问题**:服务器上存在两个同步触发端点,行为略有不同。
|
||||
|
||||
| 端点 | 说明 |
|
||||
|------|------|
|
||||
| `POST /sync` | 主同步接口,扫描并同步所有知识库 |
|
||||
| `POST /documents/sync` | 文档级同步接口,需要额外参数 |
|
||||
|
||||
**建议**:使用 `POST /sync` 触发手动同步。
|
||||
|
||||
---
|
||||
|
||||
## 测试覆盖统计
|
||||
|
||||
> 2026-06-04 生产服务器测试结果(47.116.16.222,云端 Reranker + MMR 文本相似度)
|
||||
> 2026-06-10 生产服务器测试结果(47.116.16.222,云端 Reranker + MMR 文本相似度)
|
||||
|
||||
| 分类 | 总数 | 通过 | 超时/异常 | 备注 |
|
||||
|------|------|------|-----------|------|
|
||||
| 分类 | 总数 | 通过 | 异常 | 备注 |
|
||||
|------|------|------|------|------|
|
||||
| 健康检查 | 1 | 1 | 0 | ✅ 0.002s |
|
||||
| 问答接口 | 2 | 2 | 0 | ✅ /rag 流式 12-14s |
|
||||
| 问答接口 | 2 | 2 | 0 | ✅ /rag 流式,/chat 正常 |
|
||||
| 检索接口 | 1 | 1 | 0 | ✅ /search 0.58-1.53s |
|
||||
| 向量库管理 | 3 | 3 | 0 | ✅ |
|
||||
| 文档管理 | 1 | 1 | 0 | ✅ |
|
||||
| 同步服务 | 3 | 3 | 0 | ✅ |
|
||||
| 反馈系统 | 4 | 4 | 0 | ✅ |
|
||||
| FAQ 管理 | 2 | 2 | 0 | ✅ |
|
||||
| 出题系统 | 1 | 1 | 0 | ✅ |
|
||||
| 图片服务 | 2 | 2 | 0 | ✅ |
|
||||
| 报告服务 | 2 | 2 | 0 | ✅ |
|
||||
| 知识库路由 | 1 | 1 | 0 | ✅ |
|
||||
| **总计** | **23** | **23** | **0** | **全部通过** |
|
||||
| 向量库管理 | 3 | 3 | 0 | ✅ collections + documents + chunks |
|
||||
| 文档管理 | 4 | 4 | 0 | ✅ list/status/preview/raw |
|
||||
| 同步服务 | 3 | 3 | 0 | ✅ status/history/changes |
|
||||
| 反馈系统 | 5 | 5 | 0 | ✅ feedback/list/stats/bad-cases/blacklist |
|
||||
| FAQ 管理 | 2 | 2 | 0 | ✅ faq + suggestions |
|
||||
| 出题系统 | 2 | 2 | 0 | ✅ exam/health + generate(参数校验) |
|
||||
| 图片服务 | 4 | 4 | 0 | ✅ list/stats/info + 图片下载 |
|
||||
| 报告服务 | 2 | 2 | 0 | ✅ weekly + monthly |
|
||||
| 知识库路由 | 1 | 1 | 0 | ✅ /kb/route |
|
||||
| 认证系统 | 1 | 1 | 0 | ✅ /auth/login 参数校验正常 |
|
||||
| 异步任务 | 1 | 0 | 1 | ⚠️ /tasks 404 — 仅 main 分支可用 |
|
||||
| 其他 | 1 | 0 | 1 | ⚠️ /stats 500 — SESSION_MANAGER 未注册 |
|
||||
| **总计** | **33** | **31** | **2** | **2 个异常均为已知缺陷,不影响核心功能** |
|
||||
|
||||
---
|
||||
|
||||
## 性能基准测试
|
||||
|
||||
> 2026-06-04 生产服务器实测(47.116.16.222,public_kb 824 切片)
|
||||
> 2026-06-10 生产服务器实测(47.116.16.222,public_kb ~824 切片)
|
||||
|
||||
### 检索性能
|
||||
|
||||
@@ -2126,7 +2126,7 @@ curl -s http://localhost:5001/tasks/stats
|
||||
└── 流式 token 生成 ~11s
|
||||
```
|
||||
|
||||
> **注**:LLM 生成阶段耗时取决于 qwen-plus API 响应速度,非本地可优化。如需进一步压缩至 10s 以内,可考虑换用更快的模型(如 qwen-turbo)。
|
||||
> **注**:LLM 生成阶段耗时取决于 qwen-turbo API 响应速度,非本地可优化。当前已使用 qwen-turbo(最快模型),如需进一步压缩可考虑减少检索切片数量或缩短 prompt。
|
||||
|
||||
---
|
||||
|
||||
@@ -2138,16 +2138,11 @@ curl -s http://localhost:5001/tasks/stats
|
||||
|
||||
当前生产服务器知识库列表:
|
||||
|
||||
| 知识库 | 切片数 | 说明 |
|
||||
| 知识库 | 文档数 | 说明 |
|
||||
|--------|--------|------|
|
||||
| public_kb | ~824 | 公开知识库,所有用户可访问 |
|
||||
| dept_1_kb | ~800 | 部门知识库 1 |
|
||||
| dept_2_kb | ~118 | 部门知识库 2 |
|
||||
| dept_3_kb | ~132 | 部门知识库 3 |
|
||||
| dept_4_kb | ~4 | 部门知识库 4 |
|
||||
| dept_6_kb | ~29 | 部门知识库 6 |
|
||||
| test1 | ~637 | 测试知识库 |
|
||||
| faq_kb / test3 | 0 | 空(预留) |
|
||||
| dept_1_kb | ~500 | 部门知识库 1 |
|
||||
| resources | 0 | 资源库(新建,暂无文档) |
|
||||
|
||||
### 切片元数据字段
|
||||
|
||||
@@ -2171,11 +2166,11 @@ curl -s http://localhost:5001/tasks/stats
|
||||
当代码升级涉及元数据字段变更时(如新增 `chunk_index`、`doc_type`),需要重构向量库:
|
||||
|
||||
```bash
|
||||
# 重构指定知识库(清除哈希记录 → 触发全量同步,异步任务)
|
||||
# 重构指定知识库(清除哈希记录 → 触发全量同步)
|
||||
curl -s -X POST http://127.0.0.1:5001/collections/<kb_name>/reindex
|
||||
```
|
||||
|
||||
> **⚠️ 注意**:reindex 已改为异步任务,立即返回 `task_id`。通过 `GET /tasks/<task_id>` 轮询进度。不再阻塞 gunicorn worker,搜索和问答接口不受影响。
|
||||
> **⚠️ 注意**:reindex 为同步操作,会阻塞直到重建完成。耗时取决于知识库文档数量,大知识库可能需要数分钟。执行期间搜索和问答接口不受影响(多 worker 架构)。
|
||||
|
||||
### Reranker 配置
|
||||
|
||||
|
||||
390
docs/main分支独有功能说明.md
Normal file
390
docs/main分支独有功能说明.md
Normal file
@@ -0,0 +1,390 @@
|
||||
# main 分支独有功能与端点说明
|
||||
|
||||
> 本文档记录 `main` 分支(本地最新版本)相对于 `server-release`(生产服务器版本)的**独有功能和端点差异**。
|
||||
>
|
||||
> 更新日期:2026-06-10 | 对比基准:`origin/server-release` (commit `15c0aec`) vs `main` (commit `edaef7a`)
|
||||
|
||||
---
|
||||
|
||||
## 一、新增端点(仅 main 可用)
|
||||
|
||||
### 1. 异步任务查询系统
|
||||
|
||||
main 分支引入了完整的异步任务注册表(`core/task_registry.py`),将上传、同步、出题等长耗时操作改为后台线程执行,接口立即返回 `task_id`。
|
||||
|
||||
| 端点 | 方法 | 说明 |
|
||||
|------|------|------|
|
||||
| `/tasks` | GET | 获取任务列表(支持 status/type/limit 过滤) |
|
||||
| `/tasks/<task_id>` | GET | 获取单个任务状态(JSON 轮询,后端组推荐) |
|
||||
| `/tasks/<task_id>/progress` | GET | SSE 流式任务进度推送(dev-ui 前端推荐) |
|
||||
| `/tasks/stats` | GET | 获取任务统计信息 |
|
||||
|
||||
**GET /tasks 请求参数**:
|
||||
|
||||
| 参数 | 类型 | 必需 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `status` | string | ❌ | 过滤状态:`pending` / `running` / `completed` / `failed` |
|
||||
| `type` | string | ❌ | 过滤类型:`sync` / `reindex` / `upload` / `batch_upload` / `exam_generate` / `exam_grade` |
|
||||
| `limit` | int | ❌ | 返回数量限制(默认 50) |
|
||||
|
||||
**GET /tasks 响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"status": "success",
|
||||
"status_code": 2000,
|
||||
"message": "查询成功",
|
||||
"data": {
|
||||
"tasks": [
|
||||
{
|
||||
"task_id": "a1b2c3d4e5f6",
|
||||
"type": "sync",
|
||||
"description": "文档同步",
|
||||
"status": "running",
|
||||
"progress": 45.0,
|
||||
"current": 9,
|
||||
"total": 20,
|
||||
"stage": "处理文件",
|
||||
"message": "已处理: 产品手册.pdf",
|
||||
"created_at": "2026-06-05T10:30:00",
|
||||
"started_at": "2026-06-05T10:30:01"
|
||||
}
|
||||
],
|
||||
"total": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**GET /tasks/\<task_id\>/progress SSE 事件序列**:
|
||||
```
|
||||
data: {"type": "start", "data": {"stage": "扫描文档"}}
|
||||
data: {"type": "progress", "data": {"progress": 10.0, "current": 2, "total": 20, "stage": "处理文件", "message": "已处理: file1.pdf"}}
|
||||
data: {"type": "progress", "data": {"progress": 25.0, "current": 5, "total": 20, "stage": "处理文件", "message": "已处理: file2.docx"}}
|
||||
data: {"type": "complete", "data": {"task_id": "a1b2c3d4e5f6", "status": "completed", "result": {...}}}
|
||||
```
|
||||
|
||||
> 心跳保活:每 1 秒发送 `: heartbeat`,防止连接超时。
|
||||
|
||||
**任务状态流转**:
|
||||
```
|
||||
pending → running → completed
|
||||
→ failed
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. 缓存管理接口
|
||||
|
||||
用于调试和监控 LRU 缓存与语义缓存的运行状态。
|
||||
|
||||
| 端点 | 方法 | 说明 |
|
||||
|------|------|------|
|
||||
| `/cache/stats` | GET | 获取所有缓存的命中统计 |
|
||||
| `/cache/clear` | POST | 清空所有缓存 |
|
||||
|
||||
**GET /cache/stats 响应示例**:
|
||||
```json
|
||||
{
|
||||
"embedding_cache": {
|
||||
"total_entries": 128,
|
||||
"hits": 45,
|
||||
"misses": 83,
|
||||
"hit_rate": "35.16%",
|
||||
"evictions": 0
|
||||
},
|
||||
"semantic_cache": {
|
||||
"total_entries": 50,
|
||||
"hits": 12,
|
||||
"misses": 38,
|
||||
"hit_rate": "24.00%"
|
||||
},
|
||||
"semantic_cache_intent": {
|
||||
"total_entries": 30,
|
||||
"hits": 8,
|
||||
"misses": 22,
|
||||
"hit_rate": "26.67%"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**POST /cache/clear 响应示例**:
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"cleared": {
|
||||
"lru_cache": "cleared",
|
||||
"semantic_cache": "cleared"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. 会话管理接口
|
||||
|
||||
需要 `ENABLE_SESSION=true` 配置项启用,使用 SQLite 存储会话历史。
|
||||
|
||||
| 端点 | 方法 | 说明 |
|
||||
|------|------|------|
|
||||
| `/sessions` | GET | 获取当前用户的会话列表 |
|
||||
| `/history/<session_id>` | GET | 获取指定会话的聊天历史 |
|
||||
| `/session/<session_id>` | DELETE | 删除指定会话 |
|
||||
| `/clear/<session_id>` | POST | 清空指定会话的历史(保留会话) |
|
||||
|
||||
> **注意**:这些端点在 `ENABLE_SESSION=false`(生产模式默认值)时不注册,返回 404。
|
||||
|
||||
**GET /sessions 响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"status": "success",
|
||||
"status_code": 2000,
|
||||
"data": {
|
||||
"sessions": [
|
||||
{
|
||||
"session_id": "abc-123-def",
|
||||
"created_at": "2026-06-05T10:30:00",
|
||||
"last_active": "2026-06-05T11:00:00",
|
||||
"preview": "用户最后一条消息的前50字..."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**GET /history/\<session_id\> 响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"status": "success",
|
||||
"status_code": 2000,
|
||||
"data": {
|
||||
"history": [
|
||||
{"role": "user", "content": "你好", "created_at": "2026-06-05T10:30:00"},
|
||||
{"role": "assistant", "content": "你好!有什么可以帮你的?", "created_at": "2026-06-05T10:30:01"}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. 审计日志接口
|
||||
|
||||
需要 `ENABLE_SESSION=true` 配置项启用,查询用户操作审计记录。
|
||||
|
||||
| 端点 | 方法 | 说明 |
|
||||
|------|------|------|
|
||||
| `/audit/logs` | GET | 查询审计日志(管理员) |
|
||||
|
||||
**GET /audit/logs 请求参数**:
|
||||
|
||||
| 参数 | 类型 | 必需 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `limit` | int | ❌ | 返回条数(默认 50) |
|
||||
| `days` | int | ❌ | 查询天数范围(默认 7) |
|
||||
| `action` | string | ❌ | 按操作类型过滤 |
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"status": "success",
|
||||
"status_code": 2000,
|
||||
"data": {
|
||||
"logs": [
|
||||
{
|
||||
"id": 1,
|
||||
"user_id": "admin001",
|
||||
"username": "admin",
|
||||
"action": "rag_query",
|
||||
"query": "三峡工程",
|
||||
"result_summary": "...",
|
||||
"role": "admin",
|
||||
"department": "管理部",
|
||||
"ip_address": "127.0.0.1",
|
||||
"duration_ms": 1234,
|
||||
"timestamp": "2026-06-05T12:00:00"
|
||||
}
|
||||
],
|
||||
"total": 100
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. 系统统计接口
|
||||
|
||||
| 端点 | 方法 | 说明 |
|
||||
|------|------|------|
|
||||
| `/stats` | GET | 获取系统综合统计 |
|
||||
|
||||
> **server-release 上此端点返回 500**(`KeyError: 'SESSION_MANAGER'`),因为会话管理器未初始化。main 分支在 `ENABLE_SESSION=true` 时正常工作。
|
||||
|
||||
---
|
||||
|
||||
## 二、已有端点的行为与内部逻辑差异
|
||||
|
||||
以下端点在 main 和 server-release 上 URL 相同,但**行为或内部逻辑有显著差异**。
|
||||
|
||||
### 1. 全面异步化
|
||||
|
||||
server-release 上为同步阻塞的操作,在 main 分支改为异步执行并返回 `task_id`:
|
||||
|
||||
| 端点 | server-release 行为 | main 行为 |
|
||||
|------|---------------------|-----------|
|
||||
| `POST /documents/upload` | 同步保存+向量化,直接返回结果 | 同步保存,异步向量化,返回 `task_id` |
|
||||
| `POST /documents/batch-upload` | 同步保存+向量化,直接返回结果 | 同步保存,异步向量化,返回 `task_id` |
|
||||
| `POST /sync` | 同步阻塞直到完成 | 异步执行,立即返回 `task_id` |
|
||||
| `POST /collections/<kb>/reindex` | 同步阻塞直到完成 | 异步执行,立即返回 `task_id` |
|
||||
| `POST /exam/generate` | 同步阻塞(30-60s) | 异步执行,立即返回 `task_id` |
|
||||
| `POST /exam/generate-smart` | 同步阻塞(30-60s) | 异步执行,立即返回 `task_id` |
|
||||
| `POST /exam/grade` | 同步阻塞 | 异步执行,立即返回 `task_id` |
|
||||
|
||||
**main 分支上传响应示例**(对比 server-release):
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"status": "success",
|
||||
"status_code": 2002,
|
||||
"message": "文件上传成功,已保存,向量化任务已启动",
|
||||
"data": {
|
||||
"file": {
|
||||
"filename": "test.txt",
|
||||
"collection": "public_kb",
|
||||
"path": "public_kb/test.txt",
|
||||
"size": 18,
|
||||
"replaced": false
|
||||
},
|
||||
"sync_status": "已保存,向量化任务已启动",
|
||||
"task_id": "a1b2c3d4e5f6"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **新增字段**:`task_id`(用于轮询进度)、`replaced`(是否为覆盖旧文件)。
|
||||
> server-release 上这两个字段不存在。
|
||||
|
||||
**后端调用流程变更**:
|
||||
```
|
||||
server-release: POST /upload → 等待 → 200 OK(文件已处理)
|
||||
main: POST /upload → 立即 200 OK(task_id)→ GET /tasks/<id> 轮询直到 completed
|
||||
```
|
||||
|
||||
### 2. RAG 问答增强(不涉及端口变化)
|
||||
|
||||
`POST /rag` 端点在两个分支上 URL 和响应格式一致,但**内部管道逻辑**有差异。
|
||||
|
||||
**两个分支共有的能力(engine.py 层,已同步)**:
|
||||
|
||||
| 能力 | 说明 |
|
||||
|------|------|
|
||||
| Embedding 缓存 | `_encode_cached()` LRU 缓存,减少重复编码 |
|
||||
| 表格邻居上下文扩展 | 表格切片自动扩展相邻文本上下文 |
|
||||
| MMR 去重 | 文本相似度去重,减少冗余切片 |
|
||||
| 子查询分解 | 复杂查询拆分为子查询提升召回 |
|
||||
| 图片 P0 独立召回 | 图片切片独立通道检索 |
|
||||
| 引用去重 | chunk_id 保序去重 |
|
||||
|
||||
**main 独有的增强(chat_routes.py 层)**:
|
||||
|
||||
| 能力 | 说明 | 涉及函数 |
|
||||
|------|------|----------|
|
||||
| 语义缓存闭环 | 相似问题命中缓存直接返回,跳过检索和生成 | 集成在 RAG 管道主流程中 |
|
||||
| 表格救援 | 被预算截断的表格切片补回上下文 | `_rescue_table_chunks()` |
|
||||
| 语义前缀精简 | 精简表格切片冗余语义前缀,保留章节标识 | `_strip_semantic_prefix()` |
|
||||
| 层级章节相似度 | 数值精确匹配 + Jaccard 层级系数,用于图片/表格章节过滤 | `_section_similarity()` |
|
||||
| 图片后置过滤 | 基于 LLM 回答关键词反向筛选图片 | `_filter_images_by_answer()` |
|
||||
|
||||
**server-release 独有的增强(agentic*.py 模块化层)**:
|
||||
|
||||
| 能力 | 说明 | 所在模块 |
|
||||
|------|------|----------|
|
||||
| 查询改写 | 口语→专业术语映射、实体补全、LLM 深度重写、图片指代识别 | `agentic_query.py` QueryRewriteMixin |
|
||||
| 上下文压缩 | rerank 过滤、token 截断、去重 | `agentic_context.py` ContextMixin |
|
||||
| 质量评估 | 置信度门控、答案反思 | `agentic_quality.py` QualityMixin |
|
||||
| 受限文档检查 | 权限级别感知的文档过滤 | `engine.py` check_restricted_documents() |
|
||||
|
||||
> **总结**:两个分支在引擎层(engine.py)的检索能力基本一致。差异在于上层管道编排——main 在 chat_routes.py 中增加了表格救援、语义缓存等管道函数;server-release 则通过 AgenticRAG 模块化系统实现了查询改写、上下文压缩等能力。这些差异不影响 API 端口定义。
|
||||
|
||||
### 3. 认证增强
|
||||
|
||||
| 端点 | 变更说明 |
|
||||
|------|----------|
|
||||
| `POST /auth/login` | 新增 IP 速率限制(频繁登录返回 HTTP 429) |
|
||||
| `POST /auth/change-password` | 新增旧密码验证(server-release 不验证旧密码) |
|
||||
|
||||
---
|
||||
|
||||
## 三、全局响应格式变更
|
||||
|
||||
main 分支将所有端点的响应统一为 `success_response()` / `error_response()` 封装格式。
|
||||
|
||||
**server-release 响应格式**(部分端点使用原始 jsonify):
|
||||
```json
|
||||
{
|
||||
"contexts": ["..."],
|
||||
"metadatas": [...],
|
||||
"scores": [0.99]
|
||||
}
|
||||
```
|
||||
|
||||
**main 分支响应格式**(统一封装):
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"status": "success",
|
||||
"status_code": 2000,
|
||||
"message": "查询成功",
|
||||
"data": {
|
||||
"contexts": ["..."],
|
||||
"metadatas": [...],
|
||||
"scores": [0.99]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **⚠️ Breaking Change**:如果后端直接读取响应顶层字段(如 `response.contexts`),迁移到 main 后需要改为 `response.data.contexts`。建议后端统一使用 `response.data` 访问实际数据。
|
||||
|
||||
---
|
||||
|
||||
## 四、新增状态码
|
||||
|
||||
| 状态码 | 常量名 | 说明 |
|
||||
|--------|--------|------|
|
||||
| 4014 | `TASK_NOT_FOUND` | 任务不存在 |
|
||||
| 4015 | `TASK_CONFLICT` | 任务冲突(如重复触发同步) |
|
||||
| 5040 | `REINDEX_ERROR` | 重建索引失败 |
|
||||
|
||||
---
|
||||
|
||||
## 五、架构差异总结
|
||||
|
||||
| 维度 | server-release | main |
|
||||
|------|----------------|------|
|
||||
| 长操作模式 | 同步阻塞 | 异步任务 + task_id 轮询 |
|
||||
| 响应格式 | 混合(jsonify + success_response) | 统一 success_response 封装 |
|
||||
| RAG 引擎层 | 相同(engine.py 已同步) | 相同 |
|
||||
| RAG 管道层 | AgenticRAG 模块化编排(查询改写/上下文压缩/质量评估) | chat_routes.py 单体管道(语义缓存/表格救援/图片过滤) |
|
||||
| 会话管理 | 无状态 | SQLite 会话存储(可选) |
|
||||
| 审计日志 | 无 | 操作审计记录 |
|
||||
| 缓存系统 | LRU(语义缓存已初始化但未接入流程) | LRU + 语义缓存(完整闭环) |
|
||||
| 认证安全 | 基础 | IP 速率限制 + 旧密码验证 |
|
||||
| 核心架构 | AgenticRAG 多模块 + engine.py | 统一 engine.py + chat_routes.py 管道 |
|
||||
|
||||
---
|
||||
|
||||
## 六、迁移注意事项
|
||||
|
||||
如果后续需要将 main 分支部署到服务器,需要注意:
|
||||
|
||||
1. **后端适配**:后端组需要修改调用方式,从同步等待结果改为轮询 `GET /tasks/<task_id>`。建议轮询间隔 1-2 秒。
|
||||
|
||||
2. **响应格式**:所有接口响应统一为 `{success, status, status_code, message, data}` 格式,前端/后端需要从 `data` 字段读取实际数据。
|
||||
|
||||
3. **配置项**:新增 `ENABLE_SESSION`、`ENABLE_FEEDBACK` 等开关,需要确认生产环境配置。
|
||||
|
||||
4. **RAG 管道差异**:main 和 server-release 的 RAG 增强方向不同(main 侧重语义缓存/表格救援,server-release 侧重模块化查询改写/上下文压缩),但不影响 API 端口兼容性。迁移时两者的检索效果可能略有差异,需要做效果对比测试。
|
||||
|
||||
5. **兼容性**:如果暂不迁移后端,可以只在 main 分支上保持同步模式(通过环境变量开关),避免 breaking change。
|
||||
@@ -1,180 +0,0 @@
|
||||
## RAG-Agent 代码审查报告(精简版)
|
||||
|
||||
审查日期:2026-06-05
|
||||
|
||||
排除说明:storage 模块尚未启用(暂不纳入);用户认证/权限由后端服务负责(生产环境 RAG 服务为无状态接口,不做鉴权)。
|
||||
|
||||
---
|
||||
|
||||
### 一、高危问题(6 项)
|
||||
|
||||
**H1. SSE 错误事件泄露完整堆栈信息**
|
||||
- 文件:`api/chat_routes.py:1955`
|
||||
- `/rag` 接口 SSE 生成器在异常时将 `traceback.format_exc()` 完整堆栈直接发给客户端,暴露调用栈、文件路径、代码行号、内部变量。
|
||||
- 修复:移除 traceback 字段,仅在服务端日志记录,客户端返回通用错误消息。
|
||||
|
||||
**H2. 文档更新/删除接口存在路径遍历风险**
|
||||
- 文件:`api/document_routes.py:652,714`
|
||||
- `update_document` 和 `delete_document` 直接将 URL 中的 `doc_path` 拼接到文件路径,未做安全校验。可构造 `../../` 路径遍历载荷。
|
||||
- 修复:使用 `os.path.realpath()` 解析最终路径,验证是否在 DOCUMENTS_PATH 目录下。
|
||||
|
||||
**H3. `serve_document_file` 路径遍历风险**
|
||||
- 文件:`api/document_routes.py:139`
|
||||
- 文件服务接口同样存在路径遍历风险。虽然有 DEV_MODE 开关,但默认值为 `'true'`。
|
||||
- 修复:添加 realpath 校验。
|
||||
|
||||
**H4. 文档更新接口缺少文件类型和大小校验**
|
||||
- 文件:`api/document_routes.py:621`
|
||||
- `update_document` (PUT) 未验证文件类型和大小,直接 `file.save(filepath)`。与之对比,`upload_document` 有完整校验。
|
||||
- 修复:添加与 upload 一致的 ALLOWED_EXTENSIONS 和 MAX_FILE_SIZE 校验。
|
||||
|
||||
**H5. 批量上传接口缺少文件大小校验**
|
||||
- 文件:`api/document_routes.py:350`
|
||||
- `batch_upload_documents` 对每个文件只检查了扩展名,未检查文件大小。可批量上传超大文件导致磁盘耗尽。
|
||||
- 修复:在循环内添加 MAX_FILE_SIZE 校验。
|
||||
|
||||
**H6. `main.py` debug 模式默认开启 + 监听 0.0.0.0**
|
||||
- 文件:`main.py:29-31`
|
||||
- `--debug` 默认 `True`,`--host` 默认 `0.0.0.0`。Flask 调试模式启用 Werkzeug 交互式 debugger,可通过触发异常执行任意代码。
|
||||
- 修复:`--debug` 默认值改为 `False`。
|
||||
|
||||
---
|
||||
|
||||
### 二、中危问题(13 项)
|
||||
|
||||
**M1. 多处异常响应直接暴露内部错误信息**
|
||||
- 文件:`document_routes.py:332,467,733`;`kb_routes.py:523,636`;`feedback_routes.py:98,116`;`sync_routes.py:119`;`audit_routes.py:100` 等。
|
||||
- 大量 `except` 块直接 `str(e)` 返回给客户端,可能包含数据库路径、SQL 片段、文件系统结构。
|
||||
- 修复:统一使用通用错误消息,原始异常仅记录到服务端日志。
|
||||
|
||||
**M2. `/search` 接口缺少输入安全验证**
|
||||
- 文件:`api/chat_routes.py:1974`
|
||||
- 未调用 `validate_query()` 做注入检测和长度限制,与 `/chat`、`/rag` 不一致。
|
||||
- 修复:添加 `validate_query(query)` 调用。
|
||||
|
||||
**M3. `/search` 的 `top_k` 参数未校验范围**
|
||||
- 文件:`api/chat_routes.py:1989`
|
||||
- 可传 `top_k=999999` 导致内存溢出。
|
||||
- 修复:`top_k = max(1, min(int(top_k), 50))`。
|
||||
|
||||
**M4. `context_count` 参数未校验范围**
|
||||
- 文件:`api/document_routes.py:821`
|
||||
- 未限制范围且非整数字符串会 ValueError 导致 500。
|
||||
- 修复:try/except + `max(0, min(n, 10))`。
|
||||
|
||||
**M5. CORS 配置允许所有来源**
|
||||
- 文件:`api/__init__.py:70`
|
||||
- `CORS(app)` 默认允许 `*` 跨域。生产环境应限制为已知前端域名。
|
||||
- 修复:根据 APP_ENV 条件配置 origins。
|
||||
|
||||
**M6. LIKE 通配符注入风险**
|
||||
- 文件:`api/kb_routes.py:503`
|
||||
- `kb_name` 含 `%` 或 `_` 时会导致非预期的 LIKE 匹配行为。
|
||||
- 修复:对 LIKE 特殊字符转义后再拼入模式。
|
||||
|
||||
**M7. SESSION_MANAGER 为 None 时未处理**
|
||||
- 文件:`api/session_routes.py:35,64,83,102`
|
||||
- 初始化失败时 SESSION_MANAGER 为 None,调用方法会触发 AttributeError 导致 500。
|
||||
- 修复:使用前检查 None,返回 503。
|
||||
|
||||
**M8. LLM 调用缺少统一的超时和重试机制**
|
||||
- 文件:`core/llm_utils.py`
|
||||
- 部分 LLM 调用无超时控制,长时间阻塞会耗尽 worker。`@retry` 装饰器只在部分方法上使用。
|
||||
- 修复:在 `_call_llm` 层面统一超时和重试。
|
||||
|
||||
**M9. LLM 输出 JSON 解析不够健壮**
|
||||
- 文件:`core/agentic.py`、`core/agentic_answer.py`、`core/agentic_quality.py` 等
|
||||
- 多处 LLM 返回的 JSON 解析缺少多策略提取和重试,仅靠 prompt 约束。exam_pkg 已修复但 core 模块尚未统一。
|
||||
- 修复:提取 exam_pkg 的 `_extract_json` 为公共工具,core 模块统一使用。
|
||||
|
||||
**M10. Prompt 注入风险**
|
||||
- 文件:`core/engine.py:2017`、`core/agentic_answer.py:83`
|
||||
- 用户输入直接拼入 prompt,未做净化。恶意输入可操控 LLM 输出。
|
||||
- 修复:对用户输入做基本的 prompt 注入检测(如检测 "ignore previous instructions" 等模式)。
|
||||
|
||||
**M11. `subprocess.run` 命令参数注入风险**
|
||||
- 文件:`parsers/mineru_parser.py:632`
|
||||
- file_path 中特殊字符(如以 `-` 开头的文件名)可能被命令行工具解释为选项。
|
||||
- 修复:在文件路径前插入 `--` 分隔符;对 backend、lang 参数做白名单校验。
|
||||
|
||||
**M12. Excel/文本解析器无文件大小限制**
|
||||
- 文件:`parsers/excel_parser.py:81`、`parsers/txt_parser.py:15`
|
||||
- 一次性加载全文件到内存,超大文件导致 OOM。
|
||||
- 修复:解析前检查文件大小,设定上限(如 50MB)。
|
||||
|
||||
**M13. 全局变量缓存竞态条件**
|
||||
- 文件:`api/document_routes.py:100`、`api/kb_routes.py:46`
|
||||
- 模块级全局变量 `_kb_manager` 等在多线程 gunicorn 下存在竞态。
|
||||
- 修复:使用 `threading.Lock` 保护或改用 `flask.current_app.config`。
|
||||
|
||||
---
|
||||
|
||||
### 三、低危问题(12 项)
|
||||
|
||||
**L1.** `config.py` 硬编码第三方 API 端点 `xiaomimimo.com` 作为默认值(第 20 行)— 改为空字符串,要求环境变量显式配置。
|
||||
|
||||
**L2.** `python-dotenv` 未安装时静默跳过,服务可能 fail-open 启动(`config.py:11`)— 生产环境缺失时抛异常。
|
||||
|
||||
**L3.** `assert` 校验可被 `python -O` 跳过(`api/__init__.py:236`)— 改为 `raise ValueError`。
|
||||
|
||||
**L4.** `/chat` 的 `history` 未限长度(`chat_routes.py:1247`)— 可消耗大量 token。
|
||||
|
||||
**L5.** `history` 元素结构未验证(`chat_routes.py:1117`)— 缺少字段时 KeyError 导致 500。
|
||||
|
||||
**L6.** `safe_filename` 运算符优先级不明确(`document_routes.py:94`)— 加括号明确。
|
||||
|
||||
**L7.** DocStore glob 模式未转义特殊字符(`document_routes.py:267`)— 用 `glob.escape()`。
|
||||
|
||||
**L8.** 相对路径 `.data/images` 因工作目录不同可能解析错误(`chat_routes.py:59`)— 改用 PROJECT_ROOT 绝对路径。
|
||||
|
||||
**L9.** `asyncio.run()` 在 Flask 请求上下文中兼容性问题(`chat_routes.py:1744`)。
|
||||
|
||||
**L10.** Excel 同一文件被重复读取多次(`excel_parser.py:81,89`)— 应复用 ExcelFile 对象。
|
||||
|
||||
**L11.** PDF 图片提取 `doc` 对象异常时未关闭(`image_extractor.py:78`)— 改用 `with` 语句。
|
||||
|
||||
**L12.** TXT 解析器异常用 `print` 而非 `logger`(`txt_parser.py:26`)。
|
||||
|
||||
---
|
||||
|
||||
### 四、修复优先级
|
||||
|
||||
按修复成本从低到高排序:
|
||||
|
||||
**第一批:快速修复(半天,改几行代码)**
|
||||
|
||||
| 编号 | 问题 | 改动量 |
|
||||
|:---:|---|:---:|
|
||||
| H6 | main.py debug 默认开启 | 1 行 |
|
||||
| M3 | /search top_k 范围校验 | 2 行 |
|
||||
| M2 | /search 加 validate_query | 2 行 |
|
||||
| M4 | context_count 范围校验 | 3 行 |
|
||||
| L3 | assert 改 raise | 3 行 |
|
||||
| H1 | SSE 移除 traceback 字段 | 5 行 |
|
||||
|
||||
**第二批:安全加固(1-2 天)**
|
||||
|
||||
| 编号 | 问题 | 改动量 |
|
||||
|:---:|---|:---:|
|
||||
| H2+H3 | 文档接口路径遍历 realpath 校验 | ~30 行 |
|
||||
| H4+H5 | 文档更新/批量上传加文件校验 | ~30 行 |
|
||||
| M6 | LIKE 通配符转义 | ~10 行 |
|
||||
| M1 | 异常信息统一脱敏 | 多文件,每处 2-3 行 |
|
||||
| M5 | CORS 生产环境限制来源 | ~5 行 |
|
||||
| M7 | SESSION_MANAGER None 保护 | ~10 行 |
|
||||
| M11 | subprocess 参数注入防护 | ~5 行 |
|
||||
|
||||
**第三批:架构改进(1-2 周)**
|
||||
|
||||
| 编号 | 问题 | 说明 |
|
||||
|:---:|---|---|
|
||||
| M8+M9 | LLM 调用统一超时/重试/解析 | 提取 exam_pkg 经验为公共工具 |
|
||||
| M10 | Prompt 注入防御 | 需设计检测规则 |
|
||||
| M13 | 全局变量竞态修复 | threading.Lock |
|
||||
| M12 | 解析器文件大小限制 | 统一加前置校验 |
|
||||
|
||||
---
|
||||
|
||||
### 五、做得好的方面
|
||||
|
||||
SQL 查询全部使用参数化查询,无注入风险;`validate_query()` 对聊天输入做了注入检测和违禁词过滤;`safe_filename` 对上传文件做了基本防护;`filter_response()` 能过滤 API 密钥等敏感信息;exam_pkg 的输入校验体系完整(已在本轮开发中加固);`.gitignore` 正确排除了 `.env` 等敏感文件。
|
||||
298
docs/出题系统逻辑.md
Normal file
298
docs/出题系统逻辑.md
Normal file
@@ -0,0 +1,298 @@
|
||||
# 出题系统生成逻辑
|
||||
|
||||
> 源码:`exam_pkg/`
|
||||
> 主入口:`manager.py → generate_questions_from_file()`
|
||||
> 核心生成器:`generator.py → generate_questions_structured_v2()`
|
||||
|
||||
---
|
||||
|
||||
## 1. 整体架构
|
||||
|
||||
```
|
||||
┌─────────────────────┐
|
||||
│ API 入口 (api.py) │
|
||||
│ /exam/generate │
|
||||
│ /exam/generate-smart│
|
||||
└──────────┬──────────┘
|
||||
│ 异步任务 (task_id)
|
||||
┌──────────▼──────────┐
|
||||
│ manager.py │
|
||||
│ generate_questions │
|
||||
│ _from_file() │
|
||||
└──────────┬──────────┘
|
||||
│
|
||||
┌────────────────────▼────────────────────┐
|
||||
│ retrieve_file_chunks() │
|
||||
│ 构建语义 query → 向量检索 → 文件切片列表 │
|
||||
└────────────────────┬────────────────────┘
|
||||
│
|
||||
┌────────────────────▼────────────────────┐
|
||||
│ generator.py: generate_questions_ │
|
||||
│ structured_v2() │
|
||||
│ │
|
||||
│ Phase 1 文档结构分析(按章节分组) │
|
||||
│ Phase 2 知识点规划(LLM 提取 + 去重) │
|
||||
│ Phase 3 精准出题(AI分配 + 逐点检索) │
|
||||
│ Phase 4 质量校验(去重 + 平衡 + 补题) │
|
||||
└────────────────────┬────────────────────┘
|
||||
│
|
||||
┌──────────▼──────────┐
|
||||
│ 返回题目列表 + 溯源 │
|
||||
└─────────────────────┘
|
||||
```
|
||||
|
||||
## 2. API 入口
|
||||
|
||||
### 2.1 标准出题 `/exam/generate`
|
||||
|
||||
请求体:
|
||||
|
||||
```json
|
||||
{
|
||||
"file_path": "public_kb/产品手册.pdf",
|
||||
"collection": "public_kb",
|
||||
"question_types": {
|
||||
"single_choice": 3,
|
||||
"multiple_choice": 2,
|
||||
"true_false": 2,
|
||||
"fill_blank": 2,
|
||||
"subjective": 1
|
||||
},
|
||||
"difficulty": 3,
|
||||
"exclude_stems": ["已有题干1", "已有题干2"],
|
||||
"options": { "max_source_chunks": 50 }
|
||||
}
|
||||
```
|
||||
|
||||
返回 `task_id`,通过 `GET /tasks/{task_id}` 轮询结果。
|
||||
|
||||
### 2.2 智能出题 `/exam/generate-smart`
|
||||
|
||||
不传 `question_types`,先调用 `analyze_document_for_exam()` 让 LLM 分析文档后自动推荐题型和数量,再走标准生成流程。
|
||||
|
||||
### 2.3 约束
|
||||
|
||||
| 约束项 | 值 |
|
||||
|---|---|
|
||||
| 总题数上限 | 20 道 |
|
||||
| 难度范围 | 1-5 |
|
||||
| `exclude_stems` 上限 | 100 条 |
|
||||
| 合法题型 | `single_choice`, `multiple_choice`, `true_false`, `fill_blank`, `subjective` |
|
||||
|
||||
---
|
||||
|
||||
## 3. 切片检索(Phase 0)
|
||||
|
||||
`retrieve_file_chunks()` 在出题前先检索文件的相关切片:
|
||||
|
||||
1. **构建语义 query**:根据 `question_types` 自动拼装检索词。例如需要填空题会追加"术语 公式 数值",需要主观题追加"流程 步骤 原则"。
|
||||
2. **向量检索**:调用 `engine.search_knowledge()`,按文件名过滤(`source_filter`),支持文件名和完整路径两种格式,支持多 collection 按优先级检索。
|
||||
3. **动态 top_k**:`min(50, 总题数 × 3)`,确保切片数量足够覆盖所有题目。
|
||||
|
||||
---
|
||||
|
||||
## 4. 四阶段生成流水线
|
||||
|
||||
### Phase 1:文档结构分析
|
||||
|
||||
`group_chunks_by_section(chunks)` — 将所有切片按 `section` 字段分组为 `Dict[章节名, List[切片]]`。
|
||||
|
||||
清理章节名中的 `**` 等标记,空章节归入"未分类"。
|
||||
|
||||
### Phase 2:知识点规划
|
||||
|
||||
对每个章节调用 `_extract_knowledge_points(section, chunks, max_points=3)`:
|
||||
|
||||
- **长内容(≥100 字)**:调用 LLM 提取 3 个关键知识点(短短语,5-15 字),prompt 要求"适合出考试题、不重复不重叠"。
|
||||
- **短内容(<100 字)**:直接清理后作为知识点名称,不调 LLM。
|
||||
|
||||
全局去重:所有知识点按 `name` 去重(`seen_kp_names` 集合),确保跨章节不重复。
|
||||
|
||||
每个知识点标记来源章节(`kp['section']`),供后续精准检索使用。
|
||||
|
||||
**降级路径**:如果所有章节都提取不出知识点,走 `_generate_questions_fallback()` — 把全部 chunks 拼成一个大 prompt 直接让 LLM 出题。
|
||||
|
||||
### Phase 3:精准出题
|
||||
|
||||
#### 3a. AI 分配题型
|
||||
|
||||
`_ai_assign_question_types(knowledge_points, question_types)` — 按章节轮询分配"哪个知识点出什么题型":
|
||||
|
||||
- 每种题型独立分配,确保题型覆盖。
|
||||
- 轮询章节,优先从不同章节选知识点。
|
||||
- 每个知识点最多出 1 道同题型题目。
|
||||
|
||||
输出 assignments 列表:`[{"knowledge_point": "请假流程", "question_type": "single_choice", "section": "第三章"}, ...]`
|
||||
|
||||
#### 3b. 逐知识点出题
|
||||
|
||||
对每个 assignment:
|
||||
|
||||
1. **精准检索**:`_retrieve_kp_chunks_v2(kp_name, section_chunks, top_k=5)` — 用知识点名称做关键词匹配,在该章节的切片中评分排序,取 top 5 最相关的切片。评分规则:知识点全文匹配 +100 分,关键词匹配 +10 分,内容长度适中加分。
|
||||
2. **构建上下文**:`build_source_context(kp_chunks)` — 拼接切片内容,每个切片带 `[chunk_id:xxx | 第N页 章节]` 溯源标记。
|
||||
3. **构造 Prompt**:`_build_prompt_for_kp()` — 指定核心知识点、难度、题型数量,要求"必须围绕该知识点出题、每道题不同角度、严禁非 JSON 内容"。附带 5 种题型的 JSON 格式示例。
|
||||
4. **调用 LLM**:`_generate_with_retry()` — 最多重试 2 次。每次调用后 `safe_parse_questions()` 解析 JSON(支持直接解析、提取代码块、提取数组三种方式),`validate_questions_schema()` 校验(必须有 type/stem/answer,选择题必须有 options)。
|
||||
5. **补充溯源**:`_enrich_with_source_trace()` — 给每道题附加 `source_trace`(文档名、chunk_id 列表、来源信息)。
|
||||
|
||||
### Phase 4:质量校验
|
||||
|
||||
#### 4a. 去重
|
||||
|
||||
`_deduplicate_questions(questions, exclude_stems)` — 三层去重:
|
||||
|
||||
1. **题干前缀去重**:题干前 80 字相同 → 去掉。
|
||||
2. **知识点+题型去重**:题干前 30 字 + 题型相同 → 去掉。
|
||||
3. **跨调用去重**:`exclude_stems` 中已有题目的题干前 80 字预填入去重集合,新生成的题目如果与之冲突也会被过滤。
|
||||
|
||||
#### 4b. 题型平衡
|
||||
|
||||
`_balance_question_types(questions, target_types)` — 按题型分组,每种题型按目标数量截取(多了截断)。
|
||||
|
||||
#### 4c. 补题(仅 v1 结构化路径)
|
||||
|
||||
v1 的 `generate_questions_structured()` 有补题机制 `_makeup_questions()`:如果某题型数量不足,用前 5 个 chunks 重新出一轮补充。v2 路径依赖分配阶段的精确控制,不额外补题。
|
||||
|
||||
---
|
||||
|
||||
## 5. 各题型的 JSON 结构
|
||||
|
||||
### 单选题
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "single_choice",
|
||||
"content": {
|
||||
"stem": "题干内容",
|
||||
"data": { "options": [{"key": "A", "content": "..."}, ...] },
|
||||
"answer": "B",
|
||||
"explanation": "解析..."
|
||||
},
|
||||
"referenced_chunk_ids": ["chunk_001"]
|
||||
}
|
||||
```
|
||||
|
||||
### 多选题
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "multiple_choice",
|
||||
"content": {
|
||||
"stem": "题干",
|
||||
"data": { "options": [...] },
|
||||
"answer": ["A", "C"],
|
||||
"explanation": "解析..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 判断题
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "true_false",
|
||||
"content": {
|
||||
"stem": "判断:某陈述",
|
||||
"data": {},
|
||||
"answer": "对",
|
||||
"explanation": "解析..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 填空题
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "fill_blank",
|
||||
"content": {
|
||||
"stem": "RAG的全称是___,核心在于___。",
|
||||
"data": { "blank_count": 2 },
|
||||
"answer": [["检索增强生成"], ["外部知识库", "检索"]],
|
||||
"explanation": "解析..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`answer` 是二维数组:每个空一个数组,数组内元素为该空的可接受答案(第一个为标准答案,其余为同义词)。
|
||||
|
||||
### 主观题
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "subjective",
|
||||
"content": {
|
||||
"stem": "请简述...",
|
||||
"data": {
|
||||
"scoring_points": [
|
||||
{ "point": "要点1", "weight": 0.4 },
|
||||
{ "point": "要点2", "weight": 0.3 },
|
||||
{ "point": "要点3", "weight": 0.3 }
|
||||
]
|
||||
},
|
||||
"answer": "参考范文...",
|
||||
"explanation": "解析..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 批题逻辑
|
||||
|
||||
批题入口:`POST /exam/grade`,同样是异步任务。
|
||||
|
||||
`grader.py → grade_answers()` 按题型分流:
|
||||
|
||||
| 题型 | 批阅方式 | 说明 |
|
||||
|---|---|---|
|
||||
| `single_choice` / `true_false` | **本地判分** | 直接比对答案,不调 LLM |
|
||||
| `multiple_choice` | **本地判分** | `set(student) == set(correct)`,顺序无关 |
|
||||
| `fill_blank` | **模糊匹配** | 逐空比对,支持同义词(answer 数组中的备选项),忽略空格和标点差异 |
|
||||
| `subjective` | **LLM 评分** | 将题目 stem + scoring_points + 参考答案 + 学生答案一起送给 LLM,按要点权重评分 |
|
||||
|
||||
并发控制:最多 3 路并发批阅(`threading.Semaphore(3)`),带 2 次重试。
|
||||
|
||||
---
|
||||
|
||||
## 7. 关键函数索引
|
||||
|
||||
| 函数 | 文件 | 作用 |
|
||||
|---|---|---|
|
||||
| `generate_questions_from_file` | manager.py | 出题总入口 |
|
||||
| `analyze_file_for_exam` | manager.py | AI 智能分析(推荐题型) |
|
||||
| `retrieve_file_chunks` | manager.py | 切片检索 |
|
||||
| `generate_questions_structured_v2` | generator.py | v2 四阶段生成主流程 |
|
||||
| `generate_questions_structured` | generator.py | v1 生成主流程(含补题) |
|
||||
| `group_chunks_by_section` | generator.py | 按章节分组 |
|
||||
| `_extract_knowledge_points` | generator.py | LLM 知识点提取 |
|
||||
| `_ai_assign_question_types` | generator.py | AI 题型分配 |
|
||||
| `_retrieve_kp_chunks_v2` | generator.py | 知识点精准检索 |
|
||||
| `_build_prompt_for_kp` | generator.py | 构造出题 Prompt |
|
||||
| `_generate_with_retry` | generator.py | 带重试的 LLM 调用 |
|
||||
| `safe_parse_questions` | generator.py | JSON 安全解析 |
|
||||
| `validate_questions_schema` | generator.py | 题目 Schema 校验 |
|
||||
| `_enrich_with_source_trace` | generator.py | 补充溯源信息 |
|
||||
| `_deduplicate_questions` | generator.py | 三层去重 |
|
||||
| `_balance_question_types` | generator.py | 题型数量平衡 |
|
||||
| `_generate_questions_fallback` | generator.py | 降级路径(无知识点时) |
|
||||
| `_makeup_questions` | generator.py | v1 补题机制 |
|
||||
| `grade_answers` | grader.py | 批题总入口 |
|
||||
| `grade_objective` | grader.py | 客观题本地批阅 |
|
||||
| `grade_fill_blank` | grader.py | 填空题模糊匹配 |
|
||||
|
||||
---
|
||||
|
||||
## 8. LLM 调用统计
|
||||
|
||||
一次标准出题(10 题、5 章节)的 LLM 调用次数估算:
|
||||
|
||||
| 阶段 | 调用次数 | 说明 |
|
||||
|---|---|---|
|
||||
| 知识点提取 | ~5 次 | 每章节 1 次 |
|
||||
| 题型分配 | 0 次 | 本地算法分配 |
|
||||
| 出题 | ~10 次 | 每知识点 1 次(含重试) |
|
||||
| Schema 校验 | 0 次 | 本地逻辑 |
|
||||
| 去重 / 平衡 | 0 次 | 本地逻辑 |
|
||||
| **合计** | **~15 次** | |
|
||||
|
||||
智能出题额外增加 1 次 LLM 调用(`analyze_document_for_exam`)。
|
||||
Reference in New Issue
Block a user