826 lines
32 KiB
Markdown
826 lines
32 KiB
Markdown
## 项目概述
|
||
- **名称**: 初中数学作业批改工作流
|
||
- **功能**: 上传多学生的作业图片和Word答案文件,自动识别学生答案、提取标准答案、精准批改并返回每个学生的批改结果JSON
|
||
|
||
### 数据结构(重要变更)
|
||
**输入参数**:
|
||
```json
|
||
{
|
||
"student_homework": [
|
||
{
|
||
"student_id": 0,
|
||
"student_name": "张三",
|
||
"homework_images": [
|
||
"图片URL1",
|
||
"图片URL2"
|
||
]
|
||
},
|
||
{
|
||
"student_id": 1,
|
||
"student_name": "李四",
|
||
"homework_images": [
|
||
"图片URL3",
|
||
"图片URL4"
|
||
]
|
||
}
|
||
],
|
||
"answer_doc_url": "答案文档URL(可选)",
|
||
"comment_max_length": 100,
|
||
"max_concurrent": 10
|
||
}
|
||
```
|
||
|
||
**输出结果**:
|
||
```json
|
||
{
|
||
"student_results": [
|
||
{
|
||
"student_id": 0,
|
||
"student_name": "张三",
|
||
"total_images": 2,
|
||
"image_results": [...],
|
||
"overall_comment": "优秀!5题全部正确",
|
||
"total_score": 15,
|
||
"full_score": 15,
|
||
"grade": "A+"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### 节点清单
|
||
| 节点名 | 文件位置 | 类型 | 功能描述 | 分支逻辑 | 配置文件 |
|
||
|-------|---------|------|---------|---------|---------|
|
||
| doc_extract | `nodes/doc_extract_node.py` | agent | 从Word文件(.docx)提取题干和标准答案;如无URL则返回空列表 | - | `config/doc_extract_llm_cfg.json` |
|
||
| process_images | `nodes/process_images_node.py` | looparray | 循环调用子图处理每张作业图片,生成最终批改结果 | - | - |
|
||
|
||
**类型说明**: task(普通任务节点) / agent(大模型节点) / condition(条件分支) / looparray(列表循环) / loopcond(条件循环)
|
||
|
||
## 子图清单
|
||
| 子图名 | 文件位置 | 功能描述 | 被调用节点 |
|
||
|-------|---------|------|---------|
|
||
| single_image_subgraph | `graphs/loop_graph.py` | 处理单张图片的完整批改流程(预处理→识别批改→整合→包装) | process_images |
|
||
|
||
### 子图内部节点
|
||
| 节点名 | 文件位置 | 类型 | 功能描述 |
|
||
|-------|---------|------|---------|
|
||
| image_preprocess | `nodes/image_preprocess_node.py` | task | 下载图片、自动旋转(横向→纵向)、缩放到固定宽度1000px、上传对象存储 |
|
||
| recognize_and_correct | `nodes/recognize_and_correct_node.py` | agent | **一体化识别批改**:合并识别题目和批改为一次LLM调用 |
|
||
| result_merge | `nodes/result_merge_node.py` | task | 将识别结果和批改结果合并为最终批注 |
|
||
| wrap_result | `graphs/loop_graph.py` | task | 包装子图结果为SingleImageResult输出 |
|
||
|
||
## 技能使用
|
||
- 节点 `recognize_and_correct` 使用大语言模型技能(多模态,识别+批改合并)
|
||
- 模型:`doubao-seed-2-0-pro-260215`(旗舰视觉模型,推理能力强,输出简洁)
|
||
- 节点 `doc_extract` 使用大语言模型技能
|
||
- 模型:`doubao-seed-2-0-pro-260215`(旗舰模型,复杂推理能力强)
|
||
- 使用 python-docx 解析 Word 文档
|
||
- **缓存优化**:使用 `utils/cache_manager.py` 缓存解析结果,有效期30天
|
||
|
||
## 缓存机制(优化版 v2026-03-28)
|
||
- **缓存管理器**:`src/utils/cache_manager.py`
|
||
- **双层架构**:
|
||
- 内存缓存:LRU淘汰,最大数量1000,快速访问
|
||
- 文件缓存:持久化存储,进程重启后仍可用
|
||
- **缓存有效期**:30天(自动清理过期缓存)
|
||
- **缓存内容**:AI解析后的结构化数据(CorrectAnswer列表)
|
||
- **缓存键**:answer_doc_url(MD5哈希)
|
||
- **线程安全**:使用锁保护并发访问
|
||
- **异常安全**:文件缓存失败时自动降级为纯内存模式
|
||
- **统计功能**:`get_stats()` 返回缓存统计信息
|
||
|
||
### 性能优化与超时控制
|
||
- **图片下载超时**:30秒(单次),总时间不超过60秒
|
||
- **重试机制**:图片获取失败最多重试2次
|
||
- **单图片处理超时**:120秒(含LLM调用)
|
||
- **总任务超时**:120秒 × 图片数量
|
||
- **降级处理**:超时任务返回空结果,不影响其他任务
|
||
- **并发安全**:使用 `ThreadPoolExecutor` + 超时保护
|
||
|
||
## 等级标准配置(核心规则)
|
||
- **参数名**:`grade_standards`
|
||
- **核心规则**:**A+ 和 A 的首要条件是"全对",与得分率无关**
|
||
|
||
### 等级判定逻辑(简化版)
|
||
| 等级 | 条件 | 说明 |
|
||
|------|------|------|
|
||
| A+ | 全对(错误数=0) | 所有题目都正确,与得分率无关 |
|
||
| A | (预留,全对时返回A+) | 答案全对 |
|
||
| B | 有错误,得分率≥80% | 有少量错误 |
|
||
| C | 有错误,得分率≥70% | 错误较多 |
|
||
| D | 有错误,得分率<70% | 错误很多 |
|
||
|
||
### 关键说明
|
||
- **全对 = A+**:只要所有题目都正确(incorrect_count == 0),就是A+
|
||
- **有错 = B/C/D**:有错误时,按得分率判断具体等级
|
||
|
||
### 示例
|
||
- ✅ 得分80分,错误0题 → A+(全对,得分率不重要)
|
||
- ✅ 得分95分,错误0题 → A+(全对)
|
||
- ❌ 得分95分,错误1题 → B(有错误,按得分率判断)
|
||
- ❌ 得分90分,错误2题 → B(有错误,按得分率判断)
|
||
|
||
### 配置示例
|
||
```json
|
||
{
|
||
"grade_standards": {
|
||
"A+": {"min_percentage": 95, "description": "优秀"},
|
||
"A": {"min_percentage": 85, "description": "良好"},
|
||
"B": {"min_percentage": 70, "description": "中等"}
|
||
}
|
||
}
|
||
```
|
||
|
||
## 工作流程(多图片批改架构)
|
||
|
||
```
|
||
┌─────────────────────┐
|
||
│ doc_extract │
|
||
│ (Word答案解析) │
|
||
└──────────┬──────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────┐
|
||
│ process_images │
|
||
│ (多图片循环处理) │
|
||
│ 生成最终批改结果 │
|
||
└─────────────────────┘
|
||
```
|
||
|
||
### 子图内部流程(处理单张图片 - 优化版)
|
||
|
||
```
|
||
┌─────────────────────┐
|
||
│ image_preprocess │
|
||
│ (图像预处理) │
|
||
└──────────┬──────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────┐
|
||
│recognize_and_correct│ ← 合并节点:识别+批改一次完成
|
||
│ (一体化识别批改) │
|
||
└──────────┬──────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────┐
|
||
│ result_merge │
|
||
│ (结果整合) │
|
||
└──────────┬──────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────┐
|
||
│ wrap_result │
|
||
│ (包装输出) │
|
||
└─────────────────────┘
|
||
```
|
||
|
||
## 核心功能:多学生多图片批改机制
|
||
|
||
### 输入参数
|
||
- `student_homework`: 学生作业列表(List[StudentHomework],支持多个学生)
|
||
- 每个学生包含:
|
||
- `student_id`: 学生ID(int)
|
||
- `student_name`: 学生姓名(str,可选)
|
||
- `homework_images`: 该学生的作业图片URL列表(List[str],纯字符串数组)
|
||
- `answer_doc_url`: 正确答案Word文件的URL(.docx格式,**可选**)
|
||
- `comment_max_length`: 评语最大字数(默认100字,**可选**)
|
||
- `max_concurrent`: 并行批改的最大数量(默认10,**可选**)
|
||
- `grade_standards`: 评价等级标准(**可选**,默认值如下)
|
||
```json
|
||
{
|
||
"A+": {"min_percentage": 95, "description": "答案全部正确,步骤完整规范,逻辑严谨;书写/格式整洁,无错别字、无遗漏;完成度100%,态度认真,质量上乘"},
|
||
"A": {"min_percentage": 90, "description": "答案完全正确,无任何错误;步骤合理、格式规范,无原则性问题;完成度100%,满足全部要求"},
|
||
"B": {"min_percentage": 80, "description": "存在少量非关键性错误,或步骤略有缺失;整体思路基本正确,仅细节、格式、计算等小问题;完成大部分内容,整体合格但不够严谨"},
|
||
"C": {"min_percentage": 70, "description": "错误较多,部分核心题目作答错误;步骤不完整、逻辑不够清晰;完成度一般,有明显应付、漏答情况"},
|
||
"D": {"min_percentage": 0, "description": "大面积错误,核心知识点未掌握;大量空白、敷衍、抄袭;未达到基本完成要求"}
|
||
}
|
||
```
|
||
|
||
### 输出结果
|
||
- `student_results`: 各学生的批改结果列表(List[StudentResult])
|
||
- 每个学生包含:
|
||
- `student_id`: 学生ID(int)
|
||
- `student_name`: 学生姓名(str)
|
||
- `total_images`: 该学生的总图片数
|
||
- `image_results`: 该学生各图片的批改结果列表
|
||
- `overall_comment`: 该学生的整体评价
|
||
- `total_score`: 该学生的总分
|
||
- `full_score`: 该学生的满分
|
||
- `grade`: 该学生的等级评定
|
||
|
||
### 批改优先级(严格按照以下顺序)
|
||
1. **最优先**:使用Word文档中的标准答案批改
|
||
- 当提供了`answer_doc_url`且在文档中找到对应题目时
|
||
- 严格按照标准答案判断学生答案正误
|
||
|
||
2. **降级方案**:使用专业数学老师批改
|
||
- 场景1:未提供`answer_doc_url`
|
||
- 场景2:提供了URL但文档中未找到对应题目
|
||
- 使用专业数学老师的经验自主判断答案正误
|
||
|
||
### 功能说明
|
||
1. **多图片支持**:可上传多张作业图片,系统会并行处理每张图片(并发数限制为3)
|
||
2. **Word答案提取**:从.docx文件中提取题干和标准答案
|
||
3. **子图循环处理**:使用子图封装单图片处理流程,主图调用子图处理每张图片
|
||
4. **批改结果JSON**:返回包含所有图片批改结果的结构化JSON
|
||
5. **智能降级**:无标准答案时自动切换到专业老师模式
|
||
|
||
## 优化记录
|
||
### 2026-03-27 最终图片处理方案(重要)
|
||
**问题**:如何在不上传图片的前提下,保证AI识别准确?
|
||
|
||
**方案对比**:
|
||
|
||
| 方案 | 旋转缩放 | 上传 | AI访问 | 请求体积 | 选择 |
|
||
|------|---------|------|--------|---------|------|
|
||
| 方案1 | ✅ | base64编码 | Data URL | 大 | ❌ |
|
||
| 方案2 | ❌ | ❌ | 原始URL | 小 | ✅ |
|
||
|
||
**选择方案2的原因**:
|
||
1. **AI模型足够强大**:`doubao-seed-2-0-pro-260215` 可以处理各种尺寸和方向的图片
|
||
2. **坐标系统统一**:使用相对坐标(0-1000),自动适配任意尺寸
|
||
3. **最简单高效**:不需要base64编码,不需要上传,处理速度最快
|
||
|
||
**最终逻辑**:
|
||
```python
|
||
# 获取原始图片URL和尺寸
|
||
original_url = state.homework_image.url
|
||
width, height, dpi = get_image_info(original_url)
|
||
|
||
# 直接返回原始URL(不上传)
|
||
return ImagePreprocessOutput(
|
||
image_url=original_url,
|
||
image_info=ImageInfo(width=width, height=height, dpi=dpi)
|
||
)
|
||
```
|
||
|
||
**效果**:
|
||
- ✅ 不上传新图片到Coze
|
||
- ✅ 返回原始图片URL
|
||
- ✅ 坐标自动适配原始尺寸
|
||
- ✅ 处理速度最快
|
||
|
||
### 2026-03-27 移除图片上传功能(重要)
|
||
**问题**:系统自动上传处理后的图片到Coze对象存储,用户不需要这个功能
|
||
|
||
**原逻辑**:
|
||
1. 下载原始图片
|
||
2. 自动旋转(横向→纵向)
|
||
3. 缩放到固定宽度1000px
|
||
4. **上传到Coze对象存储**
|
||
5. 返回上传后的URL
|
||
|
||
**新逻辑**:
|
||
1. 获取原始图片URL
|
||
2. 获取图片尺寸信息
|
||
3. **直接返回原始URL和尺寸**(不上传)
|
||
|
||
**优化内容**:
|
||
- 移除图片旋转功能
|
||
- 移除图片缩放功能
|
||
- 移除图片上传功能
|
||
- 直接使用原始图片URL
|
||
- 坐标系统仍然使用相对坐标(0-1000),自动适配原始图片尺寸
|
||
|
||
**代码对比**:
|
||
```python
|
||
# 优化前
|
||
img = download_and_rotate(image_url)
|
||
img = resize_to_1000px(img)
|
||
new_url = upload_to_coze(img) # 上传新图片
|
||
return ImagePreprocessOutput(image_url=new_url)
|
||
|
||
# 优化后
|
||
width, height, dpi = get_image_info(image_url)
|
||
return ImagePreprocessOutput(
|
||
image_url=original_url, # 直接返回原始URL
|
||
image_info=ImageInfo(width=width, height=height, dpi=dpi)
|
||
)
|
||
```
|
||
|
||
**效果**:
|
||
- ✅ 不再上传新图片到Coze
|
||
- ✅ 返回原始图片URL
|
||
- ✅ 减少存储空间占用
|
||
- ✅ 提升处理速度
|
||
|
||
### 2026-03-27 坐标偏移量优化(重要)
|
||
**问题**:批改标记离学生答案太远,定位不够精准
|
||
|
||
**原因分析**:
|
||
- 原偏移量设置过大(30px、20px)
|
||
- 导致标记与答案视觉距离过远
|
||
- 影响批改结果的精准度
|
||
|
||
**优化方案**:
|
||
减小所有偏移量,让标记更贴近答案:
|
||
|
||
| 策略 | 原偏移 | 新偏移 | 说明 |
|
||
|------|--------|--------|------|
|
||
| 策略1(右侧空间>80px) | +30px | **+10px** | 紧贴答案框右侧 |
|
||
| 策略2(右侧空间40-80px) | -20px | **-10px** | 答案框右上角内部 |
|
||
| 策略3(右侧空间<40px) | +20px | **+10px** | 答案框左上角 |
|
||
| Y轴偏移 | 15px | **10px** | 顶部位置 |
|
||
|
||
**代码对比**:
|
||
```python
|
||
# 优化前
|
||
mark_x = answer_bbox[2] + 30 # 偏移过大
|
||
|
||
# 优化后
|
||
mark_x = answer_bbox[2] + 10 # 紧贴答案框
|
||
```
|
||
|
||
**效果**:
|
||
- ✅ 批改标记更贴近学生答案
|
||
- ✅ 视觉定位更精准
|
||
- ✅ 避免标记离答案太远的问题
|
||
|
||
### 2026-03-27 坐标边界严格限制(重要)
|
||
**问题**:标记坐标可能超过图片宽度,导致定位错误
|
||
|
||
**修复内容**:
|
||
1. **严格边界检查**:
|
||
```python
|
||
# 确保x轴不超过图片宽度,y轴不超过图片高度
|
||
mark_x = max(10, min(mark_x, image_info.width - 10))
|
||
mark_y = max(10, min(mark_y, image_info.height - 10))
|
||
```
|
||
|
||
2. **边界优化**:
|
||
- 边距从20px减少到10px,确保标记更接近边缘
|
||
- 绝对不会超过图片宽度和高度
|
||
- 保证批改标记始终在图片可视范围内
|
||
|
||
**效果**:标记坐标始终在图片范围内,不会出现越界问题
|
||
|
||
### 2026-03-27 空答案判定优化(重要)
|
||
**问题**:学生没有作答(空白)时,判定不够明确
|
||
|
||
**修复内容**:
|
||
在Prompt中新增"空答案处理"规则:
|
||
```
|
||
# ⚠️ 重要:空答案处理
|
||
- 如果学生没有作答(空白),必须判定为**incorrect**
|
||
- status字段填写"incorrect"
|
||
- score字段填写0
|
||
- comment字段填写"未作答"或"空白,无答案"
|
||
```
|
||
|
||
**示例**:
|
||
- 正确:`"status": "correct", "score": 10, "comment": "计算正确"`
|
||
- 错误:`"status": "incorrect", "score": 5, "comment": "单位错误"`
|
||
- 空答案:`"status": "incorrect", "score": 0, "comment": "未作答"`
|
||
|
||
**效果**:空答案统一判定为错误,得分0分,评语明确
|
||
|
||
### 2026-03-27 坐标定位精准度优化(重要)
|
||
**问题**:个别批改标记过于偏右,超出答题区域,甚至与相邻题目重叠
|
||
|
||
**原因分析**:
|
||
- 原逻辑固定在答案框右侧30px,未考虑右侧空间是否充足
|
||
- 当答案框本身靠右时,标记会超出合理范围
|
||
|
||
**优化方案**(三级策略):
|
||
1. **策略1**:右侧空间充足(>100px)→ 标记在右侧(原有逻辑)
|
||
```python
|
||
mark_x = answer_bbox[2] + 30
|
||
mark_y = answer_bbox[1] + height * 0.5
|
||
```
|
||
|
||
2. **策略2**:右侧空间不足(50-100px)→ 标记在答案框右上角内部
|
||
```python
|
||
mark_x = answer_bbox[2] - 20 # 内部
|
||
mark_y = answer_bbox[1] + 15
|
||
```
|
||
|
||
3. **策略3**:右侧空间很小(<50px)→ 标记在答案框左上角
|
||
```python
|
||
mark_x = answer_bbox[0] + 20 # 左侧
|
||
mark_y = answer_bbox[1] + 15
|
||
```
|
||
|
||
**效果**:
|
||
- 批改标记始终在合理范围内
|
||
- 不会超出答题区域
|
||
- 不会与相邻题目重叠
|
||
- 视觉效果更精准
|
||
|
||
### 2026-03-27 完全并行架构优化(重要)
|
||
**问题**:原架构外层串行处理学生,内层并行处理图片,效率不高且可能有数据混乱风险
|
||
|
||
**修复内容**:
|
||
1. **完全并行架构**:
|
||
- 所有学生的所有图片同时提交到线程池
|
||
- 学生间+图片间完全并行,最大化效率
|
||
- 使用 `(student_id, image_index, image_result)` 元组确保数据关联
|
||
|
||
2. **数据隔离机制**:
|
||
- 结果按 `student_id` 分组存储
|
||
- 每个学生的 `total_score`、`full_score`、`overall_comment`、`grade` 完全独立
|
||
- 只使用该学生自己的 `image_results` 计算分数
|
||
|
||
3. **核心代码**:
|
||
```python
|
||
# 返回元组:(student_id, image_index, image_result)
|
||
return (student_id, idx, image_result)
|
||
|
||
# 按student_id分组存储
|
||
student_image_results[student_id][image_index] = image_result
|
||
|
||
# 为每个学生独立计算结果
|
||
for student in state.student_homework:
|
||
image_results = student_image_results[student_id]
|
||
# 只使用该学生的数据计算...
|
||
```
|
||
|
||
**效果**:
|
||
- 完全并行,效率最大化
|
||
- 数据严格隔离,不会混淆
|
||
- 学生A的数据绝不会出现在学生B的结果中
|
||
|
||
### 2026-03-27 输入参数格式优化(重要)
|
||
**问题**:`homework_images` 使用 `List[File]` 格式,用户输入不够简洁
|
||
|
||
**修复内容**:
|
||
1. **简化输入格式**:
|
||
- `homework_images: List[File]` → `homework_images: List[str]`
|
||
- 直接传入URL字符串数组,无需构造File对象
|
||
- 代码内部自动将URL转换为File对象
|
||
|
||
2. **输入示例**:
|
||
```json
|
||
{
|
||
"student_homework": [
|
||
{
|
||
"student_id": 0,
|
||
"homework_images": ["url1", "url2"]
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**效果**:
|
||
- 用户输入更简洁
|
||
- 减少构造对象的复杂度
|
||
- 符合用户习惯
|
||
|
||
### 2026-03-27 多学生支持(重要变更)
|
||
**问题**:原架构只支持单个学生的多图片批改,无法区分不同学生
|
||
|
||
**修复内容**:
|
||
1. **数据结构重构**:
|
||
- 输入参数:`homework_images` → `student_homework`(List[StudentHomework])
|
||
- 输出结果:`final_result` → `student_results`(List[StudentResult])
|
||
- 新增 `StudentHomework` 类型:包含 student_id 和 homework_images
|
||
- 新增 `StudentResult` 类型:包含 student_id 和批改结果
|
||
|
||
2. **处理逻辑优化**:
|
||
- 外层循环:遍历每个学生
|
||
- 内层循环:并行处理该学生的所有图片
|
||
- 每个学生独立计算分数、评语和等级
|
||
|
||
3. **返回结果独立**:
|
||
- 每个学生有自己的 overall_comment、total_score、full_score、grade
|
||
- 各学生的批改结果互不影响
|
||
|
||
**效果**:
|
||
- 支持批量批改多个学生的作业
|
||
- 每个学生的结果独立、清晰
|
||
- 符合实际教学场景需求
|
||
|
||
### 2026-03-27 Comment评语优化(重要)
|
||
**问题**:comment字段输出过于简单(仅"正确"/"错误")或输出思考过程,不符合"精练评语"要求
|
||
|
||
**修复内容**:
|
||
1. **明确comment定义**:
|
||
- **正确时**:简短说明为什么正确(如"根据称重法F浮=G-F示计算正确")
|
||
- **错误时**:指出错误原因并给出正确答案(如"应为1.2N,注意单位换算")
|
||
- **字数限制**:不超过comment_max_length字(默认100字)
|
||
- **禁止**:不输出思考过程、不输出详细解析
|
||
|
||
2. **提供comment示例**:
|
||
```
|
||
✅ 正确:根据称重法F浮=G-F示计算正确
|
||
✅ 正确:浮力产生原因理解正确
|
||
✅ 错误:应为1.2N,根据F浮=ρ液gV排计算
|
||
✅ 错误:应选ACE,控制变量法应用错误
|
||
|
||
❌ 错误:正确(过于简单)
|
||
❌ 错误:根据...(思考过程)...所以正确(包含思考过程)
|
||
```
|
||
|
||
3. **参数传递优化**:
|
||
- comment_max_length正确传递到Jinja2模板
|
||
- LLM根据该参数生成符合长度要求的评语
|
||
|
||
**效果**:
|
||
- comment既简洁又有意义
|
||
- 正确时说明原因,错误时指出问题
|
||
- 符合comment_max_length限制
|
||
- 无思考过程,无详细解析
|
||
|
||
### 2026-03-27 JSON解析健壮性优化(重要)
|
||
**问题**:
|
||
- LLM输出JSON包含思考过程,导致格式错误
|
||
- JSON太长(11946字符),解析失败
|
||
- annotations为空,无法识别题目
|
||
|
||
**修复内容**:
|
||
1. **新增extract_complete_objects函数**:
|
||
- 从包含思考过程的JSON中提取完整对象
|
||
- 按对象边界逐个提取,不受思考过程干扰
|
||
- 即使JSON格式错误,也能提取出有效数据
|
||
|
||
2. **新增clean_comment函数**:
|
||
- 检测思考过程特征词("不对"、"重新看"、"可能我"等)
|
||
- 在思考过程开始处截断comment
|
||
- 保留完整句子,确保结论清晰
|
||
|
||
3. **增加max_completion_tokens**:
|
||
- 从8192增加到16384,避免JSON被截断
|
||
- 确保完整输出所有题目
|
||
|
||
4. **优化Prompt**:
|
||
- 明确要求"禁止输出思考过程"
|
||
- comment只写结论:"正确"或"错误,应为X"
|
||
- 强调不要输出推理过程
|
||
|
||
**效果**:
|
||
- JSON解析成功率大幅提升
|
||
- 即使包含思考过程也能提取有效数据
|
||
- annotations不再为空
|
||
- comment简洁,无思考过程
|
||
|
||
### 2026-03-26 填空题拆分优化(重要)
|
||
**问题**:一道题有多个填空时,被合并成一个答案,批改标记无法精准定位
|
||
|
||
**修复内容**:
|
||
1. **优化Prompt**:
|
||
- 明确要求:一道题有多个填空时,**每个空单独识别为一个题目**
|
||
- 题号格式:\"3(1)第一空\"、\"3(1)第二空\"、\"4(2)第一空\"、\"4(2)第二空\"
|
||
- 每个空单独批改,单独打分
|
||
|
||
2. **示例说明**:
|
||
```
|
||
❌ 错误:3(1) → "4、1"(合并)
|
||
✅ 正确:3(1)第一空 → "4"
|
||
3(1)第二空 → "1"
|
||
```
|
||
|
||
3. **参数传递优化**:
|
||
- comment_max_length参数正确传递到Jinja2模板
|
||
- 确保LLM生成符合长度要求的comment
|
||
|
||
**效果**:
|
||
- 识别数量从9个增加到13个
|
||
- 每个填空都有独立的批改标记
|
||
- 批改标记精准定位到每个答案位置
|
||
|
||
### 2026-03-26 JSON解析优化(重要)
|
||
**问题**:LLM输出可能不完整(被max_completion_tokens截断),导致JSON解析失败
|
||
|
||
**修复内容**:
|
||
1. **新增fix_incomplete_json函数**:
|
||
- 自动检测缺失的括号(}和])
|
||
- 自动补全缺失的括号,使JSON完整
|
||
- 示例:`{"results": [{"id": 1}` → 自动补全为 `{"results": [{"id": 1}]}`
|
||
|
||
2. **增强JSON解析流程**:
|
||
- 第一步:尝试直接解析
|
||
- 第二步:尝试修复不完整的JSON(补全括号)
|
||
- 第三步:尝试提取JSON对象
|
||
- 第四步:尝试修复提取的JSON
|
||
|
||
3. **移除错误的截断逻辑**:
|
||
- 不再在解析后截断comment(可能破坏转义字符)
|
||
- 完全依赖LLM遵守comment_max_length限制
|
||
- 通过Prompt明确要求LLM控制comment长度
|
||
|
||
4. **参数正确传递**:
|
||
- comment_max_length参数正确传递到Prompt
|
||
- LLM根据该参数生成符合长度的comment
|
||
|
||
**效果**:
|
||
- JSON解析成功率大幅提升
|
||
- 能够处理不完整的JSON输出
|
||
- comment长度由LLM控制,避免截断破坏格式
|
||
|
||
### 2026-03-26 识别优化:禁止标注实验装置图(重要)
|
||
**问题**:
|
||
1. 在实验装置图(如弹簧测力计、烧杯等)上标注了批改气泡
|
||
2. 坐标定位不够精准
|
||
|
||
**修复内容**:
|
||
1. **Prompt优化**:
|
||
- 明确禁止标注实验装置图、示意图、电路图
|
||
- 明确禁止标注图中标注的字母(如A、B、C、D、E、F、G)
|
||
- 强调只标注学生手写答案
|
||
|
||
2. **工程规范优化**:
|
||
- 从config文件读取sp和up(符合工程规范)
|
||
- 使用Jinja2模板渲染Prompt
|
||
- 代码中只保留动态部分构建(标准答案、图片尺寸等)
|
||
|
||
3. **识别流程优化**:
|
||
- 找题号 → 找学生答案 → 框选答案 → 判断正误
|
||
- 强调学生答案的特征:手写、填写空白处、计算结果
|
||
|
||
**效果**:不再误标注实验装置图,只标注学生手写答案
|
||
|
||
### 2026-03-26 新增并行数量控制参数
|
||
**优化前**:硬编码并发数限制为3,不够灵活
|
||
**优化后**:添加max_concurrent参数,默认值10,用户可自定义
|
||
|
||
**具体优化**:
|
||
1. **新增参数**:`max_concurrent`(可选,默认10)
|
||
2. **修改位置**:
|
||
- `GraphInput.max_concurrent: Optional[int] = 10`
|
||
- `GlobalState.max_concurrent: int = 10`
|
||
- `ProcessImagesInput.max_concurrent: int`
|
||
3. **使用方式**:
|
||
```json
|
||
{
|
||
"homework_images": [...],
|
||
"max_concurrent": 5 // 最多同时处理5张图片
|
||
}
|
||
```
|
||
|
||
**效果**:用户可根据服务器性能和网络情况灵活调整并发数
|
||
|
||
### 2026-03-26 学科变更
|
||
**修改**:将所有"物理"改为"数学"
|
||
- 节点描述:物理作业 → 数学作业
|
||
- Prompt中的学科引用:物理 → 数学
|
||
- 配置文件说明更新
|
||
|
||
### 2026-03-25 多图片并行处理优化
|
||
**优化前**:多图片串行处理,总时间 = 单张图片时间 × 图片数量
|
||
**优化后**:多图片并行处理(并发数限制为3),总时间大幅缩短
|
||
|
||
**具体优化**:
|
||
1. **并行处理架构**:使用 `ThreadPoolExecutor` 并行调用子图处理每张图片
|
||
- 最多同时处理3张图片
|
||
- 结果按 `image_index` 正确排序,保证顺序一致性
|
||
2. **性能提升**:
|
||
- 3张图片:时间减少约66%(从3份时间 → 1份时间)
|
||
- 5张图片:时间减少约80%(从5份时间 → 约2份时间,分两批并行)
|
||
3. **质量保证**:
|
||
- 每张图片独立处理,互不影响
|
||
- 识别逻辑、批改逻辑完全相同,质量不受影响
|
||
|
||
### 2026-03-26 坐标定位修复(重要)
|
||
**问题**:坐标定位特别不准,批改标记位置错误
|
||
**原因**:Y坐标修正逻辑错误,导致坐标被错误缩放
|
||
|
||
**修复内容**:
|
||
1. **坐标系统重构**:从绝对坐标改为相对坐标(0-1000)系统
|
||
- AI返回相对坐标(0-1000),(0,0)为图片左上角,(1000,1000)为右下角
|
||
- 代码将相对坐标转换为绝对坐标:`绝对X = 相对X × width / 1000`,`绝对Y = 相对Y × height / 1000`
|
||
|
||
2. **Prompt优化**:
|
||
- 明确要求AI返回相对坐标(0-1000)
|
||
- 添加坐标系统说明和示例
|
||
|
||
3. **转换逻辑修正**:
|
||
- 移除错误的Y坐标修正(`Y × height_ratio`)
|
||
- 实现正确的相对坐标到绝对坐标转换
|
||
|
||
**效果**:坐标定位准确,批改标记位置正确
|
||
|
||
### 2026-03-26 题目和答案识别优化(重要)
|
||
**问题**:
|
||
1. 无法准确区分"题干"和"学生答案"
|
||
2. 批改气泡不在学生答案位置
|
||
3. 题干位置被误标注为答案
|
||
|
||
**修复内容**:
|
||
1. **Prompt重写**:
|
||
- 明确定义"题干"和"学生答案"的区别
|
||
- 强调只标注学生手写答案,不标注印刷体题干
|
||
- 添加识别流程指导
|
||
|
||
2. **坐标定位优化**:
|
||
- 自动计算mark_position:答案框右侧30像素,垂直居中
|
||
- 添加边界检查,确保不超出图片范围
|
||
- 不再依赖AI返回的mark_position(可能不准确)
|
||
|
||
3. **识别指导**:
|
||
- 题号识别:如1、2、3、(1)、(2)等
|
||
- 答案定位:学生手写内容(不是印刷体)
|
||
- bbox框选:准确框选学生答案区域
|
||
|
||
**效果**:更准确地区分题干和答案,批改气泡位置更精准
|
||
|
||
### 2026-03-25 批改速度优化
|
||
**优化前**:每张图片需要3次LLM调用(识别+批改+整体评价)
|
||
**优化后**:每张图片只需1次LLM调用
|
||
|
||
**具体优化**:
|
||
1. **合并识别和批改**:将`homework_recognize`和`correction_judge`合并为`recognize_and_correct`节点
|
||
- 识别题目、学生答案、坐标的同时进行批改
|
||
- 减少一次LLM调用,速度提升约50%
|
||
|
||
2. **简化整体评价**:不再调用LLM生成整体评价
|
||
- 使用规则直接生成评价内容
|
||
- 根据得分率和错误数量生成个性化评语
|
||
- 减少一次LLM调用
|
||
|
||
3. **子图节点精简**:从5个节点减少到4个节点
|
||
- 移除:homework_recognize、correction_judge
|
||
- 新增:recognize_and_correct(合并节点)
|
||
- 保留:image_preprocess、result_merge、wrap_result
|
||
|
||
**效果**:
|
||
- LLM调用次数:每张图片从3次减少到1次
|
||
- 预计批改时间减少约60%
|
||
|
||
### 2026-03-25 新增输入参数控制
|
||
1. **新增 `comment_max_length` 参数**:控制评语最大字数,默认100字
|
||
2. **新增 `grade_standards` 参数**:自定义评价等级标准
|
||
- 支持自定义各等级的最低得分率百分比
|
||
- 支持自定义各等级的描述
|
||
- 默认标准:A+(≥95%)、A(≥90%)、B(≥80%)、C(≥70%)、D(<70%)
|
||
3. **使用方式**:
|
||
```json
|
||
{
|
||
"homework_images": [...],
|
||
"comment_max_length": 50, // 评语最多50字
|
||
"grade_standards": {
|
||
"A+": {"min_percentage": 98, "description": "完美"},
|
||
"A": {"min_percentage": 90, "description": "优秀"},
|
||
...
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2026-03-25 评语优化与整体评价
|
||
1. **评语具体化**:批改评语要求具体说明对错原因
|
||
- 正确时:说明为什么正确
|
||
- 错误时:指出错误原因并给出正确答案
|
||
- 部分正确时:说明哪些对了哪些错了
|
||
- 字数限制:50字以内,最多不超过100字
|
||
- 不要显示思考过程,只输出结果
|
||
2. **评语示例**:
|
||
- 选择题正确:答案为B,与标准答案一致,正确。
|
||
- 填空题错误:答案应为8+√7和8-√7,学生只写了一个,不完整。
|
||
- 解答题正确:解题过程完整,步骤清晰,结果正确。
|
||
- 计算题错误:计算过程有误,正确答案是m=2,建议检查移项步骤。
|
||
3. **整体评价**:根据所有批改内容自动生成简短的整体评价
|
||
- 调用LLM生成个性化评价
|
||
- 评价不超过50字
|
||
- 包含主要问题或优点
|
||
- 给出简短建议
|
||
4. **HTML报告优化**:在统计总览后显示整体评价区域
|
||
|
||
### 2026-03-25 自动旋转功能
|
||
1. **新增横向图片自动旋转**:如果上传的图片宽度大于高度(横向图片),系统会自动旋转-90度使其变为纵向
|
||
2. **旋转时机**:在图像预处理阶段,下载图片后、缩放前进行旋转
|
||
3. **旋转方向**:逆时针旋转90度(rotate(-90)),确保文字方向正确
|
||
4. **日志记录**:添加详细的旋转日志,便于调试
|
||
|
||
### 2026-03-25 多图片批改功能
|
||
1. **新增多图片支持**:从单图片批改升级为支持多图片批量批改
|
||
2. **新增子图架构**:创建 `loop_graph.py` 封装单图片处理流程
|
||
3. **新增循环节点**:创建 `process_images_node.py` 循环调用子图处理每张图片
|
||
4. **重构状态定义**:
|
||
- `GraphInput.homework_image` → `homework_images: List[File]`
|
||
- 新增 `SubgraphState`、`SubgraphInput`、`SubgraphOutput` 子图状态
|
||
- 新增 `SingleImageResult` 单图片批改结果
|
||
- 新增 `FinalResult.image_results` 多图片结果列表
|
||
5. **重构HTML生成**:支持生成包含所有图片批改标注的HTML报告
|
||
6. **优化主图编排**:简化为三节点线性流程(doc_extract → process_images → html_generate)
|
||
|
||
### 2026-03-25 双模式批改机制
|
||
1. **新增智能降级逻辑**:优先使用标准答案,无标准答案时自动切换专业老师模式
|
||
2. **修改state.py**:`answer_doc_url`改为可选字段,支持不提供答案URL的场景
|
||
3. **升级correction_judge_node**:实现题目分离逻辑,有标准答案和无标准答案分别处理
|
||
4. **更新Prompt**:批改节点支持两种模式(标准答案模式 + 专业老师模式)
|
||
5. **优化doc_extract_node**:无URL时返回空列表,不中断工作流
|
||
|
||
### 2026-03-25 Word答案解析功能
|
||
1. 新增 `doc_extract_node` 节点:从Word文件(.docx)提取题干和标准答案
|
||
2. 使用 python-docx 提取 Word 文档内容
|
||
3. 并行处理架构:图像识别与答案解析同时进行
|
||
4. 基于 Word 中的标准答案进行精准批改
|
||
|
||
### 2026-03-25 OCR识别能力优化
|
||
1. 问题:识别节点把学生答案中的"8"错认成"9",导致误判
|
||
2. 优化识别节点prompt:增加OCR识别特别提示,强调区分8和9、6和0、1和7等相似字符
|
||
3. 效果:第7题正确识别为"8+√7,8-√7",满分通过
|
||
|
||
### 2026-03-25 批改能力升级
|
||
1. 升级批改节点模型:`doubao-seed-1-6-vision-250815` → `doubao-seed-2-0-pro-260215`
|
||
2. 原因:较小模型对选择题判断准确率不足
|
||
3. 效果:选择题判断准确率大幅提升,推理过程更严谨
|
||
|
||
### 2026-03-24 重构(学习豆包APP方式)
|
||
1. 从8个节点简化为5个节点(现调整为子图+主图架构)
|
||
2. 采用一体化识别:AI识别answer_bbox,代码计算mark_position
|
||
3. 实现精准坐标计算,Y坐标与答案垂直中心完美对齐
|
||
|
||
## TODO
|
||
- 提供真实的多页作业图片进行完整流程测试
|
||
- 优化HTML报告的图片展示布局
|
||
- 支持PDF格式答案文档
|