
In my Agent Protocols - MCP, A2A, A2UI, AG-UI
and Agent to UI Protocol (A2UI) with Agent Development Kit (ADK)
posts, I introduced A2UI—Google’s declarative generative UI protocol that lets AI agents return
rich, interactive user interfaces across web, mobile, and desktop using JSON messages (createSurface,
updateComponents, updateDataModel, and deleteSurface).
While standard JSON is an ideal wire protocol for client renderers (Lit, Angular, React, Flutter), having an LLM generate deeply nested wire JSON token-by-token is verbose, token-heavy, and adds latency. Recently, the A2UI team introduced Swappable Inference Formats and made A2UI Express the new default format in the A2UI Agent SDKs—cutting output tokens by ~3x–4x, shrinking system prompts by more than half, and speeding up UI generation without requiring any changes to client renderers.
In this post, I’ll briefly introduce A2UI Express and walk through how to use it, based on the interactive explorer sample in my genai-beyond-basics repository.
What is A2UI Express?
The key insight behind Swappable Inference Formats is decoupling the inference format (what the LLM writes) from the wire format (the standard A2UI JSON that client renderers consume):

Using the official A2UI Python Agent SDK (a2ui-agent-sdk, along with
@a2ui/agent in TypeScript and a2ui_agent in Dart), you can swap the
inference format factory on the server while keeping the wire protocol 100% identical:
- Direct JSON (
DirectJsonFormatFactory): The original<a2ui-json>format where the LLM generates full A2UI Wire JSON directly. - A2UI Express (
ExpressFormatFactory, Default): A compact, Python-like positional DSL enclosed in<a2ui>...</a2ui>tags (root = Card(main_col),_for omitted optional arguments,$/path = valuefor data-model bindings, and?required(...)for validation checks). It cuts system prompt size by ~52–55%, reduces output tokens by ~3x–5.5x, and generates valid UIs reliably even with model thinking turned off (thinking_budget=0). - Atom (
AtomFormatFactory, Experimental): A Lisp S-expression DSL ((Card (Column :id "main_col" ...))). - Elemental (
ElementalFormatFactory, Experimental): An HTML Custom Elements DSL (<ui-card id="root"><ui-column id="main_col">...).
A2UI Express & Swappable Inference Formats Explorer
To see how this works in practice and compare the formats side-by-side, I built an interactive
A2UI Express & Swappable Inference Formats Explorer
using a2ui-agent-sdk, google-genai, FastAPI, and the official @a2ui/lit
v0.9 Web Component renderer.
You can run the explorer locally with a single uv command:
uv run --with a2ui-agent-sdk --with google-genai --with fastapi --with uvicorn python app.py
Open http://127.0.0.1:8080 in your browser. At the top of the page, you can switch between 3 built-in UI examples
(Restaurant Reservation Form, Login Form with Validation, and Notification Permission Card), toggle which format
to compare against Direct JSON (A2UI Express, Atom, or Elemental), or enter a custom prompt and click
⚡ Generate Live with gemini-3.8-flash (thinking_budget=0):

Notice the summary metrics for the Restaurant Reservation Form example: A2UI Express uses 4.0x fewer output
tokens (262 vs. 1,054 tokens), runs 4.0x faster, and shrinks the system prompt footprint by 55% (6,059
vs. 13,424 input tokens) by replacing full JSON Schemas with compact function signatures.
Let’s look at each part of the explorer and the code under the hood.
Python Agent SDK Code (a2ui-agent-sdk)
On the server, we configure A2uiGenerator with our component catalog (BasicCatalog(ProtocolVersion.V0_9)), optional
few-shot examples, and our chosen inference_format_factory:
from a2ui.core.basic_catalog import BasicCatalog
from a2ui.core.schema import ProtocolVersion
from a2ui.inference_formats import ExpressFormatFactory # or DirectJsonFormatFactory
from a2ui.processor import A2uiGenerator, CatalogConfig
catalog = BasicCatalog(ProtocolVersion.V0_9)
generator = A2uiGenerator(
[CatalogConfig(catalog)],
examples=[example_models],
inference_format_factory=ExpressFormatFactory(), # Default (or AtomFormatFactory / ElementalFormatFactory)
)
processor = generator.create_processor(
{"v0.9": {"supportedCatalogIds": [catalog.catalog_id]}}
)
As shown in the explorer, the Python SDK setup is virtually identical between Direct JSON on the left and A2UI
Express on the right—the only difference is passing DirectJsonFormatFactory() vs. ExpressFormatFactory():

