Files
rag/docs/出题批题后端对接指南.md
lacerate551 e40989eeab docs: 全面重写出题批题后端对接指南 v2.0
- 新增 generate-smart 接口文档(含 ai_analysis 字段)
- 判断题返回 bool(true/false)格式说明
- 填空题二维数组答案格式详细说明
- 统一响应格式、状态码、错误码
- 5种题型批阅结果真实示例
- curl 调用示例、性能参考
- 基于生产服务器实测数据
2026-06-30 10:33:55 +08:00

745 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 出题批题接口 — 后端对接指南
> **版本**: v2.0 | **更新**: 2026-06-30 | **基于生产服务器实测**
---
## 一、接口概览
| 接口 | 方法 | 功能 | 模式 | 超时建议 |
|------|------|------|------|----------|
| `/exam/generate` | POST | 参数出题 | 同步阻塞 | 5 分钟 |
| `/exam/generate-smart` | POST | AI 智能出题 | 同步阻塞 | 5 分钟 |
| `/exam/grade` | POST | 批阅答案 | 同步阻塞 | 2 分钟 |
| `/exam/health` | GET | 健康检查 | 即时 | 5 秒 |
> **服务地址**: `http://<host>:5001`URL 前缀 `/exam`
>
> **重要**: 所有接口为同步阻塞模式,请求直到完成后才返回。后端请设置合理超时(建议 5 分钟以上)。
---
## 二、统一响应格式
所有接口返回统一 JSON 信封。
**成功**:
```json
{
"success": true,
"status": "success",
"status_code": 2020,
"message": "出题成功",
"data": { ... }
}
```
**失败**:
```json
{
"success": false,
"status": "failed",
"error_code": "MISSING_PARAMS",
"status_code": 4000,
"message": "缺少 file_path 或 collection 参数"
}
```
**业务状态码**:
| status_code | 常量 | 说明 |
|-------------|------|------|
| 2020 | EXAM_SUCCESS | 出题成功 |
| 2021 | GRADE_SUCCESS | 批阅完成 |
| 4000 | BAD_REQUEST | 请求参数错误 |
| 5020 | EXAM_ERROR | 出题失败 |
| 5021 | GRADE_ERROR | 批阅失败 |
---
## 三、出题接口
### 3.1 POST /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"],
"request_id": "uuid-optional",
"options": {
"include_explanation": true,
"max_source_chunks": 50
}
}
```
#### 请求参数
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| `file_path` | string | 是 | — | 文件路径(向量库内相对路径) |
| `collection` | string | 是 | — | 向量库名称 |
| `question_types` | object | 是 | — | 题型及数量,键名见下方合法值,值为非负整数,总题数上限 **20** |
| `difficulty` | int | 否 | 3 | 难度等级 1-5 |
| `exclude_stems` | string[] | 否 | — | 排除已有题干(跨调用去重),最多 100 条 |
| `request_id` | string | 否 | — | 幂等性标识,原样返回 |
| `options.include_explanation` | bool | 否 | true | 是否生成答案解析 |
| `options.max_source_chunks` | int | 否 | 50 | 最大检索切片数 |
**question_types 合法键值**: `single_choice``multiple_choice``true_false``fill_blank``subjective`
#### 成功响应
```json
{
"success": true,
"status": "success",
"status_code": 2020,
"message": "出题成功",
"data": {
"success": true,
"request_id": "uuid",
"questions": [ ... ],
"total": 10,
"requested_types": {"single_choice": 3, "fill_blank": 2},
"actual_types": {"single_choice": 3, "fill_blank": 2},
"source_chunks_used": 15,
"warnings": ["fill_blank: 请求 3 道,实际生成 2 道"]
}
}
```
**data 字段说明**:
| 字段 | 类型 | 说明 |
|------|------|------|
| `success` | bool | 是否成功 |
| `request_id` | string/null | 请求标识原样返回 |
| `questions` | array | 题目列表(结构见第五章) |
| `total` | int | 实际生成题数 |
| `requested_types` | object | 请求的题型和数量 |
| `actual_types` | object | 实际生成的题型和数量 |
| `source_chunks_used` | int | 使用的文档切片数 |
| `warnings` | string[] | 警告信息(某题型实际生成少于请求时出现) |
---
### 3.2 POST /exam/generate-smart — AI 智能出题
AI 自动分析文档内容,决定题型和数量后生成题目。与 `/exam/generate` 的区别是**不需要传 question_types**。
#### 请求体
```json
{
"file_path": "public_kb/产品手册.pdf",
"collection": "public_kb",
"difficulty": 3,
"max_total": 20,
"exclude_stems": ["已有题目的题干1"],
"request_id": "uuid-optional"
}
```
#### 请求参数
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| `file_path` | string | 是 | — | 文件路径 |
| `collection` | string | 是 | — | 向量库名称 |
| `difficulty` | int | 否 | 3 | 难度等级 1-5 |
| `max_total` | int | 否 | — | AI 出题总数上限(正整数),不传则不限制 |
| `exclude_stems` | string[] | 否 | — | 排除已有题干 |
| `request_id` | string | 否 | — | 幂等性标识 |
#### 成功响应
`/exam/generate` 格式相同,`data` 中额外包含 `ai_analysis` 字段:
```json
{
"success": true,
"status": "success",
"status_code": 2020,
"message": "AI 智能出题成功",
"data": {
"success": true,
"request_id": "uuid",
"questions": [ ... ],
"total": 5,
"requested_types": {"single_choice": 2, "true_false": 1, "fill_blank": 1, "multiple_choice": 1},
"actual_types": {"single_choice": 2, "true_false": 1, "fill_blank": 1, "multiple_choice": 1},
"source_chunks_used": 30,
"ai_analysis": {
"total_knowledge_points": 29,
"question_types": {"single_choice": 2, "true_false": 1, "fill_blank": 1, "multiple_choice": 1},
"suitable_types": ["single_choice", "true_false", "fill_blank", "multiple_choice"],
"reason": "文档涵盖大量概念性内容..."
}
}
}
```
> **注意**: `requested_types` 来自 AI 分析结果,可能包含值为 0 的题型(表示 AI 认为该题型不适合当前文档)。`actual_types` 仅包含实际生成数量大于 0 的题型。
**ai_analysis 字段说明**:
| 字段 | 类型 | 说明 |
|------|------|------|
| `total_knowledge_points` | int | AI 提取的知识点总数 |
| `question_types` | object | AI 推荐的题型和数量 |
| `suitable_types` | string[] | 适合该文档的题型列表 |
| `reason` | string | AI 的分析说明 |
---
## 四、批阅接口
### 4.1 POST /exam/grade — 批题
逐题批阅并返回评分结果。支持 5 种题型混合批阅。
#### 请求体
```json
{
"request_id": "uuid-optional",
"answers": [
{
"question_id": "q-001",
"question_type": "single_choice",
"content": {"answer": "B"},
"student_answer": "B",
"max_score": 2
},
{
"question_id": "q-002",
"question_type": "true_false",
"content": {"answer": "对"},
"student_answer": "对",
"max_score": 2
},
{
"question_id": "q-003",
"question_type": "fill_blank",
"content": {"answer": [["答案1", "同义词"], ["答案2"]]},
"student_answer": ["学生答案1", "学生答案2"],
"max_score": 4
},
{
"question_id": "q-004",
"question_type": "subjective",
"content": {
"stem": "简述...",
"data": {"scoring_points": [{"point": "要点1", "weight": 0.5}, {"point": "要点2", "weight": 0.5}]},
"answer": "参考范文..."
},
"student_answer": "学生作答内容...",
"max_score": 10
}
]
}
```
#### 请求参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `request_id` | string | 否 | 幂等性标识 |
| `answers` | array | 是 | 答案列表 |
**answers 每道题的字段**:
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `question_id` | string | 是 | 题目唯一标识 |
| `question_type` | string | 是 | 题型 |
| `content` | object | 是 | 题目内容(与出题接口返回的 `questions[].content` 结构一致,后端直接透传) |
| `student_answer` | any | 是 | 学生答案(类型随题型变化,见下方表格) |
| `max_score` | number | 否 | 该题满分,默认:客观题 2 / 填空 4 / 主观 10 |
**各题型 content 和 student_answer 格式**:
| 题型 | content.answer | student_answer |
|------|---------------|----------------|
| single_choice | `"B"` | `"A"` |
| multiple_choice | `["A", "C"]` | `["A", "B"]` |
| true_false | `"对"``"错"` | `"对"``"错"` |
| fill_blank | `[["答案1", "同义词"], ["答案2"]]` | `["学生答案1", "学生答案2"]` |
| subjective | 参考范文字符串 | 学生作答字符串 |
> **content 透传说明**: `content` 字段的结构与出题接口返回的 `questions[].content` 完全一致。后端在存储题目时保存整个 `content` 对象,批题时原样传入即可。
#### 成功响应
```json
{
"success": true,
"status": "success",
"status_code": 2021,
"message": "批阅完成",
"data": {
"success": true,
"request_id": "uuid",
"results": [ ... ],
"total_score": 10.5,
"total_max_score": 21.0,
"score_rate": 50.0
}
}
```
**data 顶层字段**:
| 字段 | 类型 | 说明 |
|------|------|------|
| `success` | bool | 是否成功 |
| `request_id` | string/null | 请求标识 |
| `results` | array | 逐题评分结果(顺序与输入一致) |
| `total_score` | float | 总得分(保留 1 位小数) |
| `total_max_score` | float | 总满分 |
| `score_rate` | float | 得分率百分比(保留 1 位小数) |
---
### 4.2 批阅结果 — 逐题结构
每道题的评分结果在 `results` 数组中,结构因题型而异。
#### 客观题 (single_choice / multiple_choice / true_false)
```json
{
"question_id": "q-001",
"score": 2,
"max_score": 2,
"grading_status": "success",
"details": {
"correct": true,
"student_answer": "B",
"correct_answer": "B",
"feedback": "正确!"
}
}
```
#### 判断题 — 特殊说明
判断题的 `student_answer``correct_answer` **统一返回 bool**`true`/`false`),无论输入时传的是 `"对"`/`"错"` 还是 `true`/`false`
```json
{
"question_id": "q-tf-001",
"score": 2,
"max_score": 2,
"grading_status": "success",
"details": {
"correct": true,
"student_answer": true,
"correct_answer": true,
"feedback": "正确!"
}
}
```
答错时:
```json
{
"question_id": "q-tf-002",
"score": 0,
"max_score": 2,
"grading_status": "success",
"details": {
"correct": false,
"student_answer": true,
"correct_answer": false,
"feedback": "正确答案: false"
}
}
```
> 后端对接时判断题的 `student_answer` / `correct_answer` 直接按 `boolean` 类型处理即可。
#### 填空题 (fill_blank)
```json
{
"question_id": "q-fb-001",
"score": 2.0,
"max_score": 4,
"grading_status": "success",
"details": {
"blank_scores": [2.0, 0],
"total_blanks": 2,
"correct_blanks": 1
}
}
```
> 填空题支持部分给分。`blank_scores` 为每空得分,支持模糊匹配(编辑距离容错 + 标点归一化)。
#### 主观题 (subjective)
```json
{
"question_id": "q-sub-001",
"score": 7.5,
"max_score": 10,
"grading_status": "success",
"details": {
"scoring_breakdown": [
{
"point": "技术防线",
"weight": 0.4,
"achieved": 0.9,
"comment": "表述准确,覆盖了主要技术手段"
}
],
"highlights": ["技术防线表述全面"],
"shortcomings": ["制度防线描述不够具体"],
"overall_feedback": "整体回答较好,建议补充制度层面的具体规定。"
}
}
```
#### 批阅失败(兜底)
```json
{
"question_id": "q-005",
"score": 0,
"max_score": 10,
"grading_status": "failed",
"details": {
"error": "LLM 调用超时"
}
}
```
> 单题批阅失败不影响其他题目,失败题目得 0 分。
#### grading_status 取值
| 值 | 说明 |
|----|------|
| `success` | 评分成功 |
| `failed` | 评分失败LLM 超时/解析异常score 为 0 |
---
## 五、题目数据结构
出题接口 `questions` 数组中每道题的完整结构:
```json
{
"question_type": "single_choice",
"difficulty": 3,
"content": {
"stem": "根据XX制度以下哪项是正确的",
"data": {
"options": [
{"key": "A", "content": "选项A内容"},
{"key": "B", "content": "选项B内容"},
{"key": "C", "content": "选项C内容"},
{"key": "D", "content": "选项D内容"}
]
},
"answer": "B",
"explanation": "根据XX制度第3条规定..."
},
"source_trace": {
"document_name": "public_kb/产品手册.pdf",
"chunk_ids": ["chunk_001", "chunk_002"],
"page_numbers": [3, 5],
"sources": [
{
"chunk_id": "chunk_001",
"page": 3,
"section": "第三章 管理制度",
"snippet": "原文相关片段前200字符..."
}
]
}
}
```
### 5.1 各题型 content 差异
**单选题 (single_choice)**:
```json
{
"content": {
"stem": "以下哪项是正确的?",
"data": {
"options": [
{"key": "A", "content": "..."},
{"key": "B", "content": "..."},
{"key": "C", "content": "..."},
{"key": "D", "content": "..."}
]
},
"answer": "B",
"explanation": "..."
}
}
```
**多选题 (multiple_choice)**:
```json
{
"content": {
"stem": "以下哪些是正确的?(多选)",
"data": {
"options": [
{"key": "A", "content": "..."},
{"key": "B", "content": "..."},
{"key": "C", "content": "..."},
{"key": "D", "content": "..."}
]
},
"answer": ["A", "C"],
"explanation": "..."
}
}
```
**判断题 (true_false)**:
```json
{
"content": {
"stem": "公司数据应定期备份(判断对错)",
"data": {},
"answer": "对",
"explanation": "..."
}
}
```
> 出题时 `content.answer` 为 `"对"` 或 `"错"`(字符串)。批阅接口返回时会自动归一化为 `true`/`false`bool
**填空题 (fill_blank)**:
```json
{
"content": {
"stem": "公司财务报表应在每季度结束后______天内提交。",
"data": {
"blank_count": 1
},
"answer": [["15", "十五日"]],
"explanation": "..."
}
}
```
> `answer` 为二维数组:外层对应每空,内层为该空的可接受答案列表(第一个为标准答案,其余为同义词/等价答案)。
**主观题 (subjective)**:
```json
{
"content": {
"stem": "简述公司数据安全的三道防线。",
"data": {
"scoring_points": [
{"point": "技术防线(防火墙、加密、访问控制)", "weight": 0.4},
{"point": "制度防线(安全规定、审批流程)", "weight": 0.3},
{"point": "人员防线(安全培训、意识教育)", "weight": 0.3}
]
},
"answer": "公司数据安全的三道防线包括1.技术防线...",
"explanation": "..."
}
}
```
> `scoring_points[].weight` 为评分要点的权重0-1批题时 LLM 按要点逐项评分。
### 5.2 溯源信息 (source_trace)
| 字段 | 类型 | 说明 |
|------|------|------|
| `document_name` | string | 来源文件名 |
| `chunk_ids` | string[] | 来源切片 ID 列表 |
| `page_numbers` | int[] | 来源页码(已排序去重) |
| `sources` | object[] | 详细来源信息 |
| `sources[].chunk_id` | string | 切片 ID |
| `sources[].page` | int/null | 页码 |
| `sources[].section` | string | 所属章节标题 |
| `sources[].snippet` | string | 来源切片前 200 字符 |
---
## 六、后端对接指南
### 6.1 典型流程
```
1. 后端调用 POST /exam/generate 或 /exam/generate-smart
2. RAG 返回 questions 数组
3. 后端将 questions 存入 MySQL保存完整 content 对象和 source_trace
- 后端自行分配 question_id (UUID)
- 后端自行设定 score (满分) 和 status
4. 学生答题后,后端组装 answers 数组
- 将存储的 content 对象直接透传到 answers[].content
- 填入 student_answer 和 max_score
5. 后端调用 POST /exam/grade
6. RAG 返回 results 数组,后端存储评分结果
```
### 6.2 后端存储建议
题目表建议至少存储以下字段:
| 字段 | 来源 | 说明 |
|------|------|------|
| `question_id` | 后端自行分配 UUID | 出题接口不返回 ID |
| `question_type` | `questions[].question_type` | 题型标识 |
| `difficulty` | `questions[].difficulty` | 难度 |
| `content` | `questions[].content` | 完整 JSON 存储(含 stem/data/answer/explanation |
| `source_trace` | `questions[].source_trace` | 溯源 JSON |
| `source_file` | 请求参数 `file_path` | 来源文件 |
| `collection` | 请求参数 `collection` | 来源向量库 |
| `score` | 后端自行设定 | 满分 |
| `status` | 后端自行设定 | 状态(待审核/已通过/已拒绝) |
> **重要**: RAG 服务不返回 `question_id` 和 `score` 字段,均由后端在入库时指定。
### 6.3 字段来源速查
| 字段 | 出题接口返回 | 批阅接口需要 | 说明 |
|------|:-----------:|:-----------:|------|
| question_id | 不返回(后端生成) | 原样传回 | UUID |
| question_type | 返回 | 原样传回 | 题型 |
| difficulty | 返回 | 不需要 | 难度 |
| content | 返回 | **透传** | 完整题目内容 JSON |
| source_trace | 返回 | 不需要 | 溯源信息 |
| score/max_score | 不返回(后端设定) | 传入 | 满分 |
| student_answer | — | 传入 | 学生答案 |
### 6.4 curl 调用示例
**参数出题**:
```bash
curl -X POST http://<host>:5001/exam/generate \
-H "Content-Type: application/json" \
-d '{
"file_path": "public_kb/产品手册.pdf",
"collection": "public_kb",
"question_types": {"single_choice": 3, "true_false": 2},
"difficulty": 3
}'
```
**AI 智能出题(限制 5 题)**:
```bash
curl -X POST http://<host>:5001/exam/generate-smart \
-H "Content-Type: application/json" \
-d '{
"file_path": "public_kb/产品手册.pdf",
"collection": "public_kb",
"max_total": 5
}'
```
**批题**:
```bash
curl -X POST http://<host>:5001/exam/grade \
-H "Content-Type: application/json" \
-d '{
"answers": [
{
"question_id": "q-001",
"question_type": "single_choice",
"content": {"answer": "B"},
"student_answer": "B",
"max_score": 2
},
{
"question_id": "q-002",
"question_type": "true_false",
"content": {"answer": "对"},
"student_answer": "对",
"max_score": 2
}
]
}'
```
---
## 七、批阅规则
| 题型 | 批阅方式 | 得分规则 |
|------|----------|----------|
| single_choice | 精确匹配(大小写不敏感) | 正确得满分,错误得 0 |
| multiple_choice | 精确匹配 | 全部选对才给分,少选/多选/错选均得 0 |
| true_false | 精确匹配 | 正确得满分,错误得 0。输入支持 "对"/"错"/"true"/"false"/bool输出统一为 bool |
| fill_blank | 模糊匹配 | 每空独立评分。支持同义词匹配 + 标点归一化 + 编辑距离容错(>=4 字符允许 <=2 差异)。每空得分 = 满分 / 总空数 |
| subjective | LLM 评分 | 按 scoring_points 逐项评分0~1最终得分 = Σ(weight × achieved × max_score) |
---
## 八、注意事项
1. **题目 ID**: 出题接口不生成 ID由后端在存储时分配 UUID。
2. **分值**: RAG 服务不返回分值,满分由后端在入库时设定,批题时通过 `max_score` 传入。
3. **总题数上限**: `/exam/generate` 单次请求不超过 20 题;`/exam/generate-smart` 通过 `max_total` 参数控制。
4. **排除题干**: `exclude_stems` 最多 100 条,用于跨批次去重。后端可在多轮出题时将前批题干传入。
5. **判断题返回格式**: 批阅接口返回的 `student_answer` / `correct_answer` 统一为 `true`/`false`bool 类型),后端直接按 boolean 处理。
6. **填空题答案格式**: 二维数组 `[["标准答案", "同义词"], ...]`,外层每空一个元素,内层为该空的可接受答案列表。批阅时支持模糊匹配。
7. **主观题评分**: 依赖 LLM存在一定非确定性。同一答案多次评分可能有小幅波动。
8. **同步阻塞**: 所有接口为同步阻塞模式,请求直到完成后才返回。请后端设置合理的请求超时(建议 5 分钟以上)。
9. **单题失败不影响整体**: 批阅时单题失败(如 LLM 超时),该题得 0 分,其余题目正常评分。`grading_status` 字段标识每题的批阅状态。
---
## 九、错误码汇总
| HTTP | error_code | 场景 |
|------|------------|------|
| 400 | MISSING_PARAMS | 缺少必填参数 |
| 400 | INVALID_PARAMS | 参数格式或值无效 |
| 500 | EXAM_ERROR | 出题过程异常 |
| 500 | GRADE_ERROR | 批阅过程异常 |
---
## 十、性能参考
| 操作 | 预估耗时 | 说明 |
|------|----------|------|
| 参数出题5 题) | 60-90s | 主要耗时在多次 LLM 调用 |
| AI 智能出题20 题) | 3-5min | 含 AI 分析 + 多次 LLM 调用 |
| 批题10 题混合) | 15-30s | 主观题 LLM 评分较慢 |
| 批题(纯客观题) | 1-3s | 无需 LLM纯匹配 |
> 耗时受 LLM 响应速度影响较大。
---
## 变更记录
| 日期 | 版本 | 变更内容 |
|------|------|---------|
| 2026-06-30 | 2.0 | 全面重写:统一响应格式、新增 generate-smart、判断题返回 bool、填空题 2D 格式说明、生产实测数据 |
| 2026-05-17 | 1.1 | 初版 |