Das generierte Dashboard mit Bordkarten, gebuchten Flügen, Flugsuche, Mietwagen, Hotels und Wetter About 40 seconds. Over 46,000 tokens. For *one single* dashboard. That was the initial version of my A2UI solution — and nobody wants to see something like that in production, neither the users staring at an empty screen nor the person paying the provider's bill at the end of the month. Today, the same solution takes **around 0.1 seconds** and **just over 1,500 tokens**. That's a speedup of **300×** and a token reduction of **30×**. The really interesting part, however, isn't the number itself, but the surprisingly simple lever behind it — and the lesson that can be drawn from it for any LLM-driven UI generation. In a previous article, I demonstrated how to generatively create entire dashboards with A2UI. The result looked like this: Trace der ursprünglichen Implementierung: rund 40 Sekunden, viele Tool-Calls und ein langer Schritt für die A2UI-Generierung In this article, I show how I drastically accelerated exactly this solution — and what consequences this optimization entails. Because every performance gain has its price.

The Problem: Why A2UI Generation Was Slow and Expensive

To put things in context: A2UI is a protocol that allows user interfaces — here, a dashboard — to be described in a way that an LLM can generate. The model doesn't produce finished HTML, but rather a structured description of the interface that the client then renders. Precisely this generation was the bottleneck in the first version. To generate A2UI, one first needs a correspondingly long prompt with good examples — classic one-shot or few-shot prompting. Since LLMs handle such examples excellently, I didn't even copy the complete specification or the JSON schema of A2UI into the prompt. Nevertheless, the whole thing was very extensive: in the example shown at the start, we ended up with **over 43,000 input tokens**. In addition, generating the A2UI markup based on this description took correspondingly long. The model has to produce an extensive structure token by token — and that's exactly what's expensive. Beyond that, the model constantly has to issue function calls to fetch the data for the individual tiles. Each of these calls costs additional reasoning and thus time and tokens. And finally, perhaps the most unpleasant problem: when generating A2UI, the model can get confused and produce faulty markup. The basic design of A2UI — which deliberately avoids deep nesting, for instance — noticeably helps LLMs. But errors can't be entirely ruled out, and every error forces further correction loops. Especially weaker (and thus cheaper) models are affected by this. That's what a complete run looked like in the original variant:
{
  "toolName": "renderDashboard",
  "toolCallId": "call_2mO2fjJD4ZpGHna4yXsW8QG0",
  "toolInput": {
    "tiles": [
      {
        "type": "boardingPasses",
        "count": 2
      },
      {
        "type": "bookedFlightsList",
        "showCheckInButton": true
      },
      {
        "type": "flightSearch",
        "defaultFrom": "Graz",
        "defaultTo": "Hamburg"
      },
      {
        "type": "rentalCars"
      },
      {
        "type": "hotels"
      },
      {
        "type": "weatherList"
      }
    ]
  }
}
Clearly visible: the model first collects data via several tool calls and then generates the complete A2UI markup in a single step lasting over 30 seconds. This one step dominates the entire runtime.

The Solution: an Application-Specific DSL Instead of A2UI Markup

The idea is simple: a small, application-specific DSL (Domain-Specific Language) — i.e., a deliberately minimal description language tailored to exactly one purpose — that can describe exactly what should be generated: a dashboard with specific content fitting the application. In our case, this DSL looks like this: Cover des eBooks Agentic UI with Angular The model is now only instructed to translate the user's request into this DSL. That's all it does. Since this leads to a very short result, the conversion is fast, less error-prone, and even weaker models handle it without any problems. There's another, often underestimated advantage: the application is thus forced more strongly into a defined framework. The model can no longer do things that the application design hasn't anticipated. The result becomes significantly more controllable — despite the fundamentally non-deterministic behavior of an LLM. This DSL is subsequently used for two purposes:
  1. Conversion to A2UI — deterministic, in code, without any model.
  2. Fetching the necessary data — also without further reasoning by the model.
The key point: both the translation into A2UI and the data retrieval now happen in ordinary application code. The model is no longer involved in this. And the client doesn't notice any of this. It still receives A2UI and therefore doesn't need to know any details of the application logic. The DSL is a pure implementation decision on the server side.

Consequences: Pros and Cons of the DSL Approach

As always in software architecture, there's no such thing as "free." Let's note the advantages and disadvantages. Pros:
  • Significantly more performant.
  • Significantly lower token consumption — and thus significantly lower costs.
  • Behavior becomes more controllable.
  • Easier for weaker (cheaper) models.
  • The client doesn't need to know all possible display options in advance.
Cons: The controllable behavior is simultaneously the central disadvantage: the dynamism is limited to those aspects that the DSL explicitly provides for. If one wants, for instance, to limit the amount of output data (e.g., a maximum of three hotels), add weather information, or omit the check-in button, the DSL must explicitly offer possibilities for that. What it doesn't provide for is simply not possible. Our example DSL, for instance, cannot express which information about a flight should be presented or whether it should be displayed as a table or a list. So you're trading part of the generative flexibility for speed, cost, and control. For the vast majority of business applications, this is an excellent trade — because there, predictability and performance are almost always more important than boundless freedom in presentation.

New: Agentic UI with Angular

If you want to embed such performance and architecture decisions around A2UI not just in isolated cases, but cleanly into larger Angular applications: in my book Agentic UI with Angular, I go into exactly these patterns and trade-offs — including DSLs, caching, and open standards like AG-UI, A2UI, and MCP — in detail.

