📎 配套脚本附件正文未内嵌 · 共 14 个 · 点开可在线查看
📚 另需公共库(正文未重复内嵌,点名字直接看):_agent_cfg.py、_bootstrap.py、_config.py、_plugin_util.py
📂 查看全部 761 个脚本附件 →
⚠️ 脚本内含明文凭据(与本页正文一致),请勿在公网环境直接运行。
Dify · 安装手册

Dify + Qwen 安装配置手册

从 git clone 到 vLLM、bge-m3、知识库、Agent 可用。步骤未改,代码块已换成完整程序。

Dify_Qwen安装配置手册_cursor.md

这是基于原文生成的 Cursor 版文档。

原文件未改动;本版只做三件事:统一阅读入口、修正已审出的明显问题、补充更适合快速排障的提示。

Cursor 版修订说明

阅读提示

运行环境:业务脚本均在 207 固定目录 /opt/workbuddy 执行,Python 为 /usr/bin/python3。 Windows 开发机仅保留 _probe_* 调试脚本;同步命令:python scripts_cursor/完整代码约定/00_部署_workbuddy到207.py。


原文主体(已纳入 Cursor 版纠错)

Dify + vLLM(Qwen3.6-27B) + BGE-M3 安装配置手册(完整部署方案)

本文定位:从零把「Dify 1.16.1 部署 → BGE-M3(Embedding) 接入 → vLLM 起 Qwen3.6-27B → 知识库 + Agent 可用」整条链路正确配置跑通的主线手册。

本文只讲"该怎么做、配什么、贴什么代码",不展开排错;每一步可能遇到的坑与对应的代码级修复,全部收在专题合集里。

本文 + 全部专题 = 一套完整的安装解决方案:本手册负责端到端把环境跑通,相关专题负责每一步"遇到 X 怎么办"。


〇、路径约定与文档地图

0.1 仓库目录约定

本文按标准 git clone 约定,Dify 源码落在 /dify/dify(docker 编排目录即 /dify/dify/docker)。 若你的实际目录名为 dify-main(本文相关专题里的路径示例即如此),只需把下文所有 /dify/dify 替换为 /dify/dify-main,其余完全一致。

0.2 文档地图(本手册 + 全部专题 = 完整方案)

文档角色覆盖内容
本手册主线从 git clone 到 Agent 可用的端到端配置步骤(含全部代码/配置)
专题一接入 Qwen在 Dify 模型供应商入口正确添加自定义 qwen3.6-27b、消除"不兼容"、设默认 LLM
专题二bge-m3Xinference 如何真正"运行起来" bge-m3(容器在跑 ≠ 模型在跑)
专题三知识库创建 Agent 时知识检索选不到已有知识库的解决
专题四问答报错Agent 问答期报错(400 System message / Connection / 工具解析)排查
专题五假成功模型"运行成功"但 Dify 仍不可用的 7 层排查
专题六weaviate 中继IPv4→[::] 套接字投递 bug 的 socat 中继解决
专题七shell providerAgent 应用启用 Shell 执行 provider(DifyShellLayer)、sandbox/ssrf_proxy/plugin_daemon 三层确认
专题八from __future__ 子坑插件 patch 的 from __future__ 顺序 SyntaxError 与正确补丁位置
专题九其余配置控制台登录、vLLM 启动三坑、共享 GPU、context_size 对齐
专题十模型列表为空 / Agent 报错插件 schema 校验失败(max_tokens 误写进 model_properties)导致 LLM 列表为空、创建 Agent 报错

阅读建议:先把本手册从头到尾做一遍;某一步卡住时,按「本章导读里的卡住查哪」或 第十章 索引跳到对应专题看根因与修复代码。

0.3 部署主线五阶段(建议先通读一遍)

阶段章节步骤号做完应达到什么
A. 平台二1~5Dify 栈 healthy、管理员可登录、openai/xinference 插件已装
B. 向量三6~9(+9.1 可选 GPU)Xinference 在跑、bge-m3 running、Dify 默认 Embedding 可用
C. 大模型四10~11vLLM :8000 返回 qwen3.6-27b,工具调用 parser 正常
D. 接入 Dify五~六12~14模型列表可选 qwen、默认 LLM 已设、context_size 对齐
E. 应用七~九15~16 + patch + 验收知识库 available、Agent 绑库、streaming 问答无 error

执行原则:按步骤顺序做;某步失败不要跳步——先在本章「本章导读」查常见报错,再打开对应专题修完,回到本步继续。

0.4 全链路常见报错 → 专题索引(速查)

你看到的症状最可能卡在哪一步优先打开
docker compose 起不来 / 容器 unhealthy步骤 2~3专题六(weaviate)、专题九
知识库索引失败、检索偶发超时步骤 2专题六
Xinference 有容器但 /v1/models 空步骤 8专题二
Dify Embedding 列表没有 bge-m3步骤 9专题二、专题五·第 3 层
vLLM 容器 Exit / 不认 qwen3_5步骤 10专题九·二、专题四·四
模型列表空 / 创建 Agent 报 schema步骤 12~14专题十、专题一
模型「不兼容」、下拉里灰掉步骤 12~13专题一
400 System message / 多条 system章节八 chat.py专题四·二、专题一·六
Connection error 但 vLLM curl 通步骤 12~14、六专题五(7 层)、专题九·四
Agent 选不到知识库步骤 16专题三
Shell/代码执行不可用步骤 16专题七

一、环境准备

