☰
基于DeepSeek与Dify的拍照解题全流程搭建指南
2026/10/26 8:58:48 网站建设 项目流程

1. 项目缘起与整体设计思路

拍照解题这个需求,最早是从家长辅导场景里冒出来的。孩子做完作业,家长拿手机拍一张,App 自动把题目识别出来,再给出解题步骤和答案——听起来简单,但真动手做,会发现它横跨了 OCR、大模型推理、工作流编排、服务部署四个完全不同的技术栈。我这次用 DeepSeek 加 Dify 搭了一套,从拍照到出解析全流程跑通,部署在一台 ECS 上,整体成本可控,效果也够用。

先说清楚这套东西是什么。DeepSeek在这里承担的是“大脑”角色,负责理解题目、推导过程、生成解析;Dify是“调度中枢”,把 OCR 识别、题目清洗、模型调用、结果格式化这些环节串成一条工作流;OCR负责把图片里的文字抠出来,是整条链路的入口;ECS就是跑这些服务的云主机。四者缺一不可,但真正决定体验上限的,是工作流怎么设计、提示词怎么写、OCR 结果怎么清洗。

为什么选 Dify 而不是自己写一套调度代码?我试过纯 Python 脚本串 DeepSeek API 的方案,能跑,但一旦要加“题目分类”“多轮追问”“结果缓存”这些功能,代码就会迅速膨胀成意大利面条。Dify 的可视化工作流把每个节点拆开,改一个环节不用动其他部分,调试时能单独看每个节点的输入输出,这对迭代速度的提升是实打实的。而且 Dify 自带知识库、变量管理、日志追踪,省掉大量脚手架代码。

为什么用 DeepSeek 而不是其他模型?数学题解析对推理能力要求高,DeepSeek 在中文理科题目上的表现,实测下来比同价位模型稳。它的 API 兼容 OpenAI 格式,接入 Dify 几乎零成本。价格方面,按 token 计费,一道题平均消耗 800 到 1500 token,成本可以忽略不计。

整套方案的适用人群很明确:想快速验证拍照解题产品的开发者、需要给现有教育类 App 加“拍照搜题”功能的团队、以及想学习 Dify 工作流编排的爱好者。不需要你精通深度学习,但得会基本的 Linux 操作、能看懂 JSON、知道 API 怎么调。下面我把整个搭建过程拆开讲,包括我踩过的坑和最后怎么绕过去的。

2. 核心组件选型与关键细节解析

2.1 OCR 环节:为什么我没用 Dify 内置的识别节点

Dify 社区版自带一个 OCR 节点,底层调的是通用文字识别接口。我一开始图省事直接用了,结果发现两个问题:一是数学公式里的上下标、分数线经常识别错,比如把 $x^2$ 认成 $x2$;二是手写体识别率明显下降,孩子写的“解”字稍微潦草一点就认不出来。后来我换成外挂一个专门的 OCR 服务,通过 HTTP 请求节点调用,识别率提升很明显。

具体做法是在 ECS 上单独跑一个 OCR 服务,用 PaddleOCR 的轻量版模型。选它是因为中文识别效果好、支持公式区域检测、而且能跑在 CPU 上,不需要额外买 GPU 实例。部署命令很简单:

pip install paddlepaddle paddleocr

然后写一个 Flask 接口包一层:

from flask import Flask, request, jsonify from paddleocr import PaddleOCR app = Flask(__name__) ocr = PaddleOCR(use_angle_cls=True, lang='ch') @app.route('/ocr', methods=['POST']) def recognize(): file = request.files['image'] img_path = '/tmp/upload.png' file.save(img_path) result = ocr.ocr(img_path, cls=True) texts = [line[1][0] for line in result[0]] return jsonify({'text': '\n'.join(texts)}) if __name__ == '__main__': app.run(host='0.0.0.0', port=8866)

这里有个细节:use_angle_cls=True一定要开,因为手机拍题经常有倾斜,不开角度分类的话识别结果会乱序。另外返回结果我做了换行拼接,保留原始排版信息,方便后续模型理解题目结构。

