任何一个真把 LLM 生成推进生产管线的人都会撞上同一堵墙:模型产出不可控,而你无法凭感觉信任它。提示词调得再细,下一次生成就可能换一种结构、漏一个交互、编一个看似合理的兜底实现。问题不在于模型不够聪明,而在于我们把"信任"建立在了一个无法被检验的对象上。
这篇文章复盘一个真实项目——Ui_dismantler,一个把已有 UI 页面逆向拆解成可复用组件库的工具。它的 v1 版本输得很惨:往返基线测试结构分只有 0.000–0.215。v2 换了架构之后,对标的 benchmark 库(1346 行、6 种范式、完整 A11y 与设计令牌)往返分数达到 0.990。中间的变化不是换了更强的模型,而是换了一种与模型相处的方式:生成与验证分离。这套方法学对所有 LLM 生成类任务都成立,所以完整写下来。
一、任务定义:UI 拆解是 UI 域的"设计恢复"
Ui_dismantler 的输入是一个自包含 HTML 案例页(内联 <style>/<script>),输出是一套数据驱动的组件库:参数化 CSS、渲染引擎 JS、数据契约、设计规范文档,以及一个用原数据复刻原页面的示例。
关键在"数据驱动"。把原页面复制一遍的静态片段只是"复制粘贴的模板",不是组件。合格的拆解必须把数据从 DOM 里抽出来变成契约(JSON/数组),渲染引擎以数据为输入重新生成视图——产物是能装新数据的模具,复刻页面只是验收手段。
这个任务在经典文献里有精确对应。Chikofsky 与 Cross(1990, IEEE Software)定义:逆向工程是"分析系统、识别构件及其关系、并在更高抽象层建立表示"的过程,其中恢复"做了什么、为什么、如何演化"的部分叫设计恢复(design recovery)。Biggerstaff(1989)进一步指出,恢复设计意图必须引入代码之外的知识——领域模型、命名线索、常识推理,纯代码分析回答不了"为什么"。他把核心难题命名为"概念指派问题":把程序构件映射到人类领域概念。
UI 拆解正是这个框架在前端的实例:--blue-500 是代码层,"品牌主色"是设计层,中间的映射就是概念指派。这个定位直接预示了 v1 为什么会失败:设计恢复需要理解力,而理解力是规则系统写不出来的。
二、失败开局:v1 规则引擎的 0.000–0.215
v1 的思路非常"工程师":写规则引擎,用正则和模板识别 HTML 模式,匹配到就套组件模板,匹配不到就兜底一个泛化结构。
结果是灾难性的。HTML 结构的可能性无限,规则只有两种命运:过拟合到见过的样本,或泛化到毫无意义。产出的组件库"合规但空壳"——命名规范、文件齐全,却没有真正捕获页面的组织方式。
往返等价测试(下文详述)把失败量化了:结构分 0.000–0.215,而文本分往往 0.8 以上。这组数字是全文最重要的教训。文本分高,是因为文本可以逐字复制对齐;结构分接近零,是因为结构必须真正理解页面的组织方式才能复刻,而规则没有理解力。
这就是那句话的来历:结构分是"理解 vs 抄写"的试金石。一个系统可以靠抄写拿到不错的综合分,同时完全没有完成任务——若只看综合分,v1 的失败甚至不会被发现,这是"总分会撒谎"的第一次预演。
v1 的教训归纳成一句:规则引擎写不全,理解力才行。出路不是写更多规则,而是重新安排谁在决策位。
三、架构原则:Agent 主控,工具验证,裁判独立
v2 的架构只有一句话:把 LLM 的不可控,关进"可验证"的笼子。展开是三条职责严格分离的分工:
- Agent 做理解与创作:读 HTML、判断主题色语义角色、识别交互模式、写渲染逻辑——开放性工作,正是 LLM 可靠性剖面里"长"的那一端。
- 确定性工具做提取与校验:颜色解析、CSS 拆分、约束校验、等价度量化必须确定性、可复现,交给脚本。让 LLM 做精确计算,或让脚本做开放理解,都是架构错误。
- 独立的量化测试做最终裁判:Agent 自检不算数,产出必须通过它无法取悦的客观测试,低分则按决策表修订复测。
第三条是地基:裁判必须独立于选手。这与红队思维同构——验证者与被验证者不能是同一立场;让生成方自评,相当于运动员兼任裁判。生成与验证分离,是这个架构敢于信任 LLM 产出的根本原因。
还有两个细节。其一,工具是"参考"而非"唯一依据":识别置信度低或结构超出既有范式时,Agent 以自己的通读理解为准,避免规则引擎"识别不到就兜底空壳"的旧病。其二,旧规则资产不丢弃,降级为工具层:v1 脚本在提取、校验、回归上做得很好,范式升级的答案不是"旧的全删掉",而是重新安排谁在决策位。
工具层自身也有两条原则。证据驱动的范式识别:识别器不只输出标签,而是输出四元组——中立结构类型(collection/sequence/form,任何领域成立)、可选语义类型(quiz/timeline,依赖领域信号)、置信度、证据。分层让识别器对新领域"先给得出结构、不硬猜语义";证据让结论可证伪,没有证据的标签无法被反驳也无法被信任。连探测器注册顺序都是设计决策:cause-chain 必须先于 timeline,因为后者的正则会误匹配前者的 timeline-nav 类名;判页面级结构用 role=tabpanel 而非 .panel 类名,因为样式类会撞名。共同主题是:判据必须取自已验证的语义标记,一般规则不能吞噬特殊规则。
另一条是结构化解析优先,正则降级兜底:正则看到的是文本,AST 看到的是程序——用正则匹配 addEventListener 无法区分真实绑定、注释示例和字符串字面量。降级可以存在,但必须是显式事件:证据标注来源(analysis: ast | attribute | semantic-control | regex-fallback)与置信度,下游对降级证据打折扣,而不是把猜测当事实。正则是救生艇,不是船。
四、度量学三轴:结构、感知、快照
验证的前提是度量,"好不好"必须变成可计算的量。Ui_dismantler 的度量体系沿三条轴展开,各自独立、不可互替:
| 度量轴 | 度量对象 | 代表方法 | 能抓什么 | 抓不住什么 |
|---|---|---|---|---|
| 结构相似度 | DOM 树结构 | 树编辑距离(Zhang-Shasha)及工程近似 | 组织方式是否被真正理解 | CSS 未命中导致的视觉全错 |
| 感知相似度 | 渲染后的外观 | pixel diff / SSIM / LPIPS | 看起来是否一致 | DOM 是死标签堆砌、交互全废 |
| 黄金快照 | 人工确认的正确输出 | 快照比对 + 差异告警 | 无 Oracle 维度的绝对基准 | 快照腐烂导致的"永远绿色" |
结构轴的理论基准是树编辑距离(Zhang & Shasha, 1989),精确但昂贵,工程上常用近似——标签序列对齐、类名多重集合对比、路径覆盖率——换取确定性、速度和可解释的差异报告("少了 3 个 .member-card,多了 1 个 div")。粒度决定修订信号质量:只输出总分,Agent 不知如何修订;输出到节点级差异,修订才有的放矢。这条轴是理解 vs 抄写的试金石,验收线单独设线:结构 ≥ 0.7、文本 ≥ 0.8、综合 ≥ 0.85。
感知轴分三层:像素级(pixel diff,严格、确定、可解释,但对亚像素平移和字体渲染环境极度敏感,1px 位移可能全屏飘红)、结构级(SSIM,Wang et al., 2004,建模人眼感知但对语义无知)、特征级(LPIPS,Zhang et al., 2018,最接近主观感受,但引入模型依赖,用于门禁需慎重)。原则是:度量选择 = 选择你能看见的差异——用像素差则看不见"整体错位但每块相似",用 LPIPS 则可能放过局部功能像素错误。门禁型场景选严格确定性度量(项目里 pixel diff 默认阈值 2%),研究型场景才引入感知度量。
快照轴回答测试理论的核心难题:没有 Oracle 怎么办。黄金快照把"对错判断"从运行时转移到快照生成时的一次性人工确认,之后所有回归检查退化为低成本的相等比较——快照的本质是延迟的 Oracle。最大风险是快照腐烂:快照随需求演化过时,"快照与实现同步漂移"会让测试永远绿色却毫无效力,治理靠版本化、定期人工抽检、更新纳入评审(更新快照 = 改变真值)。LLM 时代它还多了个新角色:检测模型升级导致的输出漂移——代码不变,换模型即回归。
三轴之间有一条不可妥协的纪律:结构相似 ≠ 视觉相似,不可互替。DOM 一致但 CSS 未命中,看着全错;截图一致但 DOM 是死标签,交互全废。两轴各自独立度量、独立设线,任何一轴被稀释,瘸腿指标就被均值洗掉。
五、门禁体系:前置预检、分层关卡、失败归因
度量给出分数,门禁决定阻断。体系有三层设计。
前置:规划预检。 写任何实现代码之前,先把任务分解为可审阅的交付契约:每个组件的职责、目标文件、交互模型、数据契约、验收标准与预计行数,全部显式声明为 JSON。超预算(单组件行数上限 150 行)、缺验收、缺证据时,流程以非零状态退出,阻止进入产出阶段。预算是拆分信号而非绩效指标:超行数不意味着"写快点",而意味着"职责没拆对"。无归属的交互(unowned interaction)是预检错误——交互漂浮无主,是组件边界划错的可靠信号。经济学逻辑只有一句:否决一份规划的成本远低于否决一份实现。预检管流程质量,后置门禁管结果质量,绕过预检靠门禁兜底,会把规划错误拖成昂贵的返工。
分层:质量门禁分层。 "质量"拆成各自独立判定的关卡,沿"结构—行为—外观—环境"四层展开:往返结构/文本分、交互状态矩阵、视口矩阵、视觉运行时、资源就绪、外部可用性、计算样式相似度、像素差、交互覆盖率等十余个门禁各司其职。一个门禁只回答一个问题;总分会撒谎,门禁不会——综合分 0.9 可能掩盖某个关键交互完全失效,门禁制下任何一项失败即整体失败。规则分三级:hard(零依赖、命名规范,不可谈判)、project(按标杆校准)、soft(给建议不阻断),避免门禁膨胀为"什么都拦、等于没拦"。
行为层值得展开。交互状态矩阵回答往返测试回答不了的问题:首次渲染相似的页面,交互之后还相似吗?做法是对原页面与组件库的两个全新实例,对称执行同一组操作(splash dismiss、quiz answer、tab switch、cause-chain navigate、whatif toggle 等),再对比结果状态。每个场景从全新实例开始——场景间共享状态是测试设计中最隐蔽的污染源。判定是双层的:确定性断言(dialog 是否打开)抓功能对错,等价分抓呈现漂移;关键交互态还要在多视口下截图对比,响应式 bug 往往只在"特定交互态 × 特定宽度"的交叉点爆发。另有防自欺的闸口:自动生成的场景默认标记 candidate: true,人工补齐断言后才算数——机器生成的测试证据必须经确认才能升级为质量依据。
归因:失败归因图。 门禁要阻断得准,失败报告就不能只给计数。"3 个资源超时"告诉你坏了几个,归因图告诉你坏在哪、该谁管:每个失败关联上下文——谁引用的、什么类型、哪个阶段、是否必需、是否外部。质量报告拆为三类责任域:转译保真度、资源就绪、外部可用性——远程 404 表现为"保真度通过、可用性失败":产品没错,环境有问题,阻断决策只能挂在与责任域匹配的分类上。必需性是过滤噪声的第一刀(懒加载图片不阻塞当前截图判定);所有失败保留截图与 diff——归因给索引,证据给复核能力。
六、无 Oracle 困境:制造预言机,慎用 LLM 裁判
往深一层,这套体系始终在和测试的元问题搏斗:没有标准答案时如何判定对错。经典答案有两个,都来自"没有预言机就制造预言机"。差分测试(McKeeman, 1998):同一输入喂给两个独立实现,输出不一致即至少一方有错,参照实现本身就是预言机。蜕变测试(Chen et al., 1998):对输入做已知变换,断言输出间必然成立的关系,关系本身就是预言机。
往返等价测试就是差分测试的 UI 实例:原页面是参照实现,组件库复刻页是被测实现,同一渲染器对称执行后对比 DOM 与文本——"不一致即有问题",无需人工定义每个像素的对错。对称性是公平比较的前提:两侧同一渲染器、同一资源策略,否则差异可能来自环境;运行脚本失败时回退静态解析必须显式报告,回退不允许悄悄发生——这是测试的诚实性。差分的盲区是"共同模式失效":两个实现可能错得一模一样,所以差分之外仍需黄金快照、人工抽检等绝对基准锚定真值。
那 LLM 裁判呢?MT-Bench 与 Chatbot Arena(Zheng et al., 2023)之后它成了开放生成任务的主流,但填补的是"规模化近似评估",不是真理。三类系统性偏差必须知情:位置偏差(偏爱先出现/后出现的答案)、冗长偏差(偏爱更长的回答)、自我偏好(偏爱同源模型的输出)——不做去偏设计(交换顺序复评、长度归一、第三方裁判)的评审结论不可直接采信。
Ui_dismantler 的立场更激进,也是我给同类系统的建议:能确定性验证的维度,不要交给裁判。结构是否等价、资源是否加载、交互是否触发——有客观真值的维度用确定性测试;LLM 裁判只用于真正主观的残余维度(语义合理性、设计美感)。这个项目用脚本而非另一个 LLM 做裁判,正是该原则的执行。若非用不可,裁判的 prompt、温度甚至模型本身应与生成方隔离,并用人工标注小样本定期校验——裁判模型的漂移会直接污染整条评估链。
七、迁移与演进:并行重写,以及"变差"的假绿
验证体系自身也要演进。Ui_dismantler 经历过一次 Python 到 TypeScript 的重写,策略是并行重写与协议兼容迁移:新旧两套实现并行存在,以共享的、语言中立的协议(JSON 可序列化的 manifest、校验报告、分数契约)为对照基准,逐模块迁移、逐模块验证——这是 Fowler 绞杀者模式(Strangler Fig)的典型执行,旧 Python 入口最终降级为不超过 11 行的薄桥接。
协议是迁移的对照系:新旧实现跑同一批 benchmark,对齐同一份协议产物,最终逐项一致到 structure 0.997 / text 0.983 / overall 0.99。没有中立协议,"新实现是否等价"永远是糊涂账;有了协议,等价性变成可执行的测试。顺序是先契约后引擎:先迁纯逻辑层,再替换重型依赖(jsdom 升级到 Playwright/Chromium),替换时不改上层 JSON 协议——接口冻结,实现才能安全换血。
最有教益的插曲是:新实现新增更严质量门后,原本"全绿"的 benchmark 暴露出 3 个未命中修饰类与 2.4% 像素差。这不是迁移引入的回归,而是旧测量体系的假绿。更精确的测量总会在初期显得"变差"——此时正确的响应是校准基准,而不是怀疑新工具。迁移的目的也值得一再强调:不是换语言,而是把隐含在代码里的核心闭环固化成显式、可复用的架构。
结语:把信任问题转化为验证问题
回顾整条线索:v1 败在没有理解力,逼出了"Agent 理解、工具验证"的分工;分工要可信,逼出了"裁判必须独立于选手";裁判要判得准,逼出了度量学三轴与门禁分层;门禁要阻断得对,逼出了失败归因图;没有 Oracle 的维度,逼出了差分、快照与对 LLM 裁判的审慎。
这套体系的本质,是把"信任"问题转化为"验证"问题。我们不再问"这个模型可不可靠"——那是没有操作性答案的问题——而是问"这个产出通过了哪些它无法取悦的检验"。信任不再是产出的前提,而是验证的副产品:可量化、可回归、可审计。
它不只属于 UI 拆解。任何 LLM 生成类任务——代码生成、文档生成、数据迁移、设计稿转代码——都可套用同一框架:找到任务里的"结构分"(抄写刷不动、必须真理解才能拿分的轴),为每一维质量找一个确定性裁判,让生成方永远够不到裁判席。LLM 时代我们第一次有了廉价的理解力,代价是必须为它建一座可验证的笼子。笼子建得好,不可控就不再是风险,而是产能。
本页是这篇文章的静态镜像。在互动版中打开,可使用附件下载、作者署名与目录导航。