首页
/ A2UI Atom 推理格式优化实录:Run 025「紧凑目录签名」为何被评分模型回滚

A2UI Atom 推理格式优化实录:Run 025「紧凑目录签名」为何被评分模型回滚

2026-09-13 12:24:16作者:董灵辛Dennis

本篇技术文章围绕 a2ui 仓库中一次完整的推理格式迭代实验记录(eval/iterative_format_optimizer/history/atom/run_025_974f43f2_compact_catalog_signatures/report.md)展开:它记录了 Atom S 表达式格式的一次"紧凑目录签名"(Compact Catalog Signatures)优化尝试的完整证据链——修改假设、Git Diff、评测指标、pytest 输出与最终回滚裁决。读完后,你将理解 a2ui 的迭代格式优化体系如何运作、S_opt 复合评分模型的裁决规则,以及如何从一次"看似成功"(Schema/Quality 双 100%)却被回滚的实验中学到 Prompt 压缩与输出膨胀之间的工程权衡。

1. Run 025 在 a2ui 迭代优化体系中的位置

a2ui 仓库内置了一套"推理格式迭代优化器"(iterative format optimizer),位于 eval/iterative_format_optimizer/,用于对 Atom、Express、Elemental 等候选推理格式做持续的性能与正确性优化。其工作流定义在技能文档 SKILL.md 中,共 6 步:

  1. Analyze History:检查 history/<format>/ 下的历史运行与 history_summary.md,避免重复已被回滚的假设;
  2. Implement Hypothesis:修改 agent_sdks/python/a2ui_agent/src/a2ui/inference_formats/experimental/<format>/ 下的 compiler.pyprompt_generator.pyparser.py
  3. Run Unit Conformance Tests:确认 pytest 单测通过;
  4. Execute Benchmark Evaluation:运行 python scripts/optimize_format.py --format <format>
  5. Evaluate Decision Rules:必须通过 pytest、保持基线精度,且 Code Output Tokens 不得膨胀超过 +5%,否则回滚(git reset --hard HEAD);
  6. Archive & Synchronize:用 --archive 归档运行产物,并更新历史索引。

文档 SKILL.md 还提供了 CLI 速查表,例如快速验证评测 python scripts/optimize_format.py --format <format>、编译测试 --compile "(Card (Text \"Hi\"))"、与基线对比 python scripts/compare_results.py --baseline <path> <log_dir> 等。

每次运行归档为一个独立目录(含 report.mdpatch.diffrun_meta.json 三个文件)。Run 025 是 Atom 格式 50+ 次迭代中的第 25 次,其目录 run_025_974f43f2_compact_catalog_signatures/ 记录了本次实验的全部证据。

1.1 实验假设与最终裁决

run_meta.json 记录了本次运行的元数据:

  • hypothesis(假设):"Compact catalog signature formatting (using concise parameter type hints in dynamic signatures)."——在动态生成的目录签名中使用紧凑的参数类型提示;
  • statusBacktracked(回滚);
  • notes"Pytest 100% pass, 100.0% Schema Acc, 100.0% Quality Score. Reduced input tokens by -2.4% (4,342 vs 4,452) and reasoning tokens by -30.9% (3,572 vs 5,168). However, Code Output Tokens increased by +24.4% (328 vs 264) and Non-reasoning output time increased by +39.2%. Score S_opt dropped from +0.600 to +0.579 (-0.021). Reverted per Rule 2 and Rule 3."

一句话概括:这次修改让模型"想得更少"(推理 token 大幅下降),却让模型"写得更啰嗦"(代码输出 token 暴涨 24.4%),触发了效率上限红线,最终被回滚。

2. 背景:Atom 格式与动态目录签名的生成机制

要理解这次修改,先要理解 Atom 格式的 Prompt 是怎么拼装的。

2.1 Atom S 表达式格式

Atom 是 a2ui 的一种超紧凑、面向模型优化的 S 表达式(Lisp 风格括号 AST)推理格式,其规格定义在 specification/proposals/atom/a2ui_atom.md。核心设计目标包括:

  • Token 效率:消除 var_1 = ... 这类左侧变量赋值样板、children=[...] 数组括号与 </ui-column> 闭合标签重复;
  • Top-Down 流式 TTFC:父容器节点先于子节点输出,宿主渲染器可在 token 索引 0 处立即实例化布局骨架;
  • 100% Schema 韧性:属性使用带冒号的关键词对(:justify "center")或按目录 schema 顺序的位置参数,新增可选参数不会破坏既有 payload;
  • 确定性自愈:结构边界完全由单 token 的 () 界定,LLM 流提前终止时解析器会在 EOF 处确定性补全缺失的 )

典型 Atom 输出被包裹在 <a2ui>/</a2ui> 哨兵标签内:

<a2ui>
; Initial data state
(data $/title "Notification")

(Card
  (Column :align "center"
    (Icon $/icon)
    (Text $/title)
    "Get alerts for order status changes"
    (Row :justify "center"
      (Button :action (Event "accept") (Text "Yes"))
      (Button :action (Event "decline") (Text "No")))))
</a2ui>

2.2 目录签名的动态生成

为了让模型"只使用目录中存在的属性名",Agent 端会动态地把组件目录(catalog JSON Schema)编译成签名文本注入系统提示。实现位于 prompt_generator.py

  • ATOM_RULESprompt_generator.py)是 11 条语法规则:组件节点形如 (ComponentName :key1 val1 ...)、原始类型的写法、带冒号的属性键顺序无关、子组件必须严格嵌套、$/ 绝对数据路径、$ 模板相对路径、(data ...) 数据模型填充、(template ...) 列表模板、(Event ...) 动作事件,以及"严禁发明 CSS 属性"的严格目录遵守条款;
  • _generate_component_signaturesprompt_generator.py)遍历 CatalogSchemaHelper 中按名字排序的全部组件,为每个属性生成 :prop(必填)或 :prop?(可选)标签,并附加属性描述行与枚举值行(Must be one of: 'a', 'b');
  • _generate_function_signaturesprompt_generator.py)对目录中的函数(如 formatStringformatDate)做同样的签名编译。

schema 访问层是 schema_helper.pyget_property_schema 会沿 allOf 子 schema 爬取组件属性定义(schema_helper.py);get_function_property_schema 则从函数的 properties.args.properties 路径提取函数参数 schema(schema_helper.py);get_property_type 通过 $ref 爬取解析出 ChildList/Child/Action 等语义类型(schema_helper.py)。

修改前,一个组件的签名形如:

- (Button :action :child :label?)
  - 按钮描述
  - :label: 标签说明文字
  - :variant: Must be one of: 'primary', 'secondary'

即"一行签名 + N 行属性描述",属性描述对 LLM 来说是最容易压缩的冗余——这正是 Run 025 的切入点。

3. 核心改动解析:prompt_generator.py 的紧凑签名 Diff

Run 025 的完整补丁见 patch.diffreport.md 的 "Active Git Diff" 一节同样内嵌了该补丁。代码改动全部集中在 prompt_generator.py,分为三块:

3.1 组件签名:参数类型提示内联 + 描述行抑制

关键 diff(组件签名部分):

+                p_type = self.schema_helper.get_property_type(name, p)
+                enum_vals = _get_schema_enum(p_schema)
+
+                if enum_vals:
+                    type_hint = f"<{'/'.join(enum_vals)}>"
+                elif p_type:
+                    type_hint = f"<{p_type}>"
+                else:
+                    type_hint = ""
 
-                arg_label = f":{p}{opt_suffix}"
+                arg_label = f":{p}{opt_suffix}{type_hint}"
                 ordered_args.append(arg_label)
 
                 p_desc = p_schema.get("description") if isinstance(p_schema, dict) else None
-                enum_vals = _get_schema_enum(p_schema)
-
-                if p_desc or enum_vals:
-                    p_line_parts = []
-                    if p_desc:
-                        p_line_parts.append(p_desc)
-                    if enum_vals:
-                        enum_vals_str = ", ".join([f"'{v}'" for v in enum_vals])
-                        p_line_parts.append(f"Must be one of: {enum_vals_str}")
-                    prop_details.append(f"  - :{p}: {' '.join(p_line_parts)}")
+                if p_desc and not enum_vals and not p_type:
+                    prop_details.append(f"  - :{p}: {p_desc}")