项值(本文环境)说明
Dify 服务器192.168.110.207(root / 123456)Docker + docker compose v2,目录 /dify/dify/docker
GPU 主机116.62.10.183(prod102)H20 ~96GB,与业务 mxh_env/mxh_live 共享
大模型Qwen3.6-27B路径 /models/Qwen3.6-27B,config.json 的 model_type = qwen3_5
推理服务vLLM OpenAI 兼容http://116.62.10.183:8000/v1,served-model-name qwen3.6-27b
Embeddingbge-m3(Xinference)http://192.168.110.207:9997,model_uid bge-m3
Dify 登录38205036@qq.com / Difyai123456控制台 API 密码需 base64
Xinference Keyxf-K8H3LiS27H4188XdOyWJj74MWMOlIR9tZbiybDbxbSas7eAIDify 对接 bge-m3 用

前置要求:Docker ≥ 24、docker compose v2、能访问 docker.m.daocloud.io(vLLM 镜像源)、国内机配 HF_ENDPOINT=https://hf-mirror.com。


二、获取并部署 Dify

本章导读

- 干什么:拉 Dify 源码、配 .env、起 compose、建管理员、装 openai/xinference 两个插件。

- 完成标志:docker compose ps 全 healthy;浏览器能 /install 或登录;插件市场两个插件已安装。

- 卡住怎么办:见下表;weaviate 相关见 专题六。

常见现象可能原因处理
compose pull 超时镜像源不可达配 docker.m.daocloud.io 或国内 mirror
某容器 Restarting端口冲突、内存不足docker logs <容器>;对照 .env
插件装不上plugin_daemon 未就绪等栈 healthy 后重试;见专题九
步骤 2 改 weaviate 后仍索引失败未加 relay 或未改 compose按步骤 2 注释 + 专题六 完整 socat 方案

步骤 1 · 拉取 Dify 源码并进入编排目录

完整脚本:scripts_bash_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_01.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_01.sh

bash
#!/usr/bin/env bash
set -euo pipefail

mkdir /dify
cd /dify
git clone https://github.com/langgenius/dify
cd /dify/dify/docker      # 后续所有 docker compose 命令都在该目录执行

说明:标准 git clone 会把仓库落到 /dify/dify,编排文件在 /dify/dify/docker。若你重命名为 dify-main,下文 /dify/dify 全部换成 /dify/dify-main(见 0.1)。

步骤 2 · 生成并调整环境变量

bash
cp .env.example .env

完整脚本:scripts_bash_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_02.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_02.sh

bash
#!/usr/bin/env bash
set -euo pipefail

  WEAVIATE_ENDPOINT=http://192.168.110.207:8088
  

并在 docker-compose.yaml 增加 weaviate_relay 中继服务(socat 8088 → 127.0.0.1:8080),详见 专题六。

步骤 3 · 启动 Dify 栈

完整脚本:scripts_bash_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_03.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_03.sh

bash
#!/usr/bin/env bash
set -euo pipefail

docker compose up -d
docker compose ps        # 全部 healthy/running 即就绪

步骤 4 · 初始化管理员

浏览器访问 http://<你的IP>/install 创建管理员账号(本文 38205036@qq.com / Difyai123456)。

步骤 5 · 安装模型插件(控制台 → 插件市场)

插件代码位于 plugin_daemon 卷内,本文后续两处 patch(llm.py / chat.py)就在该卷。


三、部署 Xinference + 启动 bge-m3(Embedding)

本章导读

- 干什么:起 Xinference 容器 → 装依赖 → 显式 launch bge-m3 → Dify 接为默认 Embedding。

- 完成标志:GET /v1/models 含 bge-m3;POST /v1/embeddings 返回 1024 维;Dify 模型页 bge-m3 为默认 Embedding。

- 关键认知:容器在跑 ≠ 模型在跑(步骤 8 必须做)。

- 卡住怎么办:专题二(运行机制);选不到知识库时还要查 专题三。

常见现象可能原因处理
launch 后 models 仍空未 POST launch / model_type 错重跑步骤 8 curl;model_type 必须是 embedding
sentence-transformers 相关报错步骤 6 未 pip install进容器补装
Dify 测 embedding 失败URL 带了 /v1 或 Key 错URL 只写到 :9997;核对 xf- Key
索引极慢CPU 跑 embedding可选 步骤 9.1 迁 GPU;见专题二、专题九·三

步骤 6 · 起 Xinference 容器并装依赖

完整脚本:scripts_bash_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_04.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_04.sh

bash
#!/usr/bin/env bash
set -euo pipefail

docker run -d --name xinference --restart always -p 9997:9997 \
  -e HF_ENDPOINT=https://hf-mirror.com \
  xprobe/xinference:latest

# 进入容器补装 bge-m3 运行依赖(缺它 bge-m3 启动会失败)
docker exec -it xinference pip install sentence-transformers
# 国内 pip 慢可先:docker exec -it xinference pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple

docker logs -f xinference   # 看到 Uvicorn running on http://0.0.0.0:9997 即就绪

步骤 7 · 创建 Xinference 管理员账号

Xinference 启用鉴权,建账号 admin / admin1234(Web UI 注册或内置管理接口均可);在用户中心生成 Dify 对接用的 API Key(形如 xf-...)。

步骤 8 · 显式启动 bge-m3 模型(关键:容器 ≠ 模型在跑)

Xinference 只是调度器,容器起来后必须再显式 launch 一次 bge-m3,它才会变成 running 实例。推荐 REST 方式(可脚本化):

完整脚本:scripts_bash_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_05.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_05.sh

bash
#!/usr/bin/env bash
set -euo pipefail

# 启动(model_type 必须是 embedding,model_uid 固定 bge-m3)
curl -X POST 'http://192.168.110.207:9997/v1/models' \
  -H 'Content-Type: application/json' \
  -d '{"model_uid":"bge-m3","model_name":"bge-m3","model_type":"embedding"}'

# 验证已在跑
curl 'http://192.168.110.207:9997/v1/models'        # 期望含 "bge-m3"

