← 返回文章列表

Dify 应用的可演进性:好的应用,改动是加法而不是重写

📖 摘要:交付后要接监控、换数据源、对接系统——你的 Dify 应用是加法还是重写?可演进性是设计职责:预留接口、输出契约、演进路径图成本≈0 必须做,功能实现等真实需求再付费。H3C 16/16 通过,附落地方法。

📌 本文要解决的核心痛点

  • 应用交付后客户说「以后能不能接监控告警」,你发现入口写死、要改主链路——加法变重写
  • 应用输出是给人看的文字,业务系统想直接用结果(自动记录、自动处置),得专门写程序去「读」这段文字——字段还不固定,今天能解析出来,明天可能就解析不出来
  • 换个知识库、换个模型,本来应该是改个设置的事,结果要动应用内部结构——牵一发动全身
  • 「可演进」听起来重要,但没人告诉你设计时具体查什么、验收时怎么确认兑现

结论

可演进性是 Dify 应用工程的设计职责,不是可选项。 设计表达(预留接口、输出契约、演进路径图)成本约等于零——定义几个变量、声明输出结构、写一页路径说明——小单大单都该做;功能实现(采集器、审批流、审计回滚)是付费项,等真实需求出现再投入。

这句话可以被实践推翻,因此才成立:如果一个应用在接新输入来源、接下游系统、换知识源时,都需要重写主链路而非加节点或改配置,那么这个应用的演进性设计就是失败的。 反过来,能通过「加节点、改配置」承载新需求的应用,演进性设计就是成功的——它的演进性,是设计出来的。

一句话记住方法论:留契约不留实现、留位置不留功能、留路径不留代码——接口位置现在定(免费),功能开发是要花钱花时间的,等真实需求出现再投入——不为「可能永远不会来的未来」预支开发成本。

场景:客户问「这应用以后能加功能吗」

交付现场最常见的一句追问:「这个应用以后能加功能吗?比如接我们的监控系统。」

我们最早的回答是「可以,加节点就行」。直到 H3C 故障诊断助手第一版交付后,客户提出两个新需求:把企微里人工提问的入口,换成监控平台的告警事件自动触发;把诊断结果结构化,供下游处置系统直接消费。我们对着 DSL 看了一下午,结论是:入口写死在对话链路里、输出是给人读的自由文本——加需求等于重构主链路。

「能跑」和「能长」是两件事。能跑,验证的是今天;能长,设计的是明天。而明天的需求,客户说不清楚,我们预测不准——唯一能做的,是在今天的设计里为「不知道的明天」留出位置。

推导链:为什么可演进性是可以被设计的

可演进性不是玄学,它可以从 Dify 平台的三条约束直接推出来。

第一,应用是一张图,改动影响面由切分方式决定。 Dify 工作流是节点图,一个新需求落在哪里,取决于节点怎么切。按「功能」切——一个「客服」节点、一个「报表」节点——需求一来就要拆节点、重连边,影响面扩散;按「变换」切——改写、检索、合并、回答,每个节点只做一件事——需求来了是在链上加节点或换节点,影响面被隔离在单点上。做法是管道思维:先画数据流(输入→变换→输出),再切节点,不按功能清单切。同一个「知识库问答」应用,按功能切是「知识库节点+回答节点」两个大块,按变换切是「改写→检索→合并→回答」四个单点——后者加一个意图分类、加一个来源适配,都只是链上插节点。