逻辑解读:

  1. 类型提示内联:每个参数标签后追加尖括号类型提示。优先级是"枚举值 > 语义类型 > 无"——有枚举时输出 :variant<primary/secondary>,否则有语义类型(Child/ChildList/Action)时输出 :child<Child>,都没有则不加后缀;
  2. 描述行抑制:原来"有描述或有枚举就输出详情行",改为"只有既无枚举又无语义类型时才输出描述行"。枚举信息已经压缩进尖括号(且比 Must be one of: 'primary', 'secondary' 更省 token),描述行整体减半;
  3. 修改后签名形如:
- (Button :action<Action> :child<Child> :label?<string>)
  - 按钮描述(仅保留无枚举/无类型属性的描述)

3.2 函数签名:修正 schema 查询入口

-                p_schema = self.schema_helper.get_property_schema(name, p)
+                p_schema = self.schema_helper.get_function_property_schema(name, p)
+                enum_vals = _get_schema_enum(p_schema)
 
-                arg_label = f":{p}{opt_suffix}"
+                type_hint = f"<{'/'.join(enum_vals)}>" if enum_vals else ""
+                arg_label = f":{p}{opt_suffix}{type_hint}"

这一处修正了一个查询路径错误:函数参数的 schema 位于目录的 properties.args.properties 下,而 get_property_schema 是为组件属性设计的(查 components[name].properties)。从源码结构看,修改前对函数签名调用 get_property_schema 大概率取不到 schema(返回 None),导致函数参数的枚举提示从未生效;切换到 schema_helper.pyget_function_property_schema 后,函数签名也能拿到真实的参数 schema 并生成枚举内联提示。

3.3 改动边界

除签名生成逻辑外,补丁还包含两处 docstring 措辞更新("Compiles component definitions into S-expression signatures.""...into compact S-expression signatures.")以及对 eval/iterative/current_report.mdeval/iterative/history_summary.md 的报告文件更新(patch.diff 后半部分)。编译器 compiler.py 本次未改动——这是一个纯 Prompt 侧(prompt-side)优化,而非编译器侧(compiler-side)优化,这一区分对后文的复盘结论很重要。

4. 评测指标与 S_opt 评分模型的裁决

4.1 报告中的 Summary Table

report.md 的摘要表(Strategy: atom,Evaluation Model: google/gemini-3.5-flash):

Metric Baseline Current Diff
Pytest Conformance PASS FAIL -
Overall Pass Rate 100.0% 100.0% 0.0%
Algorithmic Schema Pass Rate 100.0% 100.0% 0.0%
Inference Duration (sec) 8.79s 7.94s -9.7%
Avg Input Tokens 0 0 -
Avg Output Tokens 0 0 -

以及评测明细的结论行:## Failure Details (Count: 0 / 6) → 6 个评测样本全部通过(报告原文标注 "All tests passed successfully!")。

4.2 裁决依据:S_opt 复合评分与效率上限

评分与裁决规则定义在 references/scoring_model.md,由三层构成:

第一层:正确性护栏(不可协商,失败即回滚)

  1. Pytest 单测必须 PASS;
  2. SchemaAcc(输出 payload 对目标目录 JSON Schema 的通过率,a2ui_scorer)不得低于基线;
  3. QualityScore(模型打分的语义意图匹配,measured_model_graded_qa)不得低于基线。

第二层:效率回归上限(不可协商的回滚触发器)

  • Code Output Tokens 增长 > 5%
  • 流式时延(Non-reasoning Output Time)增长 > 10%
  • Reasoning Tokens 增长 > 15%

第三层:复合优化得分

[ S_{opt} = 0.50 \cdot SchemaAcc + 0.30 \cdot QualityScore - 0.15 \cdot \frac{CodeTok}{BaseCodeTok} - 0.05 \cdot \frac{ReasonTok}{BaseReasonTok} - 0.03 \cdot \frac{InputTok}{BaseInputTok} ]

