1. 为什么要在Windows10上折腾Dify和Ollama?

如果你和我一样,是个喜欢在本地“捣鼓”AI模型的开发者或爱好者,那么这套组合拳对你来说可能是个宝藏。在云端调用API固然方便,但成本、隐私和网络延迟总是让人心里不踏实。把一切都部署在自己的电脑上,那种“一切尽在掌握”的感觉,是云服务给不了的。

Dify,你可以把它想象成一个“AI应用乐高工厂”。它提供了一个可视化的界面,让你不用写太多代码,就能通过拖拽组件的方式,快速搭建出基于大语言模型的应用,比如智能客服、文档分析助手或者创意写作工具。它帮你处理了背后复杂的流程编排、知识库管理(RAG)和模型调度,你只需要关心业务逻辑就行。

Ollama,则是你的“本地模型管家”。它让在本地运行各种开源大模型(比如通义千问、Llama、Qwen等)变得像安装一个软件一样简单。一条命令就能拉取模型,再一条命令就能启动一个API服务,完美避开了复杂的Python环境配置和CUDA版本地狱。

而Windows10 WSL2,就是我们这次的主战场。它本质上是在Windows里跑了一个完整的Linux子系统,让你既能享受Windows的便利桌面环境,又能获得Linux下强大的命令行和开发工具链,尤其是对Docker的原生支持。这相当于在Windows上开辟了一块“Linux实验田”,完美解决了在纯Windows环境下部署这类开源项目时可能遇到的各种路径、权限和依赖问题。

所以,这套方案的核心价值就是:在你最熟悉的Windows系统里,用最低的成本和门槛,搭建一个完全私有、可控、功能强大的AI应用开发与运行平台。 接下来,我就带你一步步走通这条路,并重点解决两个最可能让你“卡壳”的深水区问题。

2. 搭建舞台:WSL2与Docker环境准备

工欲善其事,必先利其器。在开始部署Dify之前,我们需要确保舞台是稳固的。这一步看似基础,但却是后续所有操作顺利进行的基石。

2.1 启用并配置WSL2

首先,确认你的Windows10版本。你需要Windows 10版本 2004 及更高版本(内部版本 19041 及以上)。在PowerShell(以管理员身份运行)中,执行以下命令来启用WSL和虚拟机平台功能:

# 启用适用于 Linux 的 Windows 子系统
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart

# 启用虚拟机平台
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart

执行完成后,重启你的电脑。这个重启是必须的,否则后续步骤可能会失败。

重启后,再次打开PowerShell(管理员),将WSL的默认版本设置为2:

wsl --set-default-version 2

接下来,我们需要安装一个Linux发行版。微软商店里有很多选择,对于我们的AI部署场景,我强烈推荐Ubuntu 22.04 LTS。它社区支持好,软件包丰富,和Dify的官方文档兼容性也最佳。打开微软商店,搜索“Ubuntu 22.04 LTS”并安装。

安装完成后,从开始菜单启动Ubuntu。第一次启动会需要几分钟来初始化,并让你设置一个用户名和密码。这个密码在后续使用sudo命令时会经常用到,请务必记住。这里设置的用户名,我建议用简单易记的英文,比如你的名字拼音,避免特殊字符。

安装好Ubuntu后,我们可以在PowerShell里查看一下WSL的状态:

wsl -l -v

你应该能看到类似下面的输出,确认你的Ubuntu发行版正在使用WSL2:

  NAME            STATE           VERSION
* Ubuntu-22.04    Running         2

2.2 在WSL2中安装Docker

Dify是使用Docker Compose来部署的,所以我们需要在WSL2的Ubuntu系统里安装Docker引擎。别担心,过程比在纯Windows上安装Docker Desktop要清爽得多。

打开你的Ubuntu终端,依次执行以下命令:

# 1. 更新软件包索引
sudo apt update