# 实跑一次 embedding,确认真能出向量(1024 维)
curl -X POST 'http://192.168.110.207:9997/v1/embeddings' \
  -H 'Content-Type: application/json' \
  -d '{"model":"bge-m3","input":"这是一个测试文本"}'
# 期望返回 "data":[{"embedding":[0.01,-0.03,...],"index":0}]

也可用 Web UI(Models → Launch Model → 搜 bge-m3 → Model UID 设 bge-m3 → Launch)或 Python 客户端(client.launch_model(...))。详见 专题二。

步骤 9 · Dify 接入 bge-m3 作为默认 Embedding

控制台 → 模型供应商 → 添加 Xinference:

等价控制台 API(本次这类「往供应商里加自定义模型」都是这条;找不到 UI 时直接跑):

完整程序:/opt/workbuddy/scripts_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_01.py

运行/引用:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_01.py

python
from dify_api import Dify, pp

XF_KEY = "xf-K8H3LiS27H4188XdOyWJj74MWMOlIR9tZbiybDbxbSas7eAI"
d = Dify("38205036@qq.com", "Difyai123456")
d.login()
body = {
    "model": "bge-m3",
    "model_type": "embedding",
    "credentials": {
        "server_url": "http://192.168.110.207:9997",
        "model_uid": "bge-m3",
        "api_key": XF_KEY,
        "invoke_timeout": "60",
        "max_retries": "3",
    },
    "name": "bge-m3",
}
r = d.post(
    "/workspaces/current/model-providers/langgenius/xinference/xinference/models/credentials",
    body,
)
pp("add embedding", r)
r = d.post(
    "/workspaces/current/default-model",
    body={
        "model_settings": [{
            "model_type": "text-embedding",
            "provider": "langgenius/xinference/xinference",
            "model": "bge-m3",
        }]
    },
)
pp("default embedding", r)
d.close()

对应专题:专题二(bge-m3 运行机制)、专题三(知识库选不到时优先怀疑 bge-m3 是否真在跑)。


⚠️ 特殊说明:本次 bge-m3 走的是 CPU 模式,生产环境应部署在 GPU 资源上

本次现状:本环境 Xinference 部署在 192.168.110.207(Dify 那台机),bge-m3 以 CPU 模式 launch。

之所以走 CPU,是因为当时资源有限——GPU 服务器 116.62.10.183 的 H20 显存已被 Qwen3.6-27B(权重+KV,压到 0.75)和 mxh_env/mxh_live 业务进程占去,没有余量再塞 embedding。

CPU 模式能跑通(文档已验证 POST /v1/embeddings 返回 1024 维向量、知识库可建索引),但 embedding 是矩阵运算密集型,CPU 下索引慢、检索有可感知延迟,不适合大文档量 / 高频检索的生产场景。

生产建议:bge-m3 这类 embedding 模型应当跑在 GPU 上。约 2~4GB 显存(FP16),对 H20(~96GB) 是轻量负载;上 GPU 后索引与检索均为秒级,是"正常"的生产形态。

是否真挪,取决于两点:①知识库文档量 / 检索并发是否到了 CPU 扛不动的程度;②GPU 服务器挪过去后 Qwen 与业务进程仍有余量、不被挤压到 OOM。

步骤 9.1 · 切换到 GPU 服务器运行 bge-m3(生产推荐,操作步骤)

思路:把 Xinference 改到 GPU 主机 116.62.10.183 以 --gpus all 运行,bge-m3 在该实例上 launch(自动用 GPU),Dify 侧只把"服务器 URL"的 host 从 192.168.110.207 改成 116.62.10.183,model_uid / API Key / 向量维度(1024) 全部不变,已有的知识库向量仍兼容、无需重建索引。

① 先确认 GPU 余量(关键前置,避免挤压业务) 完整脚本:scripts_bash_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_06.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_06.sh

bash
#!/usr/bin/env bash
set -euo pipefail

# 在 GPU 主机 116.62.10.183 上
nvidia-smi
# 确认:Qwen(约 0.75 利用率) + 业务(~11GB) 之外,还剩 > 4GB 给 bge-m3。
# 若已无余量,不要挪,继续用 CPU 模式。

② 在 GPU 主机起 Xinference(加 --gpus all) 完整脚本:scripts_bash_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_07.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_07.sh

bash
#!/usr/bin/env bash
set -euo pipefail

# 原 CPU 版(在 207 上,无 --gpus):docker run ... xprobe/xinference:latest
# GPU 版(在 116 上):
docker run -d --name xinference-gpu --restart always --gpus all -p 9997:9997 \
  -e HF_ENDPOINT=https://hf-mirror.com \
  xprobe/xinference:latest

docker exec -it xinference-gpu pip install sentence-transformers   # 依赖仍要装
docker logs -f xinference-gpu   # 看到 Uvicorn running on http://0.0.0.0:9997 即就绪

③ launch bge-m3(强制 GPU) 完整脚本:scripts_bash_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_08.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_08.sh

bash
#!/usr/bin/env bash
set -euo pipefail

# 在 GPU 主机上,launch 时显式指定 device 用 cuda(不指定 Xinference 也会自动探测到 GPU)
curl -X POST 'http://116.62.10.183:9997/v1/models' \
  -H 'Content-Type: application/json' \
  -d '{"model_uid":"bge-m3","model_name":"bge-m3","model_type":"embedding","device":"cuda:0"}'

curl 'http://116.62.10.183:9997/v1/models'            # 期望含 "bge-m3" 且 state=running
curl -X POST 'http://116.62.10.183:9997/v1/embeddings' \
  -H 'Content-Type: application/json' \
  -d '{"model":"bge-m3","input":"这是一个测试文本"}'  # 期望 1024 维向量

④ 确认 Dify(207) 能访问新端点 完整脚本:scripts_bash_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_09.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_09.sh

bash
#!/usr/bin/env bash
set -euo pipefail