{
    "version": "v0.9",
    "updateComponents": {
        "surfaceId": "dash-bf0140eb-f2e4-4c53-a74b-56e9445cc7dc",
        "components": [
            [...],
            {
                "id": "t0-t0",
                "component": "TicketWidget",
                "ticketId": {
                    "path": "/t0/tickets/0/ticketId"
                },
                "from": {
                    "path": "/t0/tickets/0/from"
                },
                "to": {
                    "path": "/t0/tickets/0/to"
                },
                "date": {
                    "path": "/t0/tickets/0/date"
                },
                "delay": {
                    "path": "/t0/tickets/0/delay"
                }
            },
            [...]
        ]
    }
}

Learn more about the eBook →

Even More Performance: Caching

As long as the description remains the same, the generated markup can also remain the same. As a rule, that's even desirable: a re-interpretation that leads to slight deviations would rather confuse users. Only the data needs to be fetched anew with each call. Here, a fundamental feature of A2UI plays into our hands: you can **separate structure and data**. An updateComponents message can be used to communicate the desired structure:
{
    "version": "v0.9",
    "updateDataModel": {
        "surfaceId": "dash-bf0140eb-f2e4-4c53-a74b-56e9445cc7dc",
        "path": "/t0",
        "value": {
            "tickets": [
                {
                    "ticketId": 1,
                    "from": "Graz",
                    "to": "Hamburg",
                    "date": "2026-06-06",
                    "delay": 0
                },
                {
                    "ticketId": 2,
                    "from": "Hamburg",
                    "to": "Graz",
                    "date": "2026-06-06",
                    "delay": 0
                }
            ]
        }
    }
}
This can contain data-binding expressions that refer to a data model. In the example shown, the property path points to an array /t0/tickets, whose entries have properties like ticketId, from, to, etc. This data can subsequently be sent to the client with its own messages: Trace der ursprünglichen Implementierung: rund 40 Sekunden mit langem A2UI-Generierungsschritt This separation is the basis for our caching: with the same description, the structure is deterministically reproducible; mainly the data transmitted via updateDataModel needs to be re-determined. In our implementation, we cache the compact DSL. From it, the entire — and usually relatively extensive — updateComponents message is generated deterministically in application code for subsequent calls; the model is no longer needed for that. In our implementation, a hash of the textual description supplied by the user serves as the key. In practice, the respective dashboard is often stored as a dataset in a design mode of the application — and exactly the ID of this dataset, which represents the dashboard, then serves as the cache key.

The Result: 300× Faster, 30× Fewer Tokens

The DSL enables drastic speedups. Let's compare the two worlds directly again: Trace der optimierten Implementierung: das Modell erzeugt nur noch die kompakte DSL – wenige Sekunden statt rund 40 Sekunden Workshop: Agentic AI mit Angular – AG-UI, A2UI, MCP Apps & HITL-Patterns The model only creates the DSL from the request — no longer the complete A2UI markup. This results in a nice side effect: the necessary tool calls for data fetching can also be derived from the DSL. The model therefore no longer has to worry about this itself, which leads to additional, smaller performance improvements — simply because less reasoning is required for individual tool calls. The second major improvement comes from caching: here, the model is no longer needed at all for identical dashboards. The DSL comes directly from the cache; the structure described with A2UI is generated from it deterministically in application code, and only the current data is freshly loaded. And with that, we're back at the numbers from the beginning: from **around 40 seconds** and **over 46,000 tokens** to **around 0.1 seconds** and **just over 1,500 tokens** — a factor of 300 in speed, a factor of 30 in tokens. Important: the token reduction by a factor of 30 already results from the DSL alone, because the model only has to generate this compact description instead of the complete A2UI markup. Caching builds on top of that: on a cache hit, the model isn't called at all — so no tokens are incurred whatsoever.

Bottom Line: Let the LLM Handle Intent, Let Code Handle Structure

The core idea here is a deliberate reassignment of duties. Instead of asking the LLM to produce extensive A2UI markup, it now only converts the request into a terse, domain-specific DSL. All the demanding work — building out the structure and retrieving the data — is then carried out by deterministic application code.

The numbers tell the story: 300 times faster, 30 times fewer tokens, and significantly more predictable behavior, with smaller and cheaper models benefiting the most. When caching is added, the model can be taken off the hot path entirely for recurring dashboards.

The trade-off is a reduction in generative freedom — the app can only render what the DSL supports. For most real-world business applications, that is not a loss but a win, since predictability matters more than unbounded dynamism. The overarching takeaway: let the LLM do what it does best — grasp intent — and leave the construction of reliable structures to your code.

Interested in Production-Ready Agentic UI Architectures?

In my workshop we take a deep dive into AG-UI, A2UI, MCP Apps, HITL patterns, and modern Angular architectures for real-world agentic systems.

Workshop: Agentic AI mit Angular – AG-UI, A2UI, MCP Apps & HITL-Patterns

See all details →

FAQ

Why is direct A2UI generation with an LLM slow and costly?
The model has to generate a large structure token by token from a long few-shot prompt (over 43,000 input tokens in the example), while also making several tool calls to fetch data. That single generation step dominates the runtime and can easily produce faulty markup, which then forces correction loops.

How does a domain-specific DSL speed up generation?
The LLM only needs to translate the request into a compact DSL instead of full A2UI markup. The output is short, fast, and much less error-prone. The expensive work — converting to A2UI and fetching data — is subsequently handled by deterministic application code, with no model involved.

What additional benefit does caching provide?
A2UI allows separating structure (updateComponents) from data (updateDataModel). In our implementation, the compact DSL is cached; the typically large structure is then generated deterministically from it in application code, and for subsequent calls only the small data messages with fresh data need to be built. For identical dashboards, the model is not needed at all.

What is the downside of the DSL approach?
Dynamism is limited to what the DSL explicitly covers. Anything it does not represent is simply not possible. You are effectively swapping some generative flexibility for speed, cost, and control — a good trade for most business applications.