多库检索与存储修复: - 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 - 更新多篇现有文档
20 KiB
20 KiB
出题批卷系统设计
文档类型: 系统设计文档 创建日期: 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, -- 题目ID(UUID)
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 | 服务器内部错误 |
九、注意事项
- 题目ID生成:使用UUID,确保全局唯一
- 文件哈希:用于检测文件变更,建议使用MD5或SHA256
- 批量批卷性能:简答题批卷耗时,建议使用异步处理或并发
- 错误处理:批卷失败时返回默认结果,不影响整体流程
- 认证方式:出题系统使用 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 | 初始版本:按文件出题功能设计 |