很多团队有 DAG,但没有真正的节点契约。节点看起来能跑,系统却经常在跨节点交接时出错:字段缺失、状态歧义、重放失败。
如果把这件事讲得更直白:多数“工作流漂移”不是模型在胡说,而是节点边界从未被系统正式定义。
契约不是一层,而是三层
实际落地里,节点契约至少分为三层:
| 层级 | 关注点 | 由谁维护 |
|---|---|---|
| 数据契约 | 输入/输出字段与类型 | 工程团队 |
| 执行契约 | 前置条件、重试、超时 | 平台团队 |
| 风险契约 | 副作用、补偿、审批 | 治理/业务团队 |
只做数据契约,系统仍会在执行与风险层面失控。
最小节点契约模板
建议每个节点都具备以下字段:
inputSchemaoutputSchemapreconditionspostconditionstimeoutMsretryPolicysideEffectscompensationPolicy
其中 sideEffects 建议强制结构化,例如:
| 字段 | 含义 |
|---|---|
| effectType | 写库、外发、状态变更 |
| target | 影响对象 |
| idempotencyKey | 幂等识别 |
| reversible | 是否可逆 |
没有这些字段,平台无法做统一补偿治理。
常见边界错位,不是“偶发 bug”
以下三类问题最常见,且会反复出现:
- 推断字段污染:上游把可选字段当推断结果,下游当必填输入。
- 输出落地不一致:输出写在临时上下文而非共享状态,导致重放丢失。
- 异常语义不一致:有的节点返回错误码,有的只返回文本。
这些问题本质上都是契约未被 runtime 强制执行。
契约验证应在两个时机执行
| 时机 | 目标 |
|---|---|
| 发布前(静态验证) | schema、前后条件、策略配置完整性 |
| 运行时(动态验证) | 输入合法性、状态合法性、副作用合法性 |
只做静态验证会漏掉状态类问题;只做运行时验证会把低级错误带到线上。
失败案例:版本升级后“兼容成功”,业务却失真
某团队将节点输出字段 risk_score 改为 risk.level,并加了兼容适配层,技术上未报错。问题是下游审批规则仍读取旧字段,导致高风险内容被当低风险通过。这个问题持续两周才被业务发现。
根因不是字段重命名,而是契约变更缺少“语义级回归”。
修复动作:
- 在契约变更流程中引入语义回归样本。
- 将关键字段标记为“策略依赖字段”。
- 变更后自动验证相关规则命中情况。
契约发布治理:像 API 一样治理节点
建议引入以下规则:
- 向后兼容:新增字段可选,删除字段走废弃周期。
- 版本语义:major 破坏性、minor 增量、patch 修复。
- 依赖声明:下游节点声明依赖字段与语义。
这样做的结果,是节点不再是“局部实现细节”,而是平台级可治理接口。
看板应直接暴露契约健康度
推荐新增三类指标:
- 契约校验失败率
- 语义回归失败率
- 兼容适配触发率
兼容适配触发率长期高,通常说明该进入正式迁移,而不是无限期临时兼容。
一个节点契约示例
下面是一个“风险评估节点”的简化契约。重点不是字段形式,而是它把输入、输出、前置条件和副作用分开声明:
{
"nodeId": "risk_review.v2",
"inputSchema": {
"draftArtifactId": "string",
"evidencePackId": "string",
"tenantPolicyVersion": "string"
},
"outputSchema": {
"riskLevel": "low | medium | high",
"blockedReasons": "string[]",
"requiresHumanApproval": "boolean"
},
"preconditions": [
"draftArtifact.status == 'ready'",
"evidencePack.status == 'verified'"
],
"postconditions": [
"riskDecisionId is persisted"
],
"sideEffects": [],
"failurePolicy": {
"validation_error": "stop",
"policy_timeout": "human_review"
}
}
这里最重要的设计是:风险评估节点不直接发布、不改客户状态、不写外部系统。它只产出可审计的 riskDecisionId。这样下游审批和发布节点可以引用同一个稳定事实,而不是重新解释一段文本。
契约评审应该问的 8 个问题
每个新节点上线前,至少要过一轮契约评审:
- 这个节点是否真的需要独立存在?
- 它的输出是否会被两个以上下游节点复用?
- 输入字段里哪些是用户提供,哪些是系统推断?
- 输出字段里哪些会影响策略或计费?
- 失败时是否返回结构化错误?
- 是否产生外部副作用?如果有,补偿动作是什么?
- 契约升级是否影响旧 run 恢复?
- 是否有语义回归样本覆盖关键字段?
这 8 个问题能拦住很多“写起来方便,跑起来混乱”的节点。
版本兼容:不要只看字段是否存在
很多团队以为字段还在就是兼容。实际上,语义变化比字段变化更危险。例如 riskLevel: medium 在 v1 表示“需要人工复核”,在 v2 表示“允许自动发布但记录风险”,字段没变,业务后果完全变了。
因此,节点契约版本至少要区分两类变化:
| 变化类型 | 示例 | 处理方式 |
|---|---|---|
| 结构变化 | 新增字段、重命名字段 | schema 兼容与适配层 |
| 语义变化 | 风险等级含义变化 | major 版本 + 回归样本 |
语义变化必须走更严格发布流程,不应该混在 patch 里悄悄上线。
Contract Registry 的作用
当节点数量超过十几个时,建议建立 contract registry。它至少提供:
- 节点契约版本查询
- 上下游依赖图
- 废弃字段和废弃节点列表
- 契约变更历史
- 回归样本绑定关系
没有 registry,团队会很快失去对“哪些节点依赖哪些字段”的判断力。到那时,每次改节点都像在拆一座看不见结构的楼。
契约治理 Checklist
- 节点输入输出均可 schema 校验
- pre/post 条件由 runtime 强制检查
- 失败输出同样结构化,不混杂自由文本
- side effect 和补偿策略可验证
- 节点契约变更有版本化与废弃周期
- 关键字段变更触发语义回归测试
延伸阅读:


