Files
rag/docs/出题系统测试报告.md
lacerate551 a31ee4bba0 feat(exam_pkg): 优化出题系统稳定性与推理模型适配
- core/llm_utils: MiMo模型自动注入thinking=disabled参数,全局生效
- config: 新增LLM_DISABLE_THINKING配置项(默认true)
- generator: 推理模型自适应max_tokens(1.5x)、429限流重试+指数退避、v2管线补题机制
- generator: analyze_document_for_exam新增max_total参数控制AI出题上限
- generator: validate_questions_schema兼容type和question_type字段
- grader: 主观题max_tokens从1000提升至2000、fuzzy_match增加编辑距离容错
- manager: results变量初始化防NameError、透传max_total参数
- api: /exam/generate-smart支持max_total请求参数
2026-06-22 18:51:11 +08:00

8.4 KiB
Raw Permalink Blame History

出题系统测试报告

测试日期: 2026-06-22 测试环境: mimo-v2.5关闭推理模式、2.docx文明吸烟环境建设标准 测试方式: curl 接口测试 + 代码审查


一、测试总览

# 测试项 接口 结果 耗时
1 参数出题 POST /exam/generate 成功 ~87s (4题)
2 AI一键出题 POST /exam/generate-smart ⚠️ 成功但有上限失控 ~244s (50题)
3 偏门题型(纯主观) POST /exam/generate 成功 ~87s (5题)
4 偏门题型(多选+填空) POST /exam/generate 成功 ~60s (6题)
5 跨调用去重 POST /exam/generate + exclude_stems 去重有效 ~60s (5题)
6 批题(4题型混合) POST /exam/grade 全部成功 ~30s

二、测试详情

Test 1: 参数出题 (single_choice:2, fill_blank:1, true_false:1)

  • 结果: 4题全部生成成功题型匹配
  • 问题:
    • 题目多样性不足4题中3题考同一个知识点"标准的解释机构"
    • source_trace 数据缺失chunk_id=?, page=?

Test 2: AI一键出题 (generate-smart)

  • AI分析: 检测到54个知识点推荐50道题20单选+15判断+10多选+5填空
  • 结果: 50题全部生成12个不同章节0题干重复
  • 问题:
    • P0 上限失控: prompt 要求"不超过20"但AI返回50代码没有 enforce 上限校验
    • ⚠️ 50题意味着20-30次LLM调用极易触发429限流

Test 3: 纯主观题 (subjective:5)

  • 结果: 5题全部生成覆盖不同章节scoring_points 完整
  • 耗时: 87秒关闭推理模式后正常

Test 4: 多选+填空 (multiple_choice:3, fill_blank:3)

  • 结果: 6题全部生成
  • 问题:
    • 填空题答案格式不一致: content.answer[["消费水平较高"]] (list of list),但没有 data.reference_answer 字段,只有 data.blank_count
    • ⚠️ 与 grader 期望的格式可能不匹配

Test 5: 跨调用去重 (exclude_stems)

  • 结果: 3个排除题干全部未出现去重有效

Test 6: 批题 (grade)

  • 单选题答错: score=0, correct=false
  • 判断题答对: score=2, correct=true
  • 填空题部分对: score=4.0 (满分4), blank_scores=[4.0]
  • 主观题(低质量回答): score=2.0/10, 4个scoring_point逐项评分
  • 总评: 得分率44.4%,评分合理

三、发现的问题(按严重度排序)

P0 - 严重问题

1. 🚨 AI一键出题上限失控 (generator.py:1053-1068)

位置: analyze_document_for_exam() 函数 现象: prompt 写了"所有数量之和不要超过 min(total_knowledge_points * 2, 20)",但 LLM 返回50题代码直接采纳 根因: 代码只做了"题型合法性"校验,没有对总数做上限 enforce

# 当前代码 (generator.py:1053-1059) - 只校验单题型合法性,无总数限制
valid_types = ['single_choice', 'multiple_choice', 'true_false', 'fill_blank', 'subjective']
question_types = {}
for q_type in valid_types:
    count = result.get('question_types', {}).get(q_type, 0)
    if isinstance(count, int) and count >= 0:
        question_types[q_type] = count  # 直接采纳,无上限

建议: 增加总数上限校验,超过 max_questions建议20时按比例缩减

total = sum(question_types.values())
max_questions = 20
if total > max_questions:
    ratio = max_questions / total
    question_types = {k: max(0, round(v * ratio)) for k, v in question_types.items()}

2. 🚨 LLM 调用无限流机制429 风暴 (generator.py:290-303, 540-543)

