Files
rag/docs/出题批阅接口变更说明(2026-06-05).md
lacerate551 6f863ddfd7 docs(exam): 出题批卷接口变更说明及文档同步
- 新增《出题批阅接口变更说明(2026-06-05)》面向后端的迁移指南
- 同步更新 curl测试手册 和 后端对接规范 中的出题批卷章节
2026-06-05 14:06:17 +08:00

14 KiB
Raw Blame History

出题批阅接口变更说明2026-06-05

本文档面向后端开发人员,汇总出题/批卷接口本次升级的所有变更点,以及后端需要适配的代码修改。


一、变更总览

变更项 变更类型 影响范围 后端是否必须改
批卷请求字段 question_contentcontent 破坏性变更 /exam/grade 请求体
批卷结果格式统一为 grading_status + details 格式变更 /exam/grade 响应体
出题总题数上限 20 道 新增校验 /exam/generate 需注意
批卷 question_type 枚举校验 新增校验 /exam/grade 需注意
exclude_stems 跨调用去重 新增参数 /exam/generate/exam/generate-smart 建议使用
出题结果新增 requested_typesactual_typeswarnings 新增字段 /exam/generate 响应体 可选消费
主观题缺少评分标准时返回 warnings 新增字段 /exam/grade 响应体 可选消费

二、破坏性变更(必须修改)

2.1 批卷请求字段重命名:question_contentcontent

变更前

{
  "question_id": "q-001",
  "question_type": "single_choice",
  "question_content": {
    "stem": "题干",
    "answer": "B",
    "data": {"options": [...]}
  },
  "student_answer": "B",
  "max_score": 2
}

变更后

{
  "question_id": "q-001",
  "question_type": "single_choice",
  "content": {
    "stem": "题干",
    "answer": "B",
    "data": {"options": [...]}
  },
  "student_answer": "B",
  "max_score": 2
}

为什么要改:出题接口 /exam/generate 返回的每道题里,题目内容字段叫 content。批卷接口改用同名 content 后,后端可以直接把出题结果的 content 透传到批卷接口,不需要做任何字段映射。

后端代码修改示例

# 修改前
grade_answers.append({
    "question_id": qid,
    "question_type": question.question_type,
    "question_content": question.content,  # ← 旧字段名
    "student_answer": ans['answer'],
    "max_score": question.score
})

# 修改后
grade_answers.append({
    "question_id": qid,
    "question_type": question.question_type,
    "content": question.content,  # ← 新字段名,与出题接口一致
    "student_answer": ans['answer'],
    "max_score": question.score
})

2.2 批卷结果格式统一

变更前:各题型返回格式不统一,客观题直接返回 correct + feedback,填空题返回 details.blank_scores,主观题返回 details.scoring_breakdown,没有统一的结构标识。

// 旧 - 客观题
{"question_id": "q1", "score": 2, "max_score": 2, "correct": true, "feedback": "正确!"}

// 旧 - 填空题
{"question_id": "q4", "score": 2, "max_score": 4, "details": {"blank_scores": [2, 0]}}

// 旧 - 主观题
{"question_id": "q5", "score": 7, "max_score": 10, "details": {"scoring_breakdown": [...]}}

变更后:所有题型统一使用 grading_status + details 结构。

// 新 - 客观题
{
  "question_id": "q1",
  "score": 2,
  "max_score": 2,
  "grading_status": "success",
  "details": {
    "correct": true,
    "student_answer": "B",
    "correct_answer": "B",
    "feedback": "正确!"
  }
}

// 新 - 填空题
{
  "question_id": "q4",
  "score": 2.0,
  "max_score": 4.0,
  "grading_status": "success",
  "details": {
    "total_blanks": 2,
    "correct_blanks": 1,
    "blank_scores": [2.0, 0],
    "feedback": "2 个空中答对 1 个"
  }
}

// 新 - 主观题
{
  "question_id": "q5",
  "score": 7.5,
  "max_score": 10.0,
  "grading_status": "success",
  "details": {
    "scoring_breakdown": [
      {"point": "核心概念", "weight": 0.5, "achieved": 0.8, "comment": "概念描述准确"}
    ],
    "highlights": ["条理清晰"],
    "shortcomings": ["缺少应用场景"],
    "overall_feedback": "整体回答较好,建议补充实际应用场景。"
  }
}