注意:PaddleOCR 首次运行会自动下载模型文件,大概 200MB 左右,确保 ECS 磁盘空间充足。如果网络环境受限,可以提前把模型文件放到~/.paddleocr/目录下。

2.2 DeepSeek 接入 Dify 的两种方式与选择依据

Dify 接入 DeepSeek 有两条路:一是用 Dify 内置的模型供应商配置,直接填 API Key;二是通过 HTTP 请求节点自己调 API。我两种都试过,最后选了内置方式,原因如下。

内置方式的好处是 Dify 会自动处理 token 计数、超时重试、流式输出,而且能在工作流里直接用“LLM 节点”,配置提示词和变量非常方便。缺点是灵活性稍差,比如你想改temperature之外的参数(如top_p、frequency_penalty),得去模型配置里改,不能每个节点单独设。

HTTP 请求方式则完全自由,你可以自己拼请求体,想传什么参数传什么。但代价是得自己处理错误码、自己解析返回的 JSON、自己实现流式输出。对于拍照解题这个场景,我只需要稳定的单轮推理,不需要流式,所以内置方式完全够用。

配置步骤:在 Dify 的“模型供应商”里选 OpenAI 兼容,Base URL 填https://api.deepseek.com/v1,API Key 填你自己的,模型名写deepseek-chat。保存后测试一下连通性,能返回结果就说明通了。

提示:DeepSeek 的 API 有速率限制,免费额度和付费额度的并发数不同。如果做压力测试,建议在 Dify 的模型配置里把“最大并发数”调低一点,避免触发限流导致工作流中断。

2.3 工作流编排的核心逻辑:从图片到解析的完整链路

整条工作流我拆成了六个节点,顺序如下:

  1. 开始节点:接收用户上传的图片文件。
  2. HTTP 请求节点:把图片转发给 OCR 服务,拿到识别文本。
  3. 代码节点:清洗 OCR 结果,去掉多余空行、合并断行、修正常见错字。
  4. LLM 节点:把清洗后的题目文本喂给 DeepSeek,生成解析。
  5. 代码节点:对模型输出做后处理,提取答案、步骤、知识点。
  6. 结束节点:返回结构化 JSON。

这个拆分方式的好处是每个环节可独立调试。比如 OCR 识别不准,我只需要看第二个节点的输出,不用跑完整条链路。代码节点用 Python 写,Dify 内置了运行环境,直接写函数就行。

清洗节点的代码我改了好几版,最终版逻辑是:先按行分割,去掉长度小于 2 的行(通常是噪点),然后把以“=”“+”“-”结尾的行和下一行合并(处理公式断行),最后用正则把连续空格替换成单个空格。这段代码不长,但效果立竿见影,模型解析准确率大概提升了 15%。

3. 实操过程与核心环节实现

3.1 ECS 环境准备与 Dify 部署

我用的 ECS 配置是 4 核 8G,Ubuntu 22.04,系统盘 40G。这个配置跑 Dify 加 OCR 服务绰绰有余,如果并发量不大,2 核 4G 也能凑合,但 OCR 识别时会吃内存,建议至少 4G。

Dify 部署用 Docker Compose,官方仓库直接拉:

git clone https://github.com/langgenius/dify.git cd dify/docker cp .env.example .env docker compose up -d

启动后默认监听 80 端口,浏览器访问 ECS 公网 IP 就能看到登录页。第一次登录要设置管理员账号,这个账号密码记好,后面所有配置都在这个后台做。

注意:Dify 的.env文件里有个CONSOLE_API_URL和APP_WEB_URL,如果你绑了域名,这两个要改成域名地址,否则上传图片时会报跨域错误。我一开始没改,卡了半小时才找到原因。

OCR 服务我单独跑在一个 Python 虚拟环境里,用nohup挂后台:

nohup python ocr_server.py > ocr.log 2>&1 &

这样 Dify 和 OCR 互不干扰,OCR 服务挂了也不影响 Dify 本身运行。

3.2 工作流各节点的详细配置与参数说明