# 2. 安装必要的依赖包,以便让apt可以通过HTTPS使用仓库
sudo apt install -y ca-certificates curl gnupg lsb-release

# 3. 添加Docker的官方GPG密钥
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

# 4. 设置Docker的稳定版仓库
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# 5. 再次更新软件包索引(这次会包含Docker仓库)
sudo apt update

# 6. 安装Docker引擎、命令行工具、containerd和Docker Compose插件
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

# 7. 验证Docker Compose插件是否安装成功
docker compose version

如果最后一条命令输出了Docker Compose的版本号(比如 v2.24.0),说明安装成功。

接下来还有一个关键操作:将你的当前用户添加到docker用户组,这样以后运行Docker命令就不需要每次都加sudo了,能省去很多麻烦。

# 将当前用户加入docker组
sudo usermod -aG docker $USER

执行完这条命令后,你需要完全退出当前的Ubuntu终端,然后重新打开一个新的终端窗口,这个组权限变更才会生效。重新登录后,你可以运行一个测试命令来验证:

# 运行一个测试容器,如果成功则说明Docker安装和权限配置都正确
docker run hello-world

你应该会看到“Hello from Docker!”的欢迎信息。至此,我们的基础舞台——一个配备了Docker引擎的WSL2 Ubuntu环境——就搭建完毕了。这个环境干净、独立,是后续所有操作的理想沙盒。

3. 部署Dify 1.9:避开第一个大坑

环境准备好,我们就可以开始部署Dify了。Dify的官方文档推荐使用Docker Compose一键部署,这本来应该是个很顺畅的过程,但在WSL2这个混合环境下,我们很可能会踩到一个经典的权限坑。别急,我带你一步步走,并把这个坑填平。

3.1 拉取代码与初次启动尝试

首先,我们在Ubuntu的家目录(~)下创建一个工作目录,这样路径清晰,也避免了后续的权限问题。然后拉取Dify 1.9.2版本的代码。

# 进入家目录并创建项目文件夹
cd ~
mkdir ai-projects && cd ai-projects

# 克隆Dify的1.9.2版本代码
git clone https://github.com/langgenius/dify.git --branch 1.9.2

# 进入docker部署目录
cd dify/docker

接下来,我们需要复制环境变量配置文件,并使用Docker Compose启动服务。

# 复制环境变量示例文件
cp .env.example .env

# 使用Docker Compose启动所有服务(-d表示后台运行)
docker compose up -d

这时,你可能会满怀期待地看着屏幕,希望看到一串“✔”和“Started”。但现实往往是,在启动到docker-db-1(PostgreSQL数据库)容器时,进程卡住了,然后报错退出。其他容器也因为依赖数据库而无法启动。你会看到类似这样的错误信息:

...
✘ Container docker-db-1 Error
dependence failed to start: container docker-db-1 is unhealthy

3.2 诊断与解决PostgreSQL目录权限问题

问题出在哪里?我们查看一下数据库容器的日志:

docker logs -f docker-db-1

日志里很可能会显示这样的关键错误:

chmod: /var/lib/postgresql/data/pgdata: Operation not permitted
PostgreSQL Database directory appears to contain a database; Skipping initialization
FATAL: data directory "/var/lib/postgresql/data/pgdata" has wrong ownership
HINT: The server must be started by the user that owns the data directory.

这个错误的根源是什么? 简单来说,是用户身份“错位”了。Docker容器内部默认以postgres用户(UID通常是999)来运行PostgreSQL服务,并尝试操作容器内映射的宿主机数据目录。在WSL2中,如果你把Dify项目放在了/mnt/f/或/mnt/c/这类挂载的Windows驱动器路径下,这个目录的实际所有者是你在Windows下的用户身份,到了Linux子系统里,其UID/GID与容器内的postgres用户完全不匹配。PostgreSQL出于安全考虑,严格禁止这种所有权不一致的操作,于是直接罢工。

