← 返回文章列表

Dify 韧性验证实验(01):故障注入与降级链——如何用故障注入验证 AI 应用的降级链?

1. 业务场景

先讲一个我们实际遇到的场景。

一家做客服工单 SaaS 的公司,凌晨两点,用户在 App 里提交了一个「账号无法登录」的工单。系统本该自动创建工单、生成摘要、给用户发一条「已受理」通知——但当晚通知 API 恰好挂掉了(http 500)。结果呢?用户没收到任何反馈,以为工单没提交成功,又连着提交了两次——同一故障变成了三张重复工单,第二天客服光合并工单就花了半小时。更糟的是另一种情况:模型服务 429,摘要节点直接失败,整个流程崩掉,工单压根没建上,用户的求助石沉大海。

我们第一次接这类需求时,第一反应是「把降级分支配上就完事了」。真正动手才发现——配了降级分支,不等于降级链能用。没人真正走过降级路径,真出事时降级分支自己也是坏的;后来我们干脆把「主动制造故障、逐路验证降级」写进上线前检查项,这个实验就是这么来的。

这不是个例。任何「用户提交 → 系统处理 → 通知用户」的线上链路都是这个模式:电商下单后的支付回调、物流系统的签收通知、SaaS 平台的告警推送——下游任何一个环节抖动,用户感知到的都是「系统坏了」。

2. 场景痛点

这套链路的问题,在工单系统上体现得最直接:

本质上,上线前最该被证明的不是「好的时候能用」,而是「坏的时候不崩」——而「坏的时候不崩」只能靠主动制造故障来证明

3. 方案:为什么是故障注入

Dify 工作流里验证降级链最直接的方法,就是故障注入:主动制造故障(http 节点失败 / 模型异常 / 知识库空结果),验证应用的降级路径真的兜住——而不是只在 happy path 下跑通。

选它的理由:

这篇文章我们就用它搭一个「工单流程应用」:创建工单 → 校验 → 通知,然后主动注入三类故障,逐路验证降级链真的兜得住。

4. 整体架构

graph TD start["开始:order_id / issue_text / notify_url"] extract["参数提取:校验订单号非空"] kb["知识库检索:工单分类参考"] kb_ref{"是否有参考"} llm["LLM 生成工单摘要"] llm_empty{"LLM 输出是否为空"} fallback["固定话术分支(降级)"] normal["正常摘要"] no_ref["无参考分支(不中断流程)"] notify["通知 API:http,error_strategy: fail-branch"] record_ok["记录成功"] degraded["降级记录 + 固定话术(站内记录)"] end1["结束:多出口 end_ok / end_degraded / end_reask"] start --> extract --> kb --> kb_ref kb_ref -- "有" --> llm llm --> llm_empty llm_empty -- "空" --> fallback llm_empty -- "非空" --> normal kb_ref -- "无" --> no_ref no_ref --> notify normal --> notify notify -- "成功口" --> record_ok notify -- "fail 口" --> degraded record_ok --> end1 degraded --> end1

链路很清晰:入口收工单 → 参数校验 → 知识库参考 → LLM 摘要 → 通知 → 多出口结束。关键设计是每一处可能失败的点(模型、http、知识库)都接了降级分支,且降级信息显式输出——不静默、不裸奔。

5. 模块设计

5.1 降级链三件套

降级链设计是本实验核心,三路降级缺一不可:

  1. LLM 降级:LLM 节点后置 IF-ELSE 判断 text 为空 → 固定话术分支(模型异常时不裸奔)
  2. http 降级:通知节点 error_strategy: fail-branch → 失败分支接降级 code(记录 + 话术)→ 降级 end
  3. 知识库降级:检索节点后 IF-ELSE 判断结果为空 → 无参考分支(流程不中断)

5.2 http 节点 fail-branch 配置

失败分支的 handle 必须写作 fail-branch,不是 fail(实测 104 修正——写错 UI 不画失败分支线、后端不执行降级):

- id: http_notify

  data:

    type: http-request

    title: 通知API

    url: '{{#start.notify_url#}}'

    method: GET

    error_strategy: fail-branch

    timeout:

      max_connect_time: 5

      max_read_time: 10

      enable_retry: true

      retry_interval: 1000

      retry_times: 3

5.3 多出口 end 变量唯一

每个出口有唯一输出变量名(104 多 end 教训):result_ok / result_degraded / result_reask / result_fail——下游消费不歧义,排障时可从出口名直接判断走了哪条路径。

5.4 故障注入方法(三路)

注入路 触发方式 预期降级行为
http 故障 notify_url 指向不可达地址(如 http://172.19.0.50:9999/none fail-branch 降级链 → result_degraded 含明确降级信息
模型故障 LLM 节点配置不可用模型 / mock 空输出 text 为空 → 固定话术分支
知识库空 提问库外内容(如「量子计算集群的冷却液配方」) 无参考分支,流程不中断

6. 运行验证

用例 输入要点 预期 结果
正常路径 order_id=OD2026080501 + notify_url=KV /echo succeeded → result_ok「通知成功,工单已全链路完成」 通过(实测)
注入 http 故障 notify_url=不可达地址 partial-succeeded → result_degraded 含「(已降级)通知通道异常」 通过(实测,fail-branch 降级链真实生效,非静默)
注入模型故障 (LLM 空分支) 固定话术降级节点存在且 reachable 通过(实测结构验证)
注入 KB 空 提问库外内容 succeeded → end_ok(无参考分支不中断流程) 通过(实测)
参数缺失 order_id 空 succeeded → result_reask「请提供订单号后再创建工单」 通过(实测)
反例验证 故意断降级 code 出边 → check_dsls C1-A4 静态检查抓出「fail 分支不可达 end」 通过(实测,生成时按完整降级链设计无断链)

7. 实战坑

现象 修复
容错应用自身不容错 http 500→exception 后链路断裂、partial-succeeded 且出口空 故障注入逐路验证降级链,出口空 = P1(实测,104-17)
失败分支 handle 写错 fail 而不是 fail-branch → 降级分支不执行 统一写 error_strategy: fail-branch + 失败分支 handle 同名(实测,104-03)
降级分支静默 降级只返回默认值不报「已降级」→ 用户不知情 降级分支必须输出明确降级信息(record + 话术)(实测,102-06)
静态检查查不出类型问题 code 输出类型不匹配导入不报错、运行才炸 降级链可达性用 C1-A4 静态检查兜底,类型问题靠运行验证(实测,104-01)

8. 实验文档及源码获取

文章聚焦核心配置与采坑点;实验的完整分步操作(节点搭建/参数表/调试指引)见实验文档原文。

联系我

15088711270

手机端点击号码可直接拨打 · 桌面端可复制

微信二维码

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