开始节点:定义一个文件类型的变量,命名为question_image,必填。Dify 支持的文件类型包括图片、PDF、文本等,这里选图片。

HTTP 请求节点:方法选 POST,URL 填http://127.0.0.1:8866/ocr,Body 类型选form-data,Key 写image,Value 引用开始节点的question_image。这里有个坑:Dify 的文件变量传到 HTTP 节点时,默认是文件 ID 而不是文件内容,需要在节点配置里勾选“发送文件内容”,否则 OCR 服务收到的是个空文件。

代码节点(清洗):输入变量引用 HTTP 节点的响应体,Python 代码大致如下:

def main(ocr_text: str) -> dict: lines = ocr_text.split('\n') cleaned = [] buffer = '' for line in lines: line = line.strip() if len(line) < 2: continue if line.endswith(('=', '+', '-', '×', '÷')): buffer += line else: if buffer: cleaned.append(buffer + line) buffer = '' else: cleaned.append(line) if buffer: cleaned.append(buffer) result = ' '.join(cleaned) result = re.sub(r'\s+', ' ', result) return {'cleaned_text': result}

这段代码的核心思路是“断行合并”:如果一行以运算符结尾,说明公式没写完,要和下一行拼起来。实测下来,这个逻辑能修复大部分公式断裂问题。

LLM 节点:模型选 DeepSeek,提示词我打磨了很久,最终版如下:

你是一位经验丰富的理科老师,擅长用通俗易懂的方式讲解题目。 请根据以下题目内容,给出详细的解题步骤和最终答案。 题目:{{cleaned_text}} 要求: 1. 先判断题目类型(数学、物理、化学等) 2. 分步骤讲解,每步说明依据 3. 最终答案用【答案】标注 4. 如果题目信息不完整,指出缺失部分并给出可能的假设

这里temperature我设成 0.3,太低会死板,太高会胡编。max_tokens设 2000,足够覆盖大部分题目的解析长度。

代码节点(后处理):从模型输出里提取答案和步骤,用正则匹配【答案】后面的内容,然后把步骤按序号拆成数组。这样前端拿到的是结构化数据,渲染起来方便。

结束节点:定义输出变量,包括question_type、steps、answer、raw_output,返回 JSON。

3.3 联调与效果验证:我实际跑通的测试用例

部署完成后我拿了 50 道题做测试,涵盖小学算术、初中代数、高中物理。结果如下:

题目类型测试数量完全正确部分正确失败
小学算术201910
初中代数201631
高中物理10721

失败的案例主要出在 OCR 识别上,比如一道物理题里有电路图,OCR 只识别出文字没识别出图,模型就缺了关键信息。部分正确的案例多是模型计算过程对但最后一步算错,这种可以通过在提示词里加“请验算最终结果”来改善。

整体响应时间:从上传图片到返回结果,平均 4.2 秒。其中 OCR 占 1.5 秒,模型推理占 2.5 秒,其余是网络和调度开销。这个速度对于拍照解题场景来说可以接受,如果追求更快,可以把 OCR 换成更轻量的模型,或者把 DeepSeek 换成更小的版本。

4. 常见问题与排查技巧实录

4.1 OCR 识别不准的三种典型情况及应对

情况一:公式识别错误。比如把 $x^2$ 识别成 $x2$,把 $\frac{1}{2}$ 识别成 $1/2$。这是通用 OCR 的通病,解决办法是在清洗节点里加正则替换,把常见的错误模式修正过来。我整理了一个替换表:

错误模式修正为说明
x2x^2上标丢失
1/2\frac{1}{2}分数格式
<=\leq符号识别
>=\geq符号识别

情况二:手写体识别率低。这个没有太好的办法,只能建议用户拍照时尽量工整。如果产品化,可以在前端加一个“请确保字迹清晰”的提示。

情况三:图片倾斜导致文字乱序。前面提过,开use_angle_cls=True能解决大部分情况。如果还不行,可以在 OCR 之前加一个图像预处理步骤,用 OpenCV 做透视校正。

4.2 Dify 工作流调试的实用技巧

