A2UI mit AG-UI in Angular integrieren

  1. AG-UI verstehen: Der Standard für Agentic User Interfaces
  2. AG-UI in der Praxis: Das SDK für TypeScript
  3. AG-UI mit Angular umsetzen
  4. A2UI: Wie KI dynamische UIs zur Laufzeit erzeugt
  5. A2UI mit AG-UI in Angular integrieren
  6. Custom Catalogs in A2UI: Eigene Komponenten für KI-generierte UIs

Vom hartcodierten Beispiel zur echten Agent-Anbindung über AG-UI.

In der Praxis stammen A2UI-Nachrichten nicht aus statischem Code, sondern aus den Antworten eines echten Sprachmodells – und die müssen erst einmal vom Agent zum Client transportiert werden. AG-UI bietet dafür die passende Transportschicht, doch die offizielle Spezifikation lässt offen, wie A2UI darüber zu übertragen ist. Dieser Artikel zeigt eine pragmatische Lösung und eine Angular-Abstraktion, die beide Protokolle verbindet.

Dieser zweite Teil der dreiteiligen Serie schließt damit an den ersten Teil an, in dem wir A2UI und den Angular-Renderer noch mit hartcodierten Nachrichten betrieben haben.

📂 Source Code (siehe Branch a2ui-dynamic)

Wie überträgt man A2UI-Nachrichten über AG-UI?

Bisher haben wir A2UI für sich allein betrachtet. In der Praxis kommuniziert der Client jedoch über HTTP-Aufrufe mit dem Agent. Genau hier kommt das im vorigen Artikel dieser Reihe erwähnte Protokoll AG-UI ins Spiel, das die Kommunikation zwischen Frontend und Agent standardisiert.

Wir können A2UI elegant in diese Welt einbetten, indem wir die A2UI-Nachrichten in einer AG-UI-Nachricht vom Typ ACTIVITY_SNAPSHOT übermitteln. Einen solchen Snapshot zeigen die Developer Tools des Browsers:

Browser-Devtools-Trace: A2UI-Operationen createSurface, updateComponents und updateDataModel eingebettet in einen AG-UI-ACTIVITY_SNAPSHOT

In textueller Form sieht der Snapshot folgendermaßen aus:

{
  "type": "ACTIVITY_SNAPSHOT",
  "messageId": "call_6mk44Dh0MNnmPDETq7tvjkON",
  "activityType": "a2ui-surface",
  "content": {
    "operations": [
      {
        "version": "v0.9",
        "createSurface": {
          "surfaceId": "srf-france-confirm",
          "catalogId": "https://a2ui.org/specification/v0_9/basic_catalog.json"
        }
      },
      {
        "version": "v0.9",
        "updateComponents": {
          [...]
        }
      },
      {
        "version": "v0.9",
        "updateDataModel": {
          "surfaceId": "srf-france-confirm",
          "path": "/dummy",
          "value": ""
        }
      }
    ]
  }
}

Leider gibt es keine offizielle Definition dafür, wie A2UI-Nachrichten über AG-UI zu übertragen sind. Die hier gezeigte Lösung, ein ACTIVITY_SNAPSHOT mit einem entsprechenden activityType, passt jedoch gut zur Semantik von AG-UI und wird auch so von der Lösung CopilotKit verwendet. Da die Macher von CopilotKit zu den Initiatoren von AG-UI gehören, kommt ihrer Interpretation des Standards naturgemäß eine große Bedeutung zu.

Als Alternative zu einem ACTIVITY_SNAPSHOT wäre auch ein serverseitiger Toolcall denkbar, zumal AG-UI den Client über Toolcalls sowie deren Ergebnisse informiert. In diesem Fall müssten sich jedoch Client und Agent auf den Namen eines solchen Tools einigen, das A2UI als Ergebnis liefert.

Angular-Abstraktion für A2UI und AG-UI

Es gibt zwar einen Angular-Renderer für A2UI sowie ein TypeScript-SDK für AG-UI, allerdings reicht das in der Praxis nicht aus, um beide Konzepte nahtlos in eine Anwendung zu integrieren. Für eine komfortable Nutzung ist eine zusätzliche Abstraktionsschicht erforderlich, die die Protokolldetails kapselt.

Im Beispiel-Repository findet sich diese Schicht im libs-Ordner. Für die folgenden Betrachtungen behandeln wir sie als Blackbox und greifen darauf über @intern/ag-ui-angular zu.

Diese Implementierung leitet das Sprachmodell per Prompting dazu an, A2UI-Strukturen zu erzeugen – allerdings nicht direkt für den Client, sondern als Input für ein serverseitiges Tool. Dieses Tool spielt eine zentrale Rolle: Es validiert die vom LLM generierten A2UI-Nachrichten und stellt sicher, dass nur konsistente und ausführbare Strukturen weiterverarbeitet werden.

