- generate-smart 的 requested_types 可能包含值为0的题型(AI分析结果) - actual_types 仅包含实际生成数量大于0的题型 - 文档已通过生产服务器三端点实测验证
768 lines
22 KiB
Markdown
768 lines
22 KiB
Markdown
# 出题批卷系统设计
|
||
|
||
> **文档类型**: 系统设计文档 & 后端对接手册
|
||
> **创建日期**: 2026-04-10
|
||
> **最后更新**: 2026-06-22
|
||
> **版本**: 3.0
|
||
> **状态**: 已实施
|
||
|
||
---
|
||
|
||
## 一、系统概述
|
||
|
||
### 1.1 定位
|
||
|
||
出题批卷系统是 RAG 知识库服务的扩展模块(`exam_pkg`),负责基于知识库文档自动生成题目和自动批阅。RAG 服务只负责出题和批题的无状态计算,题目的存储、审核、状态管理由后端服务负责。
|
||
|
||
### 1.2 职责边界
|
||
|
||
| 职责 | RAG 服务 | 后端服务 |
|
||
|------|----------|----------|
|
||
| 题目生成 | 基于文档生成题目 + 溯源 | 审核入库 + 状态管理 |
|
||
| 题目批阅 | 逐题评分 + 反馈 | 汇总报告 + 成绩存储 |
|
||
| 题目存储 | 无状态,不存储 | MySQL 持久化 |
|
||
|
||
### 1.3 模块结构
|
||
|
||
```
|
||
exam_pkg/
|
||
├── generator.py # 出题引擎(v2管线:章节分组→知识点提取→精准出题→去重补题)
|
||
├── grader.py # 批阅引擎(客观题精确匹配 + 填空题模糊匹配 + 主观题LLM评分)
|
||
├── manager.py # 编排层(检索切片→调用引擎→组装返回)
|
||
├── api.py # Flask Blueprint(exam_bp, url_prefix=/exam)
|
||
└── local_db.py # 本地题库缓存(SQLite,辅助功能)
|
||
```
|
||
|
||
### 1.4 服务信息
|
||
|
||
| 项目 | 值 |
|
||
|------|-----|
|
||
| 服务地址 | `http://<host>:5001` |
|
||
| URL前缀 | `/exam` |
|
||
| 健康检查 | `GET /exam/health` |
|
||
| 运行模式 | gunicorn + gthread (2 workers) |
|
||
|
||
---
|
||
|
||
## 二、统一响应格式
|
||
|
||
所有接口返回统一 JSON 信封:
|
||
|
||
### 2.1 成功响应
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"status": "success",
|
||
"status_code": 2020,
|
||
"message": "出题成功",
|
||
"data": { ... }
|
||
}
|
||
```
|
||
|
||
### 2.2 失败响应
|
||
|
||
```json
|
||
{
|
||
"success": false,
|
||
"status": "failed",
|
||
"error_code": "MISSING_PARAMS",
|
||
"status_code": 4000,
|
||
"message": "缺少 file_path 或 collection 参数"
|
||
}
|
||
```
|
||
|
||
### 2.3 业务状态码
|
||
|
||
| 状态码 | 常量名 | 说明 |
|
||
|--------|--------|------|
|
||
| 2020 | EXAM_SUCCESS | 出题成功 |
|
||
| 2021 | GRADE_SUCCESS | 批阅完成 |
|
||
| 4000 | BAD_REQUEST | 请求参数错误 |
|
||
| 4001 | UNAUTHORIZED | 未授权 |
|
||
| 4002 | FORBIDDEN | 权限不足 |
|
||
| 5020 | EXAM_ERROR | 出题失败 |
|
||
| 5021 | GRADE_ERROR | 批阅失败 |
|
||
|
||
---
|
||
|
||
## 三、API 接口
|
||
|
||
### 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 | 是 | — | 题型及数量,见下方合法值 |
|
||
| `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`。值为非负整数,总题数上限 **20**。
|
||
|
||
#### 成功响应
|
||
|
||
```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",
|
||
"options": {}
|
||
}
|
||
```
|
||
|
||
#### 请求参数说明
|
||
|
||
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|
||
|------|------|------|--------|------|
|
||
| `file_path` | string | 是 | — | 文件路径 |
|
||
| `collection` | string | 是 | — | 向量库名称 |
|
||
| `difficulty` | int | 否 | 3 | 难度等级 1-5 |
|
||
| `max_total` | int | 否 | — | AI出题总数上限(正整数)。**不传则不限制** |
|
||
| `exclude_stems` | string[] | 否 | — | 排除已有题干 |
|
||
| `request_id` | string | 否 | — | 幂等性标识 |
|
||
| `options` | object | 否 | {} | 同 /exam/generate |
|
||
|
||
#### 成功响应
|
||
|
||
与 `/exam/generate` 格式相同,`data` 中额外包含 `ai_analysis` 字段:
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"status": "success",
|
||
"status_code": 2020,
|
||
"message": "AI 智能出题成功",
|
||
"data": {
|
||
"success": true,
|
||
"request_id": "uuid",
|
||
"questions": [ ... ],
|
||
"total": 15,
|
||
"requested_types": {"single_choice": 8, "true_false": 4, "fill_blank": 3},
|
||
"actual_types": {"single_choice": 8, "true_false": 4, "fill_blank": 3},
|
||
"source_chunks_used": 30,
|
||
"ai_analysis": {
|
||
"total_knowledge_points": 54,
|
||
"question_types": {"single_choice": 8, "true_false": 4, "fill_blank": 3},
|
||
"suitable_types": ["single_choice", "true_false", "fill_blank"],
|
||
"reason": "文档涵盖大量概念性内容..."
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**ai_analysis 字段说明**:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `total_knowledge_points` | int | AI 提取的知识点总数 |
|
||
| `question_types` | object | AI 推荐的题型和数量 |
|
||
| `suitable_types` | string[] | 适合该文档的题型列表 |
|
||
| `reason` | string | AI 的分析说明 |
|
||
|
||
> **注意**:`requested_types` 来自 AI 分析结果(与 `ai_analysis.question_types` 一致),可能包含值为 0 的题型(表示 AI 认为该题型不适合当前文档)。`actual_types` 仅包含实际生成数量大于 0 的题型。
|
||
|
||
---
|
||
|
||
### 3.3 POST /exam/grade — 批题接口
|
||
|
||
逐题批阅并返回评分结果。支持 5 种题型混合批阅。
|
||
|
||
#### 请求体
|
||
|
||
```json
|
||
{
|
||
"request_id": "uuid-optional",
|
||
"answers": [
|
||
{
|
||
"question_id": "uuid-001",
|
||
"question_type": "single_choice",
|
||
"content": {"answer": "B"},
|
||
"student_answer": "A",
|
||
"max_score": 2
|
||
},
|
||
{
|
||
"question_id": "uuid-002",
|
||
"question_type": "multiple_choice",
|
||
"content": {"answer": ["A", "C"]},
|
||
"student_answer": ["A", "B"],
|
||
"max_score": 3
|
||
},
|
||
{
|
||
"question_id": "uuid-003",
|
||
"question_type": "true_false",
|
||
"content": {"answer": "对"},
|
||
"student_answer": "错",
|
||
"max_score": 2
|
||
},
|
||
{
|
||
"question_id": "uuid-004",
|
||
"question_type": "fill_blank",
|
||
"content": {"answer": [["答案1", "同义词"], ["答案2"]]},
|
||
"student_answer": ["学生答案1", "学生答案2"],
|
||
"max_score": 4
|
||
},
|
||
{
|
||
"question_id": "uuid-005",
|
||
"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 | 是 | 题型,合法值同 question_types 键名 |
|
||
| `content` | object | 是 | 题目内容(与出题接口返回的 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位小数) |
|
||
|
||
---
|
||
|
||
### 3.4 GET /exam/health — 健康检查
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"service": "exam-api",
|
||
"version": "2.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字符..."
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4.1 字段说明
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `question_type` | string | 题型标识 |
|
||
| `difficulty` | int | 难度 1-5 |
|
||
| `content.stem` | string | 题干文本 |
|
||
| `content.data` | object | 题型相关数据(见下方) |
|
||
| `content.answer` | any | 正确答案(类型随题型变化) |
|
||
| `content.explanation` | string | 答案解析(可选) |
|
||
| `source_trace` | object | 溯源信息 |
|
||
|
||
### 4.2 各题型 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": "..."
|
||
}
|
||
}
|
||
```
|
||
|
||
**填空题 (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 按要点逐项评分。
|
||
|
||
### 4.3 溯源信息 (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 字符 |
|
||
|
||
---
|
||
|
||
## 五、批题结果数据结构
|
||
|
||
`results` 数组中每道题的评分结果,结构因题型而异:
|
||
|
||
### 5.1 客观题 (single_choice / multiple_choice / true_false)
|
||
|
||
```json
|
||
{
|
||
"question_id": "uuid-001",
|
||
"score": 2,
|
||
"max_score": 2,
|
||
"grading_status": "success",
|
||
"details": {
|
||
"correct": true,
|
||
"student_answer": "B",
|
||
"correct_answer": "B",
|
||
"feedback": "正确!"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.2 填空题 (fill_blank)
|
||
|
||
```json
|
||
{
|
||
"question_id": "uuid-004",
|
||
"score": 2.0,
|
||
"max_score": 4,
|
||
"grading_status": "success",
|
||
"details": {
|
||
"blank_scores": [2.0, 0],
|
||
"total_blanks": 2,
|
||
"correct_blanks": 1
|
||
}
|
||
}
|
||
```
|
||
|
||
> 填空题支持部分给分。`blank_scores` 为每空得分,支持模糊匹配(编辑距离容错 + 标点归一化)。
|
||
|
||
### 5.3 主观题 (subjective)
|
||
|
||
```json
|
||
{
|
||
"question_id": "uuid-005",
|
||
"score": 7.5,
|
||
"max_score": 10,
|
||
"grading_status": "success",
|
||
"details": {
|
||
"scoring_breakdown": [
|
||
{
|
||
"point": "技术防线",
|
||
"weight": 0.4,
|
||
"achieved": 0.9,
|
||
"comment": "表述准确,覆盖了主要技术手段"
|
||
},
|
||
{
|
||
"point": "制度防线",
|
||
"weight": 0.3,
|
||
"achieved": 0.5,
|
||
"comment": "提到了审批流程但缺少具体制度名称"
|
||
},
|
||
{
|
||
"point": "人员防线",
|
||
"weight": 0.3,
|
||
"achieved": 0.8,
|
||
"comment": "安全培训和意识教育均有涉及"
|
||
}
|
||
],
|
||
"highlights": ["技术防线表述全面"],
|
||
"shortcomings": ["制度防线描述不够具体"],
|
||
"overall_feedback": "整体回答较好,建议补充制度层面的具体规定。"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.4 批阅失败(兜底)
|
||
|
||
```json
|
||
{
|
||
"question_id": "uuid-005",
|
||
"score": 0,
|
||
"max_score": 10,
|
||
"grading_status": "failed",
|
||
"details": {
|
||
"error": "LLM 调用超时"
|
||
}
|
||
}
|
||
```
|
||
|
||
> 单题批阅失败不影响其他题目,失败题目得 0 分。
|
||
|
||
### 5.5 grading_status 取值
|
||
|
||
| 值 | 说明 |
|
||
|----|------|
|
||
| `success` | 评分成功 |
|
||
| `failed` | 评分失败(LLM超时/解析异常),score 为 0 |
|
||
|
||
---
|
||
|
||
## 六、出题管线内部逻辑(供参考)
|
||
|
||
v2 出题管线分四个阶段:
|
||
|
||
**Phase 1 — 章节分组**:检索文档切片,按章节标题聚类,构建文档结构概览。
|
||
|
||
**Phase 2 — 知识点提取**:逐章节调用 LLM 提取知识点列表。每次 LLM 调用间隔 1 秒防限流。
|
||
|
||
**Phase 3 — 精准出题**:按知识点 + 题型分配方案,逐知识点调用 LLM 生成题目。支持 429 限流自动重试(指数退避 1s→2s→4s)。
|
||
|
||
**Phase 4 — 后处理**:
|
||
- 四层去重:题干精确去重 → 语义相似度去重 → 知识点均衡 → 跨调用去重(exclude_stems)
|
||
- 补题机制:某知识点出题失败时自动从其他知识点补题,补题与已有题干交叉去重
|
||
|
||
---
|
||
|
||
## 七、批阅逻辑说明(供参考)
|
||
|
||
| 题型 | 批阅方式 | 匹配策略 |
|
||
|------|----------|----------|
|
||
| single_choice | 精确匹配 | 大小写不敏感 |
|
||
| multiple_choice | 精确匹配 | 全部选对才给分 |
|
||
| true_false | 精确匹配 | 完全一致 |
|
||
| fill_blank | 模糊匹配 | 标点归一化 + 编辑距离容错(>=4字符允许<=2差异) |
|
||
| subjective | LLM 评分 | 按 scoring_points 逐项评分,支持并发批阅 |
|
||
|
||
---
|
||
|
||
## 八、后端对接指南
|
||
|
||
### 8.1 出题 → 存储 → 批题的典型流程
|
||
|
||
```
|
||
1. 后端调用 POST /exam/generate 或 /exam/generate-smart
|
||
↓
|
||
2. RAG 返回 questions 数组
|
||
↓
|
||
3. 后端将 questions 存入 MySQL(保存完整 content 对象和 source_trace)
|
||
↓
|
||
4. 学生答题后,后端组装 answers 数组
|
||
- 将存储的 content 对象直接透传到 answers[].content
|
||
- 填入 student_answer 和 max_score
|
||
↓
|
||
5. 后端调用 POST /exam/grade
|
||
↓
|
||
6. RAG 返回 results 数组,后端存储评分结果
|
||
```
|
||
|
||
### 8.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` | 来源向量库 |
|
||
|
||
### 8.3 调用示例 (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 智能出题(限制20题)**:
|
||
```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": 20
|
||
}'
|
||
```
|
||
|
||
**批题**:
|
||
```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
|
||
}
|
||
]
|
||
}'
|
||
```
|
||
|
||
---
|
||
|
||
## 九、性能参考
|
||
|
||
| 操作 | 预估耗时 | 说明 |
|
||
|------|----------|------|
|
||
| 参数出题(5题) | 60-90s | 主要耗时在多次 LLM 调用 |
|
||
| AI 智能出题(20题) | 3-5min | 含 AI 分析 + 多次 LLM 调用 |
|
||
| 批题(10题混合) | 15-30s | 主观题 LLM 评分较慢 |
|
||
| 批题(纯客观题) | 1-3s | 无需 LLM,纯匹配 |
|
||
|
||
> 耗时受 LLM 响应速度影响较大。使用推理模型(如 mimo-v2.5)时已自动关闭推理链以提速。
|
||
|
||
---
|
||
|
||
## 十、错误码汇总
|
||
|
||
| HTTP 状态码 | error_code | 场景 |
|
||
|------------|------------|------|
|
||
| 400 | MISSING_PARAMS | 缺少必填参数 |
|
||
| 400 | INVALID_PARAMS | 参数格式或值无效 |
|
||
| 500 | EXAM_ERROR | 出题过程异常 |
|
||
| 500 | GRADE_ERROR | 批阅过程异常 |
|
||
|
||
---
|
||
|
||
## 十一、注意事项
|
||
|
||
1. **题目 ID**:出题接口不生成 ID,由后端在存储时分配 UUID。
|
||
2. **总题数上限**:`/exam/generate` 单次请求不超过 20 题;`/exam/generate-smart` 通过 `max_total` 参数控制。
|
||
3. **排除题干**:`exclude_stems` 最多 100 条,用于跨批次去重。后端可在多轮出题时将前批题干传入。
|
||
4. **填空题答案**:格式为二维数组 `[["标准答案", "同义词"], ...]`,批题时支持模糊匹配。
|
||
5. **主观题评分**:依赖 LLM,存在一定非确定性。同一答案多次评分可能有小幅波动。
|
||
6. **同步阻塞**:所有接口为同步阻塞模式,请求直到完成后才返回。请后端设置合理的请求超时(建议 5 分钟以上)。
|
||
|
||
---
|
||
|
||
## 十二、变更记录
|
||
|
||
| 日期 | 版本 | 变更内容 |
|
||
|------|------|---------|
|
||
| 2026-06-22 | 3.0 | 全面重写:对接 v2 管线,更新为实际 API 格式(5种题型、统一响应、generate-smart、max_total) |
|
||
| 2026-06-04 | 2.1 | 更新模块结构 |
|
||
| 2026-04-13 | 2.0 | 合并出题批卷功能改造计划 |
|
||
| 2026-04-12 | 1.2 | 新增最小字段输入格式 |
|
||
| 2026-04-10 | 1.0 | 初始版本 |
|