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

636 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 出题批卷系统设计
> **文档类型**: 系统设计文档
> **创建日期**: 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`
**请求参数**
```json
{
"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 | 每道填空题分值 |
**返回结果**
```json
{
"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 选择题
```json
{
"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 填空题
```json
{
"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 简答题
```json
{
"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`
**当前格式(完整字段)**
```json
{
"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": "第一道是技术防护,包括防火墙和加密;第二道是制度管理;第三道是员工培训。"
}
]
}
```
**优化后格式(最小字段)**
```json
{
"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 批卷输出格式
```json
{
"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)
```sql
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)
```sql
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)
```sql
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)
```sql
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)
```sql
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 联动逻辑
```sql
-- 查找受影响的题目
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`
**请求参数**
```json
{
"file_path": "public/产品手册.pdf",
"new_hash": "新的文件哈希"
}
```
**返回结果**
```json
{
"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 错误响应格式
```json
{
"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 | 初始版本:按文件出题功能设计 |