A2UI Atom 推理格式优化实录:Run 025「紧凑目录签名」为何被评分模型回滚
本篇技术文章围绕 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 步:
- Analyze History:检查
history/<format>/下的历史运行与 history_summary.md,避免重复已被回滚的假设; - Implement Hypothesis:修改
agent_sdks/python/a2ui_agent/src/a2ui/inference_formats/experimental/<format>/下的compiler.py、prompt_generator.py或parser.py; - Run Unit Conformance Tests:确认 pytest 单测通过;
- Execute Benchmark Evaluation:运行
python scripts/optimize_format.py --format <format>; - Evaluate Decision Rules:必须通过 pytest、保持基线精度,且 Code Output Tokens 不得膨胀超过 +5%,否则回滚(
git reset --hard HEAD); - 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.md、patch.diff、run_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)."——在动态生成的目录签名中使用紧凑的参数类型提示;
- status:
Backtracked(回滚); - 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_RULES(prompt_generator.py)是 11 条语法规则:组件节点形如(ComponentName :key1 val1 ...)、原始类型的写法、带冒号的属性键顺序无关、子组件必须严格嵌套、$/绝对数据路径、$模板相对路径、(data ...)数据模型填充、(template ...)列表模板、(Event ...)动作事件,以及"严禁发明 CSS 属性"的严格目录遵守条款;_generate_component_signatures(prompt_generator.py)遍历CatalogSchemaHelper中按名字排序的全部组件,为每个属性生成:prop(必填)或:prop?(可选)标签,并附加属性描述行与枚举值行(Must be one of: 'a', 'b');_generate_function_signatures(prompt_generator.py)对目录中的函数(如formatString、formatDate)做同样的签名编译。
schema 访问层是 schema_helper.py:get_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.diff,report.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}")
逻辑解读:
- 类型提示内联:每个参数标签后追加尖括号类型提示。优先级是"枚举值 > 语义类型 > 无"——有枚举时输出
:variant<primary/secondary>,否则有语义类型(Child/ChildList/Action)时输出:child<Child>,都没有则不加后缀; - 描述行抑制:原来"有描述或有枚举就输出详情行",改为"只有既无枚举又无语义类型时才输出描述行"。枚举信息已经压缩进尖括号(且比
Must be one of: 'primary', 'secondary'更省 token),描述行整体减半; - 修改后签名形如:
- (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.py 的 get_function_property_schema 后,函数签名也能拿到真实的参数 schema 并生成枚举内联提示。
3.3 改动边界
除签名生成逻辑外,补丁还包含两处 docstring 措辞更新("Compiles component definitions into S-expression signatures." → "...into compact S-expression signatures.")以及对 eval/iterative/current_report.md 和 eval/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,由三层构成:
第一层:正确性护栏(不可协商,失败即回滚)
- Pytest 单测必须 PASS;
SchemaAcc(输出 payload 对目标目录 JSON Schema 的通过率,a2ui_scorer)不得低于基线;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.5、reasoning_tokens_median: 3572.5、input_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
结合上下文可以从源码结构看出一组相互印证的证据:
- 本次 pytest 运行发生在优化器的独立 git worktree(rootdir 为
worktrees/opt-atom-run25),且运行环境提示VIRTUAL_ENV与项目.venv不匹配、随后新建虚拟环境只安装了 22 个包——测试收集阶段在import a2ui、import yaml、import google、import a2a处就全部报错,共 28 个收集错误,没有任何一条是断言失败(assertion failure); - 全部错误均为
ModuleNotFoundError(缺a2ui、a2ui.core、a2a、yaml、google等包),错误栈要么停在测试文件的 import 行,要么停在a2ui/schema/catalog.py:26的from a2ui.core.catalog import Catalog——这是依赖缺失的典型特征; - 本次代码 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 输出格式调优"有普适价值的几条经验:
- 单一维度优化不等于整体收益:优化 Prompt 长度(输入/推理侧)时,必须同时盯住输出侧 token。
S_opt把四个维度加权成单一标量(scoring_model.md),正是为了避免"局部改善、全局回退"的误判; - 效率上限是硬约束而非建议:+5% 代码 token、+10% 流式时延、+15% 推理 token 三条红线保证了一个性质——任何被保留的改动都必须是"帕累托改善",这解释了为何台账中大量"指标看起来更好"的运行仍被回滚;
- Prompt 侧压缩 vs 编译器侧归一化:对 S 表达式这类"模型输出短文本、宿主编译器做重活"的架构,把归一化逻辑下放到编译器(如
AtomCompiler的自动包裹、默认值省略、事件参数映射)往往比在 Prompt 里删描述更安全——因为编译器改动不改变模型可见的语义上下文; - 阅读实验报告要交叉验证三个文件:
report.md(原始指标 + 原始日志 + 内嵌 Diff)、run_meta.json(结构化指标与最终 notes)、patch.diff(完整补丁),并与台账 history_summary.md 对照。Run 025 的 pytest 段落(worktree 环境收集失败)就是一个"原始日志与台账结论不一致"的实例,理解其成因(见第 5 节)是正确解读这类归档的前提。
想要深入本主题的仓库路径:
- 本次运行的完整报告/补丁/元数据:report.md、patch.diff、run_meta.json;
- 完整运行台账:history_summary.md,Atom 各次运行的独立目录位于 history/atom/;
- 优化流程与评分规则:SKILL.md、scoring_model.md;
- 签名生成实现:prompt_generator.py、schema_helper.py;
- Atom 格式规格:a2ui_atom.md。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust4.24 K638- DDeepSeek-V4.1-FlashDeepSeek-V4.1-Flash 是一个多模态混合专家(MoE)模型,拥有 5520 亿骨干参数,并支持最多一百万 token 的上下文长度。该模型原生支持图像和文本输入,并以自回归方式生成文本Python650
SlideSCIPPT插件,支持素材库、AI助手、一键添加图片标题,复制粘贴位置、一键图片对齐、一键插入Markdown(加粗、超链接等行内样式、代码块、LaTeX等块级样式)、便捷导出图片!C#180
hello-agents📚 《从零开始构建智能体》——从零开始的智能体原理与实践教程Python52774
new-apiAI模型聚合管理中转分发系统,一个应用管理您的所有AI模型,支持将多种大模型转为统一格式调用,支持OpenAI、Claude、Gemini等格式,可供个人或者企业内部管理与分发渠道使用。🍥 A Unified AI Model Management & Distribution System. Aggregate all your LLMs into one app and access them via an OpenAI-compatible API, with native support for Claude (Messages) and Gemini formats.Go22545
JeecgBoot🔥企业级低代码平台集成了AI应用平台,帮助企业快速实现低代码开发和构建AI应用!前后端分离架构 SpringBoot,SpringCloud、Mybatis,Ant Design4、 Vue3.0、TS+vite!强大的代码生成器让前后端代码一键生成,无需写任何代码! 引领AI低代码开发模式: AI生成->OnlineCoding-> 代码生成-> 手工MERGE,显著的提高效率,又不失灵活~Java36351