// 新 - 评分失败LLM 异常或超时)
{
  "question_id": "q6",
  "score": 0,
  "max_score": 10.0,
  "grading_status": "failed",
  "details": {
    "error": "评分结果解析失败"
  }
}

后端需要关注的点

  1. 判断评分是否成功:改用 grading_status 字段判断,不再依赖 correctscore > 0
    • "success" → 正常评分,scoredetails 有效
    • "failed" → 评分失败,score 为 0details.error 包含失败原因
  2. 获取反馈信息:原来客观题的 correct/feedback 现在统一在 details
  3. 成绩更新逻辑grading_status: "failed" 的题目,后端可选择标记为"待重批"而非直接记 0 分

后端代码修改示例

# 修改前
for result in grade_result['results']:
    is_correct = result.get('correct', False)
    feedback = result.get('feedback', '')
    # 更新成绩...

# 修改后
for result in grade_result['results']:
    status = result.get('grading_status', 'success')
    details = result.get('details', {})

    if status == 'failed':
        # 评分失败,标记待重批
        mark_for_regrade(result['question_id'], details.get('error', ''))
        continue

    is_correct = details.get('correct', None)  # 仅客观题有此字段
    feedback = details.get('feedback', '')
    # 更新成绩...

三、新增校验规则(需注意)

3.1 出题接口入参校验

POST /exam/generate 新增以下校验,不满足时返回 HTTP 400error_codeINVALID_PARAMS