第二,输出就是接口,字段名就是契约。 下游节点用 {{#节点id.字段名#}} 引用上游输出,字段名一旦变化,所有引用它的节点都要跟着改——改名等于全链路重写。所以字段名和输出结构是接口,要优先定死再实现。这就是契约优先——说白了就是接口怎么定义:字段名、结构、含义先定下来,定下来就不能随便改(下游全按字段名引用),改接口必须全链路评估。

第三,不确定性要外置。 输入来源(人话、事件、文件)、知识库、模型——这些「未来会变的东西」要留在适配层、聚合点、参数里,不写死在主链路。入口来源写死成对话变量,将来接监控事件就要改主链路;入口留一个来源字段,将来只是往字段里加一个枚举值。

三条合起来,就是检验一个设计是否可演进的三问:

答案如果是「改节点、改链路」,这个设计就没给未来留位置。

正例:一个把「未来改动」当设计输入的应用

H3C 故障诊断助手第二版(进化版)是把可演进性当设计输入做的,三个实证点。

1. 入口来源留了适配位(感知接口)。 输入来源现在是企微人话,未来可能是监控告警事件。DSL 里定义了一个来源变量 source_type,默认不启用:

# workflow.conversation_variables 中的感知接口(注释为作者标注)

- id: a1b24533-38b3-41b1-b62d-004b749351e3

  name: source_type            # 输入来源

  value_type: string

  default_value: null          # 接口位已留,默认不启用——未来消费者出现时再填

  description: 感知接口:输入来源(wecom=企微人话 / monitor=监控事件,未来)

关键在 default_value: null——接口位定了,但实现不预埋。未来监控事件接入时走同一入口,主链路(改写→检索→合并→回答)零改动。这就是「留契约不留实现」:位置留好,内容等真实需求,绝不为「可能出现的未来」提前实现采集器。

2. 输出留了结构化契约(认知接口)。 检索结果合并节点的输出声明了结构化字段:

# 检索结果合并节点的输出契约

outputs:

  evidence:

    type: array[object]        # 证据三元组:ref 编号 / source 来源 / content 内容

  result:

    type: array[object]

evidence 三元组让诊断结论的每一条依据都可溯源、可被下游系统直接消费——未来接处置系统时不需要解析自由文本。这是「输出即接口」的落地:字段结构在 DSL 里显式声明,消费者按契约接入,字段名就是合同。

3. 输出文本形态也定了契约。 LLM 回答按固定分段标题组织:【结论】(一句话 + 置信度:高/中/低)、【依据】([N] 编号引用)、【排查步骤】、【修复建议】、【风险提示】。分段标题就是字段的文本形态——未来要 JSON 化,只改渲染层,语义字段不动。形态是文本还是 JSON 会演进,字段语义是稳定的。

验收闭环: 这套设计不是拍脑袋。第二版走完设计-验收全流程,独立验收 16/16 用例通过,其中包含「输出契约结构断言」——验收时确认 evidence 结构真实产出、ref 连续编号、source 有值,防止契约漂移。可演进性被当成验收项查了,不是设计完就没人管。

反例:第一版没留位置,三个月后重写

正例的反面就是第一版。H3C 故障诊断助手 v3(线上版)是典型「能跑」应用:输入写死在对话链路、输出是自由文本报告。客户提出接监控事件 + 结构化输出两个需求后:

注意区分壳与核:这个案例里「H3C、故障诊断、企微」是壳,换成任何应用都成立;「入口写死 + 输出无契约 + 按功能切节点 → 新需求变重写」是核。壳会变,核不会。

反例的教训是成本级的:第一版「省掉」的设计表达,在重写时连本带利还回去——重设计、重测试、重验收,代价是初版做接口设计的几十倍。省掉的不是成本,是未来。

实践动作:四个环节怎么落地

设计时:检查清单 + 预留度三问。 设计 DSL 时逐项过核心几项:

检查项 怎么算过
数据流主干 输入→变换→输出画清楚了,先有流后有图
节点职责边界 按变换切节点,每个节点输入/输出/职责写清楚
入口适配层 输入来源有抽象位,不是写死成人话
输出契约 结构化字段声明(array[object] 等),不是只有给人看的文本
依赖参数化 知识库/模型/Key 走变量,不硬编码
检验三问 加库=加节点?换模型=改配置?加来源=加适配层?

拿不准留不留时用预留度三问:断裂点距离近(相邻演化)→ 留接口;改动方向是加节点/改配置 → 留接口,是范式跃迁 → 只写演进路径图声明位置;预留成本约等于零(字段/结构)→ 现在定,成本高(采集器/审批流)→ 等真实需求。

细化时:契约写进文档。 三个位置:进化愿景(这个应用 1-2 年后感知/认知/行动各自到什么程度)、接口契约表(感知/认知/行动三接口的字段与结构)、契约守护(细化时用自验证用例断言契约结构,防漂移)。文档的价值在「契约有主」——字段名改没改、结构变没变,有据可查。

验收时:确认设计表达真的兑现。 五项检查:契约存在性(接口在 DSL 里真实存在吗)、契约有效性(加了契约后当前行为没变吗)、改造回归(模拟一次演进,主链路零改动吗)、演进路径图(交付物里给了吗,和 DSL 一致吗)、信任机制匹配(自主性每上一级,审批/审计/回滚跟上吗)。关键判定纪律:「未来消费者还没出现,契约字段没被消费」不是缺陷——这是设计预期,别当 bug 报。

工具化:静态评估自动体检。 可演进性的静态部分(入口抽象、输出契约、分段标题、知识源参数化、模块化)是确定性检查,可以固化进规则引擎——上传 DSL 即出「可演进性评估」:每项 有/部分/无 + 演进潜力(高/中/低)。工具的价值在于:设计者自己过清单会偷懒,机器不会;而且它把「可演进性」从一句口头承诺,变成了每份体检报告里可量化的一节。

graph LR A["设计时:检查清单 + 预留度三问"] --> B["细化时:接口契约表 + 契约守护"] B --> C["验收时:检查表确认兑现"] C --> D["工具化:静态评估自动体检"] D --> A

边界与版本

适用: 需要长期迭代、可能接入外部系统的应用——企业客服、故障诊断、运营看板这类会「长大」的应用。

不适用: 一次性工具、演示 demo、轻档交付——这类应用评估为「低潜力」是正常状态,不是缺陷。可演进性设计是成本约等于零的表达,但它服务的对象是「会长的应用」;确定不会长的,不做也不欠债。别把「没有预留接口」报成错误,那是常态。

版本: 本文基于 Dify 1.16.x 实测。节点格式、字段写法版本相关;「留契约不留实现」「输出即接口」「按变换切节点」是理念层,版本无关。升级 Dify 后需复核节点格式与接口契约是否受影响(如新节点类型出现、旧格式废弃)。

收尾

回到开头那句「这应用以后能加功能吗」。现在我们的回答是:「能——前提是设计时把未来当输入。」可演进性不预测未来,它只做一件事:让未来的每个改动都尽量是加法,而不是重写。 而加法还是重写,在设计那一刻就已经决定了。

💬 你交付的应用里,有没有遇到过「加需求等于重写」的时刻?当时怎么处理的?评论区聊聊。

本文基于 Dify 1.16.x 实测(2026-08)。AI 参与创作声明:本文由 AI 辅助写作,内容基于作者真实实测记录。

这套方法,如何应用到您的项目?看看方案与服务 →

联系我

邮箱contact@fishsun.cn

点击邮箱直接写信 · 扫码加微信沟通

微信

微信二维码

扫码加微信 · 备注「门户」更快通过