1. 从"给 API 打工"说起:为什么我决定自己搭一套 AI 平台
去年有段时间,我每个月的 API 账单都在四位数上下浮动。项目里跑的是文档摘要、知识库问答、代码辅助这几类任务,调用量不算夸张,但架不住单价摆在那里,再加上偶尔手滑写了个死循环,一晚上就能烧掉小几百。更让人难受的是,有些内部资料根本不敢往云端送——不是技术问题,是合规和保密的问题。那段时间我一直在想一件事:能不能把大部分日常推理放在本地,只在真正需要大模型能力的时候才走云端?
这个想法落地之后,就是标题里说的这套东西:Dify 做编排层,Ollama 做本地推理引擎,DeepSeek 做云端兜底。三者各司其职,Dify 负责工作流、知识库、对话管理这些"业务逻辑",Ollama 负责把开源模型跑在自己的机器上,DeepSeek 则作为能力上限的补充,处理本地模型搞不定的复杂任务。
这套架构解决的核心问题有三个。第一是成本可控,日常 80% 的请求走本地,只有 20% 的硬骨头才调用云端 API,账单直接砍掉一大半。第二是数据可控,敏感文档在本地完成向量化和推理,不出内网。第三是能力可控,本地模型能力不足时能无缝切换到云端,不会因为本地模型"答不上来"就卡住整个流程。
适合谁来参考?如果你满足下面任意一条,这篇内容应该对你有用:手上有闲置的显卡或者一台配置还行的机器;团队有内部文档问答、知识库检索的需求;被 API 账单折磨过;或者单纯想搞清楚 Dify、Ollama、DeepSeek 这三样东西到底怎么串起来。我会把选型逻辑、部署步骤、踩过的坑、以及"本地优先、云端兜底"这个策略具体怎么配置,全部讲清楚。
需要提前说明的是,这套方案不是"一键部署"的玩具,中间涉及 Docker、模型量化、路由策略这些需要动手的环节。但只要你跟着走一遍,后面维护起来其实很省心。我自己这套环境跑了小半年,除了偶尔更新模型,基本没怎么动过。
2. 三层架构的选型逻辑:为什么是 Dify + Ollama + DeepSeek
2.1 Dify 在这套体系里到底扮演什么角色
很多人第一次接触 Dify,会把它当成"又一个聊天界面"。这就把它看小了。Dify 真正的价值在于它是一个LLM 应用编排平台——你可以用可视化工作流把"接收问题 → 检索知识库 → 组装提示词 → 调用模型 → 后处理 → 返回结果"这一整条链路串起来,而且每个环节都能替换。
在这套架构里,Dify 承担的是"大脑皮层"的角色。它不负责推理,负责的是决定谁来推理、用什么上下文推理、推理完怎么处理。具体来说:
- 知识库管理:文档上传、切分、向量化、检索,Dify 内置了完整的 RAG 流水线。你不需要自己写 LangChain 代码,配置一下就能用。
- 工作流编排:条件分支、循环、代码节点、HTTP 请求节点,这些让"本地优先、云端兜底"的策略可以真正落地成逻辑。
- 模型接入层:Dify 支持接入 OpenAI 兼容接口的模型。Ollama 暴露的就是 OpenAI 兼容接口,DeepSeek 官方 API 也是 OpenAI 兼容格式,所以两者可以挂在同一个 Dify 实例下,用同一个工作流调度。
- 对话与应用管理:多轮对话、变量、会话记忆这些,Dify 都帮你处理了。
我选 Dify 而不是自己用 FastAPI 撸一套,核心原因是省时间。自己写一套 RAG + 工作流引擎,光是文档切分策略、向量检索调优、会话管理这些就够折腾一两个月,而且 bug 一堆。Dify 把这些都做成了开箱即用的模块,我只需要关注业务逻辑。
2.2 Ollama 为什么比直接跑 llama.cpp 更省事
本地推理引擎的选择其实不少:llama.cpp、vLLM、Text Generation Inference、Ollama。我最后选 Ollama,理由很实际。
llama.cpp 性能确实好,但你要自己编译、自己管理模型文件、自己写 HTTP 服务包装。vLLM 吞吐量高,但显存要求也高,而且对消费级显卡的兼容性不如 Ollama 友好。Ollama 的优势在于它把模型下载、量化版本管理、服务暴露、GPU 调度这些脏活全包了。
一条ollama run qwen2.5:7b就能把模型拉下来跑起来,它自动根据你的硬件选择合适量化的版本。而且 Ollama 默认在11434端口暴露 OpenAI 兼容接口,Dify 直接填http://host.docker.internal:11434/v1就能接上,几乎零配置。
当然 Ollama 也有它的短板,后面讲踩坑的时候我会细说,比如并发能力弱、长上下文处理吃内存、模型切换有延迟。但对于个人和小团队场景,它的性价比是最高的。
2.3 DeepSeek 作为兜底层,选它的理由和边界
云端兜底为什么选 DeepSeek 而不是别的?三个原因。
第一是价格。DeepSeek 的 API 定价在同类里属于相当能打的,尤其是输入侧的价格,做知识库问答这种"输入长、输出短"的场景特别划算。第二是能力,它在中文理解、代码、推理这几块的表现,应付本地小模型搞不定的任务是够的。第三是接口兼容,OpenAI 格式,Dify 接入零成本。
但要注意边界:DeepSeek 是兜底,不是主力。如果所有请求都走它,那这套架构就失去意义了。所以关键在于路由策略——什么情况下走本地,什么情况下切云端。这个我后面会用具体的工作流配置来讲。
2.4 三者组合的架构全景
把这三层串起来,数据流是这样的:
| 层级 | 组件 | 职责 | 部署位置 |
|---|---|---|---|
| 编排层 | Dify | 工作流、知识库、对话管理、路由决策 | Docker 容器 |
| 本地推理层 | Ollama | 跑开源模型,处理日常请求 | 宿主机或独立容器 |
| 云端兜底层 | DeepSeek API | 处理复杂任务、长上下文、高难度推理 | 云端 |
| 存储层 | PostgreSQL + Redis + 向量库 | Dify 的元数据、缓存、向量索引 | Docker 容器 |
请求进来之后,Dify 先做意图判断和知识库检索,然后根据预设规则决定走 Ollama 还是 DeepSeek。简单问答、文档摘要、格式转换这类走本地;复杂推理、超长上下文、本地模型明确答不好的走云端。返回结果统一由 Dify 后处理,用户侧感知不到背后换了模型。
这套架构的精髓在于降级和兜底是自动的。本地模型超时或者返回质量不达标,工作流可以自动重试到云端,用户那边只是稍微慢一点,不会报错。
3. 部署实操:从 Docker 到模型跑通
3.1 Docker 环境准备与 Dify 部署
Dify 官方推荐用 Docker Compose 部署,这是最省事的方式。前提是你机器上装了 Docker 和 Docker Compose。Windows 用户装 Docker Desktop 就行,Mac 用户同理,Linux 用户直接装 docker-ce 和 docker-compose-plugin。
拉代码:
git clone https://github.com/langgenius/dify.git cd dify/docker cp .env.example .env然后关键一步,改.env里的配置。默认配置能跑,但有几个地方建议调整:
# 暴露端口,默认 80,如果被占用改成别的 EXPOSE_NGINX_PORT=8080 # 数据库密码,生产环境务必改 POSTGRES_PASSWORD=your_strong_password # 向量库选择,默认 weaviate,也可以换 qdrant VECTOR_STORE=weaviate改完直接起:
docker compose up -d第一次拉镜像会比较慢,耐心等。起来之后访问http://localhost:8080,会让你设置管理员账号。这一步做完,Dify 本体就 OK 了。
注意:Docker Desktop 在 Windows 上默认给容器的内存有限,Dify 全家桶(api、worker、web、db、redis、weaviate、nginx)跑起来大概要 4-6G 内存。如果你机器内存紧张,建议在 Docker Desktop 设置里把内存上限调到 8G 以上,否则容器会莫名其妙被 OOM kill。
3.2 Ollama 安装与模型拉取的加速技巧
Ollama 的安装,Linux 一条命令:
curl -fsSL https://ollama.com/install.sh | shWindows 和 Mac 直接下安装包。装完之后验证:
ollama --version接下来是拉模型。这里有个大坑:默认从官方源拉模型,国内速度可能慢到怀疑人生。一个 7B 的模型几个 G,慢的时候能拉一晚上。
解决办法有两个。一是用离线安装包,Ollama 的模型文件本质上是 GGUF 格式,你可以从其他渠道下载好 GGUF 文件,然后用 Modelfile 导入:
# 创建一个 Modelfile cat > Modelfile << 'EOF' FROM ./qwen2.5-7b-instruct-q4_k_m.gguf PARAMETER temperature 0.7 PARAMETER num_ctx 8192 EOF # 导入 ollama create qwen2.5-local -f Modelfile二是配置镜像源。Ollama 支持通过环境变量指定模型仓库地址,具体配置方式根据你使用的镜像服务而定,这里不展开。核心思路就是别硬扛官方源的龟速,提前准备好模型文件能省掉大量等待时间。
模型选择上,我的建议是:
| 场景 | 推荐模型 | 显存需求 | 说明 |
|---|---|---|---|
| 日常问答、摘要 | qwen2.5:7b | 6-8G | 中文好,速度快 |
| 代码辅助 | qwen2.5-coder:7b | 6-8G | 代码专用,补全强 |
| 轻量任务 | qwen2.5:3b | 4G | 配置低的机器用 |
| 复杂推理 | deepseek-r1:14b | 12G+ | 本地推理天花板 |
拉模型:
ollama pull qwen2.5:7b ollama pull qwen2.5-coder:7b拉完测试一下:
ollama run qwen2.5:7b "用一句话解释什么是RAG"能正常返回就说明本地推理通了。
3.3 让 Dify 连上 Ollama:那个经典的连接问题
这是最容易卡住的一步。Dify 跑在 Docker 容器里,Ollama 跑在宿主机上,容器默认是访问不到宿主机的localhost的。
解决方案是在 Dify 的模型配置里,把 Ollama 的地址填成:
http://host.docker.internal:11434host.docker.internal是 Docker 提供的一个特殊域名,指向宿主机。Linux 上如果这个域名不生效,可以在docker-compose.yml里给 api 和 worker 服务加一行:
extra_hosts: - "host.docker.internal:host-gateway"然后在 Dify 后台:设置 → 模型供应商 → Ollama,填上地址,模型名称填qwen2.5:7b,保存。如果连接成功,会显示模型列表。
注意:Ollama 默认只监听
127.0.0.1:11434,容器访问不到。需要设置环境变量OLLAMA_HOST=0.0.0.0:11434让它监听所有网卡。Linux 上改 systemd 服务文件,Windows 上在系统环境变量里加。
3.4 DeepSeek API 接入与密钥管理
DeepSeek 的接入就简单多了。去官网申请 API Key,然后在 Dify 的模型供应商里选 OpenAI 兼容,填:
- Base URL:
https://api.deepseek.com/v1 - API Key: 你的 key
- 模型名:
deepseek-chat或deepseek-reasoner
保存即可。
这里有个常见的报错:unexpected status 401 unauthorized: incorrect api key provided。这个错误 90% 的情况是 key 填错了,或者 key 前后有空格。还有一种情况是你复制的时候把sk-前缀漏了。检查一遍基本能解决。
密钥管理上,千万别把 key 硬编码在工作流的代码节点里。Dify 支持环境变量,把 key 放在环境变量里,工作流里引用变量名。这样迁移和分享工作流的时候不会泄露。
4. "本地优先、云端兜底"的路由策略怎么落地
4.1 判断该走本地还是云端的几个维度
这是整套架构的灵魂。路由策略设计得好,成本和体验都能兼顾;设计得烂,要么本地模型被滥用导致体验差,要么云端调用过多导致账单爆炸。
我用的判断维度有四个:
第一,任务类型。文档摘要、格式转换、简单问答、关键词提取这类"模式化"任务,本地模型完全够用。复杂推理、多步计算、代码调试、长文档深度分析,这些交给云端。
第二,输入长度。本地模型的上下文窗口有限,qwen2.5:7b 默认 8K,超过这个长度要么截断要么报错。所以输入超过一定阈值(我设的是 6000 token)就直接走云端。
第三,本地模型置信度。这个稍微复杂一点。我让本地模型在回答时附带一个自评,如果它明确表示"不确定"或者回答明显敷衍,就触发云端重试。
第四,用户显式指定。有些场景用户就是想要最好的结果,那就直接走云端,不用绕。
4.2 用 Dify 工作流实现条件路由
在 Dify 里新建一个工作流,结构大概是这样:
开始节点 ↓ 知识库检索节点(可选) ↓ 条件分支节点(判断输入长度、任务类型) ↓ ├─ 分支A:本地模型(Ollama) │ ↓ │ 质量检查节点 │ ↓ │ ├─ 合格 → 输出 │ └─ 不合格 → 转分支B │ └─ 分支B:云端模型(DeepSeek) ↓ 输出条件分支的配置,用 Dify 的表达式:
{{#start.query#}} 的长度 > 6000 或者 {{#start.task_type#}} == "complex"满足就走云端,否则走本地。
质量检查节点可以用一个代码节点实现,判断本地模型的输出是否包含"我不确定""无法回答"这类关键词,或者输出长度是否异常短。命中就标记为不合格。
4.3 兜底触发条件与降级处理
兜底不只是"本地答不好就切云端",还要考虑本地服务本身挂掉的情况。我遇到过几次 Ollama 进程被系统 OOM kill 的情况,这时候如果工作流还傻傻地等本地模型,用户就会一直卡着。
所以我在工作流里加了超时控制。Dify 的模型调用节点可以设置超时时间,我设的是 30 秒。超过 30 秒没返回,直接走云端分支。
降级处理的逻辑:
| 触发条件 | 处理方式 |
|---|---|
| 本地模型超时(>30s) | 切云端 |
| 本地模型返回质量不合格 | 切云端 |
| 输入超长(>6000 token) | 直接走云端 |
| 本地服务不可用 | 切云端并记录告警 |
| 云端也不可用 | 返回友好提示,建议稍后重试 |
这套逻辑跑下来,用户侧基本感知不到背后的切换,只是偶尔响应慢一点。
4.4 实测数据:本地和云端的成本与延迟对比
我拿实际跑的数据做了个对比,任务类型是"500 字文档摘要",跑 100 次取平均:
| 指标 | Ollama (qwen2.5:7b) | DeepSeek API |
|---|---|---|
| 平均延迟 | 3.2 秒 | 2.1 秒 |
| 单次成本 | 约 0.002 元(电费) | 约 0.015 元 |
| 质量评分(主观) | 7.5/10 | 9/10 |
| 并发能力 | 弱(1-2 并发) | 强 |
延迟上云端反而更快,因为本地显卡不是顶级卡。但成本上本地优势明显,100 次摘要本地花 2 毛,云端花 1 块 5。量大了差距就出来了。
所以我的策略是:质量要求不高的批量任务走本地,质量要求高的走云端。比如批量给文档打标签,本地跑;给客户生成正式报告,走云端。
5. 踩坑实录:那些让我熬夜的报错
5.1 Dify 安装时的 SSL 错误与网络问题
第一次装 Dify 的时候,docker compose up卡在拉镜像那一步,报 SSL 错误。这个问题的根源是 Docker 拉镜像走的是默认源,网络不稳定。
解决办法是配置镜像加速。在 Docker Desktop 的设置里,或者 Linux 的/etc/docker/daemon.json里加:
{ "registry-mirrors": [ "https://your-mirror.example.com" ] }改完重启 Docker 服务。具体用哪个镜像源,根据你所在网络环境选择可用的。
还有一个坑是 Dify 的 web 容器启动后访问白屏。这个通常是 nginx 配置或者前端资源加载问题,检查docker compose logs nginx和docker compose logs web能看到具体报错。多数情况是端口冲突或者.env里CONSOLE_API_URL配置不对。
5.2 Ollama 模型加载失败与显存不足
ollama run qwen3.5:2b报500 internal server error: llama-server process,这个错误我遇到过好几次。原因通常是:
- 显存不够。模型加载需要显存,如果显卡被其他进程占用,或者模型量化版本选大了,就会加载失败。解决方法是换更小的量化版本,比如从 q4 换到 q4_k_m 或者 q3。
- 模型文件损坏。下载中断会导致文件不完整,删掉重新拉。
- Ollama 版本和模型不兼容。升级 Ollama 到最新版。
查看显存占用:
nvidia-smi如果显存快满了,先停掉其他占显存的进程。
5.3 401 报错与 API Key 的那些坑
unexpected status 401 unauthorized: incorrect api key provided: sk-svcac****这个报错,我见过太多次了。排查顺序:
- 检查 key 是否完整,有没有多余空格
- 检查 key 是否过期或者额度用完
- 检查 Base URL 是否写对,DeepSeek 是
https://api.deepseek.com/v1,别写成别的 - 检查模型名是否正确,
deepseek-chat和deepseek-reasoner是两个不同的模型
还有一种情况是 Dify 的凭证校验报an error occurred during credentials validation,这个通常是网络问题,Dify 容器访问不到 DeepSeek 的 API。检查容器的网络配置,确认能出网。
5.4 上下文超长与知识库处理报错
api error: 400 this model's maximum context length is 1048576 tokens这个报错看着吓人,其实意思是你的输入超过了模型上限。虽然 100 万 token 听起来很多,但如果你把整个知识库塞进去,还是会超。
解决办法是控制检索返回的文档数量。Dify 的知识库检索节点可以设置 Top K 和 Score 阈值,别一次返回太多。我一般设 Top K = 3,Score 阈值 0.7,只返回最相关的几段。
另一个报错dify unstructured api url is not configured for doc file processing,这个是因为你上传了 Dify 默认解析器处理不了的文档格式(比如某些 PDF 或者扫描件),需要配置 Unstructured API。如果你不需要处理复杂格式,把文档转成 txt 或 markdown 再上传就行。
5.5 工作流上下文超长的处理经验
Dify 工作流跑多轮对话的时候,上下文会累积。跑久了就会遇到上下文超长的问题。
我的处理方式是:
- 会话记忆限制轮数。Dify 的会话变量可以设置最大保留轮数,我设的是 10 轮,超过就丢弃最早的。
- 知识库检索结果做摘要。如果检索回来的文档很长,先用本地模型做一次摘要,再把摘要塞进上下文。
- 长文档分段处理。不要一次性把整个文档塞进去,切成段分别处理再汇总。
6. 让这套平台真正好用的几个进阶配置
6.1 知识库流水线的切分策略调优
Dify 的知识库默认切分是固定长度,但不同文档类型适合不同的切分方式。我的经验:
- 技术文档:按标题层级切分,每个小节一段,保留上下文。
- FAQ 类:按问答对切分,一问一答作为一个 chunk。
- 长篇文章:按段落切分,每段 300-500 字,重叠 50 字。
切分粒度直接影响检索质量。切太碎,检索到的片段缺乏上下文;切太大,检索精度下降。我一般先用默认配置跑一遍,看看检索效果,再针对性调整。
6.2 模型切换的平滑过渡
本地模型和云端模型的输出风格不一样,切换的时候用户能感觉到。我的做法是在工作流最后加一个"风格统一"节点,用固定的提示词把输出格式规范化。这样不管背后是哪个模型,用户看到的格式是一致的。
另外,切换的时候可以在响应里加一个不显眼的标记,方便自己排查问题,但用户侧不展示。
6.3 监控与告警:知道什么时候该扩容
跑了一段时间之后,你需要知道本地模型的负载情况。我用的方案是:
- Ollama 的日志里能看到每次请求的耗时和 token 数
- Dify 的日志里能看到工作流的执行情况
- 用一个简单的脚本定时检查 Ollama 服务是否存活
如果发现本地模型经常超时,说明该升级硬件或者调整路由策略了。如果云端调用比例持续上升,说明本地模型能力跟不上需求,该考虑换更大的模型。
6.4 数据备份与迁移注意事项
Dify 的数据都在 Docker volume 里,迁移的时候别只备份代码,要把 volume 一起备份。关键数据包括:
- PostgreSQL 数据(工作流、知识库元数据)
- 向量库数据(文档向量)
- 上传的文件
备份命令:
docker compose down docker run --rm -v dify_postgres_data:/data -v $(pwd):/backup alpine tar czf /backup/postgres_backup.tar.gz /data docker compose up -d迁移到新机器的时候,把 volume 恢复回去,改一下.env里的地址配置,基本就能无缝切换。
7. 我个人在实际操作中的几点体会
这套平台我断断续续维护了小半年,最大的感受是:"本地优先"不是目的,"成本和质量的最优平衡"才是。一开始我有点执念,什么都想走本地,结果用户体验很差,本地模型答得慢还答不好。后来调整了策略,把本地定位成"处理 80% 简单任务",云端处理"20% 复杂任务",整体体验才上来。
另一个体会是别追求一步到位。我一开始想搞得很复杂,又是多模型路由又是自动评估,结果配置了一堆,自己都记不住。后来简化成"长度 + 任务类型"两个判断维度,反而稳定好用。工具是为人服务的,别被工具绑架。
最后分享一个小技巧:给本地模型设置合理的超时时间。我一开始设的 60 秒,结果用户等得花儿都谢了。后来改成 30 秒,超时直接切云端,用户感知好很多。本地模型快的时候几秒就返回,慢的时候 30 秒也够呛能出好结果,不如早点切。
这套东西后续还能扩展,比如接入更多本地模型做 A/B 测试,或者把知识库做成多租户隔离。但那是后面的事了,先把基础跑稳再说。