Das ist entscheidend, denn LLMs liefern nicht garantiert valide Ergebnisse. Schlägt die Validierung fehl, wird das Feedback zurück an das Modell gegeben, sodass es einen neuen Versuch unternehmen kann. Erst wenn eine valide Struktur vorliegt, wird sie weitergeleitet.

Die serverseitige Implementierung transformiert die validierten A2UI-Nachrichten anschließend in einen ACTIVITY_SNAPSHOT. Dieser wird Teil des regulären AG-UI-Streams und kann dadurch von der Client-Abstraktion einheitlich verarbeitet werden.

Eine kleine, aber praktische Konvention hilft beim Umgang mit Diagrammen: Da A2UI selbst keine Chart-Komponente vorsieht, weisen wir das LLM im Prompt explizit an, Diagramme über eine Image-Komponente mit einer QuickChart-URL zu rendern. Dabei handelt es sich um einen Webdienst, der die anzuzeigenden Daten als URL-Parameter entgegennimmt und ein Bild mit einem Diagramm zurückliefert. So bleibt der Komponenten-Katalog schlank und das LLM erhält trotzdem einen klaren Pfad für visuelle Daten.

Alternative Umsetzung

Eine mögliche Vereinfachung besteht darin, das LLM nicht direkt A2UI erzeugen zu lassen, sondern eine reduzierte, auf den Anwendungsfall zugeschnittene DSL (Domain-Specific Language). Das serverseitige Tool übernimmt die Übersetzung dieser DSL in gültige A2UI-Operationen und fungiert damit als zentrale Kontrollinstanz. Aus Sicht des Clients ändert sich nichts. Er erhält weiterhin einen ACTIVITY_SNAPSHOT mit regulärem A2UI.

Die DSL macht die Generierung für das LLM deutlich robuster und eignet sich besonders für schwächere oder kostengünstigere Modelle. Gleichzeitig verlagert sich mehr Logik auf den Server, was Kontrolle und Stabilität erhöht. Der Preis dafür ist eine zusätzliche Transformationsschicht sowie eine geringere Ausdrucksstärke und Flexibilität im Vergleich zu direktem A2UI.

Eine Implementierung dieses Ansatzes findet sich im Beispielprojekt im Branch a2ui-dsl.

Clientseitige Anbindung mit agUiResource

Auf der Client-Seite kümmert sich die agUiResource um die Verbindung zum Agent. Sie verwaltet den Chatverlauf, führt clientseitige Tool Calls aus und stellt die empfangenen Nachrichten zur Verfügung. Der Konsument registriert bei dieser Resource unter anderem die URL des Agents und die angebotenen Client-Tools:

import { agUiResource } from '@internal/ag-ui-client';

[...]

@Injectable({ providedIn: 'root' })
export class TicketingChatService {
  private readonly config = inject(ConfigService);
  private readonly chatStore = inject(ChatRegistry);
  private readonly destroyRef = inject(DestroyRef);

  private readonly chat = agUiResource({
    url: this.config.agUiUrl,
    model: this.config.model,
    useServerMemory: true,
    tools: [
      findFlightsTool,
      getLoadedFlightsTool,
      toggleFlightSelectionTool,
      getCurrentBasketTool,
      displayFlightDetailTool,
    ],
  });

  constructor() {
    registerHandlers({
      checkIn: (action) => checkInAction(action),
      submitAnswer: (action) => submitAnswerAction(action, this.chat),
    });

    this.destroyRef.onDestroy(() => this.cleanupChat());
  }

  public init(): void {
    this.chatStore.setChat(this.chat);
  }

  private cleanupChat(): void {
    this.chat.dispose();
    this.chatStore.clearChat();
  }
}

Die Hilfsfunktion registerHandlers definiert die Event-Handler und delegiert dazu intern an die im ersten Teil besprochene Eigenschaft onAction. Zum Senden einer Nachricht an den Agent bietet die agUiResource eine Methode sendMessage an:

this.chat.sendMessage({
  role: 'user',
  content: 'Did I book my flight to France?',
});

NOTE

Neu: Agentic UI with Angular

Wenn du A2UI nicht nur integrieren, sondern sauber in größere Architekturen einbetten willst:
In meinem Buch Agentic UI mit Angular gehe ich genau auf diese Patterns und Trade-offs im Detail ein.

Cover des eBooks Agentic UI with Angular

Mehr zum eBook →

Chatverlauf und A2UI-Surfaces im Template anzeigen

Der gesamte Chatverlauf befindet sich im value der AgUiResource. Es handelt sich um ein Signal, das ein Array mit AgUiChatMessages enthält und sich im Angular-Template ausgeben lässt:

@for (message of chat.value(); track message.id) {

  @if (message.content) {
    <div>{{ message.content }}</div>
  }

  @for (widget of message.widgets; track widget.id) {
    <app-widget-container [widget]="widget" />
  }

  @for (toolCall of message.toolCalls; track toolCall.id) {
    <div>Tool Call: {{ toolCall.name }}</div>
  }

}

Die agUiResource wertet die ACTIVITY_SNAPSHOT-Nachrichten aus und hängt die darin enthaltenen A2UI-Nachrichten an die AgUiChatMessages an. Der ebenfalls von @intern/ag-ui-angular gelieferte WidgetContainer delegiert diese Nachrichten an den A2UI-Renderer und zeigt das empfangene Surface an. Dazu nutzt er die im ersten Teil dieser Serie besprochene SurfaceComponent des Angular-Adapters für A2UI.

Zur Bereitstellung des Basic Catalogs bietet @internal/ag-ui-client die Funktion provideA2uiCatalog. Zusätzlich ist auch hier mit provideMarkdownRenderer eine Markdown-Implementierung anzubinden:

import { provideMarkdownRenderer } from '@a2ui/angular/v0_9';
import { provideA2uiCatalog } from '@internal/ag-ui-client';

[...]

export const appConfig: ApplicationConfig = {
  providers: [
    provideA2uiCatalog(),
    provideMarkdownRenderer(async (markdown) =>
      marked.parse(String(markdown ?? '')),
    ),
  ],
};

Damit ist die Brücke zwischen Agent und A2UI-Renderer geschlagen: Der Agent liefert seine A2UI-Nachrichten über AG-UI-ACTIVITY_SNAPSHOTs, die agUiResource nimmt sie entgegen, und der WidgetContainer rendert die enthaltenen Surfaces. Die einzelnen Komponenten unserer Anwendung müssen weder das AG-UI- noch das A2UI-Protokoll im Detail kennen.

Zusammenfassung

In Kombination mit AG-UI fügt sich A2UI nahtlos in eine bestehende Agentic-Architektur ein. AG-UI übernimmt die strukturierte Kommunikation zwischen Client und Agent, während A2UI die UI-spezifischen Inhalte transportiert. Die gezeigte Angular-Abstraktion auf Basis der agUiResource ermöglicht die einfache und idiomatische Nutzung in Angular.

Ein nicht zu unterschätzender Vorteil dieser Architektur liegt in der Validierung auf Server-Seite: Sie schützt den Client vor inkonsistenten Antworten des Sprachmodells und sorgt dafür, dass nur überprüfte Strukturen am Ende im Browser landen. Die optionale DSL-Variante zeigt darüber hinaus, dass sich derselbe Mechanismus für unterschiedlich starke Modelle ausbalancieren lässt.

Der nächste Schritt

Bislang haben wir uns auf den Basic Catalog gestützt – im nächsten Teil zeigt sich, wie sich eigene, fachliche Komponenten als Custom Catalog ergänzen lassen.

Nächster Artikel →


FAQ

Wie überträgt man A2UI-Nachrichten über AG-UI?

Eine offizielle Festlegung gibt es nicht. In der Praxis bewährt sich, die A2UI-Operationen in einer AG-UI-Nachricht vom Typ ACTIVITY_SNAPSHOT mit einem eigenen activityType (zum Beispiel a2ui-surface) zu transportieren. Diese Variante passt zur Semantik von AG-UI und wird auch von CopilotKit genutzt.

Warum gehört eine serverseitige Validierung dazu?

Sprachmodelle liefern nicht garantiert valide A2UI-Strukturen. Eine serverseitige Validierung – etwa in einem Tool – fängt fehlerhafte Antworten ab, gibt Feedback an das Modell zurück und stellt sicher, dass nur konsistente und ausführbare Strukturen den Client erreichen.

Was macht die agUiResource in Angular?

Die agUiResource aus der Angular-Abstraktion verwaltet die Verbindung zum Agent. Sie bündelt den Chatverlauf, führt clientseitige Tool Calls aus, registriert Event-Handler und liefert die empfangenen Nachrichten als Signal an das Template. Damit müssen einzelne Komponenten weder AG-UI noch A2UI im Detail kennen.

Wann ist eine eigene DSL statt direktem A2UI sinnvoll?

Eine reduzierte, anwendungsspezifische DSL kann die Generierung für schwächere oder kostengünstigere Modelle deutlich robuster machen. Der Server übersetzt sie zentral nach A2UI. Der Preis dafür ist eine zusätzliche Transformationsschicht und etwas weniger Ausdrucksstärke gegenüber direktem A2UI.

Agentic UI with Angular

Architecting Agentic AI with Open Standards

Integriere AI-Agents in Angular mit offenen Standards.

Mehr zum Buch