Skip to content

Dify 私有部署一周:低代码平台到底"低"掉了什么

一个 8 年 Java 后端,把 Dify 用 Docker Compose 拉起来,接火山方舟、建知识库、调检索参数,最后跟自己前三周手写的 LangGraph4j 硬碰硬对比的一周复盘。

核心结论一句话:Dify 工作流是 DAG,Agent 才是循环。低代码没有降低复杂度,只是把复杂度挪了个位置——从代码挪到了配置,从工程师挪到了业务方。


缘起:手写了三周,为什么要回头看低代码

Week 1 用 OkHttp 手撕 ReAct 的 while 循环,Week 2 换 Spring AI 一行 .tools().call(),Week 3 用 LangGraph4j 把循环画成图、用 MCP 把工具拆成协议。三周下来抽象层次一路往上爬,惯性会让人觉得"接下来该更深"。

结果 Week 4 反着来:装 Dify。理由很务实——

  • 面试要"能落地"的东西。四个 Demo 里三个是"跑在 IDE 里的 main 方法",缺一个"能打开链接演示"的应用。
  • 岗位技能云连续 6 天把 Agent 框架挂榜首,但招聘 JD 里 Dify / Coze 出现频率同样在涨。企业要的不是"最先进",是"下周就能上线"。
  • 想验证一个判断:低代码平台是不是只是把我手写的那套东西包了个 UI?

一周跑完,第三个问题有了答案,而且比预想的有意思。


一、部署:三个坑,两个跟 Dify 无关

环境是 Windows + WSL2 Ubuntu,Docker Desktop 在 Windows 侧。第一个判断是代码放哪:

bash
cd ~ && mkdir -p ~/agent-learning && cd ~/agent-learning
git clone https://github.com/langgenius/dify.git
cd dify/docker && cp .env.example .env
docker compose up -d

放 WSL2 家目录,不放 /mnt/f/。WSL2 内 Linux 文件系统的 IO 是 Windows 挂载点的 3-5 倍,Dify 首次启动要拉 8 个镜像(api / web / worker / db / redis / weaviate / nginx / sandbox)约 3-4 GB,跑在挂载点上是自找麻烦。

坑 1:docker.sock: no such file or directory

failed to connect to the docker API at unix:///var/run/docker.sock

这个报错分两种,排查路径完全不同:

报错病因修法
no such file or directoryDocker Desktop 的 WSL Integration 没开Settings → Resources → WSL Integration 勾 Ubuntu → 重开终端
permission denied当前用户不在 docker 组usermod -aG docker $USER

ls -la /var/run/docker.sock 一条命令就能分清——文件不存在是集成问题,文件存在但读不了才是权限问题。

坑 2:打开 8080 看到 Spring Boot 的 Whitelabel Error Page

这个坑很值得说,因为报错本身就是最好的线索。Dify 是 Python + Node 栈,它不可能返回 Spring 的错误页。看到 Whitelabel 的第一反应不该是去翻 Dify 容器日志,而是——8080 被 Week 1/2/3 忘关的 Spring Boot Demo 占了

bash
sed -i 's/^EXPOSE_NGINX_PORT=.*/EXPOSE_NGINX_PORT=8888/' .env
docker compose down && docker compose up -d

顺手记一条 Docker Compose 的硬规矩:改了 .envdocker-compose.ymlrestart 不生效,必须 down + up -drestart 只重启进程不重读配置,这个坑能让人怀疑人生半小时。

坑 3:Dify 不认模型名,只认 endpoint ID

接火山方舟的时候卡了一下:Settings → Model Provider → 火山方舟,填的不是 doubao-seed-1-6 这种模型名,而是控制台"在线推理 → 已接入的推理点"里的 ep-xxxxxxxx。Chat 和 Embedding 各要一个,而且加 Embedding 模型时模型类型必须选 Text Embedding,选成 LLM 后面建知识库会找不到向量模型。


二、知识库:RAG 三要素里,参数是最不重要的那个

Day 3 的实验是把 Week 1/2/3 三篇周记扔进知识库,做一个能查自己博客的 Chatbot——一鱼两吃,既测 RAG 又当博客搜索。

2.1 父子分段:解决"检索要小、生成要大"的矛盾

先用默认的通用分段(\n\n 切、500 tokens、重叠 50),效果不理想:

  • Python 代码块被切碎,def 和函数体分到两段
  • 中文破折号 —— 被当成分段边界
  • 段落偏短,信息密度不够

换 Dify 的父子分段后明显好转:

父段:1000 tokens,用 \n\n 切  → 保留完整章节,喂给 LLM 生成
子段:200 tokens,用 \n 切     → 精准匹配,用于向量检索

这是对"段落粒度矛盾"的一个工程解。检索希望段落小(越小越精准),生成希望段落大(上下文越足越连贯),传统单层分段只能二选一。父子分段用子段索引、父段回填,类比就是:子段是目录,父段是正文

