# 出题系统测试报告 **测试日期**: 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 ```python # 当前代码 (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)时按比例缩减 ```python 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')` ```python # 当前代码 (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+ tokens,content 为空 **当前状态**: 关闭推理模式后本次测试通过,但**开启推理模式会再次失败** **建议**: 基础 max_tokens 从 1000 提升至 2000 #### 6. ⚠️ local_db._detect_question_type 使用旧格式 (local_db.py:378-383) **位置**: `_detect_question_type()` 方法 **现象**: 检测 `options` 和 `reference_answer` 字段来判断题型,但新格式用 `content.data.options` 和 `content.answer` ```python # 当前代码 - 检查顶层 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 | 题目多样性控制 | 中等 | 出题质量 |