- 新增 generate-smart 接口文档(含 ai_analysis 字段) - 判断题返回 bool(true/false)格式说明 - 填空题二维数组答案格式详细说明 - 统一响应格式、状态码、错误码 - 5种题型批阅结果真实示例 - curl 调用示例、性能参考 - 基于生产服务器实测数据
22 KiB
出题批题接口 — 后端对接指南
版本: 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 信封。
成功:
{
"success": true,
"status": "success",
"status_code": 2020,
"message": "出题成功",
"data": { ... }
}
失败:
{
"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 — 参数出题
根据指定的题型和数量,基于文档内容生成题目。
请求体
{
"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
成功响应
{
"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。
请求体
{
"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 字段:
{
"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 种题型混合批阅。
请求体
{
"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对象,批题时原样传入即可。
成功响应
{
"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)
{
"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:
{
"question_id": "q-tf-001",
"score": 2,
"max_score": 2,
"grading_status": "success",
"details": {
"correct": true,
"student_answer": true,
"correct_answer": true,
"feedback": "正确!"
}
}
答错时:
{
"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)
{
"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)
{
"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": "整体回答较好,建议补充制度层面的具体规定。"
}
}
批阅失败(兜底)
{
"question_id": "q-005",
"score": 0,
"max_score": 10,
"grading_status": "failed",
"details": {
"error": "LLM 调用超时"
}
}
单题批阅失败不影响其他题目,失败题目得 0 分。
grading_status 取值
| 值 | 说明 |
|---|---|
success |
评分成功 |
failed |
评分失败(LLM 超时/解析异常),score 为 0 |
五、题目数据结构
出题接口 questions 数组中每道题的完整结构:
{
"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):
{
"content": {
"stem": "以下哪项是正确的?",
"data": {
"options": [
{"key": "A", "content": "..."},
{"key": "B", "content": "..."},
{"key": "C", "content": "..."},
{"key": "D", "content": "..."}
]
},
"answer": "B",
"explanation": "..."
}
}
多选题 (multiple_choice):
{
"content": {
"stem": "以下哪些是正确的?(多选)",
"data": {
"options": [
{"key": "A", "content": "..."},
{"key": "B", "content": "..."},
{"key": "C", "content": "..."},
{"key": "D", "content": "..."}
]
},
"answer": ["A", "C"],
"explanation": "..."
}
}
判断题 (true_false):
{
"content": {
"stem": "公司数据应定期备份(判断对错)",
"data": {},
"answer": "对",
"explanation": "..."
}
}
出题时
content.answer为"对"或"错"(字符串)。批阅接口返回时会自动归一化为true/false(bool)。
填空题 (fill_blank):
{
"content": {
"stem": "公司财务报表应在每季度结束后______天内提交。",
"data": {
"blank_count": 1
},
"answer": [["15", "十五日"]],
"explanation": "..."
}
}
answer为二维数组:外层对应每空,内层为该空的可接受答案列表(第一个为标准答案,其余为同义词/等价答案)。
主观题 (subjective):
{
"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 调用示例
参数出题:
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 题):
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
}'
批题:
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) |
八、注意事项
- 题目 ID: 出题接口不生成 ID,由后端在存储时分配 UUID。
- 分值: RAG 服务不返回分值,满分由后端在入库时设定,批题时通过
max_score传入。 - 总题数上限:
/exam/generate单次请求不超过 20 题;/exam/generate-smart通过max_total参数控制。 - 排除题干:
exclude_stems最多 100 条,用于跨批次去重。后端可在多轮出题时将前批题干传入。 - 判断题返回格式: 批阅接口返回的
student_answer/correct_answer统一为true/false(bool 类型),后端直接按 boolean 处理。 - 填空题答案格式: 二维数组
[["标准答案", "同义词"], ...],外层每空一个元素,内层为该空的可接受答案列表。批阅时支持模糊匹配。 - 主观题评分: 依赖 LLM,存在一定非确定性。同一答案多次评分可能有小幅波动。
- 同步阻塞: 所有接口为同步阻塞模式,请求直到完成后才返回。请后端设置合理的请求超时(建议 5 分钟以上)。
- 单题失败不影响整体: 批阅时单题失败(如 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 | 初版 |