# 在 Dify 服务器 192.168.110.207 上
curl -s http://116.62.10.183:9997/v1/models | head -c 200   # 能返回即网络通
# 若不通:检查 116 防火墙是否放行 9997、两机是否同网段/可路由。

⑤ 改 Dify 的 Xinference 供应商配置(唯一必改项)

text
控制台 → 模型供应商 → Xinference → 编辑:
- 服务器 URL:http://116.62.10.183:9997   # 仅 host 由 207 改 116,仍不带 /v1
- 模型 UID:bge-m3                        # 不变
- API Key:xf-K8H3LiS27H4188XdOyWJj74MWMOlIR9tZbiybDbxbSas7eAI   # 不变
保存。bge-m3 仍为默认 Embedding,无需动知识库。

⑥ 需要改的代码/配置汇总(切换前后对照)

位置切换前(本次 CPU)切换后(GPU 生产)
Xinference 容器 docker run在 207,无 --gpus在 116,加 --gpus all
bge-m3 launch device省略(CPU 默认)"device":"cuda:0"
Dify 供应商「服务器 URL」http://192.168.110.207:9997http://116.62.10.183:9997
model_uid / API Key / 维度bge-m3 / 原 Key / 1024全部不变
知识库索引已建(CPU 产出 1024 维)无需重建(GPU 同样 1024 维,向量兼容)
GPU 显存占用0(CPU 机)+约 2~4GB(需确认余量)

注意:vllm_qwen_run.sh 等 LLM 相关配置不用动;只有 Xinference 这一路 + Dify 供应商 URL 需要改。若 GPU 主机另有防火墙策略,放行 9997 即可。

对应专题:专题二(bge-m3 运行机制,含 GPU/CPU 启动差异)、专题九·三(共享 GPU 显存保护)。


四、启动 vLLM 大模型(qwen3.6-27b)

本章导读

- 干什么:在 GPU 机 116 上 docker 起 vLLM,加载 /models/Qwen3.6-27B,暴露 OpenAI 兼容 :8000。

- 完成标志:步骤 11 三条 curl 通过——列模型、单轮对话、tool_calls 被 qwen3_xml 正确解析。

- 卡住怎么办:启动参数见下表「关键参数」;系统性排查 专题五·第 1 层、专题九·二/三(共享 GPU)。

常见现象可能原因处理
容器秒退 / CrashLoop镜像太旧不认 qwen3_5必须用 vllm-openai:latest(0.25.1+)
OOM / CUDA errorgpu-memory-util 太高降到 0.75;确认业务进程占显存
/v1/models 空或 502模型还在加载等 1~3 分钟;docker logs -f
tool_calls 乱码/未解析parser 错必须 --tool-call-parser qwen3_xml(不是 qwen3)

步骤 10 · 在 GPU 主机执行启动脚本

在 116.62.10.183 上执行 vllm_qwen_run.sh(脚本先 docker rm -f 同名旧容器,可重复运行):

完整脚本:scripts_bash_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_10.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_10.sh

bash
#!/usr/bin/env bash
# vLLM 启动 qwen3.6-27b —— H20(96GB) 共享 GPU、业务进程不可受影响 优化版
# 模型 model_type = qwen3_5;镜像必须用 latest(实测 vLLM 0.25.1) 才能识别该架构。
# 工具解析器:标准 Qwen3 instruct 必须用 --tool-call-parser qwen3_xml(qwen3 不存在)。
# 另加 --reasoning-parser qwen3:把思考块从工具调用中分离。
# GPU 约束:业务(mxh_env/mxh_live)已占 ~11.3GB,必须留余量。

docker rm -f vllm-qwen3.6-27b 2>/dev/null || true

docker run -d \
  --name vllm-qwen3.6-27b \
  --restart always \
  --gpus all \
  --shm-size=8g \
  -p 8000:8000 \
  -v /models/Qwen3.6-27B:/model \
  -e HF_HUB_OFFLINE=1 \
  --log-opt max-size=100m \
  --log-opt max-file=3 \
  docker.m.daocloud.io/vllm/vllm-openai:latest \
  /model \
  --trust-remote-code \
  --dtype bfloat16 \
  --tensor-parallel-size 1 \
  --enable-auto-tool-choice \
  --tool-call-parser qwen3_xml \
  --reasoning-parser qwen3 \
  --max-model-len 65536 \
  --gpu-memory-utilization 0.75 \
  --max-num-seqs 8 \
  --served-model-name qwen3.6-27b \
  --enable-prefix-caching \
  --host 0.0.0.0 \
  --port 8000

关键参数对照

参数值作用 / 踩坑点
镜像latest(0.25.1)必须,0.10.2 不认 qwen3_5 架构会崩溃
模型位置参数/model替代已弃用的 --model
--tool-call-parserqwen3_xmlqwen3 不存在会 KeyError;qwen3_coder/hermes 不适用
--reasoning-parserqwen3分离思考块与工具调用
--max-model-len65536上下文上限,需与 Dify context_size 匹配
--gpu-memory-utilization0.75共享 GPU 保护,给业务留余量
--max-num-seqs8共享 GPU 下降低 KV 压力
--served-model-nameqwen3.6-27bDify 模型名必须与之完全一致
--host 0.0.0.0开否则 Dify 服务器连不上

启动三坑(parser 名、镜像版本、--model 弃用告警)与共享 GPU 显存保护,详见 专题九·二、三。

步骤 11 · 确认 vLLM 真在对外服务(三次 curl)

完整脚本:scripts_bash_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_11.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_11.sh

bash
#!/usr/bin/env bash
set -euo pipefail

docker logs -f vllm-qwen3.6-27b      # 看到 "Application startup complete" 即就绪