决策规则:S_opt(Current) > S_opt(Baseline) 则 KEEP,否则 REVERT。

4.3 Run 025 的裁决过程

对照三层规则逐项检查 Run 025(run_meta.json 记录了中位数指标:code_tokens_median: 328.5reasoning_tokens_median: 3572.5input_tokens_median: 4342.5):

  • 正确性护栏:SchemaAcc 100%(持平)、QualityScore 100%(持平)——通过;6/6 评测样本通过;
  • 效率上限:Reasoning Tokens 下降 30.9%(5,168 → 3,572,远好于 15% 上限);但 Code Output Tokens 上升 24.4%(264 → 328),远超 +5% 上限,Non-reasoning 输出时间上升 39.2%——两项均触发回滚红线;
  • 复合得分S_opt 从基线的 +0.600 降至 +0.579(-0.021)。虽然输入 token -2.4%、推理 token -30.9% 带来负向惩罚项的改善,但代码 token 膨胀在公式中占 0.15 的最大惩罚权重,主导了结果;
  • 最终裁决:Reverted per Rule 2 and Rule 3(效率上限 + 复合得分下降),状态记为 Backtracked

这正是这次实验最大的信息量所在:"模型推理负担变轻"与"模型输出更短"并不必然同向。把目录里的长描述替换成尖括号类型提示后,模型少了可读的属性语义上下文,在生成 Atom 代码时反而更保守/更啰嗦,输出膨胀吞掉了推理侧的全部收益。

5. 报告中的 Pytest 失败段落:环境伪影还是代码回归?

report.md 的 "Pytest Unit Test Failures" 一节收录了完整的 pytest 输出,摘要如下(原文保留):

platform linux -- Python 3.13.14, pytest-9.1.1, pluggy-1.6.0
rootdir: /usr/local/google/home/gspencer/code/a2ui/worktrees/opt-atom-run25
collected 8 items / 28 errors
...
E   ModuleNotFoundError: No module named 'a2ui'
E   ModuleNotFoundError: No module named 'a2a'
E   ModuleNotFoundError: No module named 'google'
E   ModuleNotFoundError: No module named 'yaml'
E   ModuleNotFoundError: No module named 'a2ui.core'
...
!!!!!!!!!!!!!!!!!!! Interrupted: 28 errors during collection !!!!!!!!!!!!!!!!!!!
============================== 28 errors in 0.44s ==============================

warning: `VIRTUAL_ENV=/usr/local/google/home/gspencer/code/a2ui/atom_format/.venv` does not
         match the project environment path `.venv` and will be ignored;
         use `--active` to target the active environment instead
Using CPython 3.13.14 interpreter at: /usr/bin/python3
Creating virtual environment at: .venv
Installed 22 packages in 72ms

结合上下文可以从源码结构看出一组相互印证的证据:

  1. 本次 pytest 运行发生在优化器的独立 git worktree(rootdir 为 worktrees/opt-atom-run25),且运行环境提示 VIRTUAL_ENV 与项目 .venv 不匹配、随后新建虚拟环境只安装了 22 个包——测试收集阶段在 import a2uiimport yamlimport googleimport a2a 处就全部报错,共 28 个收集错误,没有任何一条是断言失败(assertion failure);
  2. 全部错误均为 ModuleNotFoundError(缺 a2uia2ui.corea2ayamlgoogle 等包),错误栈要么停在测试文件的 import 行,要么停在 a2ui/schema/catalog.py:26from a2ui.core.catalog import Catalog——这是依赖缺失的典型特征;
  3. 本次代码 diff 只触及 prompt_generator.py 的签名生成逻辑,不涉及任何 import 结构、打包配置或测试桩,从改动边界看它无法导致模块缺失。

可以推断,报告中的 FAIL 是运行环境伪影(worktree 里新建的虚拟环境未完整安装 a2ui-agent 及其依赖,而报告流水线把这次收集失败的 pytest 输出原样归档):同一份实验在归档台账 history_summary.md 中 Run 025 行的 Pytest 列为 PASS,run_meta.json 的 notes 也写明 "Pytest 100% pass"。阅读此类实验报告时应区分"报告内嵌的原始日志"与"归档台账的最终裁决"——本报告的回滚裁决由效率上限(Rule 2/3)触发,而非由 pytest 触发。

