从踩坑到定理(三):架构决策轴——为什么状态会散落、失败靠运气、验收翻车?
从踩坑到定理:Dify 应用工程的通用理论 · 3/7
基于 Dify 1.16.x + 69 个实战实验实测(2026-08)
📖 摘要:架构决策没有唯一解,但有一条经过验证的优选路径:状态最小化 → 失败外置 → 测试分层。三条定理顺序依赖,分别回答状态怎么管、失败怎么办、怎么证明它可靠,并落地为可直接使用的评审清单。
📌本文要解决的核心痛点
- 工单应用状态散落各处,多轮对话后错乱?
- 失败路径靠运气,线上偶发崩溃?
- 验收时「看起来能跑」,上线就翻车?
- 本文用三条设计法则(状态最小化、失败外置、测试分层)回答:状态怎么管、失败怎么办、怎么证明它可靠。
前两个维度都是「约束」:平台约束违反必错,LLM 行为违反必不稳定。架构决策轴是自由度——平台不规定你怎么做,模型也不限制你怎么做。状态存哪、失败怎么处理、验证怎么分层,都是我们的选择。
自由度的坏消息是:没有唯一正确答案,方案好坏全看设计功力。好消息是:自由度的空间里藏着可复用的设计法则——不是「必须这样」,是「这样经过验证更优」。
场景
做一个企业级工单流转应用:用户提交工单 → 状态机流转(待处理/处理中/已完成)→ 过程中可能失败(外部系统不可用、模型超时)→ 多轮对话收集信息。
如果设计时每个决策都「临时想」,会出现什么?状态散落在各处(有的在会话变量、有的在临时拼的参数里)、失败路径靠运气(没想过外部系统挂了会怎样)、验证靠手工点几遍「看起来能跑」。
我们第一版就长这样。直到验收阶段被一连串问题打回:状态不一致、失败后重试把数据搞脏、验证漏掉的边界在线上炸了。这版经验直接逼出了这一篇的三条定理。
结论
架构决策没有唯一解,但有一条经过验证的优选路径:状态最小化 → 失败外置 → 测试分层。
三条定理,一条比一条靠后——只有前一条做对了,后一条才有意义。
定理 3:状态最小化——无状态优先,状态必须显式化。
定理 4:失败外置——失败路径是设计出来的,不是运行时发现的。
定理 5:测试分层——节点级形状断言 + 链路级端到端,两种失效模式要两种验证手段。
推导链:三条定理怎么推出来的
定理 3 的推导。LLM 应用里「状态」是最危险的东西:它不可复现(模型输出是采样)、它容易污染(跨轮次上下文传染)、它难以排查(看不到中间值)。所以状态越少越好,且每一份状态必须显式化——放在看得见、查得到的地方(会话变量、外部存储),而不是散落在节点参数或隐式上下文里。
显式化的标准动作是:需要跨运行保存的状态,放显式存储(如 Dify 的会话变量、外部 KV 容器),而不是依赖「这次运行刚好还留着」。状态最小化不是「不用状态」,是「每一份状态都有名字、有出处、有生命周期」。
定理 4 的推导。LLM 应用的外部依赖多(模型 API、知识库、外部系统),任何一步都可能失败。失败不是异常,是默认情形。如果不设计失败路径,失败时应用会怎样?报错、卡死、返回垃圾数据——都是运行时才发现。失败外置的意思是:在设计期就把「失败时走哪条路」定好——重试、降级、兜底、明确报错,每条失败路径都是设计出来的,不是撞出来的。
EDD 对定理 4(失败外置)的应对是全流程嵌入的,按阶段对应: 一、设计期——失败场景先划(不是运行时撞) - 边界卡「边界清单带来源」+ 禁区五问(钱/权/数据/嘴/转)——「哪些场景会失败、哪些不做」在 TR0 就定死 - 排雷(接单评估只读体检):检索源当场测 3 个问题看召回——原话「把数据账算在接单前,不等到开工后发现数据一塌糊涂」——「数据不可得」这类失败在设计期就发现,拒单或加价,根本不进运行期 二、执行期——失败响应预案先定 - 介入纪律 ABC:A 报错强制介入——失败信号立即升级,纠正评估标准重跑,不在运行期闷头撞 - 熔断四字段(写进每个 MRT 定义):max_steps / token_budget / forbidden_actions(边界卡投影)/ escalate_to_human——「跑飞了怎么办」是执行前定好的,不是跑飞了才想 - 失败传播三原则(v2.1):① 失败分类路由——瞬时错误→重试、业务错误→降级、契约错误→快速失败(各走各的路,不混)② 失败向上冒泡但语义化——主工作流知道哪块失败/为什么/影响什么,原始报错不直抛用户 ③ 错误消息当 LLM 自纠错的燃料 - 错误分类桶(数据/逻辑/工具/提示四桶)+ 归因三分——失败发生时先判「哪层错」,有分类路径可走 三、验证期——失败路径被验证 - 用例三来源:「边界禁区 → 负向用例(测 AI 不会越界)」——失败路径的验证用例在设计时就有来源,不是测试时补 - 硬断言可执行:禁区触发 / forbidden action 是机器可执行的断言 - TR4 缺陷分级 P0-P3、严重=0 才通过——失败后果分级管理,不是一把抓 四、沉淀期——失败教训复用 - 跑通记录四要素(错了什么/怎么修的)→ 错误清单 → TR2 约束 → 下一单复用 - 三道阻尼器——失败认知防扩散成毒资产(推测→泛化→固化→复利) 一句话对照:定理 4 管「应用内部失败路径」——Dify 侧实现是失败契约三件套(error_code/error_message/retryable);EDD 管「交付过程失败路径」——禁区/熔断/兜底/负向用例。两层同构,都在实践「失败路径是设计出来的」——而且 EDD 多了一层:失败本身变成资产(错误清单进 TR2 复用),不只是防御。定理 5 的推导。这来自一个被反复验证的事实:节点是局部的、链路是全局的。LLM 节点的非确定性是局部的(这个节点输出可能漂移),链路的可靠性是全局的(数据流跨多个节点,某处断裂全链失败)。两种失效模式需要两种验证手段:节点级验证用形状断言(输入输出是否符合契约),链路级验证用端到端用例(真实业务路径是否通)。只用一种验证,必然漏掉另一种失效模式。
正例实证:工单流转应用的完整正确设计
按三条定理重做后的工单应用,验收一次通过:
- 状态最小化:工单状态唯一存在会话变量里,状态机流转全部用确定性节点实现,每步状态迁移有明确的输入输出。多轮对话中状态不丢、不重、不串。
- 失败外置:外部系统不可用时走降级路径(缓存数据兜底 + 明确提示);模型超时走重试(有限次 + 退避);LLM 输出不合法走归一化(重试一次 + 规则兜底)。每条失败路径在设计文档里画得清清楚楚,上线后没有一条是运行时才发现的新失败路径。
- 测试分层:节点级——每个确定性节点配形状断言,LLM 节点配输出校验;链路级——按业务路径枚举端到端用例(提交→流转→完成、提交→外部失败→降级→完成、提交→超时→重试→完成)。两层验证覆盖了「节点坏了」和「链路断了」两类问题。
反例实证:第一版怎么翻的车
第一版的三宗罪,正好是三条定理的反例:
反例 1(状态最小化):状态散落。
现象:工单状态同时存在会话变量、拼接参数、临时字段里,互相不同步,多轮对话后状态错乱。
根因:没做状态设计——「用的时候随手放一个地方」。
修复:状态收敛到唯一显式存储,其余全部删掉。删完状态相关 bug 归零。
反例 2(失败外置):失败路径靠运气。
现象:外部系统测试时正常,上线后偶发不可用,应用直接报错卡死,用户看到一屏英文错误。
根因:设计时没想过「外部系统会挂」——失败路径是空的。
修复:补全降级/重试/兜底路径。之后外部系统故障时,应用行为是「设计好的行为」,不是「崩溃」。
反例 3(测试分层):验证漏边界。
现象:验收时手工点了几遍主路径「看起来能跑」,上线的第一周,链路边界场景(半路失败、极端输入)连续出问题。
根因:只做了链路级「能不能跑」,没做节点级断言,也没按路径枚举用例。
修复:补节点级形状断言 + 路径枚举用例。之后边界场景由用例覆盖,不再是线上「惊喜」。
扩展:三条定理怎么落地成评审清单
理论要可操作,就得能变成评审时的检查项。我们实际用的评审清单,就是三条定理的直接展开:
定理 3(状态最小化)检查项:
- 这份状态必须存在吗?——能不能现场算出来?能,就别存。
- 存在哪?——是显式存储(会话变量/外部存储),还是散落在节点参数里?散落的,收敛。
- 谁改它?——多个节点都能改同一个状态?改出冲突谁负责?只有一个写入方,最稳。
定理 4(失败外置)检查项:
- 这个环节可能失败吗?——模型调用、外部 API、知识库检索、数据解析,四个默认高危点。
- 失败走哪条路?——重试?降级?兜底?明确报错?必须有设计好的路径,不能是「报错就行」。
- 失败路径测试过吗?——每条失败路径至少有一个用例,不能只测成功路径。
定理 5(测试分层)检查项:
- 节点级断言覆盖了哪些节点?——确定性节点全覆盖,LLM 节点形状断言。
- 链路级用例覆盖了哪些路径?——按拓扑枚举:主路径、分支、失败路径、边界路径。
- 两层有空白吗?——只测链路不测节点(节点坏了测不出来)、只测节点不测链路(链路断了测不出来),都是空白。
清单化的价值在于:评审不再靠「感觉对不对」,而是逐项过检查点。我们实测过,带清单评审比不带清单,漏检率显著下降——因为人的记忆会漏,清单不会。
实践动作
- 设计时:三问——「这份状态必须存在吗?存在哪?谁改它?」(定理 3);「这个环节可能失败吗?失败走哪条路?」(定理 4);「这条链路的失效模式是局部的还是全局的?」(定理 5)。
- 评审时:检查状态是否有唯一显式存储;检查每个外部依赖是否有设计过的失败路径;检查用例是否同时覆盖节点级与链路级。
- 排障时:状态错乱先查「状态是不是散落了」;偶发失败先查「失败路径设计了没」;验证漏网先查「用例分层全不全」。
边界与版本
版本无关:三条定理是 LLM 应用的普遍设计法则,与平台无关。Dify 只是实现它们的载体之一。
边界说明:定理 4 的验证覆盖了模型超时、外部 API 不可用、LLM 输出不合法三类失败;其他失败类型(如平台自身故障、数据源损坏)按同样方法设计,但具体路径未全部实测。
收尾
三条定理合起来回答了架构决策轴的核心问题:状态怎么管、失败怎么办、怎么证明它可靠。它们之间是顺序依赖——状态最小化让失败路径简单(状态少,失败面小),失败外置让链路可控(每条路都有设计),测试分层让这一切可证明(两层验证全覆盖)。
下一篇:从踩坑到定理(四):数据/记忆轴——为什么页面能搜到、工作流里却召回失败?
这是四个维度里坑源最密集的一个:RAG 的检索质量、知识新鲜度、query 归一化,每一个都是「看起来简单,做起来全是坑」。
💬讨论区:你的应用里状态散落过吗?失败路径是设计出来的还是线上撞出来的?验收时有没有「看起来能跑、交付就翻车」?评论区聊聊你的架构决策故事。
如果觉得有收获,欢迎点赞 + 收藏 + 关注,这是激励我更新这个硬核系列的最大动力。
本文基于真实项目交付经验撰写(Dify 1.16.x 环境、69 个实验与验收记录)。文中数据均来自我们自己的实测记录,理论部分以「已验证 / 推断待验证」标注边界。