前两篇分别拆了两件事:设计稿端到端出码最硬的核是「2D→1D 带层级」的翻译;物料驱动搭建的命门是「业务逻辑既不在设计稿也不在物料里」。这一篇回答一个更实操的问题:既然今天的 AI 是可以被控制、被配置的,那我应该怎么组织一套流程(harness),让 AI 真正把页面连同业务逻辑一起搭出来? 我会把这套 harness 设计到能照着搭的程度——含 SKILL.md、工具契约、中间产物 schema 和编排流程。
一句话主线
一套好的搭建 harness,本质是把「一步到位地还原整个页面」这个欠约束难题,改造成「一条逐步降低不确定性的窄专家流水线」——每一步只做一个窄决策、产出一个可校验的 typed artifact,而 AI 的各个可配置切面(skill / tool / MCP / 结构化输出 / 编排 / 校验门 / 人机门)就是你装在这条流水线上的控制阀。
这句话里有三个关键词,撑起全篇:窄专家、typed artifact、控制阀。
为什么不能「一个大 agent 一步到位」
最诱人的方案是:把设计稿、物料库、PRD 全塞给一个强模型,让它直接吐出完整页面代码。这个方案会死,死因有三,且都在前两篇埋过伏笔:
- 欠约束:业务逻辑、响应式行为、边界态根本不在输入里(上一篇的命门)。一个大 agent 面对缺失信息,只会幻觉补全——它一定会给你编一套「看起来合理」的逻辑。
- 不可校验:一坨端到端生成的代码,你只能整体看「像不像」。而 Design2Code 那篇教过的一课是——单一指标一定会骗你。整体 90 分的页面,逻辑可能全错。
- 不可控:出了错你无法定位是哪一步坏的,也无法只重跑坏的那一步。
所以正确的思路是反过来的:把大问题切成一串小问题,每个小问题窄到「信息基本充分、结果可以被单独验证」,让 AI 在每一步只做一个它擅长的窄决策。 这不是新发明——它就是 pix2code 的 DSL 思路(先造中间表示)推广到 agent 流水线:用 typed artifact 当每一步之间的 IR(中间表示)。
设计原则一:artifact-centric,而不是 message-centric
大多数人搭多 agent 系统,是让 agent 之间传自然语言消息。这在这个场景是灾难——自然语言无法被 schema 校验,错误会静默传播。
正确做法:agent 之间传的是有 JSON Schema 约束的 typed artifact。 每个 agent 的工作被定义成一次「artifact 变换」:读入上一个 artifact,产出一个更丰富、更接近成品的 artifact。整条流水线就是一串 IR 的逐级精化:
| # | Artifact(IR) | 它新增了什么信息 | 由谁产出 |
|---|---|---|---|
| 0 | DesignInput + MaterialCatalog + BusinessSpec | 三个原始输入 | 外部 |
| 1 | LayoutTree | 把设计稿切成带 bbox 和嵌套关系的视觉块 | 分块器 |
| 2 | BindingMap | 每个块 → 物料候选 + 置信度 | 匹配器 |
| 3 | PropsMap | 每个块 → 反推出的物料 props(文案/色/变体) | 属性反推器 |
| 4 | CompositionTree | 解析出布局模型(flex/grid)的组件树 | 组合器 |
| 5 | DataBindingMap | 物料字段 ↔ 接口字段的映射 | 数据绑定器 |
| 6 | LogicGraph | 事件→动作、跨物料联动、状态 | 逻辑编排器 |
| 7 | BoundarySpec | 每个数据依赖块的 loading/empty/error | 边界态规划器 |
| 8 | Output | 代码 或 运行时 schema | 出码器 |
| 9 | VerifyReport | 多维保真度报告 | 校验器 |
关键设计点:每个 artifact 都带 confidence 和 provenance(这条信息是模型推断的、还是人确认的、还是从接口文档来的)。这两个字段是后面「人机门」和「防幻觉」的物理基础——不确定性必须被显式记录,而不是被抹平。
设计原则二:把「AI 可配置的切面」当控制阀
这是你问题的核心——从 AI 能被控制、被配置的切面来看,怎么组织。 我把今天可用的控制切面全列出来,并标明在这套 harness 里各自绑到哪:
| 控制切面 | 作用(控制什么) | 在本 harness 里绑定到 |
|---|---|---|
| System prompt / 角色约束 | 把 agent 窄化到单一职责,禁止越界 | 9 个专家 agent 各自的 role |
| SKILL.md | 声明能力、触发条件、步骤、可用工具、约束 | 每个 agent 一份(下有范例) |
| Tools(function calling) | AI 的「手」:检索物料、取色、渲染、读接口 | catalog_search / renderer / color_picker / api_schema_reader… |
| MCP | 接外部真实系统 | Figma API、组件注册中心、Swagger/OpenAPI 网关、Design Token 服务 |
| 结构化输出 / schema 约束 | 强制产出合法 IR,模型跑偏就重试 | 每个 artifact 一个 JSON Schema |
| 上下文工程 | 每个 agent 只看它该看的那一片 | 只喂当前 block + 检索到的相关物料,绝不喂整页 |
| 编排 / workflow | 确定性控制流(不是模型自己决定下一步) | pipeline + fan-out + loop-until-verified |
| 校验门(eval gate) | 每步之间自动检查,不达标不放行 | 多维 verifier + 对抗 critic |
| 人机门(HITL) | 低置信度路由给人 | confidence < 阈值 触发人工确认 |
| 记忆 / 反馈 | 纠正回流,越用越准 | 人工修正样本回灌目录 + few-shot 库 |
一句话概括这张表:模型只是引擎,harness 是变速箱、离合器和刹车。 你对结果的掌控力,几乎全部来自这些切面怎么配,而不是模型本身多强。下面把最关键的几个切面展开。
切面展开一:每个 agent 一份 SKILL.md
窄专家的「窄」,靠 SKILL.md 落地。它声明:这个 agent 是谁、什么时候被调用、能用哪些工具、按什么步骤、有哪些绝对约束。以匹配器为例(这是全链路最容易幻觉的一环,约束要写得最狠):
---
name: material-matcher
description: 把 LayoutTree 里的每个视觉块匹配到物料目录中的物料,
输出候选 + 置信度。当已有 LayoutTree 且需要选料时调用。
tools: [catalog_search, visual_embed, token_resolver]
output_schema: BindingMap
---
# 职责(单一)
输入一个 block(bbox、截图切片、OCR 文本、语义标签、父节点 id),输出:
1. 排序后的物料候选(material_id + 匹配分 + 理由)
2. 每个候选的 confidence
3. 若最高分 < 0.75,置 needs_human=true,**不要臆造**
# 步骤
1. visual_embed 取 block 切片视觉向量 → catalog_search(mode=visual, topk=8)
2. 用语义标签 catalog_search(mode=semantic, topk=8)
3. 融合两路排序,计算 confidence
4. 校验组合契约:候选能否合法嵌入 parent_material_id
5. 按 output_schema 输出结构化结果
# 绝对约束
- 只做匹配,不反推 props(那是 prop-reverser 的职责)
- material_id 必须来自 catalog_search 的返回,**严禁输出目录里不存在的 id**
- 不确定就降级为 needs_human,宁可漏,不可幻觉
注意这份 SKILL.md 的三个设计:职责单一(只匹配,不反推 props——避免一个 agent 干两件事时互相干扰)、工具白名单(它只能用这三个工具,物理上无法越界)、防幻觉硬约束(id 必须来自工具返回,直接掐死「编一个物料」的可能)。九个 agent 各写一份,整条流水线的行为就被钉死了。
切面展开二:工具契约(AI 的手)
工具是 AI 与真实世界的唯一接口。把工具的 schema 设计好,等于给 AI 的动作划定了轨道。物料检索工具的契约:
{
"name": "catalog_search",
"description": "在物料目录中检索物料,支持视觉向量或语义标签两种模式,可按父节点过滤组合契约",
"input_schema": {
"type": "object",
"properties": {
"mode": { "enum": ["visual", "semantic"] },
"query": { "type": "string", "description": "语义查询词或 visual_embed 返回的向量 id" },
"parent_material_id": { "type": "string", "description": "当前父节点;用于过滤不满足组合契约的候选" },
"topk": { "type": "integer", "default": 8 }
},
"required": ["mode", "query"]
}
}
parent_material_id 这个参数是点睛之笔——它把上一篇讲的组合契约(谁能嵌进谁)下沉到了工具层。检索时就把非法候选滤掉,AI 根本没机会选到一个不能放进当前容器的物料。能在工具层约束的,就不要留给模型自觉。
切面展开三:结构化输出是可靠性的地基
每个 artifact 配一个 JSON Schema,用 schema-constrained 的结构化输出强制模型产出合法结构,不合法就自动重试。这一条看起来平平无奇,却是整套 harness 能不能「工程化」的分水岭:它把「模型输出对不对」从一个概率问题,变成了一个可校验、可重试的确定性问题。 没有它,下游每个 agent 都要花一半精力去容错上游的脏数据。
编排:一条 pipeline,几个 gate,一个回修环
有了 artifact 和 agent,剩下的是用确定性的控制流把它们串起来——注意是「确定性」,不是让某个 master agent 自己决定下一步调谁(那又把不可控请回来了)。整体是一条 pipeline,中间插置信度门,末尾挂一个 loop-until-verified 的回修环:
flowchart TD
D[设计稿 / Figma JSON] --> SEG[分块 Segmenter]
M[(物料目录)] -.检索.-> MATCH
SEG --> LT[LayoutTree]
LT --> MATCH[匹配 Matcher]
MATCH --> BM[BindingMap + 置信度]
BM --> G1{置信度门}
G1 -->|低| H1[人工确认]
G1 -->|高| PROP[属性反推 PropReverser]
H1 --> PROP
PROP --> CT[组合 Composer → CompositionTree]
API[(接口 Schema<br/>via MCP)] -.-> DB
CT --> DB[数据绑定 DataBinder]
PRD[业务规格 BusinessSpec] --> LOGIC
DB --> LOGIC[逻辑编排 Orchestrator]
LOGIC --> BND[边界态规划 BoundaryPlanner]
BND --> EMIT[出码/出Schema Emitter]
EMIT --> VERIFY[多维校验 Verifier]
VERIFY -->|不达标| LOOP((定位坏环<br/>回修))
LOOP --> MATCH
VERIFY -->|达标| OUT[产物]
几个编排上的关键决策:
- 能并行的并行:
LayoutTree出来后,每个 block 的匹配是相互独立的——fan-out 成 N 个 matcher 并发跑,而不是串行。属性反推同理。 - 门放在最容易错的地方:置信度门放在匹配之后(匹配是幻觉重灾区),不是每步都放(那样人工负担过重)。门的位置本身是个成本/质量的权衡。
- 回修环要能定位坏环:校验不达标时,
VerifyReport要能指出是「元素漏了」(回到 matcher)还是「逻辑错了」(回到 orchestrator),只重跑坏的那一段,而不是从头再来。这是 artifact-centric 设计的红利——每个中间产物都是一个可回退的存档点。
命门:业务逻辑的「第三输入」子系统
前一篇反复强调:业务逻辑不在设计稿、不在物料。所以这套 harness 里最不能省的,是一个专门把「设计稿之外的信息」结构化喂进来的子系统。它有三个入口,分别对应三层逻辑:
入口一:接口 Schema(喂数据层) —— 通过 MCP 连到 Swagger/OpenAPI 网关,api_schema_reader 工具把接口的字段结构拉进来。数据绑定器据此做「物料字段 ↔ 接口字段」的映射,并给出建议由人确认。数据结构是相对客观的,这一层自动化程度可以很高。
入口二:PRD → 交互规格(喂逻辑层) —— 这是最难的一环。PRD 是自然语言,充满歧义。做法是加一个 intake agent,把 PRD 散文结构化成一份 InteractionSpec(谁触发、什么条件、做什么、影响谁),然后必须过人工确认——因为这里模型的幻觉代价最高。结构化后的交互规格,才喂给逻辑编排器去生成 LogicGraph。
// InteractionSpec 的一条规则(intake agent 从 PRD 抽取,人确认后生效)
{
"trigger": { "component": "submitBtn", "event": "click" },
"guard": "form.valid === true",
"actions": [
{ "type": "api", "call": "POST /order", "payload": "form.values" },
{ "type": "onError", "do": "keepDraft && toast('提交失败')" },
{ "type": "onSuccess", "do": "navigate('/order/:id')" }
],
"provenance": "prd#3.2",
"confidence": 0.6,
"needs_human": true
}
入口三:搭建器里的人工配置(兜底层) —— 任何 PRD 和接口都覆盖不到的决策(「这个按钮要不要二次确认」),留一个顺手的可视化配置口子让人补。关键不是消灭人工,而是让人工只出现在「只有人知道答案」的地方。
这三个入口合起来,就是把上一篇那个抽象的「第三输入」落成了具体的工程模块。没有它,这套 harness 只能还原一个死页面。
校验 harness:按「失败的种类」分层验,还要对抗验
呼应 Design2Code 那一课——单一指标会骗你。所以 Verifier 不是一个打分器,是一组按不同失败种类分工的检查器,且引入对抗校验(专门找茬,默认怀疑):
- 视觉保真:渲染产物截图,与设计稿做 CLIP/结构相似度——抓「整体像不像」。
- 元素召回:block-level 匹配,抓「有没有漏元素、位置对不对」——这是前沿模型都最弱的一环。
- 逻辑正确:这一层最容易被忽略,也最重要。光看截图验不出逻辑。做法是让
LogicGraph可执行——为每条InteractionSpec生成一个可运行的断言(点击提交按钮 → 是否真的发出了 POST /order),在渲染环境里跑一遍。逻辑必须被执行来验证,不能被观看来验证。 - 对抗 critic:一个专门被 prompt 成「挑刺者」的 agent,默认假设产物有问题,去找「哪个元素其实没还原」「哪条逻辑其实没接上」。多个 critic 从不同 lens 投票,多数认为有问题才判不达标——避免单个校验器自己也幻觉。
三层验分开报,绝不合成一个总分。 一个视觉 95 分、逻辑 40 分的页面,合成总分会显示 70 分的「良好」,而真相是它根本不能用。
预设反对:「搞这么重,还不如人直接写」
会有两个质疑,我正面接住:
「这套 harness 比直接让 GPT 出码复杂太多了。」 —— 对,但复杂度是必要的,不是自找的。复杂度守恒:你要么把它放在 harness 的显式结构里(可控、可调、可复用),要么把它塞进一个大 prompt 里赌模型自己搞定(不可控、每次都赌)。前者是资产,后者是负债。 而且这套结构里真正「重」的是一次性搭建;单次跑一个页面时,大部分 agent 是轻量窄任务。
「模型再强一点,这些中间步骤不就都能省了?」 —— 会省掉一部分,但省不掉命门。模型变强能让分块、匹配、布局这些「信息在输入里、只是难提取」的环节越来越自动(上一篇说的局部属性类难题)。但业务逻辑、边界态这些「信息根本不在输入里」的欠约束难题,模型再强也变不出来——它只会变得更擅长编造看起来合理的逻辑,这反而更危险。第三输入子系统和逻辑执行校验,是任何模型强度下都省不掉的结构。
收敛:这套 harness 的三条设计律
抽掉所有细节,真正可迁移到任何 AI 系统设计的,是这三条:
- 用 typed artifact 当 IR,别让 agent 传自然语言。 把不确定性显式化成
confidence/provenance字段,才谈得上控制它。 - 能在结构里约束的,就不要留给模型自觉。 组合契约下沉到工具、职责边界写进 SKILL.md、合法性交给 schema——每一条都是把「祈祷模型别犯错」换成「模型没机会犯错」。
- 区分「信息在输入里」和「信息不在输入里」的两类难题。 前者交给模型 + 校验回路自动化;后者必须建「第三输入」子系统显式引入,并用可执行的校验兜底。判断力花在这条线上,比花在调 prompt 上值钱得多。
写在最后:三篇连起来是一条完整的认知路径——出码的根本难题 → 搭建范式的命门 → 用可配置的 AI 把它组织成 harness。而 harness 设计的终极心法,跟我更早那篇是同一句:机械层可以外包给 AI,判断与综合层要留给自己。这套 harness 干的全部事情,就是把机械层(分块、匹配、布局)尽量自动化,把判断层(业务逻辑该是什么)用结构逼出来交给人。工具在变,这条分界线不变。