# 1) 列模型
curl http://127.0.0.1:8000/v1/models          # 返回 id = "qwen3.6-27b"

# 2) 单轮对话(单条 system)
curl -X POST http://127.0.0.1:8000/v1/chat/completions \
  -H 'Content-Type: application/json' -H 'Authorization: Bearer 123456' \
  -d '{"model":"qwen3.6-27b","messages":[{"role":"system","content":"你是助手"},{"role":"user","content":"你好"}],"max_tokens":64}'

# 3) 工具调用(验证 qwen3_xml parser)
curl -X POST http://127.0.0.1:8000/v1/chat/completions \
  -H 'Content-Type: application/json' -H 'Authorization: Bearer 123456' \
  -d '{"model":"qwen3.6-27b","messages":[{"role":"user","content":"北京今天天气如何?用工具查"}],
       "tools":[{"type":"function","function":{"name":"get_weather","description":"查询天气","parameters":{"type":"object","properties":{"city":{"type":"string"}}}}}]}'
# 期望返回里 tool_calls 被正确解析

对应专题:专题五·一(第 1 层)、专题四·四(C 类工具解析)。


五、Dify 接入 qwen3.6-27b(OpenAI 兼容)

本章导读

- 干什么:在 OpenAI 供应商下单独添加自定义模型 qwen3.6-27b → patch llm.py → 设为默认 LLM。

- 完成标志:LLM 列表里 qwen 可点、无「不兼容」;默认模型已是 qwen3.6-27b。

- 卡住怎么办:专题一(接入/不兼容/schema);列表为空见 专题十。

常见现象可能原因处理
模型列表没有 qwen只配了 base 未「单独添加」步骤 12 自定义模型区域必做
「不兼容」灰显插件 schema 查不到基座专题一 patch get_customizable_model_schema
保存凭据 Connection errorvLLM 未起或 Base URL 错先验 :8000/v1/models;Base 勿多斜杠
LLM 列表突然变空max_tokens 写进 model_properties专题十

步骤 12 · 模型供应商入口添加自定义 Qwen

控制台 → 模型供应商 → 找到 OpenAI → 设置/添加模型:

光配 openai_api_base 不够——必须作为自定义模型单独添加,否则根本不出现在模型列表。

等价控制台 API(本次添加自定义 LLM 走的就是这条,不是只点 UI):

完整程序:/opt/workbuddy/scripts_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_02.py

运行/引用:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_02.py

python
from dify_api import Dify, pp

d = Dify("38205036@qq.com", "Difyai123456")
d.login()
body = {
    "model": "qwen3.6-27b",
    "model_type": "llm",
    "credentials": {
        "openai_api_key": "123456",
        "openai_api_base": "http://116.62.10.183:8000/v1",
        "openai_organization": "",
        "api_protocol": "chat",
    },
    "name": "qwen3.6-27b",
}
r = d.post(
    "/workspaces/current/model-providers/langgenius/openai/openai/models/credentials",
    body,
)
pp("add qwen", r)
r = d.get("/workspaces/current/models/model-types/llm")
pp("llm list", r)
d.close()

已有凭据时改用 PUT 并带 credential_id,见专题九。

步骤 13 · 设为默认 LLM(两处都要改)

① 控制台:点 qwen3.6-27b 右侧「设为默认」(System Model → LLM)。找不到该按钮时用 API:

完整程序:/opt/workbuddy/scripts_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_03.py

运行/引用:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_03.py

python
from dify_api import Dify, pp
d = Dify("38205036@qq.com", "Difyai123456")
d.login()
r = d.post(
    "/workspaces/current/default-model",
    body={
        "model_settings": [{
            "model_type": "llm",
            "provider": "langgenius/openai/openai",
            "model": "qwen3.6-27b",
        }]
    },
)
pp("default llm", r)
d.close()

② 数据库(确保持久生效):默认模型记录在 tenant_default_models,把 llm 行从插件自带的 gpt-5.6 改成 qwen3.6-27b:

完整脚本:scripts_bash_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_12.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_12.sh

bash
#!/usr/bin/env bash
set -euo pipefail

docker exec -it docker-db_postgres-1 psql -U postgres -d dify \
  -c "UPDATE tenant_default_models SET model='qwen3.6-27b' WHERE model_type='llm';"
cd /dify/dify/docker && docker compose restart api plugin_daemon

步骤 14 · Patch 插件 llm.py:让 qwen 有可派生的 model schema(消除"不兼容")

OpenAI 插件的 get_customizable_model_schema() 按 base model 名查预定义 schema,qwen3.6-27b 没有对应基座会抛 Base model qwen3.6-27b not found,前端标"不兼容"、Agent 选不到。需让它在找不到基座时 clone 一个通用 chat schema。

插件升级后:卷内可能并存多个 openai-1.0.x@hash。必须给当前运行版本(及建议给全部版本)打补丁,否则会出现「昨天还好、今天模型不可用」。详见 专题十七。

一键:python scripts_cursor/Dify_专题一_集成Qwen3.6-27B/专题一_04_改llm_schema_修改后.py

文件(容器内路径,host 对应 plugin_daemon 卷;<hash> 随机,用 ls 查;不要只改其中一个旧版本):

text
/dify/dify/docker/volumes/plugin_daemon/cwd/langgenius/openai-<版本>@<hash>/models/llm/llm.py

先备份:cp llm.py llm.py.bak_qwenfix

改动位置:替换 get_customizable_model_schema 整个函数体:

完整补丁:patches_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_01.py

运行/引用:见 patches_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_01.py

