
1. 项目缘起与整体设计思路拍照解题这个需求最早是从家长辅导作业的场景里冒出来的。孩子拍一张数学题照片系统自动识别题目、给出解题步骤和答案听起来简单但真正落地时会发现链路比想象中长图像预处理、文字识别、题目结构化、解题推理、答案格式化输出每一步都有坑。我这次用 DeepSeek 加 Dify 搭了一套完整的智能拍照解题工作流部署在 ECS 上从拍照到出答案控制在几秒内实测下来稳定性可以接受。先说清楚这套东西是什么。核心思路是把拍照解题拆成两个阶段感知阶段负责把图片变成干净的文字推理阶段负责把文字变成有步骤的解答。感知阶段用 OCR 完成推理阶段交给 DeepSeek 大模型。Dify 在这里扮演的是编排层的角色它把 OCR 服务、大模型调用、提示词模板、结果格式化这些环节串成一条可视化的工作流改起来不用动代码调参和换模型都很方便。为什么选 Dify 而不是自己写一套后端我试过纯代码方案用 FastAPI 把 OCR 和模型调用硬编码在一起前期跑得挺顺但后面想换 OCR 引擎、想调整解题提示词、想加一个只给答案不给步骤的开关时每次都要改代码重新部署。Dify 的工作流把每个环节做成独立节点改提示词就是改一个文本框换 OCR 就是换一个 HTTP 请求节点这种灵活性在快速迭代阶段太重要了。而且 Dify 自带日志和调试面板哪个节点慢了、哪个节点报错了一眼就能看到排查效率比翻服务器日志高得多。选 DeepSeek 的理由也很直接。解题这个任务对模型的推理能力要求高尤其是数学题需要模型能一步步推导而不是直接蒙答案。DeepSeek 在推理类任务上的表现配合它相对友好的调用成本做拍照解题这种高频调用的场景很合适。另外 DeepSeek 支持较长的上下文遇到题干很长的物理题、化学题也不会被截断。ECS 的选择上我用的是一台 4 核 8G 的通用型实例。OCR 服务本身不吃太多 CPU但如果用本地 OCR 引擎内存要给够Dify 社区版跑起来大概占 2G 左右内存加上 Docker 本身的开销8G 是比较稳妥的起步配置。如果并发量不大2 核 4G 也能跑但调试阶段容易因为内存不足触发 OOM反而浪费时间。整套方案的适用人群其实挺广想给孩子做个辅导工具的家长、想练手 AI 工作流的开发者、想验证 OCR 加 LLM 组合方案的产品经理都能从这套流程里拿到可复用的东西。下面我把每个环节拆开讲包括我踩过的坑和最后怎么解决的。2. 核心环节拆解与关键技术点2.1 OCR 识别拍照解题的第一道关卡OCR 是整条链路的入口它的质量直接决定后面模型能不能看懂题目。我一开始图省事直接用了一个通用的 OCR 接口结果发现两个问题一是数学公式里的符号识别不准比如把乘号识别成字母 x把分数线直接丢掉二是手写体识别率低孩子写的数字稍微潦草一点就认错。后来我调整了策略把 OCR 分成两步走。第一步用通用 OCR 把整张图里的文字全部提取出来得到一个粗糙的文本第二步针对数学场景做后处理把常见的识别错误做映射替换。比如把孤立的x在数字语境下替换成乘号把÷和/统一处理。这个后处理逻辑不复杂但效果立竿见影。关于 OCR 引擎的选择我对比过几种方案。云端 OCR 接口识别率高、接入快但按调用次数计费高频使用成本不低本地 OCR 引擎比如 PaddleOCR免费且可离线但需要自己部署和调优对服务器资源有一定要求。我最后用的是本地部署的 PaddleOCR配合自定义的后处理规则在 ECS 上跑起来内存占用大概 1.5G识别一张普通题目图片耗时在 1 秒左右。这里有个关键细节图片预处理。直接拿手机拍的原图去识别效果往往不好因为光线不均、角度倾斜、背景杂乱。我在 OCR 之前加了一个预处理节点做三件事灰度化、二值化、透视校正。灰度化是把彩色图转成黑白减少干扰二值化是把灰度图变成纯黑白让文字和背景对比更明显透视校正是把倾斜拍摄的图片拉正。这三步做完OCR 的识别率能提升不少。预处理可以用 OpenCV 实现代码不复杂但效果很明显。注意二值化的阈值不要设死不同光线条件下最优阈值不一样。我用的方法是先算图片的灰度直方图取双峰之间的谷值作为阈值这样自适应效果比固定阈值好很多。2.2 Dify 工作流编排把散件串成流水线Dify 的工作流是整个方案的中枢。它的核心价值在于把 OCR、模型调用、结果处理这些独立能力编排成一条有向无环图每个节点负责一件事节点之间通过变量传递数据。这种设计的好处是关注点分离调 OCR 的时候不用管模型怎么调改提示词的时候不用管 OCR 怎么配。我搭的工作流大致是这样的开始节点接收用户上传的图片然后进入图片预处理节点接着是 OCR 识别节点OCR 输出的文本进入一个条件判断节点判断识别结果是否为空或者明显异常。如果异常走一条分支返回请重新拍摄的提示如果正常进入 DeepSeek 解题节点最后经过结果格式化节点输出。这里重点说几个 Dify 工作流里的实操要点。第一变量传递要清晰。Dify 里每个节点的输出都可以被后续节点引用但引用的语法要写对比如{{ocr_node.text}}这种形式。我一开始因为变量名写错导致模型收到的是空字符串排查了半天才发现是引用路径的问题。第二条件分支要设好边界。判断 OCR 结果是否有效不能只看是否为空还要看文本长度是否过短、是否包含大量乱码字符。我设的规则是文本长度小于 5 个字符或者乱码字符占比超过 30%就判定为识别失败。第三超时和重试要配置。OCR 节点和模型节点都可能因为网络波动或服务负载出现超时Dify 允许给每个节点设置超时时间和重试次数。我给 OCR 节点设了 10 秒超时、重试 2 次给模型节点设了 30 秒超时、重试 1 次。这样偶发的网络抖动不会直接导致整个流程失败。2.3 DeepSeek 解题提示词设计让模型按步骤思考提示词是决定解题质量的关键。我试过很多版本最后稳定下来的提示词结构是这样的先给模型一个角色设定告诉它是一位耐心的老师然后给出解题要求包括分步骤解答、每步说明理由、最后给出答案接着是输出格式要求用固定的标记区分步骤和答案最后才是题目文本。为什么要把角色设定放在最前面因为 DeepSeek 这类模型对开头的指令比较敏感角色设定能引导它进入教学模式而不是简单地甩一个答案。我对比过有角色设定的版本解题步骤明显更详细也更愿意解释每一步的依据。输出格式这块我踩过坑。一开始没做格式约束模型有时候把答案写在步骤中间有时候用一堆花哨的符号前端解析起来很麻烦。后来我强制要求模型用固定的格式输出比如每一步用步骤N开头答案用最终答案开头。这样前端可以用简单的字符串匹配把步骤和答案分开渲染成不同的样式。还有一个细节是题目类型的判断。拍照解题可能遇到数学、物理、化学、英语等不同科目不同科目的解题逻辑不一样。我在提示词里加了一段让模型先判断题目类型再按对应科目的方法解答。比如数学题要写清楚公式和推导过程英语题要解释语法点和词汇。这个判断不需要额外调用模型就在同一个提示词里完成成本几乎不增加。提示提示词里的示例很重要。我给模型塞了两个示例一个数学题一个物理题展示期望的输出格式。有了示例之后模型输出格式的稳定性明显提升基本不需要再做后处理纠正。2.4 ECS 部署与网络配置让服务稳定跑起来ECS 上的部署我用的是 Docker Compose把 Dify、OCR 服务、数据库、缓存这些组件都容器化。这样做的好处是环境隔离每个服务有自己的依赖不会互相干扰。Dify 官方提供了 docker-compose 模板我在此基础上改了几处把 OCR 服务加进去调整了各服务的内存限制配置了 Nginx 做反向代理。Nginx 在这里的作用是统一入口和负载均衡。外部请求先到 NginxNginx 根据路径转发到 Dify 或者 OCR 服务。比如/api/workflow转发到 Dify/api/ocr转发到 OCR 服务。这样前端只需要知道一个地址不用关心后端有几个服务。Nginx 的 location 配置我调了几次主要是处理上传图片的大小限制和超时时间。默认的client_max_body_size是 1M手机拍的照片经常超过这个大小我改成了 10M。超时时间也从默认的 60 秒调到了 120 秒避免大图处理时连接被断开。ECS 的安全组配置也要注意。我只开放了 80 和 443 端口给外部访问数据库和缓存的端口只在内部网络开放。这样即使某个服务有漏洞也不会直接暴露到公网。另外我配了定期快照每天凌晨自动备份一次万一系统出问题可以快速回滚。3. 完整实操流程与关键步骤实现3.1 环境准备与依赖安装先列一下我用的环境ECS 实例是 4 核 8G操作系统 Ubuntu 22.04Docker 版本 24.0Docker Compose 版本 2.20。这些版本不是硬性要求但版本太老可能会遇到兼容性问题建议 Docker 至少 20.10 以上。第一步是装 Docker 和 Docker Compose。Ubuntu 上用 apt 装就行但要注意 apt 源里的 Docker 版本可能比较老我建议用官方脚本装。装完之后用docker --version和docker compose version确认一下。第二步是拉取 Dify 的代码。Dify 社区版在代码托管平台上有仓库直接 clone 下来就行。clone 之后进入 docker 目录里面有个.env.example文件复制成.env然后改配置。重点改这几个数据库密码、Redis 密码、Dify 的访问地址。访问地址要填 ECS 的公网 IP 或者域名不然前端调接口会跨域。第三步是部署 OCR 服务。我用的是 PaddleOCR 的 Docker 镜像拉下来之后写一个简单的 Flask 接口包一层对外提供/ocr接口接收图片返回识别文本。这个接口的代码不长核心就是调 PaddleOCR 的识别方法然后把结果拼成字符串返回。注意要处理一下异常比如图片格式不对、图片太大等情况返回明确的错误信息。第四步是配置 Nginx。在 ECS 上装 Nginx然后写一个配置文件把 80 端口的请求按路径转发到不同服务。配置写完后用nginx -t检查语法没问题就 reload。3.2 Dify 工作流搭建详细步骤登录 Dify 后台创建一个新的工作流应用。工作流编辑器是可视化的左边是节点面板中间是画布右边是节点配置。从开始节点开始。开始节点需要定义输入变量我定义了一个image变量类型是文件用来接收用户上传的图片。然后拖一个 HTTP 请求节点到画布上用来调 OCR 服务。这个节点的配置里URL 填 OCR 服务的地址方法选 POSTBody 里把image变量传过去。注意 Body 的格式要选 form-data因为传的是文件。OCR 节点后面接一个代码节点用来做文本后处理。Dify 的代码节点支持 Python我在这里写了前面提到的错误映射逻辑把常见的 OCR 识别错误纠正过来。代码节点的输入是 OCR 返回的原始文本输出是处理后的文本。代码节点后面接条件分支节点。条件设两个文本长度是否大于 5乱码占比是否小于 30%。两个条件都满足才走正常分支否则走异常分支。异常分支直接连到结束节点返回识别失败请重新拍摄。正常分支接 LLM 节点也就是调 DeepSeek 的地方。在 LLM 节点里选模型提供商为 DeepSeek填上 API Key然后写提示词。提示词里用{{text}}引用前面代码节点输出的文本。模型参数方面温度我设的 0.3因为解题需要稳定和准确不需要太多创造性最大 token 设的 2000足够容纳详细的解题步骤。LLM 节点后面再接一个代码节点用来解析模型输出把步骤和答案分开。模型输出是纯文本我用字符串匹配找到最终答案的位置前面的部分是步骤后面的部分是答案分别输出两个变量。最后是结束节点把步骤和答案两个变量返回给前端。整个工作流就搭完了点右上角的运行可以测试上传一张题目图片看每个节点的输出是否符合预期。3.3 前端对接与图片上传处理前端这块我做得比较简单就是一个 HTML 页面包含一个文件上传控件和一个显示结果的区域。用户选图片后前端把图片转成 FormDataPOST 到 Dify 的工作流接口。Dify 的工作流接口需要 API Key这个在 Dify 后台的应用设置里可以生成。图片上传前我做了压缩。手机拍的原图动辄好几 M直接上传慢而且浪费带宽。我用 Canvas 把图片最长边压到 1600 像素质量压到 0.8这样一张图大概 200-300K上传速度明显提升而且对 OCR 识别率几乎没有影响因为 OCR 需要的是文字清晰度不是像素数量。接口返回的是 JSON包含步骤和答案两个字段。前端拿到之后步骤部分按行分割每行渲染成一个段落答案部分加粗显示。整个页面不需要框架原生 JavaScript 就够了。注意Dify 的工作流接口是流式的如果前端用普通的 fetch 拿到的可能是流式响应。我一开始没注意解析 JSON 一直报错。后来改成用response.text()拿到完整文本再解析或者用支持流式的库处理。如果不需要流式效果可以在 Dify 应用设置里关掉流式输出。3.4 性能调优与成本控制性能方面瓶颈主要在 OCR 和模型调用两处。OCR 的优化空间在于图片预处理把图片质量提上去识别一次就能成功不用重试。模型调用的优化在于提示词提示词越精准模型输出的无用内容越少token 消耗越低。成本控制我做了两件事。一是缓存同样的题目图片如果重复上传直接返回缓存结果不重复调 OCR 和模型。缓存的 key 用图片的 MD5 值存在 Redis 里过期时间设 24 小时。二是限流每个用户每分钟最多调 10 次防止恶意刷接口。限流在 Nginx 层做用limit_req模块配置。实测下来一张普通数学题图片从上传到返回结果平均耗时 3-5 秒。其中图片预处理 0.3 秒OCR 1 秒左右模型推理 2-3 秒剩下的是网络传输和格式化。这个速度对于拍照解题场景是可以接受的用户拍完照等几秒看到答案体验还算流畅。4. 常见问题与排查技巧实录4.1 OCR 识别率低的排查思路OCR 识别率低是最常见的问题表现是模型收到的题目文本缺字、错字、乱码。排查的时候按这个顺序来先看原图质量如果图片本身模糊、光线暗、角度歪那 OCR 再强也没用得先解决拍摄问题再看预处理是否生效把预处理后的图片保存下来看一眼确认二值化和校正有没有做对最后看 OCR 引擎的配置不同引擎对不同字体和语言的识别率不一样可能需要换引擎或者调参数。我遇到过一次识别率突然下降的情况排查了半天发现是预处理里的二值化阈值被改错了导致图片变成全黑。这种问题看预处理后的图片就能立刻发现所以保留中间结果很重要Dify 的每个节点输出都可以在调试面板里看到排查时善用这个功能。4.2 模型输出格式不稳定的处理模型输出格式不稳定表现为有时候有步骤有时候没有有时候答案藏在步骤里。这个问题的根源通常是提示词不够明确。我的解决方法是加示例和加强制标记。示例让模型知道期望的输出长什么样强制标记让模型有明确的格式锚点。如果加了示例还是不稳定可以试试降低温度参数。温度越低模型输出越确定格式越稳定。我一般把温度设在 0.2 到 0.4 之间太低会导致答案死板太高会导致格式飘忽。还有一个技巧是后处理兜底。不管模型输出多稳定前端解析时都要做容错。比如找不到最终答案标记时就把最后一段当作答案步骤为空时就把整个输出当作答案。这样即使模型偶尔抽风前端也不会显示空白。4.3 Dify 工作流报错的常见原因Dify 工作流报错我遇到过的原因有这么几类变量引用错误比如引用了不存在的节点输出或者变量名拼写错误节点超时OCR 或模型响应太慢超过了节点设置的超时时间网络问题Dify 访问 OCR 服务或模型接口时网络不通资源不足ECS 内存不够导致容器被 kill。排查的时候先看 Dify 的日志日志里会显示哪个节点报错、错误信息是什么。如果是变量引用错误日志会提示找不到变量如果是超时日志会显示 timeout如果是网络问题日志会有连接失败的提示。根据日志定位到具体节点再针对性解决。提示Dify 的工作流支持单节点测试可以只运行某一个节点看它的输出。调试的时候不用每次都跑完整流程省时间。4.4 常见问题速查表问题现象可能原因排查方法解决方案OCR 返回空文本图片格式不支持或图片损坏检查图片能否正常打开转换图片格式为 JPG 或 PNGOCR 文本乱码多图片质量差或预处理参数不对查看预处理后的图片调整二值化阈值重新拍摄模型输出无步骤提示词不够明确检查提示词是否包含步骤要求加强提示词增加示例工作流执行超时节点超时设置过短或服务响应慢查看各节点耗时调大超时时间优化服务性能接口返回 401API Key 错误或过期检查 Dify 应用设置里的 Key重新生成 API Key图片上传失败图片超过大小限制检查 Nginx 的 client_max_body_size调大限制或压缩图片内存不足容器重启ECS 内存不够查看系统内存使用情况升级 ECS 配置或优化内存占用4.5 几个我踩过的坑和独家技巧第一个坑是中文编码问题。OCR 返回的文本如果包含中文在 Dify 节点之间传递时可能出现编码错误表现为乱码。解决方法是确保所有环节都用 UTF-8 编码包括 OCR 服务的返回头、Dify 的配置、数据库的字符集。这个问题隐蔽性强因为英文环境下不会出现只有中文才会触发。第二个坑是图片方向。手机拍的图片有时候带有 EXIF 方向信息浏览器显示的时候会自动旋转但 OCR 服务拿到的是原始数据可能方向是错的。解决方法是在预处理阶段读取 EXIF 信息根据方向标记旋转图片。这个坑我踩了很久才发现因为浏览器里看图片是正的但 OCR 识别出来全是乱的。第三个技巧是用 Dify 的变量聚合。如果工作流里有多个分支最后可以用变量聚合节点把不同分支的输出合并成一个这样结束节点只需要处理一种数据结构简化前端逻辑。第四个技巧是给模型加不确定选项。有些题目图片模糊或者题目本身有歧义模型硬答反而会给出错误答案。我在提示词里加了一条如果题目无法辨认或信息不足输出无法确定答案而不是强行解答。这样虽然有些题目得不到答案但避免了错误答案误导用户整体体验反而更好。5. 方案扩展与个人实践体会这套拍照解题工作流跑通之后我做了几个扩展尝试。一个是多科目支持在提示词里让模型先判断科目再解答数学、物理、化学、英语都能处理实测下来数学和物理效果最好因为这两科的题目结构比较规范OCR 识别率高模型推理也有明确的逻辑链。英语题因为涉及长文本和语法分析模型输出会偏长需要调大 token 限制。另一个扩展是错题本功能。把每次解题的记录存到数据库包括题目图片、识别文本、解题步骤、答案。用户可以回看历史记录也可以按科目筛选。这个功能不复杂但实用性很强尤其是给家长用的时候能清楚看到孩子哪些知识点薄弱。还有一个想法是接入知识库。Dify 支持知识库功能可以把教材、公式手册、常见题型解析这些资料导入知识库解题时让模型先检索知识库再解答。这样对于教材内的题目答案会更准确因为模型可以参考教材的原始表述。不过知识库的构建和维护需要投入精力我目前还在试验阶段效果好的话再单独写一篇分享。最后说几个个人体会。第一OCR 的质量决定上限。模型再强如果 OCR 识别出来的题目是错的答案必然错。所以在 OCR 上多花时间是值得的预处理、后处理、引擎选型每个环节都值得仔细调。第二提示词要迭代。我现在的提示词是改了十几版之后的结果每一版都是根据实际输出的问题调整的。不要指望一次写出完美的提示词要持续观察输出、持续优化。第三监控不能少。ECS 上的 CPU、内存、磁盘、网络都要监控Dify 的工作流执行日志要定期看及时发现潜在问题。我配了简单的告警内存超过 80% 或者工作流失败率超过 5% 就发通知这样不用天天盯着也能掌握系统状态。这套方案目前跑在我自己的 ECS 上日常给孩子辅导作业够用了。如果你也想搭一套建议先从最小可用版本开始一个 OCR 接口、一个 Dify 工作流、一个简单前端跑通之后再逐步加功能。不要一上来就追求大而全容易卡在某个环节失去信心。先把主链路跑通看到效果再慢慢优化这样推进起来更顺。