怎么解决? 网上可能有几种方案,比如修改容器内用户映射,或者在Windows下调整文件夹权限。但我实测下来,最彻底、最省心的一招是:把项目移到WSL2的Linux原生文件系统里。

WSL2的Linux文件系统(比如/home/你的用户名/下的路径)和Windows文件系统是两套独立的体系。在Linux原生路径下,文件的所有权清晰明确,完全由WSL2内部的Linux用户管理,不会再和Windows身份产生冲突。

所以,我们回退一步,把项目移到家目录下:

# 假设你还在之前的目录,先停止并删除刚才启动失败的容器
docker compose down

# 退出到ai-projects目录的上一级
cd ~

# 移动整个dify文件夹到当前用户的家目录下(如果已经在~下,这步可省略,但建议明确路径)
mv ~/ai-projects/dify ~/

# 再次进入docker目录
cd ~/dify/docker

现在,我们再次尝试启动:

docker compose up -d

这次,你应该会看到令人愉悦的输出,所有容器状态都是“Healthy”或“Started”:

[+] Running 13/13
✔ Network docker_ssrf_proxy_network Created
✔ Network docker_default Created
✔ Container docker-db-1 Healthy
✔ Container docker-ssrf_proxy-1 Started
...
✔ Container docker-nginx-1 Started

等待一两分钟,让所有服务完全初始化。然后,你可以在浏览器中打开 http://localhost(如果80端口被占用,可能是http://localhost:80),就能看到Dify的初始化设置界面了!按照提示创建管理员账号,你就成功进入了Dify的后台。这个坑,我们就算迈过去了。记住这个教训:在WSL2中运行Docker服务,尤其是数据库这类对权限敏感的服务,尽量使用Linux原生路径。

4. 集成Ollama与本地模型

Dify本身只是一个平台,它需要接入具体的AI模型才能发挥作用。接下来,我们把本地的“模型管家”Ollama请上台,并让它为Dify提供服务。

4.1 在WSL2中部署Ollama

Ollama的安装极其简单。在Ubuntu终端中,一行命令搞定:

curl -fsSL https://ollama.com/install.sh | sh

安装脚本会自动添加环境变量。安装完成后,启动Ollama服务:

# 启动Ollama服务(默认会在后台运行)
ollama serve

不过,ollama serve默认在前台运行。为了让它一直在后台服务,我们可以使用系统服务来管理,或者更简单点,在启动时加上&符号放到后台,但这不是长久之计。对于生产型使用,建议配置为systemd服务。这里我们先以前台方式启动,另开一个终端窗口进行后续操作。

在另一个Ubuntu终端窗口中,我们可以拉取模型。比如,我们拉取一个体积适中、能力不错的通义千问1.8B模型:

# 拉取通义千问1.8B模型(约1.1GB)
ollama pull qwen2.5:1.8b

拉取需要一些时间,取决于你的网络速度。完成后,你可以测试一下模型是否正常运行:

# 与模型进行简单对话测试
ollama run qwen2.5:1.8b "你好,请介绍一下你自己。"

如果模型能正常回复,说明Ollama服务及模型部署成功。默认情况下,Ollama的API服务运行在http://127.0.0.1:11434。但这里有个关键点:这个地址只在WSL2内部可访问。为了让Dify(也运行在WSL2的Docker容器内)能访问到Ollama,我们需要知道WSL2实例在Docker网络内的IP地址。

4.2 在Dify中配置Ollama模型供应商

首先,获取WSL2的IP地址。在Ubuntu终端里运行:

hostname -I | awk '{print $1}'

你会得到一个类似172.xx.xx.xx的IP地址,记下它。

