From b7dffc397a9a88e4e7831eeac63d218747130c3f Mon Sep 17 00:00:00 2001 From: zhangquan Date: Mon, 30 Mar 2026 20:40:31 +0800 Subject: [PATCH] 1 --- .env | 1 + AGENTS.md | 46 +++---------------- config/comprehensive_correction_cfg.json | 2 +- config/correction_judge_llm_cfg.json | 4 +- config/doc_extract_llm_cfg.json | 2 +- config/homework_correction_cfg.json | 4 +- config/homework_recognize_llm_cfg.json | 4 +- config/question_locate_llm_cfg.json | 2 +- src/graphs/graph.py | 2 +- src/graphs/nodes/doc_extract_node.py | 2 +- src/graphs/nodes/image_preprocess_node.py | 26 ++++++++++- .../nodes/recognize_and_correct_node.py | 22 +++++++-- src/graphs/state.py | 2 +- src/utils/cache_manager.py | 3 +- 14 files changed, 66 insertions(+), 56 deletions(-) diff --git a/.env b/.env index eef00de..f833649 100644 --- a/.env +++ b/.env @@ -10,6 +10,7 @@ # LLM API 密钥(从火山引擎或OpenAI获取) LLM_API_KEY=eyJhbGciOiJSUzI1NiIsImtpZCI6ImRmZmU2NmYxLTg0MDMtNDc5Ni05ZmRhLTViMmJjZWExM2ViOCJ9.eyJpc3MiOiJodHRwczovL2FwaS5jb3plLmNuIiwiYXVkIjpbInRVaHJod1VDMmFHRmVSRjZBU01NNGxITjhoT01jOVA2Il0sImV4cCI6ODIxMDI2Njg3Njc5OSwiaWF0IjoxNzc0NjcwMTM1LCJzdWIiOiJzcGlmZmU6Ly9hcGkuY296ZS5jbi93b3JrbG9hZF9pZGVudGl0eS9pZDo3NjIxMzY5Mjg5OTI4MzQzNTY3Iiwic3JjIjoiaW5ib3VuZF9hdXRoX2FjY2Vzc190b2tlbl9pZDo3NjIyMTUwMTkzNTI1NjIwNzYyIn0.qIgzvuMGj976ekcmxZHIWlATn58PyPmFrsoYPeqTfSX4kdvkgeIXMEGR1NAX-OcOmBY_T8tvjcY2sxNDqRhLSQi-6teONHIYkTyXSrA_T5eJqaqrylFmzWPWzqX41LBsav5cyR0n4ffYzqLSd0-iAf8HdUyMEhVuTZuv-nGSpaQ-al98TqcrPtLqte71J1VbbAsjzFMrawTaSOe6WSiIQNe1qNDmsoyQpu_qdw5Sh_nMbPN8V5tjNFlX04pNV6O60M_Bqr1hDxAqY_fsUeECBhE5uG29cYawxc5oEb-xZF0vM_a0gvU5cw2jV1OktNXGJ6A2S9btRfyqoAc3fFqxmw +COZE_WORKLOAD_IDENTITY_API_KEY=eyJhbGciOiJSUzI1NiIsImtpZCI6ImRmZmU2NmYxLTg0MDMtNDc5Ni05ZmRhLTViMmJjZWExM2ViOCJ9.eyJpc3MiOiJodHRwczovL2FwaS5jb3plLmNuIiwiYXVkIjpbInRVaHJod1VDMmFHRmVSRjZBU01NNGxITjhoT01jOVA2Il0sImV4cCI6ODIxMDI2Njg3Njc5OSwiaWF0IjoxNzc0NjcwMTM1LCJzdWIiOiJzcGlmZmU6Ly9hcGkuY296ZS5jbi93b3JrbG9hZF9pZGVudGl0eS9pZDo3NjIxMzY5Mjg5OTI4MzQzNTY3Iiwic3JjIjoiaW5ib3VuZF9hdXRoX2FjY2Vzc190b2tlbl9pZDo3NjIyMTUwMTkzNTI1NjIwNzYyIn0.qIgzvuMGj976ekcmxZHIWlATn58PyPmFrsoYPeqTfSX4kdvkgeIXMEGR1NAX-OcOmBY_T8tvjcY2sxNDqRhLSQi-6teONHIYkTyXSrA_T5eJqaqrylFmzWPWzqX41LBsav5cyR0n4ffYzqLSd0-iAf8HdUyMEhVuTZuv-nGSpaQ-al98TqcrPtLqte71J1VbbAsjzFMrawTaSOe6WSiIQNe1qNDmsoyQpu_qdw5Sh_nMbPN8V5tjNFlX04pNV6O60M_Bqr1hDxAqY_fsUeECBhE5uG29cYawxc5oEb-xZF0vM_a0gvU5cw2jV1OktNXGJ6A2S9btRfyqoAc3fFqxmw # LLM API 基础URL # 火山引擎: https://ark.cn-beijing.volces.com/api/v3 diff --git a/AGENTS.md b/AGENTS.md index daf6841..42b9eb8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,5 +1,5 @@ ## 项目概述 -- **名称**: 初中物理作业批改工作流 +- **名称**: 初中数学作业批改工作流 - **功能**: 上传多学生的作业图片和Word答案文件,自动识别学生答案、提取标准答案、精准批改并返回每个学生的批改结果JSON ### 数据结构(重要变更) @@ -25,7 +25,6 @@ } ], "answer_doc_url": "答案文档URL(可选)", - "subject": "physics", "comment_max_length": 100, "max_concurrent": 10 } @@ -85,9 +84,7 @@ - 文件缓存:持久化存储,进程重启后仍可用 - **缓存有效期**:30天(自动清理过期缓存) - **缓存内容**:AI解析后的结构化数据(CorrectAnswer列表) -- **缓存键**:`{subject}:{answer_doc_url}`(MD5哈希) - - **学科隔离**:相同URL在不同学科下不会冲突 - - 示例:`physics:https://example.com/answer.docx` 和 `math:https://example.com/answer.docx` 是不同的缓存 +- **缓存键**:answer_doc_url(MD5哈希) - **线程安全**:使用锁保护并发访问 - **异常安全**:文件缓存失败时自动降级为纯内存模式 - **统计功能**:`get_stats()` 返回缓存统计信息 @@ -186,9 +183,6 @@ - `student_name`: 学生姓名(str,可选) - `homework_images`: 该学生的作业图片URL列表(List[str],纯字符串数组) - `answer_doc_url`: 正确答案Word文件的URL(.docx格式,**可选**) -- `subject`: 学科标识(str,**可选**,默认"physics") - - 用于缓存隔离,相同URL在不同学科下不会冲突 - - 支持值:physics、math、chinese、english 等 - `comment_max_length`: 评语最大字数(默认100字,**可选**) - `max_concurrent`: 并行批改的最大数量(默认10,**可选**) - `grade_standards`: 评价等级标准(**可选**,默认值如下) @@ -219,10 +213,10 @@ - 当提供了`answer_doc_url`且在文档中找到对应题目时 - 严格按照标准答案判断学生答案正误 -2. **降级方案**:使用专业物理老师批改 +2. **降级方案**:使用专业数学老师批改 - 场景1:未提供`answer_doc_url` - 场景2:提供了URL但文档中未找到对应题目 - - 使用专业物理老师的经验自主判断答案正误 + - 使用专业数学老师的经验自主判断答案正误 ### 功能说明 1. **多图片支持**:可上传多张作业图片,系统会并行处理每张图片(并发数限制为3) @@ -232,32 +226,6 @@ 5. **智能降级**:无标准答案时自动切换到专业老师模式 ## 优化记录 -### 2026-03-28 缓存键加入学科标识(重要) -**问题**:相同URL在不同学科下会使用相同的缓存,导致答案解析结果冲突 - -**修复内容**: -1. **新增 `subject` 参数**: - - 默认值:`physics` - - 支持值:physics、math、chinese、english 等 - -2. **修改缓存键生成逻辑**: - ```python - # 修改前 - cache_key = answer_doc_url - - # 修改后 - cache_key = f"{subject}:{answer_doc_url}" - ``` - -3. **缓存隔离效果**: - - `physics:https://example.com/answer.docx` - - `math:https://example.com/answer.docx` - - 两个缓存完全独立,不会冲突 - -**效果**: -- 相同URL在不同学科下可以有不同的解析结果 -- 缓存数据按学科隔离,更加灵活 - ### 2026-03-27 最终图片处理方案(重要) **问题**:如何在不上传图片的前提下,保证AI识别准确? @@ -681,9 +649,9 @@ mark_x = answer_bbox[2] + 10 # 紧贴答案框 **效果**:用户可根据服务器性能和网络情况灵活调整并发数 ### 2026-03-26 学科变更 -**修改**:将所有"数学"改为"物理" -- 节点描述:数学作业 → 物理作业 -- Prompt中的学科引用:数学 → 物理 +**修改**:将所有"物理"改为"数学" +- 节点描述:物理作业 → 数学作业 +- Prompt中的学科引用:物理 → 数学 - 配置文件说明更新 ### 2026-03-25 多图片并行处理优化 diff --git a/config/comprehensive_correction_cfg.json b/config/comprehensive_correction_cfg.json index d07d04c..519249e 100644 --- a/config/comprehensive_correction_cfg.json +++ b/config/comprehensive_correction_cfg.json @@ -7,6 +7,6 @@ "thinking": "disabled" }, "tools": [], - "sp": "你是一位专业的初中物理教师,负责批改学生的物理作业。", + "sp": "你是一位专业的初中数学教师,负责批改学生的数学作业。", "up": "请按照要求完成作业批改任务。" } \ No newline at end of file diff --git a/config/correction_judge_llm_cfg.json b/config/correction_judge_llm_cfg.json index 41fa597..acbcb85 100644 --- a/config/correction_judge_llm_cfg.json +++ b/config/correction_judge_llm_cfg.json @@ -12,6 +12,6 @@ "model": "doubao-seed-2-0-pro-260215" }, "tools": [], - "sp": "你是一位资深的初中物理特级教师,拥有20年以上教学经验,擅长精准批改学生的物理作业。\n\n【核心能力】\n1. **精确判断能力**:对选择题、填空题、解答题都能做出准确的正误判断\n2. **严谨推理能力**:能够逐步验证学生的计算过程和结论\n3. **双模式批改**:\n - **标准答案模式**:严格按照提供的标准答案判断(最优先)\n - **专业老师模式**:无标准答案时,凭借专业经验自主判断\n\n【批改原则】\n- 客观公正:严格按照标准答案判断,不主观臆断(有标准答案时)\n- 专业严谨:无标准答案时,使用专业知识验证学生答案\n- 肯定正确:如果学生答案正确,必须给予满分和肯定评语\n- 指出错误:如果学生答案错误,说明具体错误原因并给出正确答案\n\n【优先级规则】\n1. 最优先:使用提供的标准答案批改\n2. 降级:标准答案中未找到对应题目时,使用专业老师批改", - "up": "请批改以下学生的物理作业,判断每道题答案的正误并给出详细评语。" + "sp": "你是一位资深的初中数学特级教师,拥有20年以上教学经验,擅长精准批改学生的数学作业。\n\n【核心能力】\n1. **精确判断能力**:对选择题、填空题、解答题都能做出准确的正误判断\n2. **严谨推理能力**:能够逐步验证学生的计算过程和结论\n3. **双模式批改**:\n - **标准答案模式**:严格按照提供的标准答案判断(最优先)\n - **专业老师模式**:无标准答案时,凭借专业经验自主判断\n\n【批改原则】\n- 客观公正:严格按照标准答案判断,不主观臆断(有标准答案时)\n- 专业严谨:无标准答案时,使用专业知识验证学生答案\n- 肯定正确:如果学生答案正确,必须给予满分和肯定评语\n- 指出错误:如果学生答案错误,说明具体错误原因并给出正确答案\n\n【优先级规则】\n1. 最优先:使用提供的标准答案批改\n2. 降级:标准答案中未找到对应题目时,使用专业老师批改", + "up": "请批改以下学生的数学作业,判断每道题答案的正误并给出详细评语。" } diff --git a/config/doc_extract_llm_cfg.json b/config/doc_extract_llm_cfg.json index 3df9e51..5d1d3b3 100644 --- a/config/doc_extract_llm_cfg.json +++ b/config/doc_extract_llm_cfg.json @@ -12,6 +12,6 @@ "model": "doubao-seed-2-0-pro-260215" }, "tools": [], - "sp": "你是一位资深的初中物理教师,擅长从试卷中提取题目和标准答案。你的核心能力:\n\n1. **题目识别能力**:能够准确识别试卷中的所有题目,包括大题和小题\n2. **答案提取能力**:能够准确提取每道题的标准答案\n3. **结构化输出能力**:能够将提取的内容组织成结构化的JSON格式\n\n【提取原则】\n- 完整性:不遗漏任何题目\n- 准确性:答案提取要精确\n- 规范性:题号格式统一\n- 清晰性:题干和答案分离明确", + "sp": "你是一位资深的初中数学教师,擅长从试卷中提取题目和标准答案。你的核心能力:\n\n1. **题目识别能力**:能够准确识别试卷中的所有题目,包括大题和小题\n2. **答案提取能力**:能够准确提取每道题的标准答案\n3. **结构化输出能力**:能够将提取的内容组织成结构化的JSON格式\n\n【提取原则】\n- 完整性:不遗漏任何题目\n- 准确性:答案提取要精确\n- 规范性:题号格式统一\n- 清晰性:题干和答案分离明确", "up": "请从word内容中提取所有题目的题干和标准答案,返回JSON格式结果。" } \ No newline at end of file diff --git a/config/homework_correction_cfg.json b/config/homework_correction_cfg.json index df2b72a..6dbec78 100644 --- a/config/homework_correction_cfg.json +++ b/config/homework_correction_cfg.json @@ -7,6 +7,6 @@ "thinking": "disabled" }, "tools": [], - "sp": "# 角色定义\n你是一位专业的初中物理作业批改助手,具有丰富的物理教学经验和精准的视觉识别能力。你能够准确识别作业图片中的题目内容、学生答案,并判断答案的正确性。\n\n# 任务目标\n分析上传的初中物理作业图片,识别每道题目及其学生答案,判断答案是否正确,并输出结构化的批改结果JSON。\n\n# 工作流上下文\n- **Input**:作业图片(图片URL)\n- **Process**:\n 1. 仔细识别图片中的所有题目,包括题号、题目内容\n 2. 识别每道题的学生答案,注意区分小题(如(1)(2)(3))\n 3. 判断每个答案的正确性,对于解答题需要检查计算过程和结果\n 4. 为每个批改标记确定在原图上的相对坐标位置(批改标记应放置在答案末尾右侧)\n 5. 输出结构化的JSON结果\n- **Output**:包含所有批改结果的JSON对象\n\n# 约束与规则\n- 严格按照要求的JSON格式输出,不要添加任何额外文本\n- 坐标使用相对值(0-1000),(0,0)为图片左上角\n- 批改标记位置应在答案末尾的右侧,留出适当间距\n- 对于解答题,如果过程正确但结果有误,标记为错误\n- 如果答案部分正确,酌情判断\n- 图片宽高信息需要从图片本身获取\n- **重要**: explanation字段只能使用纯文本,禁止使用LaTeX公式或特殊符号\n\n# 过程\n1. 识别题目结构:扫描图片,定位所有题目,记录题号和小题号\n2. 答案识别:逐题识别学生的作答内容\n3. 正确性判断:\n - 对于计算题:检查计算过程和结果\n - 对于证明题:检查证明逻辑是否完整\n - 对于作图题:检查图形是否正确\n4. 坐标定位:确定每道题答案末尾的坐标位置\n5. 生成JSON:按要求格式输出结果\n\n# 输出格式\n仅返回如下格式的JSON对象(不要包含```json标记):\n{\n \"corrections\": [\n {\n \"question_number\": \"题号(如10)\",\n \"sub_question\": \"小题号(如(1)),无小题为空字符串\",\n \"is_correct\": true或false,\n \"bbox\": {\n \"topLeftX\": 左上角X坐标(相对值0-1000),\n \"topLeftY\": 左上角Y坐标(相对值0-1000),\n \"bottomRightX\": 右下角X坐标(相对值0-1000),\n \"bottomRightY\": 右下角Y坐标(相对值0-1000)\n },\n \"explanation\": \"简要批改说明(纯文本,禁止使用LaTeX)\"\n }\n ],\n \"image_width\": 图片宽度(像素),\n \"image_height\": 图片高度(像素)\n}", - "up": "请批改这张初中物理作业图片,识别所有题目和学生答案,判断正误并输出批改结果JSON。注意:explanation字段只能使用纯文本,禁止使用LaTeX公式。图片URL:{{image_url}}" + "sp": "# 角色定义\n你是一位专业的初中数学作业批改助手,具有丰富的数学教学经验和精准的视觉识别能力。你能够准确识别作业图片中的题目内容、学生答案,并判断答案的正确性。\n\n# 任务目标\n分析上传的初中数学作业图片,识别每道题目及其学生答案,判断答案是否正确,并输出结构化的批改结果JSON。\n\n# 工作流上下文\n- **Input**:作业图片(图片URL)\n- **Process**:\n 1. 仔细识别图片中的所有题目,包括题号、题目内容\n 2. 识别每道题的学生答案,注意区分小题(如(1)(2)(3))\n 3. 判断每个答案的正确性,对于解答题需要检查计算过程和结果\n 4. 为每个批改标记确定在原图上的相对坐标位置(批改标记应放置在答案末尾右侧)\n 5. 输出结构化的JSON结果\n- **Output**:包含所有批改结果的JSON对象\n\n# 约束与规则\n- 严格按照要求的JSON格式输出,不要添加任何额外文本\n- 坐标使用相对值(0-1000),(0,0)为图片左上角\n- 批改标记位置应在答案末尾的右侧,留出适当间距\n- 对于解答题,如果过程正确但结果有误,标记为错误\n- 如果答案部分正确,酌情判断\n- 图片宽高信息需要从图片本身获取\n- **重要**: explanation字段只能使用纯文本,禁止使用LaTeX公式或特殊符号\n\n# 过程\n1. 识别题目结构:扫描图片,定位所有题目,记录题号和小题号\n2. 答案识别:逐题识别学生的作答内容\n3. 正确性判断:\n - 对于计算题:检查计算过程和结果\n - 对于证明题:检查证明逻辑是否完整\n - 对于作图题:检查图形是否正确\n4. 坐标定位:确定每道题答案末尾的坐标位置\n5. 生成JSON:按要求格式输出结果\n\n# 输出格式\n仅返回如下格式的JSON对象(不要包含```json标记):\n{\n \"corrections\": [\n {\n \"question_number\": \"题号(如10)\",\n \"sub_question\": \"小题号(如(1)),无小题为空字符串\",\n \"is_correct\": true或false,\n \"bbox\": {\n \"topLeftX\": 左上角X坐标(相对值0-1000),\n \"topLeftY\": 左上角Y坐标(相对值0-1000),\n \"bottomRightX\": 右下角X坐标(相对值0-1000),\n \"bottomRightY\": 右下角Y坐标(相对值0-1000)\n },\n \"explanation\": \"简要批改说明(纯文本,禁止使用LaTeX)\"\n }\n ],\n \"image_width\": 图片宽度(像素),\n \"image_height\": 图片高度(像素)\n}", + "up": "请批改这张初中数学作业图片,识别所有题目和学生答案,判断正误并输出批改结果JSON。注意:explanation字段只能使用纯文本,禁止使用LaTeX公式。图片URL:{{image_url}}" } \ No newline at end of file diff --git a/config/homework_recognize_llm_cfg.json b/config/homework_recognize_llm_cfg.json index 53c28a2..a1b64a6 100644 --- a/config/homework_recognize_llm_cfg.json +++ b/config/homework_recognize_llm_cfg.json @@ -7,6 +7,6 @@ "thinking": "disabled" }, "tools": [], - "sp": "# 角色\n你是物理作业批改助手。\n\n# 禁止标注\n- 印刷体文字、实验装置图、图中字母、题干\n\n# 需要标注\n- 学生手写答案(仅答案区域)\n\n# 坐标系统(关键)\n- 使用相对坐标(0-1000),图片左上角为(0,0),右下角为(1000,1000)\n- answer_bbox: [x1, y1, x2, y2] 表示答案区域的边界框\n- x1,y1是左上角,x2,y2是右下角\n- **坐标必须精确框选学生手写答案区域**,不要包含题干\n- 答案框应紧贴手写内容,留5-10像素边距\n\n# 填空题处理(重要)\n- 一道题有多个填空时,**每个空单独识别为一个题目**\n- 题号格式:\"3(1)第一空\"、\"3(1)第二空\"或\"3.1\"、\"3.2\"\n- 每个空的坐标独立标注,只框选该空的答案\n\n# 空答案处理(必须遵守)\n- 如果学生没有作答(空白、只有涂改痕迹),必须判定为**incorrect**\n- status字段填写\"incorrect\"\n- score字段填写0\n- comment字段填写\"未作答\"\n\n# 批改准确性(核心)\n- **有标准答案时**:严格对照标准答案批改\n - 选择题:答案必须是单个字母(A/B/C/D)\n - 填空题:数值、单位、表达式必须完全匹配\n - 计算题:结果和单位都要正确\n- **无标准答案时**:根据物理知识判断\n - 公式应用是否正确\n - 计算过程是否合理\n - 单位是否正确\n\n# comment规范\n- **正确时**:简短说明原因(如\"浮力公式应用正确\")\n- **错误时**:指出错误并给出正确答案(如\"应为1.2N,注意单位换算\")\n- **空答案**:填写\"未作答\"\n- **字数限制**:不超过{{comment_max_length}}字\n- **禁止**:不要输出思考过程、不要输出详细解析\n\n# 输出格式\n{\"results\": [{\"question_id\": \"题号\", \"student_answer\": \"学生答案\", \"answer_bbox\": [x1, y1, x2, y2], \"status\": \"correct或incorrect\", \"score\": 得分, \"full_score\": 满分, \"comment\": \"精练评语\"}]}\n\n# comment示例\n- 正确:\"浮力公式F浮=ρ液gV排应用正确\"\n- 错误:\"应为1.2N,F浮=ρ液gV排=1.0×10³×10×1.2×10⁻⁴=1.2N\"\n- 空答案:\"未作答\"", - "up": "批改物理作业。**精确标注手写答案坐标**。**每个填空单独识别**。**comment写精练评语**。输出完整JSON。图片:{{image_url}}" + "sp": "# 角色\n你是数学作业批改助手。\n\n# 禁止标注\n- 印刷体文字、题干\n\n# 需要标注\n- 学生手写答案(仅答案区域)\n\n# 坐标系统(关键)\n- 使用相对坐标(0-1000),图片左上角为(0,0),右下角为(1000,1000)\n- answer_bbox: [x1, y1, x2, y2] 表示答案区域的边界框\n- x1,y1是左上角,x2,y2是右下角\n- **坐标必须精确框选学生手写答案区域**,不要包含题干\n- 答案框应紧贴手写内容,留5-10像素边距\n\n# 填空题处理(重要)\n- 一道题有多个填空时,**每个空单独识别为一个题目**\n- 题号格式:\"3(1)第一空\"、\"3(1)第二空\"或\"3.1\"、\"3.2\"\n- 每个空的坐标独立标注,只框选该空的答案\n\n# 空答案处理(必须遵守)\n- 如果学生没有作答(空白、只有涂改痕迹),必须判定为**incorrect**\n- status字段填写\"incorrect\"\n- score字段填写0\n- comment字段填写\"未作答\"\n\n# 批改准确性(核心)\n- **有标准答案时**:严格对照标准答案批改\n - 选择题:答案必须是单个字母(A/B/C/D)\n - 填空题:数值、单位、表达式必须完全匹配\n - 计算题:结果和单位都要正确\n- **无标准答案时**:根据数学知识判断\n - 解题思路是否正确\n - 计算过程是否合理\n - 结果是否正确\n\n# comment规范\n- **正确时**:简短说明原因(如\"解题步骤正确\")\n- **错误时**:指出错误并给出正确答案(如\"应为12,注意计算过程\")\n- **空答案**:填写\"未作答\"\n- **字数限制**:不超过{{comment_max_length}}字\n- **禁止**:不要输出思考过程、不要输出详细解析\n\n# 输出格式\n{\"results\": [{\"question_id\": \"题号\", \"student_answer\": \"学生答案\", \"answer_bbox\": [x1, y1, x2, y2], \"status\": \"correct或incorrect\", \"score\": 得分, \"full_score\": 满分, \"comment\": \"精练评语\"}]}\n\n# comment示例\n- 正确:\"解题步骤正确,答案准确\"\n- 错误:\"应为12,3×4=12\"\n- 空答案:\"未作答\"", + "up": "批改数学作业。**精确标注手写答案坐标**。**每个填空单独识别**。**comment写精练评语**。输出完整JSON。图片:{{image_url}}" } diff --git a/config/question_locate_llm_cfg.json b/config/question_locate_llm_cfg.json index 7c330c2..258736e 100644 --- a/config/question_locate_llm_cfg.json +++ b/config/question_locate_llm_cfg.json @@ -12,6 +12,6 @@ "model": "doubao-seed-2-0-pro-260215" }, "tools": [], - "sp": "你是一位专业的初中物理作业识别专家,擅长从作业图片中定位题目位置和提取答案区域。", + "sp": "你是一位专业的初中数学作业识别专家,擅长从作业图片中定位题目位置和提取答案区域。", "up": "请识别这张作业图片中的所有题目位置,返回准确的边界框坐标。" } \ No newline at end of file diff --git a/src/graphs/graph.py b/src/graphs/graph.py index b1f8397..6ea1242 100644 --- a/src/graphs/graph.py +++ b/src/graphs/graph.py @@ -1,4 +1,4 @@ -"""初中物理作业批改工作流主图编排 - 支持多图片批改""" +"""初中数学作业批改工作流主图编排 - 支持多图片批改""" from langgraph.graph import StateGraph, END from langchain_core.runnables import RunnableConfig from langgraph.runtime import Runtime diff --git a/src/graphs/nodes/doc_extract_node.py b/src/graphs/nodes/doc_extract_node.py index 5e26069..367a91d 100644 --- a/src/graphs/nodes/doc_extract_node.py +++ b/src/graphs/nodes/doc_extract_node.py @@ -213,7 +213,7 @@ def parse_answer_doc_with_llm(answer_doc_url: str, ctx, config: RunnableConfig) llm_config = _cfg.get("config", {}) - user_prompt = f"""你是一位资深的初中物理教师,请从以下试卷答案Word文档内容中提取所有题目的标准答案。 + user_prompt = f"""你是一位资深的初中数学教师,请从以下试卷答案Word文档内容中提取所有题目的标准答案。 【Word文档内容】 {doc_text[:20000]} diff --git a/src/graphs/nodes/image_preprocess_node.py b/src/graphs/nodes/image_preprocess_node.py index 5d520bf..85235ec 100644 --- a/src/graphs/nodes/image_preprocess_node.py +++ b/src/graphs/nodes/image_preprocess_node.py @@ -24,6 +24,23 @@ DEFAULT_IMAGE_SIZE = (1000, 1400) IMAGE_DOWNLOAD_TIMEOUT = 30 # 单次下载超时 MAX_RETRIES = 2 # 最大重试次数(减少重试) +# HTTP Headers(支持阿里云 CDN 等) +DOWNLOAD_HEADERS = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3', + 'Accept': 'image/webp,image/apng,image/*,*/*;q=0.8', + 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', +} + + +class HTTPRedirectHandler(urllib.request.HTTPRedirectHandler): + """自定义重定向处理器,保留 headers""" + def http_error_302(self, req, fp, code, msg, headers): + # 重定向时保留 headers + return super().http_error_302(req, fp, code, headers) + + def http_error_301(self, req, fp, code, msg, headers): + return super().http_error_301(req, fp, code, headers) + def get_image_info_with_retry(image_url: str, max_retries: int = MAX_RETRIES, timeout: int = IMAGE_DOWNLOAD_TIMEOUT) -> Tuple[int, int, int]: """ @@ -48,8 +65,15 @@ def get_image_info_with_retry(image_url: str, max_retries: int = MAX_RETRIES, ti break try: + # 创建带有 headers 的请求 + req = urllib.request.Request(image_url, headers=DOWNLOAD_HEADERS) + + # 创建 opener(支持重定向并保留 headers) + opener = urllib.request.build_opener(HTTPRedirectHandler) + urllib.request.install_opener(opener) + # 下载图片(带超时) - with urllib.request.urlopen(image_url, timeout=timeout) as response: + with urllib.request.urlopen(req, timeout=timeout) as response: img_data = response.read() # 检查数据大小 diff --git a/src/graphs/nodes/recognize_and_correct_node.py b/src/graphs/nodes/recognize_and_correct_node.py index c57d045..309ab08 100644 --- a/src/graphs/nodes/recognize_and_correct_node.py +++ b/src/graphs/nodes/recognize_and_correct_node.py @@ -184,7 +184,7 @@ def build_dynamic_prompt( 【标准答案】 {answers_text}""" else: - answer_hint = "\n【批改模式】无标准答案,请根据物理知识判断。" + answer_hint = "\n【批改模式】无标准答案,请根据数学知识判断。" return f""" 【图片尺寸】{image_width}×{image_height}像素 @@ -206,6 +206,23 @@ def recognize_and_correct_node( """ ctx = runtime.context + # 获取参数并验证图片 URL + image_url = state.image_url + if not image_url or not isinstance(image_url, str): + logger.error(f"Invalid image URL: {image_url}") + return RecognizeAndCorrectOutput( + question_items=[], + correction_results=[] + ) + + # 验证 URL 格式(必须是 http:// 或 https://) + if not image_url.startswith(('http://', 'https://')): + logger.error(f"Invalid image URL format: {image_url}") + return RecognizeAndCorrectOutput( + question_items=[], + correction_results=[] + ) + # 读取LLM配置 cfg_file = os.path.join(os.getenv("COZE_WORKSPACE_PATH", ""), config["metadata"]["llm_cfg"]) with open(cfg_file, "r", encoding="utf-8") as fd: @@ -215,8 +232,7 @@ def recognize_and_correct_node( sp = _cfg.get("sp", "") up = _cfg.get("up", "") - # 获取参数 - image_url = state.image_url + # 获取其他参数 image_info = state.image_info correct_answers = state.correct_answers comment_max_length = getattr(state, 'comment_max_length', 100) diff --git a/src/graphs/state.py b/src/graphs/state.py index 94568ce..af63cb5 100644 --- a/src/graphs/state.py +++ b/src/graphs/state.py @@ -1,4 +1,4 @@ -"""初中物理作业批改工作流状态定义 - 支持多学生多图片批改""" +"""初中数学作业批改工作流状态定义 - 支持多学生多图片批改""" from typing import List, Optional, Literal from pydantic import BaseModel, Field from utils.file.file import File diff --git a/src/utils/cache_manager.py b/src/utils/cache_manager.py index 2bc9df3..dd6eeca 100644 --- a/src/utils/cache_manager.py +++ b/src/utils/cache_manager.py @@ -272,8 +272,9 @@ def cached(cache_manager: CacheManager): # 创建全局缓存实例 +# 注意:缓存目录使用学科前缀,避免学科冲突 answer_doc_cache = CacheManager( - cache_name="answer_doc", + cache_name="math_answer_doc", # 使用数学专用缓存目录 maxsize=MAX_MEMORY_CACHE_SIZE, expire_days=CACHE_EXPIRE_DAYS )