本文是阶段二《设计师作为"语义翻译者"》的实操专题,回答一份 YAML 契约从空白文档到完整文件的写作过程。
阶段二的核心判断是:传统设计规范面向"人"阅读,机器无法消费,AI 生成内容时不受语义约束。Schema-As-Code 的解法是将设计意图形式化为 YAML 语义契约,在生成前预置边界与规则。
本文回答 YAML 语义契约怎么写。传统规范人读机器不读,Schema-As-Code 将其形式化为 YAML,预置生成边界。YAML 不是色值或文案,是语义令牌。
方法论总纲与开源仓库:把设计规范写成代码格式,是所有 AI 工具的上游约束方法论
一、YAML 契约的整体结构
一份 YAML 契约由 6 个顶层字段组成。你可以把它理解成一份"设计意图的身份证"------每个字段回答一个特定的问题。
yaml
intent_id: "我是谁?"
description: "我解决什么问题?"
version: "我是哪个版本?"
applicable_products: "我在哪些产品里生效?"
semantic_tokens: "我定义了什么语义?"
immutable_boundaries: "我画了什么红线?"
llm_constraints: "我对 AI 有什么强制要求?"
下面逐个拆解。
二、字段 1:intent_id ------ 契约的身份证号
设计意图
每个契约都需要一个唯一标识,就像设计稿的编号一样。当你和前端、AI 工程师沟通时,说"ERR-001"比说"那个错误状态分级的东西"要精确得多。
命名规范
yaml
intent_id: "ERR-001"
# 格式:[领域缩写]-[三位序号]
# ERR = Error(错误状态)
# PRO = Process(过程状态)
# BND = Boundary(边界动作)
# ACT = Action(操作按钮)
# ALR = Alert(告警状态)
常见错误
❌ 用自然语言命名:intent_id: "错误状态分级"------不方便引用和版本管理
✅ 用结构化编号:intent_id: "ERR-001"------精确、可排序、可检索
三、字段 2:description ------ 一句话说清契约解决什么问题
设计意图
description 是给人类看的摘要。当你打开一份契约文件,第一眼就要知道"这份契约是干嘛的"。
写法
yaml
description: "错误状态后果差异未分级:系统无法区分致命错误、网络抖动、限流提示和降级错误,导致用户无法判断后果严重程度。"
好 description 的标准
- 包含问题现象(用户看到什么)
- 包含根因(系统缺了什么)
- 包含后果(用户因此遇到什么困扰)
❌ 不好的写法:description: "错误状态规范"------太笼统,不知道解决什么问题
✅ 好的写法:见上方示例------现象 + 根因 + 后果,一目了然
四、字段 3:version ------ 语义版本号
设计意图
设计规范会迭代,YAML 契约也会迭代。version 字段让你知道"这份契约是第几版",和代码的版本管理一样重要。
语义化版本规范
yaml
version: "1.0.0"
# 格式:主版本.次版本.修订号
# 主版本:破坏性变更(如删除某个 severity 级别)
# 次版本:功能新增(如增加新的 visual_mapping)
# 修订号:bug 修复(如文案微调)
版本变更示例
yaml
# v1.0.0:初始版本,包含 fatal/transient/retryable 三级
# v1.1.0:新增 degraded 级别(次版本升级)
# v1.1.1:修正 fatal 的文案描述(修订号升级)
# v2.0.0:删除 retryable 级别,合并到 transient(主版本升级,破坏性变更)
五、字段 4:applicable_products ------ 契约在哪些产品里生效
设计意图
不同产品可能有不同的设计规范。applicable_products 定义了这份契约的生效范围,避免"一刀切"。
写法
yaml
applicable_products:
- "ChatGPT"
- "文心一言"
- "通义千问"
- "Kimi"
- "豆包"
- "DeepSeek"
# 不生效的产品:Claude(因为 Claude 的错误状态设计不同)
作用域控制
yaml
# 全局生效(所有产品)
applicable_products:
- "*"
# 特定产品生效
applicable_products:
- "ChatGPT"
- "Kimi"
# 排除特定产品
applicable_products:
- "*"
excluded_products:
- "Claude" # Claude 有自己的错误状态规范
六、字段 5:semantic_tokens ------ 契约的核心:语义令牌定义
设计意图
这是 YAML 契约最重要的字段。semantic_tokens 定义了"这个场景下有哪些语义级别,每个级别映射到什么视觉、文案和行动"。
结构拆解
yaml
semantic_tokens:
[语义令牌组名]: # 如 error_severity、process_phase
[级别名]: # 如 fatal、transient、retryable
description: "..." # 这个级别的含义
visual_mapping: {...} # 视觉映射(颜色、动效、图标)
user_action: [...] # 用户行动(按钮、操作)
llm_constraints: [...] # 针对这个级别的 LLM 强制约束
ERR-001 完整示例
yaml
semantic_tokens:
error_severity: # 语义令牌组:错误严重程度
fatal: # 级别 1:致命错误
description: "系统级故障,对话上下文可能丢失"
visual_mapping: # 视觉映射
color_token: "status.critical" # 颜色令牌:致命状态
motion_token: "pulse.red.urgent" # 动效令牌:红色脉冲
icon_token: "alert.octagon" # 图标令牌:八边形警告
background: "red.500/10" # 背景:10% 透明度红色
user_action: # 用户行动
- label: "刷新页面" # 主行动:刷新
action: "refresh"
priority: 1 # 优先级:1(最高)
- label: "导出历史" # 次行动:导出
action: "export_history"
priority: 2
llm_constraints: # LLM 强制约束
- "必须明确告知用户对话上下文可能已丢失"
- "禁止仅显示'出错了'等模糊文案"
- "必须提供恢复路径(刷新或导出)"
transient: # 级别 2:网络抖动
description: "网络层故障,系统可自动恢复"
visual_mapping:
color_token: "status.neutral" # 颜色令牌:中性状态
motion_token: "spinner" # 动效令牌:加载动画
icon_token: "loader" # 图标令牌:加载图标
background: "neutral.800" # 背景:深灰色
user_action:
- label: "等待自动恢复" # 主行动:等待
action: "wait"
priority: 1
- label: "手动重试" # 次行动:重试
action: "retry"
priority: 2
llm_constraints:
- "必须显示自动重试进度"
- "禁止使用红色背景(避免情绪过载)"
- "必须说明预计恢复时间"
retryable: # 级别 3:限流/流控
description: "请求频率已达上限,用户可自助恢复"
visual_mapping:
color_token: "status.warning" # 颜色令牌:警告状态
motion_token: "none" # 动效:无(不需要紧急感)
icon_token: "clock" # 图标令牌:时钟
background: "amber.500/10" # 背景:10% 透明度黄色
user_action:
- label: "等待倒计时" # 主行动:等待
action: "wait_countdown"
countdown_field: "retry_after" # 倒计时字段名
priority: 1
- label: "升级套餐" # 次行动:升级
action: "upgrade"
priority: 2
llm_constraints:
- "必须显示剩余等待时间(秒/分钟)"
- "必须提供升级/扩容路径"
- "禁止使用红色(避免用户恐慌)"
degraded: # 级别 4:部分可用
description: "部分功能可用,可继续生成"
visual_mapping:
color_token: "status.info" # 颜色令牌:信息状态
motion_token: "none" # 动效:无
icon_token: "info.circle" # 图标令牌:信息圆圈
background: "blue.500/10" # 背景:10% 透明度蓝色
user_action:
- label: "继续生成" # 主行动:继续
action: "continue"
priority: 1
- label: "简化问题重试" # 次行动:简化重试
action: "retry_simplified"
priority: 2
llm_constraints:
- "必须说明哪些功能仍然可用"
- "必须提供替代方案(如简化问题)"
- "禁止显示纯技术错误码(如 500 Internal Error)"
关键设计原则
| 原则 | 说明 | 示例 |
|---|---|---|
| 令牌层与呈现层分离 | color_token 是语义标识,color 是实际色值 |
color_token: status.critical → color: #EF4444 |
| 级别必须有明确区分 | 每个级别在视觉、文案、行动上都有差异 | fatal(红脉冲)vs transient(灰加载)vs retryable (黄提示)vs degraded(蓝信息) |
| 行动必须与后果匹配 | 用户行动由错误后果决定,不是随意给的 | fatal → 刷新/导出(因为对话丢了),transient → 等待/重试(因为会自动恢复) |
| LLM 约束必须可执行 | 每条约束都能被机器校验 | "禁止仅显示'出错了'" → 机器可以检查文案是否包含"出错了" |
七、字段 6:immutable_boundaries ------ 不可突破的红线
设计意图
有些规则是"绝对不能违反"的,比如高危操作必须二次确认。immutable_boundaries 定义了这些红线,一旦违反,系统直接阻断。
结构
yaml
immutable_boundaries:
- boundary_type: "safety" # 边界类型:安全
constraint_rule_ref: "rules/destructive-action.yaml" # 引用的规则文件
violation_action: "block" # 违反时的动作:阻断
- boundary_type: "compliance" # 边界类型:合规
constraint_rule_ref: "rules/data-retention.yaml"
violation_action: "escalate" # 违反时的动作:升级人工审核
ERR-001 示例
yaml
immutable_boundaries:
- boundary_type: "safety"
rule: "禁止直接执行删除操作而不显示二次确认"
violation_action: "block"
# 如果 AI 生成的代码里没有二次确认,直接阻断,不允许进入生产环境
- boundary_type: "semantic"
rule: "禁止将 fatal 错误显示为静态灰色(必须用红色脉冲)"
violation_action: "block"
# 如果 fatal 错误用了灰色静态背景,直接阻断
与 semantic_tokens 的区别
| 维度 | semantic_tokens | immutable_boundaries |
|---|---|---|
| 性质 | "应该怎么做"(推荐) | "绝对不能怎么做"(强制) |
| 违反后果 | 警告/提示 | 直接阻断/升级 |
| 灵活性 | 可调整(如颜色微调) | 不可调整(如安全红线) |
| 示例 | "fatal 建议用红色脉冲" | "destructive_action 必须二次确认" |
八、字段 7:llm_constraints ------ 对 AI 的强制要求
设计意图
LLM 在生成内容时可能会"自由发挥",比如把 Critical 写成"严重",或者漏掉二次确认。llm_constraints 是给 AI 的"强制指令"------不是建议,是必须遵守。
写法
yaml
llm_constraints:
- "必须明确告知用户对话上下文可能已丢失" # 信息完整性约束
- "禁止仅显示'出错了'等模糊文案" # 文案精度约束
- "必须提供恢复路径(刷新或导出)" # 行动完整性约束
- "禁止将 fatal 错误显示为静态灰色" # 视觉映射约束
- "禁止在 retryable 场景使用红色背景" # 场景匹配约束
约束的三种类型
| 类型 | 前缀 | 示例 | 机器校验方式 |
|---|---|---|---|
| 必须型 | "必须..." | "必须显示倒计时" | 检查字段是否存在 |
| 禁止型 | "禁止..." | "禁止用'严重'代替'Critical'" | 检查违禁词是否出现 |
| 限制型 | "只能..." | "只能使用 outline_danger 样式" | 检查值是否在枚举范围内 |
九、完整契约示例:ERR-001(从头到尾)
yaml
# 契约文件:intent/err-001.yaml
# 作者:设计师 [姓名]
# 创建日期:2026-06-30
# 最后更新:2026-06-30
# 变更记录:v1.0.0 初始版本,包含 fatal/transient/retryable/degraded 四级
intent_id: "ERR-001"
description: "错误状态后果差异未分级:系统无法区分致命错误、网络抖动、限流提示和降级错误,导致用户无法判断后果严重程度。"
version: "1.0.0"
applicable_products:
- "ChatGPT"
- "文心一言"
- "通义千问"
- "Kimi"
- "豆包"
- "DeepSeek"
semantic_tokens:
error_severity:
fatal:
description: "系统级故障,对话上下文可能丢失"
visual_mapping:
color_token: "status.critical"
motion_token: "pulse.red.urgent"
icon_token: "alert.octagon"
background: "red.500/10"
user_action:
- label: "刷新页面"
action: "refresh"
priority: 1
- label: "导出历史"
action: "export_history"
priority: 2
llm_constraints:
- "必须明确告知用户对话上下文可能已丢失"
- "禁止仅显示'出错了'等模糊文案"
- "必须提供恢复路径"
transient:
description: "网络抖动,系统可自动恢复"
visual_mapping:
color_token: "status.neutral"
motion_token: "spinner"
icon_token: "loader"
background: "neutral.800"
user_action:
- label: "等待自动恢复"
action: "wait"
priority: 1
- label: "手动重试"
action: "retry"
priority: 2
llm_constraints:
- "必须显示自动重试进度"
- "禁止使用红色背景"
- "必须说明预计恢复时间"
retryable:
description: "请求频率已达上限"
visual_mapping:
color_token: "status.warning"
motion_token: "none"
icon_token: "clock"
background: "amber.500/10"
user_action:
- label: "等待倒计时"
action: "wait_countdown"
countdown_field: "retry_after"
priority: 1
- label: "升级套餐"
action: "upgrade"
priority: 2
llm_constraints:
- "必须显示剩余等待时间"
- "必须提供升级路径"
- "禁止使用红色"
degraded:
description: "部分功能可用"
visual_mapping:
color_token: "status.info"
motion_token: "none"
icon_token: "info.circle"
background: "blue.500/10"
user_action:
- label: "继续生成"
action: "continue"
priority: 1
- label: "简化问题重试"
action: "retry_simplified"
priority: 2
llm_constraints:
- "必须说明哪些功能仍然可用"
- "必须提供替代方案"
- "禁止显示纯技术错误码"
immutable_boundaries:
- boundary_type: "safety"
rule: "禁止直接执行删除操作而不显示二次确认"
violation_action: "block"
- boundary_type: "semantic"
rule: "禁止将 fatal 错误显示为静态灰色"
violation_action: "block"
十、写作检查清单:写完一份契约后对照
markdown
- [ ] intent_id 是否符合命名规范(领域缩写-三位序号)?
- [ ] description 是否包含现象 + 根因 + 后果?
- [ ] version 是否使用语义化版本(主.次.修)?
- [ ] applicable_products 是否明确了生效范围?
- [ ] semantic_tokens 是否每个级别都有 visual_mapping + user_action + llm_constraints?
- [ ] visual_mapping 是否使用了语义令牌(color_token)而非写死色值(color: #EF4444)?
- [ ] user_action 是否与后果严重程度匹配(fatal→刷新/导出,transient→等待/重试)?
- [ ] llm_constraints 是否每条都可被机器校验(有明确的"必须/禁止/只能")?
- [ ] immutable_boundaries 是否定义了不可突破的红线?
- [ ] 是否包含变更记录和作者信息?
十一、常见错误与修正
错误 1:写死色值,不用语义令牌
❌ 错误写法:
yaml
visual_mapping:
color: "#EF4444" # 写死色值,设计系统更新时契约失效
✅ 正确写法:
yaml
visual_mapping:
color_token: "status.critical" # 语义令牌,设计系统更新时映射关系自动同步
错误 2:用户行动与后果不匹配
❌ 错误写法:
yaml
# fatal 错误(对话已丢)只给"重试"按钮
user_action:
- label: "重试"
action: "retry"
✅ 正确写法:
yaml
# fatal 错误需要"刷新"或"导出",因为对话已丢,重试无效
user_action:
- label: "刷新页面"
action: "refresh"
- label: "导出历史"
action: "export_history"
错误 3:LLM 约束不可执行
❌ 错误写法:
yaml
llm_constraints:
- "文案要友好一点" # 无法机器校验,什么叫"友好"?
✅ 正确写法:
yaml
llm_constraints:
- "禁止仅显示'出错了'等模糊文案" # 可机器校验:检查文案是否包含"出错了"
- "必须包含后果说明" # 可机器校验:检查文案是否包含"可能已丢失"等后果词
十二、下一步:写完后怎么编译
写完 YAML 契约后,下一步是编译为消费格式。详见下一章《从契约到消费格式:编译》。
一份 ERR-001.yaml 会编译为:
- Prompt 前缀:供 Claude Code / Cursor 使用
- JSON Schema:供结构校验使用
- Checklist:供设计师人工走查使用
- CI 规则:供自动化流水线使用