校验项 规则 示例(会返回 400
题型合法性 question_types 的 key 必须属于 5 种题型 {"essay": 2} → 不支持
题型数量 每种题型数量必须为非负整数 {"single_choice": -1} → 不合法
难度范围 difficulty 必须为 1-5 的整数 "difficulty": 10 → 超范围
总题数上限 所有题型数量之和不能超过 20 {"single_choice": 15, "true_false": 10} → 超限

合法的 5 种题型 keysingle_choicemultiple_choicetrue_falsefill_blanksubjective

后端建议:在前端提交出题请求时做前置校验,避免不必要的网络请求。

3.2 批卷接口 question_type 校验

POST /exam/gradeanswers 数组中,每道题的 question_type 现在也会校验。无效值返回 HTTP 400

// 返回 400
{"answers": [{"question_type": "essay", ...}]}

// 错误信息
{"error_code": "INVALID_PARAMS", "message": "第 1 题的 question_type 无效: essay合法值: fill_blank, multiple_choice, single_choice, subjective, true_false"}

3.3 错误响应格式

校验失败统一返回:

{
  "success": false,
  "error_code": "INVALID_PARAMS",
  "message": "具体错误描述",
  "status": "failed",
  "status_code": 4000
}

三点五、跨调用去重:exclude_stems(强烈建议使用)

背景问题

同一份文档多次调用出题接口时,由于知识点来源相同,新生成的题目很可能与已有题目高度重复。单次调用上限 20 道,当需要更多题目时,这个问题尤为突出。

解决方式

两个出题接口(/exam/generate/exam/generate-smart)新增可选参数 exclude_stems后端把该文件已入库的题目题干传过来RAG 在生成后自动排除匹配的题目。

请求示例

POST /exam/generate
{
  "file_path": "public_kb/产品手册.pdf",
  "collection": "public_kb",
  "question_types": {"single_choice": 10},
  "exclude_stems": [
    "重购率的定义是下列哪一项?",
    "以下哪项属于现代终端分类?",
    "客户满意度的计算公式为___"
  ]
}

参数说明

参数 类型 必填 说明
exclude_stems string[] 已有题目的题干列表,最多 100 条

去重策略

  • 基于题干前 80 个字符进行匹配
  • 新生成的题目如果与 exclude_stems 中任意一条匹配,会被自动过滤
  • 不传或传空数组时行为不变(向后兼容)

后端使用流程

第一次出题:
POST /exam/generate { question_types: {...} }
→ 返回 20 道新题 → 审核后入库

第二次出题(想多出 10 道选择题):
1. 查询数据库,获取该文件已有的所有题目题干
2. POST /exam/generate {
     question_types: {"single_choice": 10},
     exclude_stems: ["已有题干1", "已有题干2", ...]
   }
→ 返回 10 道不与已有题目重复的新题

后端代码示例

def generate_more_questions(file_path, collection, question_types):
    # 1. 查询该文件已有的题目题干
    existing_stems = db.query("""
        SELECT JSON_EXTRACT(content, '$.stem')
        FROM questions
        WHERE source_file = ?
    """, [file_path])
    
    # 2. 调用出题接口,传入已有题干
    response = requests.post('http://rag-service:5001/exam/generate', json={
        'file_path': file_path,
        'collection': collection,
        'question_types': question_types,
        'exclude_stems': [row[0] for row in existing_stems]
    })
    
    return response.json()

四、新增字段(可选消费)

4.1 出题结果新增字段

POST /exam/generatePOST /exam/generate-smartdata 对象新增三个字段:

{
  "data": {
    "questions": [...],
    "total": 8,
    "requested_types": {"single_choice": 5, "true_false": 3, "fill_blank": 2},
    "actual_types": {"single_choice": 5, "true_false": 3, "fill_blank": 0},
    "warnings": ["fill_blank: 请求 2 道,实际生成 0 道"],
    ...
  }
}
字段 类型 说明
requested_types object 后端请求的题型和数量(原样回传)
actual_types object 实际生成的题型和数量
warnings string[] 短缺提示数组,全部生成成功时为空数组

后端建议:检查 warnings 是否为空,不为空时可提示用户"部分题型生成不足",或自动补发请求。

4.2 主观题评分警告

当主观题的 content 中缺少 data.scoring_points 评分标准时,批卷结果会在 details.warnings 中给出提示:

{
  "question_id": "q5",
  "score": 7.0,
  "max_score": 10.0,
  "grading_status": "success",
  "details": {
    "scoring_breakdown": [...],
    "overall_feedback": "...",
    "warnings": ["缺少评分标准(scoring_points),评分结果仅供参考"]
  }
}

此时 grading_status 仍为 "success",但评分准确度可能受影响。后端可在前端展示时附上"评分仅供参考"的提示。


五、完整的出题→入库→批卷数据流(更新后)

┌──────────────────────────────────────────────────────────────────────────┐
│                          后端完整流程                                      │
│                                                                          │
│  1. 调用出题接口                                                          │
│     POST /exam/generate                                                  │
│     → 首次出题:不传 exclude_stems                                        │
│     → 追加出题:查询已有题干,传入 exclude_stems 避免重复                   │
│     → 返回 questions[],每题包含 question_type, content, source_trace    │
│     → 检查 warnings 是否有短缺提示                                        │
│                                                                          │
│  2. 审核入库(后端自行决定)                                               │
│     → 可删除/修改不满意的题目                                              │
│     → 存入数据库时保留 content 字段原样                                    │
│     → 生成 question_id (UUID),设置 max_score                            │
│                                                                          │
│  3. 学生作答                                                              │
│     → 收集 student_answer                                                │
│                                                                          │
│  4. 调用批卷接口                                                          │
│     POST /exam/grade                                                     │
│     → content 直接从数据库取出透传(与出题接口返回的结构一致)              │
│     → 检查 grading_status 判断每题是否评分成功                             │
│     → grading_status=failed 的题目可标记待重批                             │
│                                                                          │
│  5. 更新成绩                                                              │
│     → 根据 question_id 匹配结果,写入学生成绩表                            │
└──────────────────────────────────────────────────────────────────────────┘

关键要点:出题返回的 content 和批卷接收的 content 结构完全一致,后端只需原样存取即可,无需任何字段转换。


六、后端修改清单

序号 修改项 紧急程度 说明
1 批卷请求 question_content 改为 content 必须 否则批卷接口无法读取题目内容,所有评分失败
2 批卷结果解析改用 grading_status + details 必须 否则无法正确获取评分详情
3 处理 grading_status: "failed" 情况 建议 避免将 LLM 异常导致的 0 分直接记入成绩
4 追加出题时传入 exclude_stems 强烈建议 避免同一文件多次出题产生重复题目
5 出题请求总题数控制在 20 以内 建议 否则返回 400 错误
6 前端校验 question_types 和 difficulty 建议 减少无效请求
7 消费 warnings 字段做短缺提示 可选 提升用户体验
8 消费主观题 details.warnings 可选 提示评分可信度