Files
rag/docs/出题批卷系统设计.md
lacerate551 cb75b9b274 fix(boundary): 修复多库边界问题、版本管理及删除清理
多库检索与存储修复:
- RRF 融合去重改用 (collection, chunk_id) 复合键,修复同名文件结果被吞
- DocStore 存储路径加 collection 前缀,修复跨库同名切片数据覆盖
- search_multiple 去重改用复合键
- chunk_id 解析改用 rsplit 兼容下划线文件名

上传与版本管理修复:
- 同名文件上传改为覆盖模式,自动清理旧切片
- 修复首次上传不创建版本记录
- 修复覆盖上传版本号回退到 v1
- sync ADDED 分支改用动态版本号生成
- _generate_version_id 改为基于全部版本递增
- 废止/恢复操作同步 SQLite 版本记录
- mark_document_as_superseded 改为仅更新 SQLite

删除清理修复:
- 删除文档时同步清理 SQLite 版本记录和变更日志
- 删除向量库时同步清理该库所有版本记录
- cleanup 改为清理 SQLite 记录而非 ChromaDB

测试:
- test_version_management.py: 27 条版本管理单元测试
- test_edge_cases.py: 28 条边界用例测试
- test_upload_dedup.py: 5 条上传去重测试
- e2e_risk_test.py: 27 条端到端风险测试

文档:
- 新增风险边界问题修复注意事项.md(面向后端的对接文档)
- 新增向量库边界风险分析.md
- 更新多篇现有文档
2026-06-04 23:58:44 +08:00

20 KiB
Raw Blame History

出题批卷系统设计

文档类型: 系统设计文档 创建日期: 2026-04-10 最后更新: 2026-06-04 状态: 已实施


一、系统概述

1.1 背景

出题批卷系统是 RAG 知识库系统的扩展模块,支持:

  • 按文件出题:根据指定文档自动生成题目
  • 智能批卷:支持选择题、填空题、简答题的自动批改
  • 溯源追踪:每道题可追溯到来源文件和知识片段

1.2 模块结构

exam_pkg/                    # 考试系统
├── generator.py             # 出题逻辑(按文件/按主题生成题目)
├── grader.py                # 批卷逻辑(选择题/填空题/简答题批改)
├── manager.py               # 试卷管理与协调逻辑
├── api.py                   # Flask Blueprint (exam_bp)
└── local_db.py              # 本地题库 (SQLite)

认证模块: auth/gateway.py - 网关认证


二、出题系统设计

2.1 按文件出题接口

接口路径POST /exam/generate-by-file

请求参数

{
    "file_path": "public/产品手册.pdf",
    "collection": "public_kb",
    "choice_count": 5,
    "blank_count": 2,
    "short_answer_count": 2,
    "difficulty": 3,
    "choice_score": 2,
    "blank_score": 3
}
参数 类型 必填 默认值 说明
file_path string - 文件路径
collection string - 向量库名称
choice_count int 3 选择题数量
blank_count int 2 填空题数量
short_answer_count int 2 简答题数量
difficulty int 3 难度等级 (1-5)
choice_score int 2 每道选择题分值
blank_score int 3 每道填空题分值

返回结果

{
    "exam_id": "uuid-xxxx-xxxx",
    "source_file": {
        "path": "public/产品手册.pdf",
        "collection": "public_kb"
    },
    "choice_questions": [
        {
            "id": "q_choice_001",
            "content": "根据保密制度,公司最高机密的处理原则是什么?",
            "options": ["A. 可向客户透露", "B. 严禁外传", "C. 部门内共享", "D. 仅领导知晓"],
            "answer": "B",
            "analysis": "根据保密制度第1条规定...",
            "knowledge_points": ["保密制度", "信息安全"],
            "difficulty": 2,
            "score": 2,
            "source_file": "public/产品手册.pdf",
            "source_snippet": "该题依据的知识片段..."
        }
    ],
    "blank_questions": [...],
    "short_answer_questions": [...],
    "total_count": 9,
    "total_score": 22,
    "generated_at": "2026-04-10T14:00:00"
}

2.2 试卷状态流程

生成试卷 → draft (草稿)
     ↓
提交审核 → pending_review (待审核)
     ↓
管理员审核 → approved (通过) / rejected (驳回)
     ↓
学生答题 → 批阅 → 生成报告

状态说明

状态 说明 可见范围
draft 草稿,刚生成尚未提交审核 创建者可见
pending_review 待审核,已提交等待管理员审核 管理员可见
approved 已通过,可用于学生答题 所有用户可见
rejected 已驳回,不可使用 创建者可见

三、题目格式规范

3.1 选择题