实测效果最能说明问题:问"Checkpoint 和 State 是什么关系",三个子段命中,但 LLM 拿到的是父段的完整上下文,才能输出"State 是工作内存,Checkpoint 是虚拟机快照"这种精确回答。如果只给它 200 tokens 的碎片,答案会浮在表面。

2.2 Score 0.7 的教训:向量分数不是对错标签

调参跑了三组:

TopKScore命中题半命中题越界题
A 宽松50.3✅ 双源融合✅ 三源召回✅ 拒答
B 基线30.5✅ 正常✅ 引用偏少✅ 拒答
C 严格20.7✅ 命中❌ 引用大幅减少✅ 拒答

C 组的失败很有教育意义。中文 embedding 的分数分布本来就偏低,真正强相关的段落一般落在 0.5-0.7,卡到 0.7 意味着只有跟问题字面高度重合的段落能过关。

举个具体例子:文档里"OkHttp 客户端"和"用 OkHttp 发请求"表达同一件事,向量分可能一个 0.72 一个 0.58。卡 0.7 → 后者被误杀 → 用户问"用了什么 HTTP 库",召回的段落里恰好没有关键词,LLM 就答不上来。

结论:优先控 TopK,Score 放宽甚至去掉,把相关性判断交给 LLM,配合 Prompt 明确"没找到就说没找到"。最终定 TopK=5, Score=0.3——文档少、质量高的场景下,宁可多召回让 LLM 自己甄别。

2.3 意外收获:好知识库不是查找器,是知识关联引擎

三题回归里最惊喜的是 Q2。问的是 Week 3 的 Checkpoint,答案里主动带出了 Week 2 的 ChatMemory 做对比:

ChatMemory = 聊天日志(只有对话历史),Checkpoint = 完整执行现场(State + 程序计数器)

这不是 Prompt 让它做的。是 embedding 把两篇不同博客里的相关概念一起召回了,LLM 自然做了对比。这个效果有三个前提:

  1. 内容本身有交叉引用——我在 Week 3 博客里主动写了跟 ChatMemory 的对比
  2. 分段策略保留了上下文——父子分段功不可没
  3. Score 没卡太严——0.7 的时候这个效果直接消失

所以 RAG 三要素的重要性排序是:好内容 > 好分段 > 好参数。垃圾进垃圾出,参数只是放大器。

2.4 越界题的三段式范式

问"怎么用 Rust 写一个 MCP Server"(博客里完全没有),系统的回答是:

我在博客里没找到相关内容。博客 Week 3 里 MCP Server 的示例用的是 Python 的 FastMCP……如果你需要,我可以根据 MCP 官方规范给你一个 Rust 版的最小实现示例。

承认边界 → 说明边界在哪 → 提供替代路径。这三段式比干巴巴一句"不知道"有用得多,而且是可复现的:Prompt 约束 + LLM 对话能力 + RAG 上下文叠加的结果。Prompt 里那一句就够:

- 只用提供的上下文回答问题
- 如果上下文没有相关内容,说"我在博客里没找到相关内容",不要编造
- 引用来源用 [1] [2] 标注

三、硬碰硬:Dify Workflow vs LangGraph4j

Day 4 没写代码,纯建判断力。四个维度对比下来,最根本的差异只有一句话:Dify 工作流是 DAG,Agent 是循环。

3.1 分支控制:河道分流 vs 回旋镖

LangGraph 里循环是一等公民:

java
.addConditionalEdges("llm",
    edge_async(state -> state.nextAction()),
    Map.of("tool", "tool", "end", END))
.addEdge("tool", "llm");   // ← 这条回环边就是 ReAct 的 while

Dify 工作流里没有回环边。它是有向无环图,条件分支只能"if true 走 A,if false 走 B,最后汇聚到终点"。

Dify 工作流的条件分支是河道分流,不是回旋镖——水流出去就回不来了。 所以像"LLM 生成 → 判断质量 → 不合格重新生成"这种需求,工作流模式做不了,必须用 Agent 模式(内置 ReAct 循环)。

3.2 状态管理:reducer vs 扁平覆盖

维度LangGraph4jDify Workflow
状态定义SCHEMA 声明式 reducer,逐字段定合并策略 模板注入,节点输出→输入传递
生命周期Checkpoint 持久化,threadId 恢复单次对话内有效,无跨对话状态
复杂状态同一 State 混用 append 和覆盖两种策略无 reducer 概念,赋值即覆盖
中断干预interrupt() + Command(resume=...)不支持中间插入

LangGraph 的 State 能做到 messages 用 Channels.appender 追加、nextAction 默认覆盖,同一个状态对象里混用两种合并策略。Dify 的变量是扁平覆盖,表达不了这个。这就是为什么 Dify 适合单次执行的管道,多轮 Agent 对话得靠 LangGraph 的 Checkpoint。

