PhysicsCorrection/AGENTS.md

41 KiB
Raw Blame History

项目概述

  • 名称: 初中数学作业批改工作流
  • 功能: 上传多学生的作业图片和Word答案文件自动识别学生答案、提取标准答案、精准批改并返回每个学生的批改结果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
}

输出结果

{
  "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_urlMD5哈希
  • 线程安全:使用锁保护并发访问
  • 异常安全:文件缓存失败时自动降级为纯内存模式
  • 统计功能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有错误按得分率判断

配置示例

{
  "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: 学生IDint
      • student_name: 学生姓名str可选
      • homework_images: 该学生的作业图片URL列表List[str],纯字符串数组)
  • answer_doc_url: 正确答案Word文件的URL.docx格式可选
  • comment_max_length: 评语最大字数默认100字可选
  • max_concurrent: 并行批改的最大数量默认10可选
  • grade_standards: 评价等级标准(可选,默认值如下)
    {
      "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: 学生IDint
      • 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编码不需要上传处理速度最快

最终逻辑

# 获取原始图片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自动适配原始图片尺寸

代码对比

# 优化前
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 顶部位置

代码对比

# 优化前
mark_x = answer_bbox[2] + 30  # 偏移过大

# 优化后
mark_x = answer_bbox[2] + 10  # 紧贴答案框

效果

  • 批改标记更贴近学生答案
  • 视觉定位更精准
  • 避免标记离答案太远的问题

2026-03-27 坐标边界严格限制(重要)

问题:标记坐标可能超过图片宽度,导致定位错误

修复内容

  1. 严格边界检查

    # 确保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→ 标记在右侧(原有逻辑)

    mark_x = answer_bbox[2] + 30
    mark_y = answer_bbox[1] + height * 0.5
    
  2. 策略2右侧空间不足50-100px→ 标记在答案框右上角内部

    mark_x = answer_bbox[2] - 20  # 内部
    mark_y = answer_bbox[1] + 15
    
  3. 策略3:右侧空间很小(<50px→ 标记在答案框左上角

    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_scorefull_scoreoverall_commentgrade 完全独立
    • 只使用该学生自己的 image_results 计算分数
  3. 核心代码

    # 返回元组:(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. 输入示例

    {
      "student_homework": [
        {
          "student_id": 0,
          "homework_images": ["url1", "url2"]
        }
      ]
    }
    

效果

  • 用户输入更简洁
  • 减少构造对象的复杂度
  • 符合用户习惯

2026-03-27 多学生支持(重要变更)

问题:原架构只支持单个学生的多图片批改,无法区分不同学生

修复内容

  1. 数据结构重构

    • 输入参数:homework_imagesstudent_homeworkList[StudentHomework]
    • 输出结果:final_resultstudent_resultsList[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. 使用方式
    {
      "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_recognizecorrection_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. 使用方式
    {
      "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_imagehomework_images: List[File]
    • 新增 SubgraphStateSubgraphInputSubgraphOutput 子图状态
    • 新增 SingleImageResult 单图片批改结果
    • 新增 FinalResult.image_results 多图片结果列表
  5. 重构HTML生成支持生成包含所有图片批改标注的HTML报告
  6. 优化主图编排简化为三节点线性流程doc_extract → process_images → html_generate

2026-03-25 双模式批改机制

  1. 新增智能降级逻辑:优先使用标准答案,无标准答案时自动切换专业老师模式
  2. 修改state.pyanswer_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+√78-√7",满分通过

2026-03-25 批改能力升级

  1. 升级批改节点模型:doubao-seed-1-6-vision-250815doubao-seed-2-0-pro-260215
  2. 原因:较小模型对选择题判断准确率不足
  3. 效果:选择题判断准确率大幅提升,推理过程更严谨

2026-03-24 重构学习豆包APP方式

  1. 从8个节点简化为5个节点现调整为子图+主图架构)
  2. 采用一体化识别AI识别answer_bbox代码计算mark_position
  3. 实现精准坐标计算Y坐标与答案垂直中心完美对齐

TODO

  • 修复线上部署环境图片访问问题(阿里云 OSS 图片 URL 返回 402002 错误)
  • 添加 URL 可访问性验证和 HTTP Headers 支持
  • 实现超时保护机制单任务120秒总任务按图片数量计算
  • 删除未使用的 S3 存储模块src/storage/s3/
  • 修复 LLM 认证错误,使用 new_context() 初始化 Context
  • 修复 Docker 环境空请求体错误,添加请求体验证
  • 提供真实的多页作业图片进行完整流程测试
  • 优化HTML报告的图片展示布局
  • 支持PDF格式答案文档

代码清理2026-03-30

删除未使用的 S3 存储模块

原因

  1. 工作流已优化为直接使用原始图片 URL不上传新图片到对象存储
  2. 不再需要 S3SyncStorage 类提供的文件上传功能
  3. 减少依赖,保持代码精简

删除内容

  • 删除 src/storage/s3/ 整个目录
    • __init__.py
    • s3_storage.py

确认

  • 无代码引用该模块
  • 删除后工作流正常运行
  • 测试通过

LLM 认证修复2026-03-30

问题诊断

线上部署环境出现认证错误:

Error code: 401 - AuthenticationError: the API key or AK/SK in the request is missing or invalid

原因分析

  • 使用 ctx = runtime.context 获取 Context 时,可能没有正确初始化认证信息
  • runtime.context 返回的对象可能与 LLM Client 预期的 Context 格式不匹配

解决方案

使用 new_context() 方法显式初始化 Context

# ❌ 修复前
def recognize_and_correct_node(...):
    ctx = runtime.context
    client = LLMClient(ctx=ctx)

# ✅ 修复后
def recognize_and_correct_node(...):
    from coze_coding_utils.runtime_ctx.context import new_context
    ctx = new_context(method="invoke")
    client = LLMClient(ctx=ctx)

修改文件

  • src/graphs/nodes/recognize_and_correct_node.py
  • src/graphs/nodes/doc_extract_node.py

效果

  • 认证错误已修复
  • LLM 调用正常
  • 测试通过

图片访问优化2026-03-28 重要)

问题诊断

线上部署环境出现 402002: S3对象不存在: <!DOCTYPE HTML> 错误,原因是:

  1. 阿里云 OSS 图片 URL 可能因防盗链或 HTTP Headers 问题导致访问失败
  2. Coze 平台的 LLM 服务尝试访问外部 URL 时可能受限
  3. 无效 URL 直接传给 LLM 导致解析错误

解决方案

  1. URL 格式验证recognize_and_correct_node.py

    • 检查 URL 是否以 http://https:// 开头
    • 拒绝非 HTTP/HTTPS 协议的 URL如 file://
  2. URL 可访问性验证recognize_and_correct_node.py

    • 在调用 LLM 之前,先尝试下载图片前 100 字节
    • 验证返回内容不是 HTML 404/500 页面
    • 验证图片尺寸合理(至少 10 字节)
    • 如果验证失败,返回空结果,不影响其他任务
  3. HTTP Headers 支持image_preprocess_node.py

    • 添加 User-AgentAccept 等 HTTP Headers
    • 模拟浏览器请求,兼容阿里云 CDN 等服务
    • 支持重定向302/301
  4. 超时保护process_images_node.py

    • 单任务超时120 秒
    • 总任务超时120 秒 × 图片数量
    • 超时任务返回空结果,不影响其他任务

代码示例

# URL 验证recognize_and_correct_node.py
if not image_url.startswith(('http://', 'https://')):
    logger.error(f"Invalid image URL format: {image_url}")
    return RecognizeAndCorrectOutput(question_items=[], correction_results=[])

# 可访问性验证recognize_and_correct_node.py
try:
    import urllib.request
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
    }
    req = urllib.request.Request(image_url, headers=headers)
    with urllib.request.urlopen(req, timeout=10) as response:
        preview = response.read(100)
        if b'<html' in preview.lower() or b'<!doctype' in preview.lower():
            raise ValueError("URL returned HTML page")
except Exception as e:
    logger.error(f"Image URL not accessible: {image_url}")
    return RecognizeAndCorrectOutput(question_items=[], correction_results=[])

# HTTP Headers 支持image_preprocess_node.py
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
    'Accept': 'image/webp,image/apng,image/*,*/*;q=0.8',
    'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
    'Referer': 'https://www.aliyun.com/',
    'Accept-Encoding': 'gzip, deflate, br',
    'Connection': 'keep-alive'
}

# 超时保护process_images_node.py
SINGLE_IMAGE_TIMEOUT = 120
future_to_task = {
    executor.submit(process_single_image, ...): task_info
    for task_info in all_tasks
}

for future in concurrent.futures.as_completed(
    future_to_task, 
    timeout=SINGLE_IMAGE_TIMEOUT * len(future_to_task)
):
    try:
        result = future.result(timeout=SINGLE_IMAGE_TIMEOUT)
    except concurrent.futures.TimeoutError:
        logger.error(f"Task timeout: {task_info}")
        result = (student_id, idx, empty_result())

效果

  • 无效 URL 被过滤,不传给 LLM
  • 不可访问的图片返回空结果,不影响其他任务
  • 兼容阿里云 CDN 等需要特殊 Headers 的服务
  • 超时任务自动跳过,避免长时间阻塞
  • 线上部署环境测试通过

Docker 环境请求验证修复2026-03-30

问题诊断

在 Docker 环境中运行时报错:

Invalid JSON format: Expecting value: line 1 column 1 (char 0)

原因分析

  1. 请求体为空(例如,使用 GET 请求而不是 POST
  2. Content-Type 不是 application/json
  3. 负载均衡器或代理可能过滤了请求体

解决方案

http_runhttp_stream_run 函数中添加请求体验证:

# 检查请求体是否为空
if not body_text or body_text.strip() == "":
    logger.error(f"Empty request body for run_id={ctx.run_id}")
    raise HTTPException(
        status_code=400, 
        detail="Request body is empty. Please provide a valid JSON payload."
    )

# 检查 Content-Type
content_type = request.headers.get("content-type", "")
if "application/json" not in content_type.lower():
    logger.error(f"Invalid Content-Type: {content_type}")
    raise HTTPException(
        status_code=400, 
        detail=f"Content-Type must be 'application/json', got: {content_type}"
    )

修改文件

  • src/main.py
    • http_run 函数:添加请求体验证
    • http_stream_run 函数:添加请求体验证

正确的请求格式

使用 curl

curl -X POST http://localhost:8000/stream_run \
  -H "Content-Type: application/json" \
  -d '{
    "student_homework": [
      {
        "student_id": 1,
        "student_name": "张三",
        "homework_images": ["https://example.com/image.jpg"]
      }
    ],
    "answer_doc_url": "",
    "comment_max_length": 50,
    "max_concurrent": 5
  }'

使用 Python requests

import requests

url = "http://localhost:8000/stream_run"
headers = {"Content-Type": "application/json"}
data = {
    "student_homework": [
        {
            "student_id": 1,
            "student_name": "张三",
            "homework_images": ["https://example.com/image.jpg"]
        }
    ],
    "answer_doc_url": "",
    "comment_max_length": 50,
    "max_concurrent": 5
}

response = requests.post(url, json=data, headers=headers)
print(response.json())

注意事项

  • 必须使用 POST 请求
  • Content-Type 必须是 application/json
  • 请求体必须是非空的 JSON 字符串
  • 不要使用 GET 请求
  • 不要发送空请求体
  • 不要省略 Content-Type 头

效果

  • 空请求体返回友好的错误信息
  • 错误的 Content-Type 返回明确的错误提示
  • 测试通过

Docker 环境 LLM 调用 404 错误诊断2026-03-31

问题诊断

在 Docker 环境部署时LLM 调用返回 404 错误:

HTTP Request: POST https://api.coze.cn/chat/completions HTTP/1.1 404 Not Found

根本原因:错误的 API 端点(缺少 /v1/ 前缀)

  • 错误端点:https://api.coze.cn/chat/completions
  • 正确端点:https://api.coze.cn/v1/chat/completions

解决方案

方案 1:更新代码和 SDK推荐

# 在 Docker 容器中执行
cd /workspace/projects
git pull
pip install --upgrade coze-coding-dev-sdk
docker restart <container_id>

方案 2:配置环境变量

export COZE_API_ENDPOINT="https://api.coze.cn/v1"

诊断工具

创建以下脚本帮助诊断问题:

  • scripts/test_llm_api.py:测试不同的 LLM API 端点
  • scripts/diagnose_docker_env.py:综合诊断 Docker 环境
  • docs/fix_llm_404_error.md:详细的修复指南

验证修复

修复后运行测试:

python scripts/test_llm_api.py
# 应该看到所有端点测试成功

修改文件

  • 创建:scripts/test_llm_api.py
  • 创建:scripts/diagnose_docker_env.py
  • 创建:docs/fix_llm_404_error.md
  • 更新:AGENTS.md(本文档)

关键检查点

  • SDK 版本是否最新(pip show coze-coding-dev-sdk
  • 代码是否包含 new_context()(检查关键节点文件)
  • 文件修改时间是否为最近(确保代码已更新)
  • LLM 调用测试是否通过(test_llm_api.py