{
    "id": "q_choice_001",
    "content": "根据保密制度,公司最高机密的处理原则是什么?",
    "options": [
        "A. 可向客户透露",
        "B. 严禁外传",
        "C. 部门内共享",
        "D. 仅领导知晓"
    ],
    "answer": "B",
    "analysis": "根据保密制度第1条规定公司最高机密严禁外传仅限特定人员知晓。",
    "knowledge_points": ["保密制度", "信息安全"],
    "difficulty": 2,
    "score": 2,
    "source_file": "public/产品手册.pdf",
    "source_snippet": "原文相关片段..."
}

字段说明

字段 类型 必填 说明
id string 题目唯一标识
content string 题干内容
options array 选项列表,格式为 ["A. 选项内容", ...]
answer string 正确答案,单个字母(如 "A", "B"
analysis string 答案解析
knowledge_points array 知识点标签
difficulty int 难度等级 1-5默认 3
score int 题目分值
source_file string 来源文件路径
source_snippet string 来源文本片段

3.2 填空题

{
    "id": "q_blank_001",
    "content": "公司财务报表应在每季度结束后______天内提交。",
    "answer": "15",
    "analysis": "根据财务管理制度第5条规定季度报表需在季后15天内提交。",
    "knowledge_points": ["财务管理"],
    "difficulty": 3,
    "score": 3
}

字段说明

字段 类型 必填 说明
id string 题目唯一标识
content string 题干内容,空缺处用 ______ 表示
answer string 正确答案
analysis string 答案解析
knowledge_points array 知识点标签
difficulty int 难度等级 1-5
score int 题目分值

3.3 简答题

{
    "id": "q_short_001",
    "content": "简述公司数据安全的三道防线。",
    "reference_answer": {
        "points": [
            {"point": "技术防线(防火墙、加密、访问控制等)", "score": 3},
            {"point": "制度防线(安全规定、审批流程、应急预案)", "score": 3},
            {"point": "人员防线(安全培训、意识教育、考核机制)", "score": 4}
        ],
        "total_score": 10
    },
    "analysis": "评分要点说明...",
    "knowledge_points": ["数据安全"],
    "difficulty": 4,
    "score": 10
}

字段说明

字段 类型 必填 说明
id string 题目唯一标识
content string 题干内容
reference_answer object 参考答案,包含评分要点
reference_answer.points array 得分点列表
reference_answer.points[].point string 得分点描述
reference_answer.points[].score int 该得分点分值
analysis string 整体解析
knowledge_points array 知识点标签
difficulty int 难度等级 1-5
score int 题目总分值

四、批卷系统设计

4.1 批卷输入格式

接口路径POST /exam/grade-from-mysql

当前格式(完整字段)

{
    "exam_id": "uuid-xxxx-xxxx",
    "student_id": "STU_2023001",
    "student_name": "张三",
    "answers": [
        {
            "question_id": "q_choice_001",
            "question_type": "choice",
            "question_content": "根据保密制度,公司最高机密的处理原则是什么?",
            "options": ["A. 可向客户透露", "B. 严禁外传", "C. 部门内共享", "D. 仅领导知晓"],
            "correct_answer": "B",
            "max_score": 2,
            "student_answer": "B"
        },
        {
            "question_id": "q_blank_001",
            "question_type": "blank",
            "question_content": "公司财务报表应在每季度结束后______天内提交。",
            "correct_answer": "15",
            "max_score": 3,
            "student_answer": "10"
        },
        {
            "question_id": "q_short_001",
            "question_type": "short_answer",
            "question_content": "简述公司数据安全的三道防线。",
            "correct_answer": "{\"points\":[{\"point\":\"技术防线\",\"score\":3},{\"point\":\"制度防线\",\"score\":3},{\"point\":\"人员防线\",\"score\":4}]}",
            "max_score": 10,
            "student_answer": "第一道是技术防护,包括防火墙和加密;第二道是制度管理;第三道是员工培训。"
        }
    ]
}

优化后格式(最小字段)

{
    "exam_id": "uuid",
    "student_id": "STU_001",
    "student_name": "张三",
    "answers": [
        {
            "question_id": "q_choice_001",
            "question_type": "choice",
            "student_answer": "B"
        },
        {
            "question_id": "q_blank_001",
            "question_type": "blank",
            "student_answer": "15"
        },
        {
            "question_id": "q_short_001",
            "question_type": "short_answer",
            "student_answer": "第一道是技术防护..."
        }
    ]
}

4.2 批卷输出格式

{
    "report_id": "report-uuid-xxxx",
    "exam_id": "uuid-xxxx-xxxx",
    "student_id": "STU_2023001",
    "student_name": "张三",
    "total_score": 12,
    "max_score": 15,
    "score_rate": 80.0,
    "graded_at": "2026-04-12T14:30:00",
    "results": [
        {
            "question_id": "q_choice_001",
            "question_type": "choice",
            "correct": true,
            "score": 2,
            "max_score": 2,
            "student_answer": "B",
            "correct_answer": "B",
            "feedback": "回答正确!"
        },
        {
            "question_id": "q_blank_001",
            "question_type": "blank",
            "correct": false,
            "score": 0,
            "max_score": 3,
            "student_answer": "10",
            "correct_answer": "15",
            "feedback": "正确答案是15天请复习财务管理制度。"
        },
        {
            "question_id": "q_short_001",
            "question_type": "short_answer",
            "score": 8,
            "max_score": 10,
            "student_answer": "第一道是技术防护...",
            "score_details": [
                {"point": "技术防线", "earned": 3, "max": 3},
                {"point": "制度防线", "earned": 2, "max": 3},
                {"point": "人员防线", "earned": 3, "max": 4}
            ],
            "feedback": "整体回答较好,制度防线描述不够具体。",
            "highlights": ["技术防线表述准确"],
            "shortcomings": ["制度防线未具体说明"],
            "suggestions": ["建议补充具体的制度名称"]
        }
    ],
    "summary": {
        "strengths": ["选择题掌握较好", "简答题要点覆盖全面"],
        "weaknesses": ["填空题记忆不准确"],
        "recommendations": ["重点复习财务管理制度第3章"]
    }
}

4.3 批改流程

┌─────────────────────────────────────────────────────────────────────┐
│                        批量批改流程                                  │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  1. 前端传入 answers (最小字段)                                      │
│     └─ 只有 question_id + question_type + student_answer           │
│                                                                     │
│  2. 后端查询题目详情                                                 │
│     └─ 从数据库/缓存获取 correct_answer, max_score, content        │
│                                                                     │
│  3. 按题型分组                                                       │
│     ├─ choice 组 → 批量调用 Dify 代码执行节点                        │
│     └─ blank/short_answer 组 → 批量调用 Dify LLM 节点               │
│                                                                     │
│  4. 合并结果返回                                                     │
│     └─ 统一格式返回所有批改结果                                      │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

五、数据库设计

5.1 题目表 (questions)

CREATE TABLE questions (
    id VARCHAR(64) PRIMARY KEY,              -- 题目IDUUID
    question_type ENUM('choice', 'blank', 'short_answer') NOT NULL,
    content TEXT NOT NULL,                    -- 题干内容
    options JSON,                             -- 选择题选项JSON数组
    correct_answer TEXT NOT NULL,             -- 正确答案
    analysis TEXT,                            -- 解析
    knowledge_points JSON,                    -- 知识点JSON数组
    difficulty TINYINT DEFAULT 3,             -- 难度(1-5)
    score INT NOT NULL,                       -- 分值

    -- 溯源字段(核心)
    source_file VARCHAR(255) NOT NULL,        -- 来源文件路径
    source_collection VARCHAR(64) NOT NULL,   -- 来源向量库
    source_snippet TEXT,                      -- 来源知识片段
    source_hash VARCHAR(64),                  -- 文件哈希(用于检测文件变更)

    -- 审核状态
    status ENUM('pending', 'approved', 'rejected') DEFAULT 'pending',
    reviewed_by VARCHAR(64),
    reviewed_at DATETIME,

    -- 元数据
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    created_by VARCHAR(64),
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    INDEX idx_source_file (source_file),
    INDEX idx_source_collection (source_collection),
    INDEX idx_question_type (question_type),
    INDEX idx_status (status)
);

5.2 试卷表 (exams)

CREATE TABLE exams (
    id VARCHAR(64) PRIMARY KEY,              -- 试卷ID
    name VARCHAR(255) NOT NULL,              -- 试卷名称
    description TEXT,                         -- 描述
    total_score INT NOT NULL,                 -- 总分
    total_count INT NOT NULL,                 -- 题目总数
    duration INT DEFAULT 60,                  -- 考试时长(分钟)

    -- 状态
    status ENUM('draft', 'pending', 'published', 'archived') DEFAULT 'draft',
    published_at DATETIME,

    -- 元数据
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    created_by VARCHAR(64),
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    INDEX idx_status (status)
);

5.3 试卷题目关联表 (exam_questions)

CREATE TABLE exam_questions (
    exam_id VARCHAR(64) NOT NULL,
    question_id VARCHAR(64) NOT NULL,
    question_order INT NOT NULL,              -- 题目顺序

    PRIMARY KEY (exam_id, question_id),
    FOREIGN KEY (exam_id) REFERENCES exams(id) ON DELETE CASCADE,
    FOREIGN KEY (question_id) REFERENCES questions(id) ON DELETE CASCADE,

    INDEX idx_exam_id (exam_id),
    INDEX idx_question_id (question_id)
);

5.4 学生答卷表 (student_answers)

CREATE TABLE student_answers (
    id VARCHAR(64) PRIMARY KEY,
    exam_id VARCHAR(64) NOT NULL,
    student_id VARCHAR(64) NOT NULL,
    student_name VARCHAR(100),

    question_id VARCHAR(64) NOT NULL,
    question_type ENUM('choice', 'blank', 'short_answer') NOT NULL,
    student_answer TEXT NOT NULL,             -- 学生答案

    -- 批阅结果
    score INT DEFAULT 0,
    max_score INT NOT NULL,
    feedback TEXT,
    score_details JSON,                       -- 评分详情JSON

    -- 元数据
    submitted_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    graded_at DATETIME,

    FOREIGN KEY (exam_id) REFERENCES exams(id) ON DELETE CASCADE,
    FOREIGN KEY (question_id) REFERENCES questions(id) ON DELETE CASCADE,

    INDEX idx_exam_student (exam_id, student_id),
    INDEX idx_student_id (student_id)
);

5.5 批阅报告表 (grade_reports)

CREATE TABLE grade_reports (
    id VARCHAR(64) PRIMARY KEY,
    exam_id VARCHAR(64) NOT NULL,
    student_id VARCHAR(64) NOT NULL,
    student_name VARCHAR(100),

    total_score INT NOT NULL,
    max_score INT NOT NULL,
    score_rate DECIMAL(5,2),

    -- 整卷分析(可选)
    analysis JSON,                            -- AI生成的整卷分析

    graded_at DATETIME DEFAULT CURRENT_TIMESTAMP,

    FOREIGN KEY (exam_id) REFERENCES exams(id) ON DELETE CASCADE,

    INDEX idx_exam_id (exam_id),
    INDEX idx_student_id (student_id)
);

六、文件修改联动

6.1 触发条件

当文件被修改或删除时,通过 source_file 字段查找受影响的题目。

6.2 联动逻辑

-- 查找受影响的题目
SELECT id, source_file, source_hash
FROM questions
WHERE source_file = 'public/产品手册.pdf';

-- 如果文件哈希变更,标记题目需要重新审核
UPDATE questions
SET status = 'pending',
    source_hash = 'new_hash_value'
WHERE source_file = 'public/产品手册.pdf';

6.3 联动接口

接口路径POST /exam/check-file-changes

请求参数

{
    "file_path": "public/产品手册.pdf",
    "new_hash": "新的文件哈希"
}

返回结果

{
    "success": true,
    "file_path": "public/产品手册.pdf",
    "affected_questions": ["q_uuid_001", "q_uuid_002", ...],
    "count": 15,
    "recommendation": "建议重新生成该文件的题目"
}

七、API 接口汇总

7.1 出题接口

接口 方法 说明
/exam/generate POST 按主题生成试卷
/exam/generate-by-file POST 按文件生成题目
/exam/list GET 获取试卷列表
/exam/<exam_id> GET 获取试卷详情
/exam/<exam_id> PUT 更新试卷
/exam/<exam_id> DELETE 删除试卷
/exam/<exam_id>/submit POST 提交审核
/exam/<exam_id>/review POST 审核试卷(仅管理员)
/exam/by-file GET 查询文件关联的题目

7.2 批卷接口

接口 方法 说明
/exam/grade-from-mysql POST 基于传入题目批卷
/exam/<exam_id>/grade POST 批阅试卷
/exam/report/<report_id> GET 获取批阅报告
/exam/report/list GET 批阅报告列表

7.3 题库接口

接口 方法 说明
/exam/questions/search GET 搜索题目

7.4 联动接口

接口 方法 说明
/exam/check-file-changes POST 检查文件变更影响的题目

八、错误处理

8.1 错误响应格式

{
    "error": "错误类型",
    "message": "详细错误信息",
    "details": {}
}

8.2 常见错误码

HTTP状态码 错误类型 说明
400 bad_request 请求参数格式错误
401 unauthorized 未认证
403 forbidden 权限不足
404 not_found 资源不存在
500 internal_error 服务器内部错误

九、注意事项

  1. 题目ID生成使用UUID确保全局唯一
  2. 文件哈希用于检测文件变更建议使用MD5或SHA256
  3. 批量批卷性能:简答题批卷耗时,建议使用异步处理或并发
  4. 错误处理:批卷失败时返回默认结果,不影响整体流程
  5. 认证方式:出题系统使用 JWT Bearer Token 认证

十、变更记录

日期 版本 变更内容
2026-06-04 2.1 更新模块结构:移除已删除的 analysis.py、question_hook.py新增 generator.py、grader.py
2026-04-13 2.0 合并出题批卷功能改造计划、批卷工作流优化计划、批卷接口规范
2026-04-12 1.2 新增最小字段输入格式,优化批量批改流程
2026-04-10 1.0 初始版本:按文件出题功能设计