Files
WiseGrade/docs/GUIDE.md
T
aerospaceboy ebe33392ca fix: improve import, evidence indexing and material-scoped grading
Preserve document revisions and evidence, harden indexing and export behavior, add explicit material selection and direct-folder import, validate model JSON schemas with safe error messages, and retain student selection while loading scoring data.
2026-10-14 13:52:56 +08:00

1125 lines
46 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.
# WiseGrade(智评)项目指南
当前源码的索引版本为 5、证据政策为 2,最新迁移为 `0009`。解析与评分改进见 [第一轮优化说明](OPTIMIZATION_2026-10-12.md),历史文档修订与失败恢复见 [第二轮优化说明](OPTIMIZATION_2026-10-13.md),实际 PDF 流程发现的目录权限问题及最新升级边界见 [PDF 实流程说明](PDF_TEST_2026-10-13.md)。旧部署记录不表示本轮改动已在正式环境应用。
本指南统一维护部署、教师使用、技术设计和开发说明;项目简介和快速启动见 [README](../README.md)。整理日期:2026-09-30;会话实现说明沿用 2026-09-27 的记录。
部署与会话章节描述已有实现;“业务规则与技术设计”保留原始设计基线,其中标为初稿、建议或验收目标的内容不代表已全部实现。当前实现与验证边界见 [交接记录](../HANDOFF.md),具体接口以代码和运行中的 `/docs` 为准。开发任务提示词集中在 [prompts](prompts/README.md),不作为功能完成证明。
## 目录
- [部署与配置](#deployment)
- [教师使用](#usage)
- [业务规则与技术设计](#design)
- [会话与业务 Agent 实现](#agent)
- [开发与测试](#development)
- [故障定位与恢复](#troubleshooting)
<a id="deployment"></a>
## 部署与配置
WiseGrade 是单机单管理员教师端。Web 服务仅绑定 `127.0.0.1`,默认端口为 `18080`,可通过 `.env` 中的 `WISEGRADE_WEB_PORT` 修改。学生原件通过只读目录挂载进入容器,不经过浏览器上传。以下命令除明确切换目录外,均从仓库根目录执行。
### 准备
- Docker Engine + Compose v2(Linux),或 Windows Docker Desktop(WSL2 后端,运行 Linux 容器)。
- 一个存放学生材料的宿主机目录;其下每个批次目录包含 `学号_姓名_选题题目` 子目录。
- `deepseek-flash` 可访问的 OpenAI 兼容 API 地址和密钥;该端点必须支持文本、图片输入及原生 `tool_calls`。若只做评分而不使用对话,仍可沿用已有 JSON 调用。
- 离线嵌入模型包。默认使用 BAAI/bge-m3。内存不足时可改用 BAAI/bge-small-zh-v1.5。
仓库的 `docs/` 同时存放项目文档与课程示例资料;其中 PDF、DOC、DOCX 是课程资料,不是待导入的学生原件。旧版 `.doc` 只用于课程模板,经 LibreOffice 转换;学生输入只接受 PDF、DOCX、PPTX。
LibreOffice 已安装在后端 Docker 镜像中。按本指南使用容器时,教师不需要在 Windows 或 Linux 宿主机单独安装 LibreOffice;仅在脱离容器运行后端时才需自行安装。
### Linux 启动
```bash
cp .env.example .env
mkdir -p data/inbox data/processed data/exports models/bge-m3
```
编辑 `.env`:
```env
WISEGRADE_DATA_DIR=/absolute/path/to/WiseGradeData
DEEPSEEK_API_KEY=your-key
WISEGRADE_SECRET_KEY=用随机值替换且至少32位
EMBEDDING_MODEL_PATH=/models/bge-m3
```
也可以启动后在“模型设置”页面填写 API Key。密钥由后端加密保存,浏览器不会直接调用模型服务。`WISEGRADE_SECRET_KEY` 一旦用于加密设置,不要随意更换,否则已保存的密钥将无法解密。
```bash
docker compose up
docker compose ps
curl http://127.0.0.1:18080/api/health
```
打开 <http://127.0.0.1:18080>。`api` 启动时执行 Alembic 迁移并创建初始课程、目标、指标点和规则。评分任务由 `worker` 容器处理。
如 `18080` 端口已被占用,在 `.env` 中将 `WISEGRADE_WEB_PORT` 改为其他空闲端口,重新运行 `docker compose up -d`,并使用对应端口访问。
### Windows Docker Desktop 启动
1. 安装并启动 Docker Desktop,启用 WSL2 后端,保持 Linux 容器模式。
2. 在 PowerShell 中进入项目目录:`Copy-Item .env.example .env`。
3. 编辑 `.env` 中的 `WISEGRADE_DATA_DIR`,使用正斜杠绝对路径,例如 `D:/WiseGradeData`。不要在 `.env` 中添加引号。
4. 将离线模型目录放在 `models/bge-m3`,填写 API 配置和随机 `WISEGRADE_SECRET_KEY`。
5. 运行 `docker compose up`,访问 <http://127.0.0.1:18080>。
如果挂载失败,先确认 `WISEGRADE_DATA_DIR` 存在、Docker Desktop 已取得对应磁盘访问权,再运行 `docker compose config` 查看展开后的挂载路径。首次启动后若修改了 `.env` 中的 API Key 等配置,运行 `docker compose up -d --force-recreate api worker web`,使容器读取新值。当前仓库尚未在 Windows 实机验证上述路径和文件挂载。
### 离线模型包
应用镜像不在启动时下载 BGE-M3。将独立模型包解压到 `models/bge-m3/`,目录应包含模型权重、`config.json`、分词器文件及 SentenceTransformers 配置。容器中该目录只读映射为 `/models/bge-m3`。替代模型需同时修改:
```env
EMBEDDING_MODEL_NAME=BAAI/bge-small-zh-v1.5
EMBEDDING_MODEL_PATH=/models/bge-small-zh-v1.5
EMBEDDING_MODEL_VERSION=your-package-version
```
`EMBEDDING_MODEL_VERSION` 会写入每个评分批次快照。若模型目录缺失或内容不完整,导入仍可完成,知识库入库和评分会显示错误。可离线分发 `docker save` 生成的镜像包及单独的 `models-bge-m3.tar.zst`;目标机执行 `docker load -i docker-images.tar` 后解压模型包,运行相同的 Compose 配置。
也可以把模型复制进后端镜像,但当前 Dockerfile 和 Compose 采用只读目录挂载;若改成镜像内置模型,须同时调整构建上下文,并移除会遮盖镜像内 `/models` 的挂载。本机 BGE-M3 必需文件约 2.29 GB,后端镜像当前约 1.08 GB;直接内置后镜像未压缩体积预计约 3.37 GB。API 与 worker 可以共用镜像层,不必各存一份模型。独立模型包便于单独更新和分发。
扫描 PDF 的 OCR 使用 PaddleOCR。离线 OCR 包放在 `models/ocr/detection/` 和 `models/ocr/recognition/`,或修改 `OCR_MODEL_DIR` 指向包含这两个子目录的位置。没有 OCR 模型且 PDF 无可提取文字时,文件进入解析失败状态并显示原因;系统不会在解析时自动下载 OCR 模型。
下面这组 PP-OCRv5 mobile 中文识别模型已在 PaddleOCR 3.7.0 中验证。下载后将压缩包的顶层目录去掉,使 `detection/` 和 `recognition/` 内直接包含 `inference.yml`、`inference.json` 和 `inference.pdiparams`:
```bash
mkdir -p models/ocr/detection models/ocr/recognition
curl -fL -o models/ocr/PP-OCRv5_mobile_det_infer.tar https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/PP-OCRv5_mobile_det_infer.tar
curl -fL -o models/ocr/PP-OCRv5_mobile_rec_infer.tar https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/PP-OCRv5_mobile_rec_infer.tar
tar -xf models/ocr/PP-OCRv5_mobile_det_infer.tar --strip-components=1 -C models/ocr/detection
tar -xf models/ocr/PP-OCRv5_mobile_rec_infer.tar --strip-components=1 -C models/ocr/recognition
```
模型名称从各自的 `inference.yml` 读取,不能让 PaddleOCR 使用与离线权重不匹配的默认模型。模型目录经 Compose 只读挂载,放入权重后无需修改 `.env`。
<a id="usage"></a>
## 教师使用
### 课程、导入、评分与导出
1. 在“课程与规则”中查看初始课程版本和评分规则。已发布版本不能直接覆盖;复制草稿、编辑、保存并发布新版本。课程目标、指标点、选题方向、模板和三个 Prompt 都属于课程版本。
2. 在“知识库资料”中选课程版本并入库课程资料。入库使用本地嵌入模型和 PostgreSQL `pgvector`。
3. 在“目录扫描与导入”中浏览 `/data/inbox` 下的相对目录,选择学生总目录并扫描。修正学号、姓名、选题和材料类型;PPT 导出的 PDF 必须确认成“汇报PPT/PDF”。每类材料只选择一个文件。
确认导入后,材料自动进入后台解析、必要 OCR 和索引任务;评分前即可检索已完成索引的报告。失败任务可从索引任务列表重试。
4. 在“评分批次”中选择已发布课程和规则版本,创建并启动批次。每次评分建立独立批次;重试不会覆盖其他批次。
5. 在“评分与复核”中查看逐项模型原始分、原文证据、页码、页面图像和异常。教师可逐项填写分数、评语和修改说明;修改历史单独保留。
6. 在“导出任务”中按批次导出 Excel,按学生导出 PDF 反馈。缺失或无法评分的项目显示待处理,不自动补零。
成绩权重和评分依据见下文“业务规则与技术设计”。
### 会话、范围与操作确认
打开系统默认进入空白“会话”草稿,点击“新建”立即切换到另一空白草稿,不弹窗、不写数据库。可在输入区直接提问,并在首次发送前反复选择或清除课程版本、评分批次和学生项目。发送首条非空消息时,会话和消息在同一事务保存;此后范围固定且在界面显示为只读。旧版留下的空会话仍可编辑范围。选择批次或项目会自动带入所属课程版本,清除课程版本会同时清除下级选择。范围固定后,工具不能读取范围之外的对象。会话列表、消息、引用、待确认操作和运行状态保存在数据库,刷新后可查看历史会话;刷新或再次进入页面仍打开新草稿。
可先问“列出课程版本”“扫描 batch 目录”“查看批次 3 的失败评分项”“查询项目 12 的报告实验数据”。课程目标与规则来自数据库;指定批次时使用批次创建时的快照。课程资料和报告通过 BGE-M3/pgvector 检索,回答附资料名、页码或幻灯片编号、原文片段和来源链接。评分证据与 Agent 检索引用都使用 `KnowledgeDocument` 的文档 ID、页码,对应同一 `KnowledgePage`:`/api/knowledge/documents/{id}/pages/{page}` 返回该页完整提取文字,`/image` 返回存在的页面 PNG。评分引用另有“查看评分”链接。历史引用由迁移 `0005` 一次性改写为这些统一地址;会话读取不再猜测旧图像链接。实体 PDF、DOCX、PPTX 页面经解析后通常同时有文字和图像;数据库直接生成的课程目标、评分规则等虚拟资料没有实体页面图像,只提供文字。没有证据时应明确说明。正式回答上方的“工具调用”先整体折叠,展开后每次调用仍独立折叠,可逐项查看参数和结果;回答末尾的“引用”也默认整体折叠。会话页不显示全局后台进度卡片,正在运行的会话仍在列表中标记。
扫描只读,可直接执行。导入、课程资料入库、创建批次、启动或重试、教师复核、导出和索引重试会生成确认卡片。卡片列出目标、范围、现值、新值及异常;教师点击“确认执行”才写入。模型工具白名单没有确认工具,聊天文字也不能代替按钮。导入候选有重复、识别异常或待确认文件时,卡片禁用确认;教师在卡片中修正身份、类别和文件选择,再“更新预览”。导入确认同时授权后台解析、必要 OCR 和项目索引。
教师复核必须在当前消息明确写出分数,例如“给 8 分”。Agent 可草拟评语,确认卡片展示批次、学生、评分项、模型原分、教师原分、证据、异常、新分和修改原因。失败或无法评分的项也可人工复核;原始模型分、证据和教师修改历史保留。
<a id="design"></a>
## 业务规则与技术设计
以下保留初始业务约束、数据示例和设计要求。会话、持久任务及统一证据页的增量实现见[会话与业务 Agent 实现](#agent);原始页面清单和 API 初稿仅供理解设计。
### 项目概述
#### 项目目标
构建一个前后端分离的自动评分系统,对自动化专业综合实践课程中的以下材料进行内容评分:
1. 阶段报告 1
2. 阶段报告 2
3. 阶段报告 3
4. 最终报告
5. 汇报 PPT,或 PPT 导出的 PDF
系统输出可复核的逐项评分、原文证据、页码或幻灯片编号、缺失内容、评语和改进建议。
#### 非目标
第一版不实现:
- 学生账号和在线提交
- 现场口头答辩、答辩视频和语音评分
- 学生个人贡献度自动识别
- 代码仓库自动审查
- AI 生成内容检测
- 自动控制实验硬件的远程接入
课程评分表中的“汇报答辩 40 分”保留原名称,但本系统只依据 PPT 内容或 PPT PDF 内容评分,不评价现场口头答辩。
### 已确定的业务规则
#### 教师使用流程
系统只供教师使用,采用单机单管理员模式。教师不需要创建学生账号。
教师流程:
```text
选择课程和学期
-> 选择评分规则版本
-> 选择挂载数据目录中的学生总文件夹
-> 扫描学生子文件夹
-> 自动识别学生信息和五类材料
-> 教师确认导入结果
-> 批量解析和评分
-> 查看模型评分和证据
-> 修改分数和评语
-> 导出 Excel 成绩表和逐生 PDF 反馈
```
#### 学生目录结构
宿主机数据目录示例:
```text
WiseGradeData/
└── 2026春季第1批/
├── 1023009006_李傲琦_基于ROS2系统的自瞄移动机器人/
│ ├── 阶段报告1.pdf
│ ├── 阶段报告2.pdf
│ ├── 阶段报告3.pdf
│ ├── 自动化专业综合实践报告.pdf
│ └── 汇报PPT.pptx
└── 其他学生/
```
学生目录命名格式:
```text
学号_姓名_选题题目
```
文件夹名称只用于初步识别。系统还要从文件首页、标题和正文中抽取姓名、学号、项目题目和选题方向,并在不一致时提醒教师确认。
#### 支持文件
支持:
- PDF
- DOCX
- PPTX
- PPT 导出的 PDF
不支持旧版 `.doc` 作为学生输入。
PPT PDF 必须在导入确认阶段标记为“汇报材料”,以免与最终报告 PDF 混淆。
#### 成绩结构
总评成绩:
```text
总评 = 阶段报告1 × 10%
+ 阶段报告2 × 10%
+ 阶段报告3 × 10%
+ 期末成绩 × 70%
```
期末评分保持课程评分表结构:
| 评分项 | 分值 |
|---|---:|
| 任务完成度 | 20 |
| 报告规范性 | 20 |
| 汇报答辩(系统仅按 PPT 内容评价) | 40 |
| 结果分析 | 20 |
#### 评分批次
每次评分创建独立批次。批次之间不覆盖、不替代,也不设置唯一“正式批次”。每个批次均可查看、复核和导出。
批次必须保存:
- 课程版本
- 评分规则版本
- 课程目标版本
- `deepseek-flash` 模型配置快照
- 嵌入模型名称和版本
- 创建时间和结束时间
- 评分任务状态
### 课程和评分依据
初始资料位于 `docs/`:
- `课程导学-自动化专业综合实践.pdf`
- `自动化专业综合实践三融合选题课程概述.pdf`
- `大作业评分表-自动化专业综合实践.docx`
- `阶段报告1模板.doc`
- `阶段报告2模板.doc`
- `阶段报告3模板.doc`
- `阶段报告1.pdf`
- `阶段报告2.pdf`
- `阶段报告3.pdf`
- `自动化专业综合实践报告.pdf`
#### 课程目标和毕业要求指标点
系统初始知识库必须包含并建立关联:
- 课程目标 1:问题解决与研究能力
- 课程目标 2:知识运用与分析能力
- 课程目标 3:实验设计与操作能力
- 课程目标 4:数据处理与报告撰写能力
- 毕业要求指标点 2.3、2.4
- 毕业要求指标点 4.1、4.2、4.3、4.4
- 毕业要求指标点 11.1、11.2
#### 阶段报告要求
##### 阶段报告 1:复杂自动化工程问题解决方案论证报告
重点内容:
- 文献调研和技术现状
- 复杂工程问题分析
- 主方案和至少一套替代方案
- 方案原理、适用场景和优劣对比
- 数理或工程依据下的可行性论证
- 进度计划、资源配置、成本构成和经济性分析
初始评分框架:
| 评分项 | 分值 |
|---|---:|
| 复杂工程问题和需求分析 | 25 |
| 文献调研和技术现状总结 | 20 |
| 主方案和替代方案设计 | 25 |
| 可行性、风险和合理性论证 | 15 |
| 工程管理、进度和成本分析 | 10 |
| 报告完整性和规范性 | 5 |
| 合计 | 100 |
##### 阶段报告 2:实验系统设计与硬件搭建方案
重点内容:
- 硬件选型清单、参数和依据
- 系统架构图、硬件连接或接口关系
- 分步骤实验计划、测试方法和预期指标
- 安全风险、防护措施和操作规范
初始评分框架:
| 评分项 | 分值 |
|---|---:|
| 系统架构和实现思路 | 20 |
| 硬件选型及选型依据 | 20 |
| 系统框图、连接关系和接口设计 | 20 |
| 实验方案、测试方法和预期指标 | 20 |
| 安全风险和防护措施 | 10 |
| 阶段进展及下一阶段计划 | 10 |
| 合计 | 100 |
##### 阶段报告 3:软件设计与系统调试报告
重点内容:
- 软件逻辑、流程图或状态图
- 核心代码片段和实现说明
- 调试过程、故障现象、原因和解决方法
- 实验数据整理和初步分析
- 系统功能、性能指标和存在问题
初始评分框架:
| 评分项 | 分值 |
|---|---:|
| 软件逻辑和系统实现 | 20 |
| 核心代码和功能说明 | 20 |
| 调试过程、问题分析和解决方法 | 20 |
| 实验数据和初步分析 | 20 |
| 功能和性能指标验证 | 15 |
| 下一阶段计划及报告规范性 | 5 |
| 合计 | 100 |
阶段评分框架必须作为可编辑的初始规则导入,不能写死在业务代码中。
### 总体架构
```text
React 前端
|
FastAPI 后端
|-- 课程、版本和规则管理
|-- 挂载目录扫描和导入
|-- 文档解析和事实抽取
|-- RAG 检索
|-- 评分编排和批次管理
|-- 教师复核
|-- Excel/PDF 导出
|
PostgreSQL + pgvector
Redis + RQ
本地文件存储
本地 BGE-M3 嵌入模型
DeepSeek API(deepseek-flash)
```
采用模块化单体后端,不在第一版拆分成大量微服务。模块边界清晰,后续可以拆分。
#### 推荐技术栈
- 前端:React、TypeScript、Vite、Ant Design
- 后端:Python、FastAPI、SQLAlchemy、Pydantic
- 数据库:PostgreSQL、pgvector
- 后台任务:Redis、RQ
- PDF 解析:PyMuPDF
- DOCX 解析:python-docx
- PPTX 解析:python-pptx
- 页面转换:LibreOffice headless
- OCR:PaddleOCR,扫描 PDF 时作为回退
- 本地嵌入:`BAAI/bge-m3`
- 模型调用:OpenAI 兼容客户端,统一模型名 `deepseek-flash`
- Excel:openpyxl 或 pandas
- PDF 反馈:HTML/CSS + WeasyPrint 或 Playwright
- 部署:Docker Compose
### 宿主机目录挂载方案
原始学生材料不通过浏览器上传。前端只选择后端可见的挂载目录下的大文件夹,后端扫描和读取文件。
挂载路径与平台配置统一见[部署与配置](#deployment)。
#### 路径安全
- 后端只允许访问 `/data/inbox`
- 前端只提交挂载目录内的相对路径
- 禁止提交绝对路径
- 后端必须执行路径规范化和越界检查
- 原始输入目录只读挂载
- 解析结果、任务状态和导出文件写入独立目录
#### 文件变更检测
导入时保存:
- 相对路径
- 文件大小
- 修改时间
- SHA-256
- MIME 类型
- 解析状态
若文件哈希未变化,不重复解析和向量化。源文件变化时提示重新导入。
### 文档解析和结构化
#### 统一文档对象
每个文档解析为:
```json
{
"document_type": "stage1",
"source_path": "...",
"pages": [
{
"page_number": 1,
"text": "...",
"tables": [],
"images": [],
"rendered_image_path": "..."
}
]
}
```
必须保留页码、章节名、表格和图片关联。
#### 文件识别
支持通过以下信号识别材料类型:
- 文件名关键词
- 文件扩展名
- 文档标题
- 首页文本
- 章节标题
- PPT 页数和标题
识别结果包括:
```text
确定
不确定
缺失
重复
无法解析
```
不确定的文件进入教师确认界面。
#### PPT/PDF 视觉处理
- 提取 PPT 原生文字、表格和备注
- 将每一页渲染为图片
- PDF 每页保存页面图像
- 评分相关页面连同文字一起发送给 `deepseek-flash`
- 图片证据必须关联页码或幻灯片编号
### RAG 方案
#### 知识库分层
##### 公共课程知识库
- 课程导学
- 课程目标
- 毕业要求指标点
- 评分表
- 三个阶段模板
- 各选题方向要求
- 优秀报告
- 课程版本资料
##### 学生项目知识库
每个学生独立命名空间:
```text
course_2026/project_1023009006/
stage1/
stage2/
stage3/
final_report/
presentation/
```
禁止评分时检索其他学生的材料。
#### 文本切分
- 按标题和章节进行结构感知切分
- 普通文本块约 400~800 个中文字符
- 重叠约 80~120 个中文字符
- 表格单独保存
- 图片及图片说明单独保存
- 每块必须保留页码、页面类型、章节、文档类型和评分指标关联
- 目录页文本作为导航块单独存放,不与正文块混同
#### 检索
采用混合检索:
```text
pgvector 向量检索
+ 中文二元组关键词检索
+ 课程版本过滤
+ 学生项目过滤
+ 报告类型过滤
+ 阶段过滤
+ 页面类型过滤
```
过滤在取候选之前完成,两个召回各自独立进行,再按排名融合。默认每个评分项送模型 8~12 个候选证据块,跨目标合计上限 14 块。
#### 嵌入模型
默认使用本地 `BAAI/bge-m3`;离线包、替代模型和版本配置见[部署与配置](#deployment)。
### DeepSeek 接入
#### 统一模型
只使用一个模型配置:
```yaml
provider: deepseek
base_url: https://api.deepseek.com
model: deepseek-flash
```
同一个模型负责:
- 文本理解
- 图像理解
- 事实抽取
- 评分
- 评语生成
- PPT 分析
- 跨文档一致性检查
#### 密钥管理
教师可以在设置页面配置:
- API Key
- Base URL
- 模型名
- 并发数
- 超时时间
- 重试次数
安全要求:
- 前端不直接调用 DeepSeek
- API Key 只发送给后端
- 数据库保存加密后的密钥
- 日志禁止输出 API Key
- 支持环境变量初始化
#### 发送数据范围
原始文件保存在本地。模型请求只发送当前评分项所需的:
- 文本证据块
- 表格内容
- 必要的页面图片
- 课程评分标准
- 已抽取的项目事实
不把全部报告无条件重复发送给每一个评分项。
### 评分引擎
评分采用多阶段流程:
```text
文件导入
-> 文档解析
-> 项目事实抽取
-> 规则检查
-> 指标级 RAG 检索
-> 指标级评分
-> 跨文档一致性检查
-> 分数汇总
-> 教师复核
```
#### 事实抽取
抽取:
- 学号、姓名、项目名称
- 选题方向
- 功能目标
- 性能指标
- 硬件型号
- 软件技术
- 实验方案
- 实验数据
- 进度计划
- 成本信息
- 结论和改进方向
#### 评分输出
每个评分项必须返回结构化 JSON:
```json
{
"criterion_id": "stage1.solution_design",
"score": 17,
"max_score": 20,
"reason": "...",
"evidence": [
{
"document": "阶段报告1",
"page": 12,
"quote": "...",
"target_id": "t0"
}
],
"missing_evidence": ["..."],
"suggestion": "...",
"confidence": 0.84
}
```
模型不得对没有证据的内容自行假设。结构化输出必须经过 Pydantic 校验;失败时自动重试或进入人工处理。`score` 允许为 `null`:当规则要求正文证据而检索只能给出目录、摘要或章节标题时,模型必须返回 `null`,不得据此判断学生内容缺失。
每条引文必须通过三类校验:原文与页码一致、来源页面类型在该目标允许范围内、内容性判断不得只由目录、摘要或单独章节标题支撑。页面权限和状态转换由代码执行;模型的说明或推理不能改变它。
#### 页面类型与证据政策
每份资料的每一页都带一个页面类型,随页面持久化并可供检索、评分、复核和会话查询:
| 页面类型 | 含义 | 能否作为内容证据 |
|---|---|---|
| `cover` | 封面、标题页 | 否(结构目标可用) |
| `abstract` | 摘要、Abstract | 否(结构目标可用) |
| `toc` | 目录及其续页 | 否(结构目标可用) |
| `body` | 正文 | 是 |
| `appendix` | 附录 | 是 |
| `references` | 参考文献、致谢 | 否(结构目标可用) |
| `unknown` | 识别有疑义 | 否 |
判定结合页首标题、章节条目密度、点线与末尾页码、摘要/参考文献措辞、封面字段和页脚样式,并由文档级连续性识别跨页续目录、连续摘要和连续参考文献。它不依赖"目录"一词或固定页号;重复页眉页脚在建立索引前清理。识别依据(各项信号、置信度和分类器版本)与页面一起保存,便于教师核查。
目录与正文共处一页时标为混合页,只对该页按文本块区分:正文块仍作为正文检索,不被整页排除。
`required_evidence` 的目标带 `use` 属性,并随批次快照保存:
- `use: structure`:封面、目录、参考文献等页面可以满足,例如"章节""参考文献"。
- `use: content`:只允许正文和附录中的实质段落、数据或表格。
评分规则沿用历史上的字符串数组形式;读取时按下述语法转换,批次快照记录转换结果和 `POLICY_VERSION`,因此旧批次始终可按当时的规则重读。
#### 检索
每个必备证据目标分别检索:先限定课程版本、学生项目、材料类别和允许的页面类型,再取候选。**页面过滤发生在候选数量限制之前**,目录页不会因为得分高而挤掉正文页。
语义匹配和中文关键词匹配各自召回候选池,再按排名倒数(RRF)融合;不对未归一化的语义分和关键词分直接相加。
关键词召回不使用 PostgreSQL 的 `to_tsvector('simple', …)`:该配置没有中文分词,整个中文串会成为一个词元。在课程报告上实测,查询"文献调研"无法命中含"第2章文献调研与技术现状"的页面,因此现有实现里关键词分长期接近零。现在改用字符二元组加 ASCII/数字整词的重叠度,无需扩展且在各数据库上行为一致。
名额分配优先分散到不同页面;有了可靠章节路径后对同一章节设软上限;名额不足时回填同页或同节的其他正文片段,但不会放宽证据类型。
内容目标在正文中没有命中时,利用目录标题匹配实际正文标题,补检该章节及其相邻正文页。目录只作导航,最终内容引文必须落在实际正文页。
评分发送给模型的页面图像优先选择相关的不同正文页,而不是目录页;评分最终送模型的片段数量保持受控,Agent 搜索同样受控。
#### 待教师复核
"待教师复核"是与材料缺失、解析失败、模型调用失败并列的独立状态:
- 材料缺失、重复、待确认或解析失败 → `unscorable`
- 模型调用或校验失败 → `failed`
- 内容目标补检后仍只有目录或摘要命中 → `needs_review`
进入待复核时模型分为 `null`,总分保持待处理,系统不会自动给低分或断言学生未写。评分项记录缺失的正文证据目标和当时的检索范围,该记录在教师复核后仍然保留。批次状态为"已完成,有待复核项"(`review_required`),仪表盘的待处理数包含待复核项。
教师可对任何状态的评分项复核,包括待复核项;教师评分输入框不预填 0。
#### 跨文档一致性
至少检查:
- 阶段 1 计划是否在阶段 2、3 中落实
- 各报告中的项目名称、硬件型号和技术路线是否一致
- 实验数据是否前后一致
- 最终报告和 PPT 是否描述同一成果
- 指标是否存在明显夸大或矛盾
### 规则配置和版本管理
规则不能写死在 Prompt 或 Python 代码中。
#### 评分指标结构
```yaml
criterion_id: stage1.solution_feasibility
name: 方案合理性与可行性
max_score: 15
description: 评价方案是否具有工程依据、可实施性和风险分析
linked_outcomes: [2.3, 2.4, 4.1]
required_evidence:
- label: 主方案
use: content # 只能用正文或附录的实质段落证明
- label: 替代方案
use: content
- label: 章节
use: structure # 目录即可证明章节存在
score_levels:
- min: 12
max: 15
description: 论证充分,方案比较清晰,风险分析具体
- min: 6
max: 11
description: 基本完成论证,但证据或分析不充分
- min: 0
max: 5
description: 缺少核心论证
```
`required_evidence` 也接受历史上的字符串数组(`required_evidence: [主方案, 替代方案]`)。读取时按措辞判定 `use`:提到标题、封面、目录、章节、参考文献、格式、附录等报告包装字样的目标为 `structure`,其余为 `content`。判定结果与 `POLICY_VERSION` 一起写入批次快照,不修改已发布的规则文本。
#### 版本要求
课程、课程目标、毕业要求、模板、评分规则和 Prompt 都需要版本号。评分批次保存完整版本快照。
教师修改规则后创建新版本,不覆盖历史版本。
### 核心数据表
建议至少包含:
```text
courses
course_versions
course_objectives
graduation_indicators
topic_directions
rubric_versions
rubric_criteria
rubric_score_levels
knowledge_documents
knowledge_pages
knowledge_chunks
students
projects
submissions
submission_files
scoring_batches
criterion_scores
score_evidences
teacher_revisions
export_tasks
app_settings
audit_logs
```
#### 评分批次
```text
scoring_batches
id
course_version_id
rubric_version_id
model_name
model_config_snapshot
embedding_model_name
embedding_model_version
status
created_at
finished_at
```
#### 评分项结果
```text
criterion_scores
id
batch_id
project_id
criterion_id
model_score
teacher_score
final_score
model_comment
teacher_comment
confidence
status
```
```text
final_score = teacher_score(存在时)或 model_score
```
模型原始结果不得被教师修改覆盖。
### 前端页面
第一版页面建议:
1. 仪表盘
2. 课程管理
3. 课程版本和评分规则管理
4. 课程资料/知识库管理
5. 数据目录扫描与导入确认
6. 学生项目列表
7. 评分批次列表
8. 批量评分进度
9. 学生评分详情
10. 证据和页码查看
11. 教师复核
12. Excel/PDF 导出
13. 模型和系统设置
### 后端 API 初稿
#### 课程和规则
```text
GET /api/courses
POST /api/courses
GET /api/courses/{id}/versions
POST /api/courses/{id}/versions
GET /api/rubrics/{version_id}
PUT /api/rubrics/{version_id}
POST /api/rubrics/{version_id}/publish
```
#### 文件扫描和项目导入
```text
GET /api/storage/directories
GET /api/storage/directories/{relative_path}/children
POST /api/import/scan
POST /api/import/confirm
GET /api/projects
GET /api/projects/{id}
```
#### 评分
```text
POST /api/scoring-batches
GET /api/scoring-batches
GET /api/scoring-batches/{id}/progress
POST /api/scoring-batches/{id}/start
GET /api/scoring-batches/{id}/scores
GET /api/projects/{id}/scores?batch_id={batch_id}
PUT /api/criterion-scores/{id}/review
```
#### 导出
```text
POST /api/exports/excel
POST /api/exports/project-pdf
GET /api/exports/{id}
```
### 缺失和异常处理
材料状态:
```text
已提交
缺失
重复
无法解析
内容不完整
待人工确认
```
处理原则:
- 缺失阶段报告:该阶段进入待人工处理,不默认静默给零分
- 缺失 PPT:汇报材料项显示无法评分
- 缺少章节:按照对应评分项判断并扣分
- 文件损坏:进入人工确认列表
- DeepSeek 调用失败:自动有限重试,仍失败则标记评分项失败
- 评分批次允许部分完成,但导出时必须显示未完成项目
<a id="agent"></a>
## 会话与业务 Agent 实现
会话交互及确认规则见[教师使用](#usage)。以下记录数据、模型边界、接口与运行恢复机制。
### 数据与任务
迁移 `0003` 添加会话、消息、运行、待确认操作、索引任务和批次固定项目清单;`0004` 添加首次请求幂等键与有界工具进度记录;`0005` 回填完整资料页文字与相对图像路径,并将历史会话中的引用一次性转换为统一页面链接;`0006` 为页面和检索片段添加页面类型、混合页标记、章节路径,为评分证据添加所属证据目标与历史标记,并新增评分项的缺证据记录。检索片段的文字和 1024 维向量保存在 PostgreSQL 的 `knowledge_chunks`;供教师核对的完整页文字单独保存在 `knowledge_pages`,避免以有重叠的检索片段拼接页面。PNG 文件位于共享 `data/processed/`,数据库只存相对于处理目录的路径;学生原件仍只读挂载在 `/data/inbox`。新批次在创建时保存项目 ID 列表;评分、结果查询和导出都使用该列表。旧批次该字段为 `NULL`,界面显示“历史项目清单未知”,只能按课程版本当前项目兼容读取,不能声称还原了创建时的范围。批次状态为 `created`、`queued`、`running`、`partial`、`failed`、`completed`、`review_required`;`review_required` 表示评分已结束但仍有待教师复核项,与部分完成不同。重复启动已入队或运行中的批次返回冲突。重试保留已评分项和教师复核。
索引版本 `0006` 起为 3。分块方式已改变(目录页不再作为正文文本),因此旧版本的索引不会被就地当作已分类正文使用:文档保留原有 `index_version`,在重建完成前不参与检索,教师可先在上线前查看 `/api/knowledge/stale/{course_version_id}` 的待重建清单。重建任务类型为 `reindex`,只从已保存的解析结果重建,不重新读取或修改学生原件;重复执行会跳过已是最新版本和源文件已变化的文档,并记录到审计日志。
导入后的每个待解析文件对应持久 `IndexTask`。worker 复用评分的解析和 RAG 入库代码;同一文件用共享处理目录中的文件锁串行化,避免自动索引与评分竞争写入。任务有 queued/running/completed/failed 状态、错误和重试入口。课程资料入库在会话中也作为索引任务执行。若 Redis 不可用,导入数据仍保存,索引任务标记失败并可重试。原始学生文件只从只读 `/data/inbox` 读取,文件 SHA-256 在预览和确认时复查。
导入后的每个待解析文件对应持久 `IndexTask`。worker 复用评分的解析和 RAG 入库代码;同一文件用共享处理目录中的文件锁串行化,避免自动索引与评分竞争写入。任务有 queued/running/completed/failed 状态、错误和重试入口。课程资料入库在会话中也作为索引任务执行。若 Redis 不可用,导入数据仍保存,索引任务标记失败并可重试。原始学生文件只从只读 `/data/inbox` 读取,文件 SHA-256 在预览和确认时复查。
待确认操作保存不可变参数、20 分钟有效期、幂等键和预期数据状态。确认时再次比对课程发布状态、规则与项目范围、批次状态、成绩与文件哈希。状态变化返回“重新预览”;重复确认已完成操作返回原结果。执行与会话 ID 写入审计日志。服务异常的操作标记失败,需重新准备;若进程在执行中崩溃,卡片保留 `executing` 状态,管理员应先核查实际业务记录再处理,不应直接重复写入。
批次汇总从数据库和现有权重规则计算完成数、未完成数、状态数量、分布、模型原始分均值和教师最终分均值。只有完整项目参与成绩均值及分布;失败、无法评分和待处理项不补零。详情仍可按学生、批次和评分项状态筛选。
### 模型与安全边界
会话使用模型设置页的 OpenAI 兼容 `/chat/completions` 地址、模型和后端 API Key。适配层按 [DeepSeek 原生工具调用文档](https://api-docs.deepseek.com/guides/tool_calls) 发送 `tools`,在每轮前用不含学生资料的 `wisegrade_ping` 最小探针验证端点会返回 `tool_calls`。当前 `deepseek-flash` 端点支持自动工具选择,但拒绝强制 `tool_choice="required"`,因此探针采用 `auto` 并检查返回结构。模型不支持时会显示明确配置错误,不会伪装工具已执行。既有评分 `json_call` 保持原有 JSON 行为。
内容检索和结构查询使用与评分相同的证据政策。`search_project_material` 只返回正文和附录片段,`search_project_structure` 返回目录条目与章节结构并明确说明这些信息不能证明章节内容,`get_document_pages` 列出各页的页码、页面类型和章节,`search_document` 在单份资料内检索正文。工具返回页面类型和稳定的引用标识(`citation_id`),每项标注 `source_role` 为 `content` 或 `structure`。评分工具返回的旧引文标记为历史评分引用(`source` 为 `historical_score_evidence`),不会被当作报告正文证据。
工具输入由 Pydantic 校验,后端再校验课程、批次和项目关系。每条教师消息默认最多调用工具 32 次,成功、失败与跳过的尝试均计数;模型最多请求 33 轮,以便用完工具次数后还能生成回答。同一轮中工具名和经校验的参数(含默认分页值)完全相同时,后续调用会标为“已跳过”,模型收到前次调用标识并使用此前结果,不再执行工具;不同查询词、筛选或分页偏移仍会正常执行。可在 `.env` 中通过 `CHAT_MAX_TOOL_CALLS` 调整工具上限(1–64),修改后重新创建 API 容器。`get_scores` 每次最多返回一个项目的六项评分,响应给出 `next_criterion_offset` 和 `next_project_offset` 供继续查询;长结果保留结构化评分数据,不再变成无内容的截断提示。检索工具的运行记录保留每次调用找到的有界原文片段;回答引用对各次调用的证据去重后保存,不再只取前 20 条。工具没有任意 SQL、任意文件路径、HTTP 请求、代码执行或确认能力。检索片段只作为数据,不作为工具指令。模型只收到当前问题所需的规则摘要、检索片段或紧凑统计;API Key 不写入日志或会话表。
### API 与故障恢复
草稿仅存在于浏览器;`POST /api/conversations/first-message` 以 UUID 请求键原子创建会话、首条教师消息及运行,重复请求返回同一 ID。`POST /api/conversations` 保留给旧客户端。`GET /api/conversations` 返回列表和各会话运行标识;`GET /api/conversations/{id}` 恢复消息、卡片、工具记录和运行状态;`PUT /api/conversations/{id}/scope` 仅允许首条教师消息前编辑范围;`DELETE /api/conversations/{id}` 删除非运行中的会话。删除会话会清理消息、运行与待确认卡片,不回滚已经确认的业务操作,相关业务审计仍保留。`POST /api/conversations/{id}/messages/start` 在提交消息后启动独立后台运行;`GET /api/conversations/{id}/runs/{run_id}/events` 可按运行 ID 订阅持久 SSE 事件。旧版 `POST /api/conversations/{id}/messages` 继续提供 SSE 的 `run`、`tool`、`message`、`error`、`done` 事件。确认、取消和导入编辑分别为 `POST /api/conversations/{id}/actions/{action_id}/confirm|cancel|edit-import`。索引任务可通过 `GET /api/index-tasks`、`GET /api/index-tasks/{id}` 查询,失败任务可重试。Nginx 对消息流关闭代理缓冲并设置 900 秒读取超时。
回答生成由后台线程执行,页面切换或 SSE 断开不会停止运行;前端按运行 ID 订阅 SSE,并轮询列表和当前会话详情恢复进度,可同时运行多个不同会话。同一会话仍拒绝并发发送。刷新后默认打开新草稿,点击历史会话即可查看正在运行的工具状态或最终结果。运行中的会话删除返回冲突,应待完成后重试。服务进程异常留下的 `running` 记录超过 15 分钟,在读取会话时标为中断。模型失败时,已提交的教师消息仍保留,可在同一会话继续提问。工具记录保存有界查询词、筛选、分页及必要标识;教师复核评语和原因只显示省略标记,未知字段与密钥不保存。旧记录中未保存的检索查询词无法还原,界面明确标明;旧评分记录可从工具结果恢复实际分页偏移。检索工具记录有界原文片段,评分工具记录有界评分行,其他工具记录结构摘要。回答按安全 Markdown 渲染,HTML 不执行,链接仅允许 HTTP(S);工具过程在回答上方,引用在回答最后,二者默认折叠。待确认卡片始终独立显示。若嵌入模型或 OCR 缺失,任务保留具体失败原因;补齐离线模型后重试。若课程资料缺失、原件哈希变化、课程或批次状态变化,应重新预览。
<a id="development"></a>
## 开发与测试
### 本地开发和测试命令
Python 依赖由 `backend/pyproject.toml` 管理:
```bash
cd backend
uv sync --extra ocr --group dev
uv run alembic upgrade head
uv run uvicorn wisegrade.main:app --reload
```
测试从仓库根目录运行:
```bash
docker compose --profile test run --build --rm test
```
先验证无需真实 API Key 的测试可运行:`docker compose --profile test run --build --rm test pytest -q -m 'not integration'`。宿主机运行同一测试时,须把 `TEST_DATABASE_URL` 指向独立 PostgreSQL 测试库,并将 `EMBEDDING_MODEL_PATH` 指向本机离线模型目录。完整集成测试会访问真实模型,应单独执行和记录。
测试使用独立的 PostgreSQL 16 + pgvector 容器和 `test-redis`,不连接业务数据库或业务任务队列。每个数据库测试创建临时数据库,执行 Alembic 迁移后运行,结束时删除;`test-db` 的数据目录为 tmpfs,不会写入业务数据卷。测试会从 `.env` 读取真实 DeepSeek API Key,并加载 `models/bge-m3` 和 `models/ocr` 中的离线模型;完整流程测试会发起真实模型请求。在宿主机直接运行 `uv run pytest` 时,需先设置指向专用 `wisegrade_tests` 数据库的 `TEST_DATABASE_URL`(使用 `postgresql+psycopg` 驱动,账号需有创建数据库的权限),并提供可用的模型配置。
本地开发需提供 PostgreSQL、Redis 和与容器路径一致的挂载目录;可用 Compose 启动 `db` 和 `redis`。Vite 前端:
```bash
cd frontend
npm install
npm run dev
```
开发服务器将 `/api` 代理到 `localhost:8000`。集成测试从仓库 `docs/` 复制阶段报告 1、2、3 与最终报告,并生成一份汇报 PDF 测试材料。
### 测试要求
#### 单元测试
- 目录扫描
- 学号/姓名/题目解析
- 文件类型识别
- 路径越界防护
- 文件哈希检测
- 规则权重计算
- 缺失材料处理
- 分数汇总
- 评分 JSON 校验
- 版本绑定
#### 集成测试
- 从示例材料导入到项目生成
- PDF/DOCX/PPTX 解析
- PPT PDF 识别
- 知识库入库和检索
- 模型 API mock 调用
- 评分批次创建、运行和查询
- 教师修改分数
- Excel 导出
- PDF 反馈导出
#### 验收测试
至少使用当前 `docs/` 中的示例材料完成一次完整流程:
```text
扫描目录
-> 导入学生项目
-> 解析五类材料
-> 创建评分批次
-> mock 或真实调用 deepseek-flash
-> 查看逐项证据
-> 修改分数
-> 导出 Excel
-> 导出学生 PDF 反馈
```
不应在测试中提交真实 API Key。使用环境变量或 mock 服务。
### Git 开发约定
必须创建合理的 `.gitignore`,至少忽略:
```text
.env
data/processed/
data/exports/
models/
__pycache__/
.pytest_cache/
node_modules/
dist/
*.pyc
```
开发和测试过程中按阶段提交,推荐提交节点:
```text
chore: initialize project structure
feat: add backend foundation
feat: add course and rubric versioning
feat: add mounted directory scanning
feat: add document parsing pipeline
feat: add knowledge base and hybrid retrieval
feat: add deepseek-flash adapter
feat: add scoring batch orchestration
feat: add teacher review workflow
feat: add excel and pdf exports
test: add integration coverage
docs: add deployment and usage guides
```
每个提交应保持可理解、可回退、尽量可运行。禁止把 API Key、真实学生隐私数据和模型权重提交到 Git。
### 原始开发阶段(历史规划)
以下优先级保留为初始实施顺序,不是当前待办清单;接手事项见 [HANDOFF](../HANDOFF.md)。
#### P0:可运行骨架
- Git、README、Docker Compose
- 前端和后端健康检查
- 数据库迁移
- 单管理员设置
#### P1:课程和导入
- 课程版本和规则配置
- 挂载目录扫描
- 学生目录识别
- 文件识别和导入确认
#### P2:解析和知识库
- PDF/DOCX/PPTX 解析
- 页面渲染
- OCR 回退
- BGE-M3 嵌入
- pgvector 混合检索
#### P3:评分
- 事实抽取
- 指标级 RAG 评分
- 跨文档一致性检查
- 批次状态和失败重试
#### P4:复核和导出
- 教师修改分数和评语
- Excel 成绩表
- 逐生 PDF 反馈
- 审计日志
#### P5:验收和部署
- 示例材料完整验收
- Linux 部署文档
- Windows Docker Desktop 部署文档
- 离线模型包说明
### 第一版验收目标
以下是验收目标,不代表所有平台和场景已实测。
当以下条件全部满足时,第一版系统完成:
- Linux 上可以通过 Docker Compose 启动
- Windows Docker Desktop 有明确部署步骤
- 教师可以选择挂载目录下的大文件夹
- 系统可以识别每个学生目录
- 支持五类材料和 PPT PDF
- 规则、课程目标和课程版本可编辑
- 可创建独立评分批次
- 使用本地嵌入模型完成检索
- 使用 `deepseek-flash` 完成文本和图像评分
- 每项评分显示证据和页码/幻灯片编号
- 教师可以修改分数和评语
- 每个批次都可以导出 Excel 和逐生 PDF
- 示例材料可以完成端到端测试
- Git 历史包含清晰的阶段提交
<a id="troubleshooting"></a>
## 故障定位与恢复
- `docker compose logs api worker` 查看迁移、解析和任务异常。日志不输出 API Key。
- 模型请求失败后按配置有限重试,评分项显示失败原因;可修复配置后重试该批次。
- 对话若提示端点或模型不支持 `tool_calls`,在“模型设置”中选择支持工具调用的 OpenAI 兼容端点和模型。探针不发送课程或学生资料;普通模型对话仍有调用次数和超时限制。
- 导入后索引任务若显示失败,查看 `/api/index-tasks/{id}` 的错误并修复 Redis、离线 BGE-M3 或 OCR,再重试。批次若已入队或运行,重复点击启动会返回冲突。
- 原件哈希变化后必须重新扫描并确认导入,否则解析会报“源文件已变化”。
- 源文件和模型目录为只读挂载;解析产物在 `data/processed/`,导出在 `data/exports/`,均被 Git 忽略。
会话运行中断、待确认操作卡在 `executing`、旧批次范围兼容等情况,见[会话与业务 Agent 实现](#agent)。