fix(exam): 修复出题批卷 API 契约一致性问题
P0: - 修复 V2 降级路径响应格式,fallback 输出经过 enrich + dedup + balance - 补充主观题 format example,要求 LLM 生成 scoring_points 评分标准 P1: - 补充多选题、判断题 format example,明确 answer 为列表格式 - 统一批卷响应格式,所有题型增加 grading_status 字段 - 出题数量不足时返回 requested_types/actual_types/warnings - 修复 _call_llm 未配置时抛异常而非静默返回 0 分 P2: - API 层增加 question_types 和 difficulty 入参校验 - 修复 generate_questions_from_content 方法名和参数类型 bug
This commit is contained in:
@@ -32,6 +32,35 @@ from auth.gateway import (
|
||||
from core.status_codes import EXAM_SUCCESS, GRADE_SUCCESS, EXAM_ERROR, GRADE_ERROR, BAD_REQUEST, NO_CONTENT, LLM_ERROR
|
||||
from api.response_utils import success_response, error_response
|
||||
|
||||
# 合法题型
|
||||
VALID_QUESTION_TYPES = {'single_choice', 'multiple_choice', 'true_false', 'fill_blank', 'subjective'}
|
||||
|
||||
|
||||
def validate_question_types(question_types: dict) -> str:
|
||||
"""
|
||||
校验 question_types 参数
|
||||
|
||||
Returns:
|
||||
None 表示校验通过,字符串表示错误信息
|
||||
"""
|
||||
if not isinstance(question_types, dict):
|
||||
return "question_types 必须为对象"
|
||||
|
||||
for q_type, count in question_types.items():
|
||||
if q_type not in VALID_QUESTION_TYPES:
|
||||
return f"不支持的题型: {q_type},合法值: {', '.join(sorted(VALID_QUESTION_TYPES))}"
|
||||
if not isinstance(count, int) or count < 0:
|
||||
return f"题型 {q_type} 的数量必须为非负整数,当前值: {count}"
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def validate_difficulty(difficulty) -> str:
|
||||
"""校验 difficulty 参数"""
|
||||
if not isinstance(difficulty, int) or difficulty < 1 or difficulty > 5:
|
||||
return "difficulty 必须为 1-5 的整数"
|
||||
return None
|
||||
|
||||
# 创建蓝图
|
||||
exam_bp = Blueprint('exam', __name__)
|
||||
|
||||
@@ -69,12 +98,16 @@ def api_generate_questions():
|
||||
"request_id": "uuid",
|
||||
"questions": [
|
||||
{
|
||||
"metadata": {"question_id": "...", "question_type": "...", ...},
|
||||
"source_trace": {"document_name": "...", "sources": [...], ...},
|
||||
"content": {"stem": "...", "data": {...}, "answer": "...", ...}
|
||||
"question_type": "single_choice",
|
||||
"difficulty": 3,
|
||||
"content": {"stem": "...", "data": {...}, "answer": "...", "explanation": "..."},
|
||||
"source_trace": {"document_name": "...", "chunk_ids": [...], "sources": [...]}
|
||||
}
|
||||
],
|
||||
"total": 10,
|
||||
"requested_types": {"single_choice": 3, "fill_blank": 2},
|
||||
"actual_types": {"single_choice": 3, "fill_blank": 2},
|
||||
"warnings": ["fill_blank: 请求 3 道,实际生成 2 道"],
|
||||
"source_chunks_used": 15
|
||||
}
|
||||
"""
|
||||
@@ -91,6 +124,17 @@ def api_generate_questions():
|
||||
if not question_types:
|
||||
return error_response("MISSING_PARAMS", BAD_REQUEST, "缺少 question_types 参数", http_status=400)
|
||||
|
||||
# 校验题型和数量
|
||||
type_error = validate_question_types(question_types)
|
||||
if type_error:
|
||||
return error_response("INVALID_PARAMS", BAD_REQUEST, type_error, http_status=400)
|
||||
|
||||
# 校验难度
|
||||
difficulty = data.get('difficulty', 3)
|
||||
diff_error = validate_difficulty(difficulty)
|
||||
if diff_error:
|
||||
return error_response("INVALID_PARAMS", BAD_REQUEST, diff_error, http_status=400)
|
||||
|
||||
# 获取当前用户
|
||||
user = get_current_user()
|
||||
if not user:
|
||||
@@ -149,6 +193,12 @@ def api_generate_smart():
|
||||
if not file_path or not collection:
|
||||
return error_response("MISSING_PARAMS", BAD_REQUEST, "缺少 file_path 或 collection 参数", http_status=400)
|
||||
|
||||
# 校验难度
|
||||
difficulty = data.get('difficulty', 3)
|
||||
diff_error = validate_difficulty(difficulty)
|
||||
if diff_error:
|
||||
return error_response("INVALID_PARAMS", BAD_REQUEST, diff_error, http_status=400)
|
||||
|
||||
# 获取当前用户
|
||||
user = get_current_user()
|
||||
if not user:
|
||||
@@ -237,11 +287,23 @@ def api_grade_answers():
|
||||
{
|
||||
"success": true,
|
||||
"request_id": "uuid",
|
||||
"results": [...],
|
||||
"results": [
|
||||
{
|
||||
"question_id": "uuid",
|
||||
"score": 2,
|
||||
"max_score": 2,
|
||||
"grading_status": "success",
|
||||
"details": {"correct": true, "student_answer": "A", "correct_answer": "B", "feedback": "..."}
|
||||
}
|
||||
],
|
||||
"total_score": 10.5,
|
||||
"total_max_score": 16.0,
|
||||
"score_rate": 65.6
|
||||
}
|
||||
|
||||
grading_status 取值:
|
||||
- "success": 评分成功
|
||||
- "failed": 评分失败(LLM 超时/解析失败等),score 为 0
|
||||
"""
|
||||
try:
|
||||
data = request.json
|
||||
|
||||
Reference in New Issue
Block a user