What the LLM Generates (<a2ui-json> vs. <a2ui>)
Next, we inject processor.prompt_snippet into Gemini’s system_instruction and ask the model to generate the UI with
thinking_budget=0:
from google import genai
from google.genai import types
client = genai.Client()
response = client.models.generate_content(
model="gemini-3.8-flash",
contents="Create a reservation form for Xi'an Famous Foods with Name, Guests, Time, and Confirm button.",
config=types.GenerateContentConfig(
system_instruction=f"You are an A2UI assistant.\n\n{processor.prompt_snippet}",
thinking_config=types.ThinkingConfig(thinking_budget=0),
),
)
In the next section of the explorer, we can compare what the LLM actually has to generate token-by-token:

On the left, Direct JSON requires 168 lines (3,796 characters / 1,054 tokens) of deeply nested JSON keys
("id", "component", "path"). On the right, A2UI Express expresses the exact same reservation form—including
surface initialization, data-model bindings ($/reservation/...), layout hierarchy, a required validation check
(?required(...)), and button event actions—in just 20 lines (945 characters / 262 tokens):
<a2ui>
surface("booking", sendDataModel=true)
$/reservation/name = "Alice"
$/reservation/guests = "3"
$/reservation/time = "7:30 PM"
$/reservation/notes = "No peanuts"
root = Card(main_col)
main_col = Column([title, subtitle, divider, name_field, row_fields, notes_field, actions])
title = Text("Confirm Reservation", "h2")
subtitle = Text("Xi'an Famous Foods • Friday Evening", "caption")
divider = Divider()
name_field = TextField("Guest Name", $/reservation/name, [?required("Guest name is required")])
row_fields = Row([guests_field, time_field])
guests_field = TextField("Party Size", $/reservation/guests)
time_field = TextField("Time", $/reservation/time)
notes_field = TextField("Dietary Notes", $/reservation/notes)
actions = Row([confirm_btn, cancel_btn], "end")
confirm_btn = Button(Text("Confirm Booking"), "primary", Event("confirm", {reservation: $/reservation}))
cancel_btn = Button(Text("Cancel"), _, Event("cancel", {}))
</a2ui>
What the Renderer Gets (Validated A2UI v0.9 Wire JSON)
Once the model responds, the server calls processor.parse_response(response.text) and converts the parsed A2uiPart
models into standard wire message dictionaries via to_message_dicts():
from a2ui.inference_formats import to_message_dicts
from a2ui.parser import A2uiPart
parts = processor.parse_response(response.text)
wire_messages = [
msg
for part in parts
if isinstance(part, A2uiPart)
for msg in to_message_dicts(part.a2ui)
]
Comparing the output of processor.parse_response() across both columns in the explorer:

While the left column validates the <a2ui-json> payload and the right column compiles the <a2ui> Express DSL, both
produce the exact same 166-line (3,766-character) A2UI v0.9 Wire JSON (createSurface, updateComponents, and
updateDataModel).
Rendered UI (@a2ui/lit v0.9)
Finally, the server sends wire_messages to the browser, where @a2ui/web_core’s MessageProcessor and @a2ui/lit’s
<a2ui-surface> Web Component render the interactive UI:
import '@a2ui/lit/v0_9'; // Registers <a2ui-surface>
import { basicCatalog } from '@a2ui/lit/v0_9';
import { MessageProcessor } from '@a2ui/web_core/v0_9';
const surfaceEl = document.querySelector('a2ui-surface');
const processor = new MessageProcessor([basicCatalog], (action) => {
console.log('User triggered A2UI action:', action);
});
processor.onSurfaceCreated((surface) => {
surfaceEl.surface = surface;
});
processor.processMessages(wireMessages);
Client renderers never see <a2ui> Express, Atom, or Elemental syntax—they only consume standard A2UI v0.9 Wire JSON.
When you edit the form fields and click Confirm Booking in the explorer, the <a2ui-surface> component
automatically dispatches the confirm action event along with the bound $/reservation data model context:

Wrapping up
Decoupling the LLM inference format from the wire protocol is a great example of optimizing for both sides of an agentic system: A2UI Express gives the LLM a compact, token-efficient DSL to generate UIs up to 4x faster, while compiling down to the exact same validated A2UI JSON that client renderers already speak.
For more details, check out the following resources:
- Blog: Fast, reliable A2UI, with swappable inference formats
- Docs: Swappable Inference Formats
- Spec: A2UI Express Technical Specification
- Sample: A2UI Express & Swappable Inference Formats Explorer
- Presentation: Agent to UI Protocol (A2UI)
See also
- Agentic Commerce Protocols - UCP and AP2
- Antigravity Plugin Marketplace in Action: Auditing My Talk Slides with the Google Slides Plugin
- Testing and Serving A2A Agents from the Terminal with A2A CLI
- Google Chat Unread Messages Brief with Workspace Studio and Gemini
- Setting Up an Antigravity Workspace for Multiple Conversations