现在,打开浏览器,访问你的Dify后台(http://localhost)。登录后,按照以下步骤添加Ollama:

  1. 点击右上角头像,进入 “系统设置”。
  2. 在左侧菜单找到 “模型供应商”,点击进入。
  3. 在供应商列表中找到 “Ollama”,点击其右侧的 “添加模型” 按钮。
  4. 在弹出的表单中填写信息:
    • 模型名称: 这里可以自定义一个方便你识别的名字,比如 qwen2.5-1.8b-local。注意,这个名字是Dify内部用来标识这个配置的,不一定要和Ollama的模型名完全一致,但建议相关以便管理。
    • 模型ID: 这里填写Ollama中实际的模型名,即 qwen2.5:1.8b。这是Ollama API调用时使用的标识。
    • 基础URL: 这是最关键的一步。填写 http://<你的WSL2 IP>:11434。例如 http://172.22.112.1:11434。千万不要填http://127.0.0.1:11434,因为对于Dify的Docker容器来说,127.0.0.1指向的是容器自己,而不是宿主机的WSL2。
    • 模型类型: 选择 对话。
    • 模型上下文长度: 对于qwen2.5:1.8b,可以填写 32768(这是该模型支持的长度)。如果不确定,保守点填 4096 或 8192 也可以。
    • 最大Token上限: 指单次回复的最大长度,可以设置为上下文长度的一半或更少,比如 16384。
    • 是否支持Vision: 对于纯文本模型,选择 否。

填写完毕后,点击 “保存”。Dify会立即尝试连接你提供的Ollama地址并验证模型。如果配置正确,你会看到绿色的成功提示。现在,你就可以在创建AI应用时,在模型选择列表里找到你刚添加的 qwen2.5-1.8b-local 了。

5. 攻克高版本Ollama推理输出难题

当你满心欢喜地在Dify里创建了一个聊天助手,选用了刚才配置好的Qwen模型,输入问题点击发送后,可能会遇到一个诡异的情况:Dify的界面一直显示“思考中”或空白,仿佛模型没有响应。但如果你去查看Ollama服务所在终端的日志,却发现模型在疯狂输出文字,其中包含大量以“▌”或“Thought:”开头的“内心独白”(推理过程)。

这是怎么回事? 这个问题在Ollama版本 >= 0.1.47 之后变得非常普遍。新版本的Ollama为了支持更复杂的Agent(智能体)推理能力,默认会让模型输出其“思考过程”。然而,Dify目前的消息处理逻辑可能并未适配这种特殊的流式输出格式,导致它无法正确解析和提取出模型真正的最终回复,因此界面卡住。

5.1 问题根因分析与临时方案

问题的核心在于模型输出的格式。高版本Ollama在调用某些模型时,会默认启用一个“推理模板”,这个模板要求模型在最终答案前,先输出它的推理链(Chain-of-Thought)。这个推理链对于调试和观察模型工作机理很有用,但对于期望直接得到答案的Dify来说,就成了无法处理的“噪音”。

最直接的临时解决方案是降级Ollama版本。你可以卸载当前版本,安装一个已知稳定的旧版本,比如0.1.45或0.1.46。具体命令如下:

# 停止并卸载当前Ollama
ollama stop
sudo apt remove ollama -y # 或者根据你的安装方式,使用其他卸载命令

# 安装特定旧版本(示例,请根据实际情况调整)
curl -fsSL https://ollama.com/install.sh | OLLAMA_VERSION="v0.1.46" sh

降级后,问题通常会消失。但这并非长久之计,毕竟新版本会包含性能优化和Bug修复。我们需要一个能兼容高版本Ollama的永久解决方案。

5.2 终极方案:自定义Modelfile禁用推理

Ollama提供了一个强大的功能——Modelfile。你可以把它理解为一个模型的“配方”或“配置文件”,通过它你可以基于一个已有的基础模型,创建一个定制化的新模型,修改其系统提示词、参数、模板等。

我们的目标就是创建一个新的模型,在其对话模板中明确告诉模型:不要输出推理过程。在高版本Ollama中,通常通过在一个特殊的位置(如用户消息前)添加 /nothink 或 /no_think 指令来实现。

下面,我们为 qwen2.5:1.8b 创建一个禁用推理的Modelfile。

首先,在WSL2中找一个目录,创建一个新文件,例如 qwen2.5-no-think.Modelfile:

# 进入家目录创建文件
cd ~
vim qwen2.5-no-think.Modelfile

如果你不熟悉vim,可以用 nano 或 gedit 编辑器。

将以下内容复制到文件中。这是一个适配Qwen系列模型对话格式的模板,关键修改在于用户消息行加入了 /nothink 指令:

FROM qwen2.5:1.8b

# 设置温度等参数(可选)
PARAMETER temperature 0.7
PARAMETER top_p 0.9

# 定义对话模板,关键是在用户消息前插入 /nothink
TEMPLATE """
{{- if .Messages }}
{{- if or .System .Tools }}<|im_start|>system
{{- if .System }}
{{ .System }}
{{- end }}
{{- if .Tools }}
# Tools
You may call one or more functions to assist with the user query.
You are provided with function signatures within <tools></tools> XML tags:
<tools>
{{- range .Tools }}
{{ . }}
{{- end }}
</tools>
For each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:
<tool_call>
{"name": <function-name>, "arguments": <args-json-object>}
</tool_call>
{{- end }}<|im_end|>
{{ end }}
{{- range $i, $_ := .Messages }}
{{- $last := eq (len (slice $.Messages $i)) 1 -}}
{{- if eq .Role "user" }}<|im_start|>user
/nothink{{ .Content }}<|im_end|>
{{ else if eq .Role "assistant" }}<|im_start|>assistant
{{ if .Content }}{{ .Content }}
{{- else if .ToolCalls }}<tool_call>
{{ range .ToolCalls }}{"name": "{{ .Function.Name }}", "arguments": {{ .Function.Arguments }}} {{ end }}</tool_call>
{{- end }}{{ if not $last }}<|im_end|>
{{ end }}
{{- else if eq .Role "tool" }}<|im_start|>user
<tool_response>
{{ .Content }}
</tool_response><|im_end|>
{{ end }}
{{- if and (ne .Role "assistant") $last }}<|im_start|>assistant
{{ end }}
{{- end }}
{{- else }}
{{- if .System }}<|im_start|>system
{{ .System }}<|im_end|>
{{ end }}{{ if .Prompt }}<|im_start|>user
{{ .Prompt }}<|im_end|>
{{ end }}<|im_start|>assistant
{{ end }}{{ .Response }}{{ if .Response }}<|im_end|>{{ end }}
"""

请注意:模板内容因模型系列(Qwen, Llama, Gemma等)而异。上述模板是针对Qwen2.5的Chat格式。关键行是 {{- if eq .Role \"user\" }}<|im_start|>user\n/nothink{{ .Content }}<|im_end|>,它在每条用户消息前插入了 /nothink 指令。

保存文件后,使用这个Modelfile来创建一个新的Ollama模型:

ollama create qwen2.5-no-think:1.8b -f ./qwen2.5-no-think.Modelfile

这个命令会基于 qwen2.5:1.8b 创建一个名为 qwen2.5-no-think:1.8b 的新模型。创建完成后,你可以先用Ollama命令行测试一下:

ollama run qwen2.5-no-think:1.8b "请解释一下引力波"

观察输出,应该只有直接的答案,而没有大段的“思考:...”或“▌”等内容。

最后,回到Dify的“模型供应商”设置页面。像之前一样添加一个新的模型配置,或者修改之前的配置。

  • 模型名称: 例如 qwen2.5-1.8b-no-think
  • 模型ID: 这里填写我们新创建的模型名 qwen2.5-no-think:1.8b
  • 基础URL: 保持不变(http://<WSL2 IP>:11434)
  • 其他参数按需填写。

保存后,在你的AI应用中将模型切换为这个新配置的 qwen2.5-1.8b-no-think。再次尝试对话,你会发现Dify界面能够正常、流畅地接收并显示模型的回复了,那个恼人的“推理过程不输出”问题被彻底解决。

6. 进阶调优与实用技巧

基础功能跑通后,我们可以再做一些优化,让这个本地AI平台更顺手、更强大。这里分享几个我实战中总结的要点。

6.1 性能优化与资源管理

在个人电脑上同时运行Dify(多个容器)和Ollama(大模型),对内存和CPU是不小的考验。WSL2默认分配的资源可能不够。

调整WSL2资源限制: 在Windows用户目录下(C:\Users\<你的用户名>\)创建一个名为 .wslconfig 的文件,用记事本编辑,内容如下:

[wsl2]
memory=8GB    # 根据你电脑物理内存调整,建议至少8GB,16GB更佳
processors=4  # 分配使用的CPU核心数,建议一半以上物理核心
swap=4GB      # 交换空间大小
localhostForwarding=true

保存后,在PowerShell中执行 wsl --shutdown 关闭WSL2,再重新打开Ubuntu终端,配置即可生效。

管理Ollama模型加载: 默认情况下,Ollama会将模型加载到GPU(如果可用)和内存中。如果你的GPU显存不足,可以强制指定使用CPU运行,虽然慢但能跑起来:ollama run qwen2.5:1.8b --verbose 查看日志,或者通过环境变量 OLLAMA_HOST 和运行参数调整。对于更大的模型,你可能需要研究Ollama的 num_gpu, num_thread 等参数在Modelfile中的配置,进行精细控制。

6.2 模型管理与版本控制

你可能会尝试多个模型。Ollama拉取的模型存储在 ~/.ollama/models 目录下。定期清理不用的模型可以节省磁盘空间:

# 列出已拉取的所有模型
ollama list

# 删除不再需要的模型
ollama rm <模型名:标签>

对于自定义的Modelfile,建议用Git进行版本管理。为每个模型的Modelfile建立清晰的命名和目录结构,方便日后复现和调整。

6.3 网络与访问配置

目前Dify和Ollama都在WSL2内部,只能从本机Windows的浏览器访问 localhost。如果你想让同一局域网下的其他设备(比如手机、平板)也能访问你搭建的Dify服务,需要进行端口转发。

一种简单的方法是在Windows防火墙中允许入站连接,并使用 netsh 命令进行端口转发(例如将WSL2的80端口转发到Windows的某个端口)。但更推荐在WSL2内部启动一个反向代理,比如使用Nginx。这超出了本文范围,但思路是:在WSL2中安装Nginx,配置它将流量代理到Dify的容器网络IP和端口,然后将WSL2的Nginx端口通过Windows防火墙暴露出去。

6.4 故障排查思路

遇到问题别慌张,按顺序排查:

  1. 看日志:Docker容器日志 (docker logs <容器名>) 和 Ollama 服务日志是首要信息源。
  2. 查网络:在WSL2内部,用 curl http://172.xx.xx.xx:11434/api/tags 测试能否访问Ollama API。在Dify的容器内(docker exec -it <api容器名> bash)执行同样的curl命令,测试容器间网络。
  3. 验模型:直接用Ollama命令行 (ollama run ...) 测试模型本身是否工作正常,排除Dify配置问题。
  4. 核配置:反复检查Dify中模型配置的“基础URL”和“模型ID”,一个字母的错误都会导致失败。
  5. 清缓存:有时候Dify的Web前端会有缓存,尝试浏览器无痕模式访问,或者清除Dify浏览器缓存。

这套基于Windows10 WSL2的Dify + Ollama离线部署方案,打通了从基础设施到应用层的关键路径。它最大的魅力在于,将前沿的AI应用开发能力,以一种低成本、高可控的方式带到了个人开发者的桌面。过程中遇到的权限和版本兼容性问题,正是深入理解这套技术栈运作机理的契机。当你按照上述步骤一步步完成,并最终在本地流畅地与自己部署的AI模型对话时,那种成就感远非调用云端API可比。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