python
def get_customizable_model_schema(
        self, model: str, credentials: dict
    ) -> AIModelEntity:
    schema = {item.model: item for item in self.predefined_models()}.get(
        _base_model(model)
    )
    if schema is None:
        # Fallback for custom / non-OpenAI base models served via an
        # OpenAI-compatible endpoint (e.g. Qwen, DeepSeek, local vLLM):
        # clone a chat-capable predefined schema so the model is listable
        # and can be set as the default model.
        template = next(
            (m for m in self.predefined_models()
             if m.model_properties.get("mode") == "chat"),
            None,
        )
        if template is None:
            template = self.predefined_models()[0]
        props = dict(template.model_properties)
        mp = (credentials or {}).get("model_properties") or {}
        # Align context_size to the actual vLLM --max-model-len (65536) so Dify
        # never lets prompts exceed what the served model can accept. The
        # credential-update API does NOT persist model_properties for this
        # provider, so we hardcode the safe ceiling here as the default and
        # still honor an explicit override when one is supplied.
        VLLM_MAX_CTX = 64000
        try:
            props["context_size"] = int(mp["context_size"]) if "context_size" in mp else VLLM_MAX_CTX
        except (ValueError, TypeError):
            props["context_size"] = VLLM_MAX_CTX
        # NOTE: do NOT put max_tokens into model_properties. Dify API-side
        # ModelPropertyKey only allows {mode, context_size, ...} and does NOT
        # include MAX_TOKENS; adding it breaks Pydantic validation when the API
        # parses the schema from plugin_daemon. max_tokens is already exposed
        # as a parameter_rule by the cloned template.
        schema = template.model_copy(deep=True, update={"model_properties": props})
    return schema.model_copy(
        deep=True,
        update={
            "model": model,
            "label": I18nObject(zh_hans=model, en_us=model),
            "fetch_from": FetchFrom.CUSTOMIZABLE_MODEL,
        },
    )

改后 docker compose restart plugin_daemon 加载。

对应专题:专题一·四(含"不兼容"根因)、专题五·六(第 4 层)。


六、对齐 context_size(Dify 凭据 vs vLLM max-model-len)

本章导读

- 干什么:把 Dify 里 qwen 凭据的 context_size 与 vLLM --max-model-len 对齐(本文 64000)。

- 完成标志:脚本打印 VERIFY context_size=64000(或与你 vLLM 一致)。

- 注意:不要把 max_tokens 写进 model_properties(那是 parameter_rule,见 专题十)。

- 卡住怎么办:Connection error → 先确认 vLLM 已通;仍失败 专题九·四、专题五·第 2 层。

常见现象可能原因处理
align 脚本 Connection error116:8000 不通回到第四章验 vLLM
context_size 改完又变回去UI 保存覆盖了凭据用 API/脚本改;勿乱点供应商保存
对话截断异常短max_tokens 与 context 混淆专题十一/十二(Agent 快照 max_tokens)

克隆 schema 默认 context_size=1.05M,与 vLLM --max-model-len 65536 不匹配。把凭据 context_size 改为 64000,运行 align_qwen_ctx.py(依赖 dify_api.py)。注意:凭据级 model_properties 里的 max_tokens 不会被 Dify 持久化,且把它写进 model_properties 会让 API 端 schema 校验失败(ModelPropertyKey 不含 max_tokens),所以脚本里只改 context_size:

完整程序:/opt/workbuddy/scripts_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_04.py

运行/引用:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_04.py

python
import json, time
from dify_api import Dify

EMAIL = "38205036@qq.com"
PW = "Difyai123456"
PROVIDER = "langgenius/openai/openai"
MODEL = "qwen3.6-27b"
NEW_CTX = 64000
# 不要在这里加 max_tokens;max_tokens 是 parameter_rule,不是 model_properties。
d = Dify(EMAIL, PW)
ok, resp, csrf = d.login()
print("LOGIN ok=%s" % ok)
if not ok:
    d.close(); raise SystemExit("login failed")

# 1) GET current credential (decrypted) + credential_id
r = d.get("/workspaces/current/model-providers/%s/models/credentials?model_type=llm&model=%s"
          % (PROVIDER, MODEL))
js = r["json"] or {}
cur_cred = js.get("credentials") or {}
cid = js.get("current_credential_id")

# 2) inject/overwrite model_properties (只保留 context_size)
cred = dict(cur_cred)
cred["model_properties"] = {"context_size": NEW_CTX}
name = js.get("current_credential_name") or "Qwen3.6-27b"

# 3) PUT update
r = d.req("PUT", "/workspaces/current/model-providers/%s/models/credentials" % PROVIDER,
          body={"model": MODEL, "model_type": "llm",
                "credential_id": cid, "credentials": cred, "name": name})
print("PUT cred HTTP", r["code"], r["text"][:200])

# 4) verify
time.sleep(2)
r = d.get("/workspaces/current/models/model-types/llm")
for p in (r["json"] or {}).get("data", []):
    if p.get("provider") == PROVIDER:
        for m in p.get("models", []):
            if m.get("model") == MODEL:
                mp = m.get("model_properties") or {}
                print("VERIFY context_size=%s" % mp.get("context_size"))
d.close()
bash
python align_qwen_ctx.py

前置条件:运行脚本时 vLLM 端点 116.62.10.183:8000 必须已通(否则保存凭据报 Connection error)。详见 专题九·四、专题五·二(第 2 层)。


七、创建知识库 + 创建 Agent 应用

本章导读

- 干什么:建 high_quality 知识库(bge-m3 索引)→ 创建 Agent → 绑库 + 写系统提示词。

- 完成标志:知识库 available、命中测试有 chunk;Agent 编辑页已绑库。

- 卡住怎么办:索引失败回 第三章/专题二;选不到库 专题三;Shell 工具 专题七。

常见现象可能原因处理
知识库一直 indexingembedding 不可用 / weaviate验 bge-m3;专题六
命中测试无结果文档未分段完或 embedding 错等 available;换高质量索引
Agent 知识库列表空bge-m3 未默认 / 库未 available专题三
创建 Agent 报错LLM schema / 列表空专题十

