# 出题批题接口 — 后端对接指南 > **版本**: v2.0 | **更新**: 2026-06-30 | **基于生产服务器实测** --- ## 一、接口概览 | 接口 | 方法 | 功能 | 模式 | 超时建议 | |------|------|------|------|----------| | `/exam/generate` | POST | 参数出题 | 同步阻塞 | 5 分钟 | | `/exam/generate-smart` | POST | AI 智能出题 | 同步阻塞 | 5 分钟 | | `/exam/grade` | POST | 批阅答案 | 同步阻塞 | 2 分钟 | | `/exam/health` | GET | 健康检查 | 即时 | 5 秒 | > **服务地址**: `http://: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://: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://: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://: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 | 初版 |