Dify 的调试功能很好用,但有几个隐藏技巧官方文档没写。第一,你可以在任意节点后面加一个“变量查看”节点,把中间结果输出到日志里,这样不用跑完整条链路就能看到每个环节的数据。第二,工作流支持“单节点运行”,右键点击某个节点选“运行到此节点”,Dify 会用上一次的输入数据跑一遍,省去重复上传图片的麻烦。第三,如果某个节点报错,错误信息会显示在节点右上角,鼠标悬停能看到详细堆栈,比翻日志快得多。

提示:Dify 的工作流版本管理比较弱,改之前建议先“另存为”一个副本,否则改坏了想回退很麻烦。我就吃过这个亏,一次误操作把调好的提示词覆盖了,只能重写。

4.3 DeepSeek API 调用失败的排查顺序

遇到 API 调不通,按这个顺序查:先看 API Key 有没有过期或余额不足,DeepSeek 后台能查用量;再看 Base URL 有没有写错,必须是https://api.deepseek.com/v1,少个/v1会 404;然后看模型名对不对,deepseek-chat和deepseek-reasoner是两个不同的模型,别搞混;最后看 ECS 的出网规则,有些安全组默认禁止外网访问,需要手动放行。

如果返回 429 错误,说明触发了速率限制,解决办法是在 Dify 的模型配置里把并发数调低,或者加一个重试机制。我在代码节点里加了个简单的指数退避重试,效果不错:

import time for i in range(3): try: response = call_api() break except RateLimitError: time.sleep(2 ** i)

4.4 成本控制与性能优化的经验

DeepSeek 的价格按 token 算,输入 1 元/百万 token,输出 2 元/百万 token。一道题平均输入 500 token、输出 800 token,成本大概 0.002 元。一天 1000 道题也就 2 块钱,非常便宜。但如果做压力测试,短时间内大量调用,费用会上去,建议在 Dify 里设一个每日限额。

性能方面,最大的瓶颈是 OCR。PaddleOCR 在 CPU 上跑一张图大概 1.5 秒,如果并发高,可以起多个 OCR 实例做负载均衡。Dify 本身支持多副本部署,但社区版配置稍麻烦,需要改 Docker Compose 文件。

另外,Dify 的工作流默认是同步执行,如果 OCR 或模型响应慢,用户会一直等。可以在前端加一个“正在识别”的 loading 状态,提升体验。如果要做异步,得改 Dify 的调用方式,用它的 API 触发工作流然后轮询结果,这个我还没试,后续可以研究。

5. 后续扩展方向与个人实操体会

这套方案跑通之后,我陆续加了一些扩展功能。一个是题目分类缓存:如果同一道题被多次上传,直接返回缓存结果,省掉 OCR 和模型调用。实现方式是在 Dify 前面加一个 Redis,用图片的 MD5 做 key。另一个是多轮追问:用户拿到解析后可以继续问“这一步为什么这样算”,Dify 的对话型工作流支持这个,把之前的解析作为上下文传进去就行。

还有一个方向是接入知识库。Dify 自带知识库功能,可以把教材、公式手册、常见题型解析导进去,让模型在生成解析时参考。这样对于超纲题或者冷门题型,模型能给出更靠谱的答案。我试过导入初中数学公式大全,效果有提升,但知识库的检索精度还需要调。

我个人在实际操作中的体会是,这套方案的上限不在模型,而在工作流的细节。OCR 清洗做得好,模型解析准确率能提升一大截;提示词写得清楚,模型输出就规整;后处理逻辑完善,前端拿到的数据就能直接用。反过来,如果这些环节偷懒,再强的模型也救不回来。另外,Dify 的社区版虽然免费,但有些功能(比如多租户、高级权限)需要付费版,个人用没问题,团队用要提前评估。

最后分享一个小技巧:调试提示词时,别在 Dify 界面里反复改,效率太低。我一般先在本地用 Python 脚本调 DeepSeek API,把提示词打磨好,再复制到 Dify 里。这样改一次只要几秒钟,比在网页上点来点去快得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询