3.3 可观测性:这一局 Dify 赢

必须承认,Dify 在这里是压倒性的:

能力DifyLangGraph4j
调用链追踪内置 Trace 面板 + 耗时瀑布图需自接 LangSmith / Langfuse
Token 成本应用级看板,按模型/日期拆解手动统计
错误处理节点错误高亮 + 自动重试 + 降级兜底自己写 try/catch
Prompt 版本内置版本管理 + 一键切模型改代码重新部署

但代价是 Agent 模式内部是黑盒。你看不到 ReAct 循环里每一步的 state 变化,排查"第 3 次工具调用时 messages 列表是什么"这种问题,还是得回到 LangGraph 的 stream()

产品化的可观测性开箱即用但有天花板,自建的可观测性要成本但没有上限。


四、抽象层次不是高低,是分工

一周下来最值钱的认知是这张图:

Layer 3: Dify 产品层 ───── "业务方操作"
         UI 拖拽 / 知识库管理 / Trace 面板 / 一键部署

Layer 2: LangGraph 框架层 ─ "工程师编排"
         StateGraph / Checkpoint / ToolNode / 条件边

Layer 1: 手写代码层 ─────── "完全控制"
         while(true) / if(toolCall) / OkHttp / 自己拼 Prompt

三层不是替代关系,是同一件事的不同封装粒度,选哪层取决于团队构成,不是取决于哪个更先进。

选型上我现在的判断标准:

  • 业务方要自己调 Prompt / 管知识库 → Dify,没有第二选项
  • 简单 RAG 问答、固定流程管道、3 天出 Demo → Dify
  • 多轮 Agent、复杂状态、要 HITL 中断 → LangGraph
  • 生产环境要排查幻觉/死循环 → LangGraph,透明性是刚需
  • Java 微服务深度集成 → LangGraph4j,技术栈统一
  • 两个一起用:Dify 做业务接入层(飞书对接 + 知识库 + Prompt 迭代),LangGraph 做 Agent 引擎,Dify 的 HTTP 节点调 LangGraph 的 REST API

五、面试角度:低代码经历怎么讲才不像"只会拖拽"

这是本周想清楚的最实用的一件事。低代码平台经历讲不好会显得很水,讲好了是加分项。区别在于能不能说出它的边界

三个高频问题的答法:

Q:Dify 能替代 LangGraph 吗? "Dify 的工作流是 DAG,Agent 模式封装了 ReAct 循环但它是黑盒——不能在中间插入人工审批,也不能自定义状态合并策略。LangGraph 让循环变回'一条普通边 + 一个条件边',控制权在代码里。"

Q:Dify 的 Agent 模式内部大概怎么实现的? "它的 /chat-messages 接口返回 event: agent_messageevent: agent_thought,事件流跟 LangGraph 的 stream() 很像。内部大概率是 LLM 推理 → 判断是否需要工具 → 执行 → 观察 → 再推理,加一个最大迭代次数和超时保护。"

Q:给团队做选型,什么指标决定用哪个? "团队角色、业务复杂度、可观测性需求、技术栈、交付速度,五个维度打分。选型不是选框架,是选团队分工。"

关键是从底层原理讲到产品分工。手写过 while 循环的人讲 Dify 的黑盒问题,和只用过 Dify 的人讲,可信度完全不同。


总结:这周站到了哪一层

Week 1 手写循环,Week 2 框架封装,Week 3 图编排 + 协议解耦,Week 4 产品化平台。四层都站过之后,对"低代码"的看法变了:

它没有降低复杂度,只是把复杂度从代码挪到了配置,把决策权从工程师挪到了业务方。 该处理的问题一个都没消失——分段策略要选、检索参数要调、循环能力的缺失要用 Agent 模式绕、黑盒排查要接受。只不过这些问题从"写 200 行 Java"变成了"点几个下拉框",从而让不写代码的人也能参与。

对 8 年 Java 老兵的价值也很清楚:我不是"会用 Dify",我是"知道 Dify 在什么地方会撞墙,撞墙之后能退回代码层继续走"。这个才是差异化。

Week 4 的遗留债:Dify 接飞书还没做,Agent 工作流的实操还差一半。Week 5 的方向是 RAG 进阶(Rerank / HyDE / Ragas 评估),把"能跑"往"能量化"推。


参考:Dify 官方 docker-compose 部署文档 · 火山方舟在线推理接入点配置 · LangGraph4j 1.8.20 · 笔记 week4-day2-Dify-Setup.md / week4-day3-Dify-RAG.md / week4-day4-dify-vs-langgraph.md

手撕 → 框架 → 生产化,一步步把 AI Agent 工程化搞透。