步骤 15 · 建知识库(用 bge-m3 做 high_quality 索引)

  1. 控制台 → 知识库 → 创建知识库。
  2. 上传文档(如 dbbjthr_001_kaoqin.pdf,考勤制度)。
  3. 索引方式选 高质量(high_quality),Embedding 模型选 bge-m3。
  4. 提交后等索引完成(状态 available)。
  5. 进知识库 → 命中测试,输入"考勤"应返回相关 chunk(score≈0.6+)即 RAG 链路通。

步骤 16 · 创建 Agent 应用并绑定知识库

` 你是企业内部知识助手。请根据提供的知识库内容回答问题。 要求:

  1. 如果知识库中有明确答案,请基于知识库回答。
  2. 如果知识库中没有答案,请明确说“知识库中未找到相关内容”。
  3. 不要编造公司制度、流程或数据。
  4. 回答要简洁,适合企业内部员工阅读。

`

把它与刚绑定的知识库配合,即可得到一个「只答知识库里有的、答不出就直说、不乱编」的企内问答 Agent。

对应专题:专题三(选不到知识库时优先查 bge-m3 是否真在跑)、专题五(第 5 层之后)。


八、Patch 插件 chat.py:合并多条 system(适配 Qwen3 chat template)

本章导读

- 干什么:改 openai 插件 chat.py,把 Agent 多条 system 合并成一条再发给 vLLM。

- 完成标志:patch 后 plugin_daemon 已 restart;Agent 对话不再 400 System message。

- 卡住怎么办:from __future__ 顺序错 专题八;仍 400 专题四·二、专题五·第 7 层。

常见现象可能原因处理
patch 后 SyntaxErrorfuture import 不在文件首行专题八
仍报 system 相关 400未 restart plugin_daemondocker compose restart plugin_daemon
改错文件插件 hash 目录变了docker exec 找最新 openai-1.0.3@ 路径

Qwen3/qwen3_5 的 chat template 只接受一条 system 消息且在最前。Dify(尤其 Agent)会把多条 system/developer 提示堆在前面,需让插件出站前把多条 system 合并为一条。

文件:.../openai-1.0.3@<hash>/models/llm/chat.py 先备份:cp chat.py chat.py.bak_systemfix

改动 1 · 在模块顶部(from __future__ import annotations 之后)新增 helper:

完整程序:/opt/workbuddy/scripts_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_05.py

运行/引用:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_05.py

python
def _ensure_system_first(messages: list[PromptMessage]) -> list[PromptMessage]:
    """Qwen3/vLLM chat templates accept ONLY one system message, at the start.

    Dify (esp. Agent apps) stacks several system/developer prompts: KB assistant
    prompt + agent tool prompt + an empty system + config manifest... Merging them
    into a single system message (joined by blank lines, empties dropped) keeps it
    at the front; every other message keeps its original relative order.
    """
    system_parts: list[str] = []
    rest: list[PromptMessage] = []
    for m in messages:
        if isinstance(m, (SystemPromptMessage, DeveloperPromptMessage)):
            text = _text_content(m)
            if text.strip():
                system_parts.append(text)
        else:
            rest.append(m)
    if system_parts:
        rest.insert(0, SystemPromptMessage(content="\n\n".join(system_parts)))
    return rest

⚠️ _ensure_system_first 必须写在 from __future__ import annotations 之后,否则整文件 SyntaxError、插件加载失败(详见 专题八)。

改动 2 · 在 client.chat.completions.create(...) 的 messages= 处调用它(只改一行):

完整程序:/opt/workbuddy/scripts_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_06.py

运行/引用:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_06.py

python
    response = client.chat.completions.create(
        model=model,
        messages=cast(Any, [message(item) for item in _ensure_system_first(prompt_messages)]),
        stream=stream,
        **params
    )

改后 docker compose restart plugin_daemon。

对应专题:专题一·六、专题四·二(A 类 400)、专题五·九(第 7 层)。


九、端到端验证可用(Agent 必须 streaming)

本章导读

- 干什么:用 streaming 调 Agent API,问一道知识库里有答案的制度题。

- 完成标志:SSE 流里无 event: error,且回答引用知识库内容(非胡编)。

- 卡住怎么办:blocking 模式会被拒;Connection/400 按 专题四、专题五 分层查。

常见现象可能原因处理
event: error + ConnectionvLLM 断或 Dify→116 不通验 vLLM;专题五
流式空、无正文max_tokens 过小专题十一/十二
答非所问未绑库 / 检索失败回步骤 15~16;专题十三 Rerank 后续优化

完整脚本:scripts_bash_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_13.sh

运行/引用:bash /opt/workbuddy/scripts_bash_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_13.sh

bash
#!/usr/bin/env bash
set -euo pipefail

curl -s -N -X POST 'http://192.168.110.207/v1/chat-messages' \
  -H 'Authorization: Bearer app-v9yYVTQTrUoSFHNqAhufSGmh' \
  -H 'Content-Type: application/json' \
  -d '{"inputs":{},"query":"描述考勤与请休假管理制度的目的","response_mode":"streaming","user":"debug"}'

返回流式事件里无 event: error、且能正确检索知识库给出答案,即全链路打通。

Agent 应用必须用 streaming 模式(blocking 会被拒)。对应专题:专题四·二(验证)、专题五(最终验证)。


十、本手册 ↔ 专题合集 对应关系(完整方案索引)

