## 项目概述 - **名称**: 初中数学作业批改工作流 - **功能**: 上传多学生的作业图片和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格式答案文档