位置: generate_questions_structured() 主循环 + _generate_with_retry() 重试 现象: 并发出题时大量429错误重试退避太短(1s/2s/4s)3次全败后放弃该知识点 根因:

  • 主循环 for 逐知识点调用 LLM无请求间隔补题函数有 sleep(1),主循环没有)
  • 429 重试退避 2^attempt 秒 (1/2/4s) 对 API 限流不够
  • 无全局限流器(令牌桶/漏桶) 建议:
  1. 主循环每次 LLM 调用后加 time.sleep(1.5) 最小间隔
  2. 429 退避改为指数+抖动: min(30, 2 ** attempt + random.uniform(0, 2))
  3. 长期: 引入 tenacity 或自实现令牌桶限流器

3. 🚨 validate_questions_schema 只认 type 不认 question_type (generator.py:154)

位置: validate_questions_schema() 函数 现象: LLM 返回的题目可能用 question_type 字段,但校验只检查 q.get('type'),导致有效题目被丢弃 对比: _validate_question_types() (第448行) 做了兼容: q_type = q.get('question_type') or q.get('type')

# 当前代码 (generator.py:154) - 不兼容
if q.get('type') not in VALID_TYPES:
    continue  # question_type 字段的题目被丢弃!

# 应改为
q_type = q.get('question_type') or q.get('type')
if q_type not in VALID_TYPES:
    continue

P1 - 中等问题

4. ⚠️ 填空题答案格式不一致

现象: 出题返回 content.answer = [["答案1"], ["答案2"]] (list of list),但无 data.reference_answer 影响: 前端/本地数据库可能期望 reference_answer 字段 建议: 统一在 data 中增加 reference_answer 字段,与 content.answer 保持一致

5. ⚠️ grader max_tokens 对推理模型不足 (grader.py:438)

位置: _grade_subjective() 方法 现象: max_tokens=_get_effective_max_tokens(1000, self.model)

  • 推理模型关闭推理时: 1000 tokens 勉强够
  • 推理模型开启推理时: 思考链消耗 800+ tokenscontent 为空 当前状态: 关闭推理模式后本次测试通过,但开启推理模式会再次失败 建议: 基础 max_tokens 从 1000 提升至 2000

6. ⚠️ local_db._detect_question_type 使用旧格式 (local_db.py:378-383)

位置: _detect_question_type() 方法 现象: 检测 optionsreference_answer 字段来判断题型,但新格式用 content.data.optionscontent.answer

# 当前代码 - 检查顶层 options
if 'options' in question and question['options']:
    return 'choice'
elif 'reference_answer' in question:
    return 'short_answer'

# 应改为检查 content 内部结构
content = question.get('content', {})
data = content.get('data', {})
if data.get('options'):
    return 'choice'
elif question.get('question_type') in ('fill_blank', 'subjective', ...):
    return question['question_type']

7. ⚠️ 题目多样性不足

现象: Test 1 中 4 题有 3 题考同一知识点("标准的解释机构" 根因: _assign_questions_to_kps() 可能将多个题型分配给同一知识点 建议: 增加"同一知识点最多出 N 道题"的限制(建议 N=2

P2 - 低优先级

8. 💡 source_trace 数据偶尔缺失

现象: Test 1 中部分题目的 chunk_id 和 page 显示为 ? 可能原因: find_referenced_chunks 匹配失败时的 fallback 显示

9. 💡 多选题答案格式

现象: 多选题 answer 为 ['A', 'B']['A', 'B', 'D'] (list),前端需处理 建议: 在 API 文档中明确多选题 answer 格式为 list

10. 💡 补题机制未与已有题目交叉去重

现象: 补题时只检查本次生成的题干,不检查已有题目 建议: 补题函数接受 exclude_stems 参数


四、测试通过项

  1. 基础出题功能: 5种题型均可正常生成
  2. AI智能分析: 文档分析→题型推荐→出题流程完整
  3. 补题机制: 知识点出题失败后自动补题
  4. 跨调用去重: exclude_stems 功能正常,排除的题干不再出现
  5. 批题功能: 客观题精确匹配填空题部分给分主观题LLM逐项评分
  6. source_trace: 大部分题目有完整的溯源信息chunk_id, section, snippet
  7. JSON解析: mimo-v2.5 返回的 JSON 格式(含 markdown 包裹)可正常解析
  8. 题目格式: 单选题 options 为 list of dict [{key, content}],格式规范

五、建议修复优先级

优先级 问题 修复难度 影响范围
P0 AI出题上限失控 简单 smart 出题
P0 LLM 429 限流 中等 全部出题
P0 validate_questions_schema type 字段 简单 全部出题
P1 填空题答案格式统一 简单 填空题+批题
P1 grader max_tokens 简单 主观题批题
P1 local_db 旧格式 中等 本地存储
P1 题目多样性控制 中等 出题质量