6. 横向复盘:紧凑签名是一个反复出现、反复回滚的优化方向

把 Run 025 放回 history_summary.md 的完整台账(Atom 格式共 50 余次运行)中,会发现"压缩目录签名"是这条优化线上反复出现的方向,且各自死于不同的红线:

运行 假设(签名压缩变体) 结果与死因
014 精简组件签名的属性详情描述 Backtracked——输入 token -46.3%,但 Schema Acc/Quality 跌至 75.0%(属性上下文缺失导致模型出错)
019 选择性压缩(保留枚举、裁剪单字符串描述) Backtracked——输入 token -16.8%,但缺失属性描述引发歧义,推理 token 与延迟上升
025 紧凑签名(类型提示内联 + 描述行抑制) Backtracked——推理 token -30.9%,但代码输出 token +24.4%,S_opt +0.600 → +0.579
028 复用 025 的紧凑签名 + ATOM_RULES 显式简洁指令 Backtracked——推理 token -43.5%,但代码输出 token +40.1%
030 布尔/枚举标志的紧凑参数格式 Backtracked——省略短枚举描述在多属性组件中制造歧义,Schema Acc 与 Quality 均跌至 83.3%
045 数值/文本属性的紧凑签名格式 Backtracked——输入 token -3.9%,代码输出 token +7.0%

而同期被 KEEP 下来的运行(如 008 编译器 AST 归一化、010 动作补全规则、016 无损 AST 简化、031 事件处理器参数归一化、033 空事件 context 省略、035 单子节点槽位解析)几乎全部是编译器侧改动:在 AtomCompiler 里自动补全、归一化、去冗余,让模型可以"写得更短"而不损失语义。从 history_summary.md 的整体轨迹看,Atom 格式的基线最终收敛在编译器侧归一化上,而非 Prompt 侧压缩。

Run 025 因此提供了一个清晰的负面样本:把 schema 语义从"人类可读描述"改写成"机器紧凑标记"(尖括号类型提示)确实能降低模型的推理负担(-30.9% 推理 token、-9.7% 推理时延),但类型提示丢失了描述的语用上下文,模型在生成 Atom 代码时付出了 +24.4% 的输出膨胀代价——在 S_opt 中代码 token 权重(0.15)是推理 token 权重(0.05)的三倍,这笔账必然亏。

7. 实践要点与可复现路径

从 Run 025 这份报告可以提炼出对"LLM 输出格式调优"有普适价值的几条经验:

  1. 单一维度优化不等于整体收益:优化 Prompt 长度(输入/推理侧)时,必须同时盯住输出侧 token。S_opt 把四个维度加权成单一标量(scoring_model.md),正是为了避免"局部改善、全局回退"的误判;
  2. 效率上限是硬约束而非建议:+5% 代码 token、+10% 流式时延、+15% 推理 token 三条红线保证了一个性质——任何被保留的改动都必须是"帕累托改善",这解释了为何台账中大量"指标看起来更好"的运行仍被回滚;
  3. Prompt 侧压缩 vs 编译器侧归一化:对 S 表达式这类"模型输出短文本、宿主编译器做重活"的架构,把归一化逻辑下放到编译器(如 AtomCompiler 的自动包裹、默认值省略、事件参数映射)往往比在 Prompt 里删描述更安全——因为编译器改动不改变模型可见的语义上下文;
  4. 阅读实验报告要交叉验证三个文件report.md(原始指标 + 原始日志 + 内嵌 Diff)、run_meta.json(结构化指标与最终 notes)、patch.diff(完整补丁),并与台账 history_summary.md 对照。Run 025 的 pytest 段落(worktree 环境收集失败)就是一个"原始日志与台账结论不一致"的实例,理解其成因(见第 5 节)是正确解读这类归档的前提。

想要深入本主题的仓库路径:

登录后查看全文
热门项目推荐
相关项目推荐