本手册步骤做的是什么卡住时看
步骤 2weaviate 端点 / 中继专题六
步骤 6–9Xinference + bge-m3专题二(运行机制)、专题三(知识库选不到)
步骤 10–11vLLM 启动 + 验证专题九·二、三(启动三坑/共享GPU)、专题四·四(C 类)、专题五·一
步骤 12–14接入 Qwen + 设默认 + llm.py patch专题一(接入/不兼容)、专题五·四、六(第 4/5 层)
章节八(chat.py)/ 章节九合并 system + 验证专题四(问答报错)、专题五·七(第 7 层)
步骤 6(上下文对齐)context_size 对齐 64000专题九·四、专题五·二(第 2 层)、专题十(max_tokens 勿写进 model_properties)
步骤 15–16知识库 + Agent 绑定 / 模型列表为空专题三(选不到)、专题七(shell provider)、专题十(创建 Agent 报错 / 列表为空)

完整排查路径:当"模型运行成功但 Dify 仍不行"时,按 专题五 的 7 层从外到内逐层定位。


十一、配置清单(部署完成后应有的状态)

风险提示:两处插件补丁位于 plugin_daemon 卷内,升级/重装 openai 插件会被覆盖,需重打(备份 llm.py.bak_qwenfix、chat.py.bak_systemfix 已留在卷内)。


本专题 Python 程序一览

| 文件 | 用途 | |---|---|| Qwen手册_01_添加Embedding凭据.py | Qwen 手册 · 步骤 9:添加 bge-m3 Embedding 凭据。 | | Qwen手册_02_添加Qwen凭据.py | Qwen 手册 · 步骤 12:添加 Qwen3.6-27B 自定义 LLM 凭据。 | | Qwen手册_03_设默认LLM.py | Qwen 手册 · 步骤 13:设 qwen3.6-27b 为默认 LLM。 | | Qwen手册_04_对齐context_size.py | Qwen 手册 · 步骤 6:对齐 Qwen context_size=64000(勿写 max_tokens 进 model_properties)。 |

Qwen手册_01_添加Embedding凭据.py

运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_Qwen安装配置手册/Qwen手册_01_添加Embedding凭据.py

完整补丁:patches_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_02.py

运行/引用:见 patches_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_02.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Qwen 手册 · 步骤 9:添加 bge-m3 Embedding 凭据。"""
from __future__ import annotations

import subprocess
import sys
from pathlib import Path


def main() -> int:
    script = Path(__file__).resolve().parent / "专题二_04_添加Dify_Embedding凭据.py"
    return subprocess.call([sys.executable, str(script)])


if __name__ == "__main__":
    sys.exit(main())

Qwen手册_02_添加Qwen凭据.py

运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_Qwen安装配置手册/Qwen手册_02_添加Qwen凭据.py

完整补丁:patches_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_03.py

运行/引用:见 patches_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_03.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Qwen 手册 · 步骤 12:添加 Qwen3.6-27B 自定义 LLM 凭据。"""
from __future__ import annotations

import subprocess
import sys
from pathlib import Path


def main() -> int:
    script = Path(__file__).resolve().parent / "专题一_01_添加Qwen凭据.py"
    return subprocess.call([sys.executable, str(script)])


if __name__ == "__main__":
    sys.exit(main())

Qwen手册_03_设默认LLM.py

运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_Qwen安装配置手册/Qwen手册_03_设默认LLM.py

完整补丁:patches_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_04.py

运行/引用:见 patches_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_04.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Qwen 手册 · 步骤 13:设 qwen3.6-27b 为默认 LLM。"""
from __future__ import annotations

import subprocess
import sys
from pathlib import Path


def main() -> int:
    script = Path(__file__).resolve().parent / "专题一_02_设默认LLM.py"
    return subprocess.call([sys.executable, str(script)])


if __name__ == "__main__":
    sys.exit(main())

Qwen手册_04_对齐context_size.py

运行:/usr/bin/python3 /opt/workbuddy/scripts_cursor/Dify_Qwen安装配置手册/Qwen手册_04_对齐context_size.py

完整补丁:patches_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_05.py

运行/引用:见 patches_cursor/Dify_Qwen安装配置手册/Qwen手册_内联_05.py

python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Qwen 手册 · 步骤 6:对齐 Qwen context_size=64000(勿写 max_tokens 进 model_properties)。"""
from __future__ import annotations

import sys
import time

import _bootstrap  # noqa: F401
import _config as C
from dify_api import Dify


def main() -> int:
    d = Dify(C.DIFY_EMAIL, C.DIFY_PASSWORD)
    try:
        ok, _, _ = d.login()
        print("login ok:", ok)
        if not ok:
            return 1

        r = d.get(
            "/workspaces/current/model-providers/%s/models/credentials?model_type=llm&model=%s"
            % (C.OPENAI_PROVIDER, C.QWEN_MODEL)
        )
        js = r.get("json") or {}
        cur_cred = js.get("credentials") or {}
        cid = js.get("current_credential_id")
        print("current_credential_id:", cid)

        cred = dict(cur_cred)
        cred["model_properties"] = {"context_size": C.QWEN_CTX}
        name = js.get("current_credential_name") or C.QWEN_MODEL

        r = d.req(
            "PUT",
            "/workspaces/current/model-providers/%s/models/credentials" % C.OPENAI_PROVIDER,
            body={
                "model": C.QWEN_MODEL,
                "model_type": "llm",
                "credential_id": cid,
                "credentials": cred,
                "name": name,
            },
        )
        print("PUT cred HTTP", r.get("code"), (r.get("text") or "")[:200])

        time.sleep(2)
        r = d.get("/workspaces/current/models/model-types/llm")
        for p in (r.get("json") or {}).get("data") or []:
            if p.get("provider") == C.OPENAI_PROVIDER:
                for m in p.get("models") or []:
                    if m.get("model") == C.QWEN_MODEL:
                        mp = m.get("model_properties") or {}
                        print("VERIFY context_size=", mp.get("context_size"))
        return 0 if r.get("code") == "200" else 1
    finally:
        d.close()


if __name__ == "__main__":
    sys.exit(main())