# A2UI Express and Swappable Inference Formats

Author: [Mete Atamel](https://atamel.dev/) | Published: 2026-11-10 | Topics: GenAI, Agents, A2UI, Gemini | Canonical: https://atamel.dev/posts/2026/11-10_a2ui_express_inference_formats/


![A2UI Express and Swappable Inference Formats](/img/2026/a2ui_express_inference_formats.png)

In my [Agent Protocols - MCP, A2A, A2UI, AG-UI](https://atamel.dev/posts/2026/03-17_agent_protocols_mcp_a2a_a2ui_agui/)
and [Agent to UI Protocol (A2UI) with Agent Development Kit (ADK)](https://atamel.dev/posts/2026/03-30_a2ui_with_adk/)
posts, I introduced [A2UI](https://a2ui.org/)—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](https://developers.googleblog.com/fast-reliable-a2ui/) 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](https://github.com/meteatamel/genai-beyond-basics/tree/main/samples/protocols/a2ui/a2ui-express).

## What is A2UI Express?

The key insight behind [Swappable Inference Formats](https://a2ui.org/concepts/inference-formats/) is decoupling the
**inference format** (what the LLM writes) from the **wire format** (the standard A2UI JSON that client renderers
consume):

![A2UI Swappable Inference Pipeline](/img/2026/a2ui_inference_pipeline.png)

Using the official [A2UI Python Agent SDK](https://pypi.org/project/a2ui-agent-sdk/) (`a2ui-agent-sdk`, along with
[`@a2ui/agent`](https://www.npmjs.com/package/@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 = value` for 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](https://github.com/meteatamel/genai-beyond-basics/tree/main/samples/protocols/a2ui/a2ui-express)
using `a2ui-agent-sdk`, `google-genai`, FastAPI, and the official [`@a2ui/lit`](https://www.npmjs.com/package/@a2ui/lit)
v0.9 Web Component renderer.

You can run the explorer locally with a single `uv` command:

```sh
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)`**:

![Explorer Controls and Summary Metrics](https://raw.githubusercontent.com/meteatamel/genai-beyond-basics/main/samples/protocols/a2ui/a2ui-express/images/controls-and-metrics.png)

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`:

```python
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()`:

![Python Agent SDK Code](https://raw.githubusercontent.com/meteatamel/genai-beyond-basics/main/samples/protocols/a2ui/a2ui-express/images/step1-sdk-code.png)

### 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`:

```python
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:

![What the LLM Generates](https://raw.githubusercontent.com/meteatamel/genai-beyond-basics/main/samples/protocols/a2ui/a2ui-express/images/step2-llm-output.png)

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)**:

```text
<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()`:

```python
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:

![Validated A2UI v0.9 Wire JSON](https://raw.githubusercontent.com/meteatamel/genai-beyond-basics/main/samples/protocols/a2ui/a2ui-express/images/step3-wire-json.png)

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:

```javascript
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:

![Rendered UI and Dispatched Client Action](https://raw.githubusercontent.com/meteatamel/genai-beyond-basics/main/samples/protocols/a2ui/a2ui-express/images/step4-rendered-ui.png)

## 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](https://developers.googleblog.com/fast-reliable-a2ui/)
* [Docs: Swappable Inference Formats](https://a2ui.org/concepts/inference-formats/)
* [Spec: A2UI Express Technical Specification](https://github.com/google/A2UI/blob/main/specification/proposals/express/a2ui_express.md)
* [Sample: A2UI Express & Swappable Inference Formats Explorer](https://github.com/meteatamel/genai-beyond-basics/tree/main/samples/protocols/a2ui/a2ui-express)
* [Presentation: Agent to UI Protocol (A2UI)](https://speakerdeck.com/meteatamel/agent-to-ui-protocol-a2ui)

