
1. 为什么我要从「给 API 打工」切换到本地优先架构1.1 一个让我彻底破防的账单夜晚去年冬天的一个晚上我盯着后台的 API 消费面板整个人是懵的。那个月我做了三个小工具一个帮团队整理会议纪要的助手、一个给客户做文档问答的机器人、还有一个自己用来做代码片段检索的小应用。三个东西都不大但每个月光是调用云端大模型的费用就烧掉了我小半个月的工资。更让人难受的是有一次我半夜爬起来改一个 prompt结果发现云端接口在维护整个应用直接瘫了客户在群里问我“怎么用不了了”我只能尴尬地回一句“稍等服务商那边有点问题”。那一刻我就下定决心不能再这样给 API 打工了。我要搭一套自己的私有 AI 平台核心诉求很明确——本地优先、云端兜底。日常的、高频的、涉及隐私的请求全部走本地模型遇到本地模型搞不定的复杂任务再自动切到云端。这样既能省下大笔费用又能保证数据不出内网还能在云端挂掉的时候继续干活。这套方案我最终用Dify Ollama DeepSeek三件套落地了跑在 Docker 里前后折腾了大概两周踩了无数坑也总结出了一套相对稳定的配置。这篇文章就把我整个搭建过程、选型逻辑、踩坑记录和排查技巧全部摊开讲适合那些和我一样被 API 账单折磨、想自己掌控 AI 能力、又不想从零造轮子的朋友。1.2 三件套各自扮演什么角色很多人一上来就懵Dify、Ollama、DeepSeek 这三个东西到底谁管谁我用一个生活化的类比来解释。把整套平台想象成一家餐厅。Ollama是后厨的灶台和厨师它负责真正把菜做出来也就是跑模型推理。它最大的好处是能把各种开源模型比如 Qwen、Llama、DeepSeek 的蒸馏版拉到本地用你的显卡或者 CPU 跑起来不依赖任何外部网络。DeepSeek在这里有两个身份一是作为本地可以跑的模型比如 DeepSeek-R1 的蒸馏版本二是作为云端兜底的 API 服务当本地厨师搞不定某道复杂菜时打电话请外援。Dify则是餐厅的前厅和经理它负责接待客人提供 Web 界面和 API、管理菜单编排工作流、记住老客户的喜好知识库和上下文管理然后把订单派给后厨或者外援。这个分工非常关键。很多人误以为 Dify 自己会跑模型其实不是Dify 是个编排层它本身不产生智能它负责把请求路由到正确的地方。Ollama 是执行层DeepSeek 既是执行层本地又是兜底层云端。理解了这层关系后面配置的时候就不会晕。1.3 「本地优先、云端兜底」到底怎么落地这个策略听起来很美好但落地的时候要解决三个问题怎么判断该走本地还是云端、怎么在两者之间无缝切换、怎么保证本地模型的质量够用。我的做法是这样的。在 Dify 里配置两个模型供应商一个指向本地的 Ollama 服务一个指向 DeepSeek 的云端 API。然后在工作流里做条件分支简单的意图识别、文本分类、摘要提取、格式转换全部走本地小模型我用的是 Qwen2.5:7b 和 DeepSeek-R1-Distill-Qwen-7B涉及复杂推理、长文档深度分析、代码生成这类任务才路由到云端 DeepSeek。判断逻辑可以基于关键词、token 长度、任务类型甚至可以让本地模型先做一个“难度评估”再决定要不要升级。这样做的收益非常直观。我统计过改造之后我 80% 的请求都落在了本地月度 API 费用直接降到了原来的五分之一不到。而且因为本地模型响应快局域网内延迟基本在几百毫秒用户体验反而更好了。最重要的是那些包含客户合同、内部文档的敏感请求从头到尾都没出过我的服务器。2. 环境准备Docker 是地基别在这省事2.1 Docker Desktop 安装与国内镜像源配置整套平台我强烈建议用 Docker 部署原因很简单Dify 依赖 PostgreSQL、Redis、Weaviate 等一堆组件手动装能把人逼疯Docker Compose 一条命令全给你拉起来。但 Docker 在国内的下载体验懂的都懂镜像拉取慢到怀疑人生。先说安装。Windows 用户直接去官网下 Docker Desktop安装过程一路下一步就行但有两个坑要注意。第一安装时会问你要不要用 WSL2 后端一定要选 WSL2别选 Hyper-V后者性能和兼容性都差一截。第二装完之后如果启动报错大概率是 BIOS 里的虚拟化没开去主板设置里把 Intel VT-x 或 AMD-V 打开。装完之后第一件事是配镜像加速。打开 Docker Desktop 的设置找到 Docker Engine在 JSON 配置里加上 registry-mirrors。我常用的几个源这里不点名具体地址你可以搜“docker 镜像加速”找当前可用的配置格式是这样的{ registry-mirrors: [ https://你的加速地址1, https://你的加速地址2 ] }改完点 Apply Restart然后跑一句docker info看看 Registry Mirrors 那一栏有没有生效。这一步不做后面拉 Dify 镜像的时候你会等到睡着。提示镜像源经常失效建议一次配两三个某个挂了还能顶上。配完记得重启 Docker 引擎不重启不生效。2.2 Ollama 的安装与离线包方案Ollama 的安装本身很简单官网下对应系统的安装包双击装完命令行敲ollama --version能出版本号就成。但真正的痛点在下载模型。ollama pull qwen2.5:7b这条命令在国内网络环境下经常慢到几 KB 每秒一个 4GB 的模型能下一下午中途断了还得重来。我的解决方案是离线安装包 手动导入。具体做法是找一台网络环境好的机器或者用云服务器在上面执行ollama pull把需要的模型拉下来模型文件默认存在~/.ollama/models目录下。把这个目录整个打包拷到目标机器上对应的位置然后重启 Ollama 服务ollama list就能看到模型了。如果你不想这么折腾也可以配置国内镜像源。Ollama 支持通过环境变量指定模型仓库地址在启动服务前设置OLLAMA_HOST和相关的镜像变量即可。不过镜像源的模型更新往往滞后热门模型还好冷门的可能压根没有所以离线包方案更稳妥。我实际用下来本地必装的模型有这么几个qwen2.5:7b通用对话和中文理解很稳、deepseek-r1:7b推理任务、nomic-embed-text做知识库的向量化。显存 8GB 以上的机器跑 7B 模型基本流畅4GB 显存的话建议上 3B 或者量化版本。2.3 Dify 的 Docker Compose 部署实操Dify 官方提供了 docker-compose.yaml直接 clone 仓库然后docker compose up -d就行。但这里有几个高频报错必须提前说。第一个是SSL 错误。很多人第一次访问 Dify 的 Web 界面时浏览器报证书错误这是因为默认配置里 Nginx 用了自签名证书。本地开发环境直接点“继续访问”忽略即可生产环境需要换成正规证书。如果你在docker compose up的时候看到 SSL 相关的报错日志八成是端口 443 被占用了改一下 compose 文件里的端口映射。第二个是credentials validation 报错。这个通常出现在你配置模型供应商的时候Dify 会去验证你填的 API Key 或者服务地址是否可达。如果本地 Ollama 服务没起来或者地址填错了比如填了localhost但 Dify 跑在容器里容器内的 localhost 指向的是容器自己而不是宿主机就会报这个错。解决办法是把地址换成宿主机的内网 IP或者用 Docker 的网络别名。第三个是unstructured API 未配置。Dify 处理文档尤其是 PDF、Word的时候默认会调用 Unstructured 服务做解析。如果你没配这个上传文档就会报unstructured api url is not configured for doc file processing。解决办法是在.env文件里配置UNSTRUCTURED_API_URL或者干脆用 Dify 内置的解析器效果差一点但能用。启动命令我一般这么写git clone https://github.com/langgenius/dify.git cd dify/docker cp .env.example .env # 编辑 .env配置端口、数据库密码等 docker compose up -d起来之后访问http://你的IP:端口第一次会让你设置管理员账号。整个启动过程大概需要 2-3 分钟因为要初始化数据库和向量库。注意Dify 的向量库默认用 Weaviate如果你机器内存小于 8GB建议换成轻量级的否则启动时容易 OOM。改.env里的VECTOR_STORE配置即可。3. 核心配置把 Dify、Ollama、DeepSeek 串起来3.1 在 Dify 里接入本地 Ollama 模型Dify 起来之后第一件事是配置模型供应商。进入“设置 - 模型供应商”找到 Ollama点添加。这里要填两个关键信息模型名称和基础 URL。模型名称填你在 Ollama 里ollama list看到的那个名字比如qwen2.5:7b。基础 URL 是最容易出错的地方。如果你的 Dify 跑在 Docker 容器里而 Ollama 跑在宿主机上这里不能填http://localhost:11434因为容器里的 localhost 是容器自己。正确的填法是http://host.docker.internal:11434Docker Desktop 环境或者宿主机的内网 IP 比如http://192.168.1.100:11434。填完之后点保存Dify 会去验证连接。如果报an error occurred during credentials validation按这个顺序排查先在宿主机上curl http://localhost:11434/api/tags看 Ollama 是否正常响应再进 Dify 容器docker exec -it 容器名 curl http://host.docker.internal:11434/api/tags看容器能不能通最后检查 Ollama 是否监听了 0.0.0.0 而不是只监听 127.0.0.1。Ollama 默认只监听本地回环地址要让容器访问需要设置环境变量OLLAMA_HOST0.0.0.0然后重启 Ollama 服务。这一步不做容器永远连不上。3.2 接入 DeepSeek 云端 API 作为兜底DeepSeek 的 API 接入相对简单去官网申请一个 API Key然后在 Dify 的模型供应商里选 DeepSeek把 Key 填进去就行。但这里有个高频报错unexpected status 401 unauthorized: incorrect api key provided: sk-svcac****。这个报错基本就是 Key 的问题但原因可能有好几种。一是 Key 复制的时候带了空格或者换行尤其是从网页复制的时候特别容易带上不可见字符建议粘贴到记事本里过一遍再复制。二是 Key 已经过期或者额度用完了去后台看看余额。三是你把 Key 填到了错误的供应商下面比如把 DeepSeek 的 Key 填到了 OpenAI 的配置里那肯定 401。还有一个坑是模型名称。DeepSeek 的 API 模型名和本地 Ollama 的模型名不一样云端用的是deepseek-chat和deepseek-reasoner这类名字别填成本地的deepseek-r1:7b否则会报模型不存在的错误。配置好之后建议在 Dify 里建一个测试应用分别用本地模型和云端模型跑同一个 prompt对比一下响应速度和质量心里有个底。3.3 用工作流实现「本地优先、云端兜底」路由这是整套方案的核心。Dify 的工作流功能可以让我们用可视化的方式编排路由逻辑。我的工作流大致长这样开始节点接收用户输入然后进入一个条件分支节点。分支的判断条件我用了三个维度输入长度、任务类型关键词、本地模型置信度。输入长度很好理解超过一定 token 数比如 4000的请求直接走云端因为本地 7B 模型的上下文窗口有限硬塞进去要么报maximum context length错误要么质量暴跌。任务类型关键词就是匹配“写代码”“深度分析”“复杂推理”这类词命中就走云端。本地模型置信度这个稍微高级一点先让本地模型快速生成一个答案同时输出一个置信度评分低于阈值就升级到云端。工作流里配置模型节点的时候本地节点选 Ollama 的模型云端节点选 DeepSeek。两个节点的输出格式保持一致这样后续的处理逻辑不用改。我实测下来这套路由逻辑的准确率能到 85% 以上偶尔有误判也不影响使用因为兜底机制保证了最差情况也有云端顶着。提示Dify 工作流的条件分支支持表达式可以用{{#node_id.field#}}的方式引用上游节点的输出。写表达式的时候注意变量名别写错写错了不会报错只会静默走 else 分支排查起来很痛苦。4. 知识库与文档处理让私有数据真正可用4.1 知识库流水线的搭建思路私有 AI 平台最有价值的场景之一就是让模型能回答基于你自己文档的问题。Dify 的知识库功能就是干这个的。整个流水线分三步文档上传与解析、文本分块与向量化、检索与召回。文档解析这一步Dify 默认的解析器对纯文本和 Markdown 支持很好但遇到扫描版 PDF、复杂表格、图片混排的 Word效果就一般了。这时候可以接入 Unstructured API它对各种格式的解析能力强很多。配置方法是在.env里设置UNSTRUCTURED_API_URL指向你部署的 Unstructured 服务。文本分块是个技术活。块太大检索出来的内容冗余浪费上下文窗口块太小语义不完整模型理解不了。我的经验值是中文文档按 500-800 字分块块之间保留 50-100 字的重叠这样能保证语义连贯。Dify 里可以配置分块大小和重叠长度建议先用默认值跑一遍看看召回效果再调。向量化模型我用的nomic-embed-text跑在本地 Ollama 上速度快而且免费。如果你对检索质量要求极高可以换成更大的 embedding 模型但显存占用会上去。4.2 文档处理常见报错与解决dify unstructured api url is not configured for doc file processing这个报错我遇到过好几次。根本原因是 Dify 在处理某些格式的文档时内置解析器搞不定需要外部服务支援但你没配。解决办法有两个。一是配置 Unstructured 服务用 Docker 单独跑一个然后在 Dify 的.env里填上地址。二是把文档转成 Dify 内置解析器能搞定的格式比如把 PDF 转成 Markdown 再上传。我一般推荐第一种一劳永逸。还有一个坑是大文档上传超时。Dify 默认的上传大小限制是 15MB超过就报错。改.env里的UPLOAD_FILE_SIZE_LIMIT可以调大但别调太大否则解析时间会很长前端容易超时。我的做法是把大文档拆成几个小文档分别上传然后在知识库里用标签区分。4.3 检索效果调优的几个关键参数知识库搭好之后检索效果好不好主要看三个参数Top K、Score 阈值、重排序。Top K 是每次检索返回多少个文本块。设太小可能漏掉关键信息设太大噪音多还会挤占上下文窗口。我一般设 3-5配合重排序使用。Score 阈值是相似度过滤低于这个分数的块直接丢弃。设太高召回率低设太低噪音多。中文场景我一般设 0.5-0.6具体要看你的 embedding 模型。重排序Rerank是提升检索质量的大杀器。它用一个专门的模型对初步召回的块做二次排序把最相关的排前面。Dify 支持配置重排序模型可以用本地的也可以用云端的。开了重排序之后我的知识库问答准确率大概提升了 20%。5. 踩坑实录那些让我熬夜的报错5.1 模型相关报错速查报错信息根本原因解决办法ollama run qwen3.5:2b error: 500 internal server error: llama-server process模型文件损坏或显存不足删除模型重新 pull或换更小的量化版本maximum context length is 1048576 tokens输入超过了模型上下文窗口在 Dify 里开启上下文截断或走云端长上下文模型incorrect api key provided: sk-svcac****Key 错误、过期或填错位置重新复制 Key检查余额和供应商配置model not found模型名称填错用ollama list确认本地模型名云端用官方文档的模型名llama-server process这个报错我遇到过一次折腾了半天。最后发现是模型下载不完整文件损坏了。解决办法就是ollama rm 模型名然后重新 pull。如果重新 pull 还是不行检查一下磁盘空间Ollama 的模型文件很占地方一个 7B 模型动辄 4-5GB。5.2 网络与连接类问题排查容器连不上宿主机服务这是 Docker 部署最经典的问题。排查思路是从内到外逐层验证。先在宿主机上验证服务本身正常curl http://localhost:11434/api/tags。然后在容器内验证网络连通性docker exec -it dify-api curl http://host.docker.internal:11434/api/tags。如果宿主机通、容器不通那就是网络配置问题检查 Ollama 是否监听 0.0.0.0检查 Docker 的网络模式。还有一个隐蔽的坑是防火墙。Windows 的防火墙有时候会拦截 Docker 容器对宿主机的访问表现就是容器内 curl 超时。临时关掉防火墙测试一下如果通了就加一条入站规则放行对应端口。5.3 性能与资源占用优化本地跑模型资源占用是绕不开的话题。我的机器是 32GB 内存 12GB 显存的配置跑 7B 模型基本流畅但同时跑多个模型就会卡。优化手段有几个。一是模型量化用 Q4 或 Q5 量化版本显存占用能降一半质量损失很小。二是限制并发Ollama 默认会尽量并行处理请求但显存不够的时候会排队甚至崩溃可以在启动参数里限制并行数。三是按需加载Ollama 支持模型自动卸载设置一个空闲超时时间不用的模型自动从显存里释放。Dify 这边向量库和数据库也吃内存。如果机器配置一般建议把 Weaviate 换成更轻量的向量库PostgreSQL 也可以调小连接池。提示监控资源占用推荐用docker stats能实时看到每个容器的 CPU、内存、网络占用。发现某个容器内存一直涨大概率是内存泄漏重启一下能缓解。6. 我实际用下来的几点体会这套平台我跑了小半年最大的感受是本地优先这个策略真的能省钱但前提是你要接受本地模型的能力边界。7B 级别的模型做意图识别、文本分类、简单问答完全够用但让它做复杂推理或者写长代码质量确实不如云端。所以路由逻辑的设计比模型本身更重要把合适的任务交给合适的模型才是这套架构的精髓。另一个体会是Docker 的稳定性直接决定了整套平台的稳定性。我遇到过好几次 Dify 容器莫名其妙挂掉最后查出来都是 Docker Desktop 本身的问题重启 Docker 就好了。所以生产环境建议用 Linux 服务器跑 Docker别用 Windows 的 Docker Desktop后者更适合开发和测试。最后分享一个小技巧Dify 的工作流支持导出和导入 JSON我习惯把调好的工作流导出备份换机器或者重装的时候直接导入省得重新配。这个习惯帮我省了不少重复劳动。