AG-UI mit dem SDK am Server und am Client umsetzen
Nachdem AG-UI die fachlichen Nachrichten zwischen Client und Agent definiert, stellt sich in der Praxis sofort die nächste Frage: Wie setzt man diese Nachrichten auf Client- und Serverseite konkret um? Genau dafür liefern die offiziellen SDKs fertige Bausteine für TypeScript und Python.
Dieser zweite Teil der Serie zeigt, wie das AG-UI SDK für TypeScript am Server und im Browser eingesetzt wird, wie Tool Calls darüber laufen und wie Client-Tools beschrieben und an den Agent übermittelt werden.
📂 Source Code (siehe Branch agentic)
Was bietet das AG-UI SDK?
Damit wir nicht ganz bei Null starten müssen, kommt AG-UI nicht nur mit einer Protokollbeschreibung, sondern auch mit SDKs für TypeScript und Python. Viele der Adapter für Agent Frameworks bauen darauf auf. Das Microsoft Agent Framework kommt hingegen mit einer eigenen Implementierung, um serverseitig AG-UI auch über C# unterstützen zu können. Daneben finden sich im offiziellen AG-UI-Repo auch Community-Implementierungen für weitere Sprachen wie Java und C++ sowie für Frameworks wie Spring AI.
Die offiziellen SDKs für TypeScript und Python unterstützen HTTP über Server-sent Events (SSE): Pro Run kommt es zu einer HTTP-Anfrage, woraufhin der serverseitige Agent seine Antwort mit den einzelnen Textnachrichten und Tool Calls nach und nach an den Client sendet. Diese Nachrichten lassen sich via JSON oder binär mittels Protocol Buffer kodieren.
TypeScript SDK am Server einsetzen
Um den serverseitigen Einsatz des TypeScript SDK zu veranschaulichen, zeigt das folgende Beispiel hartcodiert und ohne Einsatz eines Sprachmodells einfache AG-UI-konforme Nachrichten. Dazu verfolgt der Server eine alte Smalltalk-Regel, wonach es nie verkehrt ist, übers Wetter zu reden:
import { HttpAgent } from '@ag-ui/client';
import { BaseEvent, EventType, RunAgentInput } from '@ag-ui/core';
import { Observable } from 'rxjs';
export class FlightWeatherAgent extends AbstractAgent {
run(input: RunAgentInput): Observable<BaseEvent> {
return new Observable((observer) => {
const { threadId, runId } = input;
observer.next({ type: EventType.RUN_STARTED, threadId, runId });
observer.next({
type: EventType.TEXT_MESSAGE_START,
messageId: '1001',
role: 'assistant',
});
observer.next({
type: EventType.TEXT_MESSAGE_CONTENT,
messageId: '1001',
delta: 'Checking flight weather for Frankfurt...',
});
observer.next({ type: EventType.TEXT_MESSAGE_END, messageId: '1001' });
[...]
observer.next({ type: EventType.RUN_FINISHED, threadId, runId });
observer.complete();
});
}
}
Der hier gezeigte FlightWeatherAgent erbt vom AbstractAgent. Die Methode run verarbeitet Benutzeranfragen. Sie startet einen Run, sendet eine Textnachricht an den Client und schließt den Run wieder ab.
Typischerweise delegiert run an ein Agent-Framework wie Mastra, LangGraph, Google ADK, Microsoft Agent Framework oder Spring AI und wandelt die empfangenen Informationen in AG-UI-konforme Nachrichten um. Diese Integration muss man in der Regel auch nicht selbst schreiben, zumal das AG-UI-SDK Adapter für viele Frameworks bereitstellt und einige Frameworks auch ihre eigene Anbindung mitbringen.
Dabei ist zu beachten, dass sich das SDK nicht um die Anbindung an das gewählte Transportprotokoll kümmert, also zum Beispiel nicht um das Versenden von Server-sent Events (SSE) via HTTP. Deswegen enthält das beiliegende Demo-Projekt auch ein wenig Gluecode, der sich beim Agent anmeldet und sämtliche Nachrichten als SSE versendet.
TypeScript SDK im Browser einsetzen
Auf der Clientseite gibt uns das TypeScript SDK die Möglichkeit, einen AgentSubscriber mit Eventhandlern für die eingehenden Nachrichten zu definieren:
import { AgentSubscriber } from '@ag-ui/client';
const subscriber: AgentSubscriber = {
onRunStartedEvent: ({ event }) => {
console.log(
`RUN_STARTED: threadId=${event.threadId}, runId=${event.runId}`,
);
},
onTextMessageStartEvent: ({ event }) => { [...] },
onTextMessageContentEvent: ({ event }) => { [...] },
onTextMessageEndEvent: ({ event }) => { [...] },
[...]
onRunFinishedEvent: ({ event }) => { [...] },
};
Für jeden Nachrichtentyp ist ein eigener Handler vorgesehen. Mit dem ebenfalls vom SDK angebotenen HttpAgent lässt sich eine Verbindung zum Agent auf der Serverseite aufbauen:
import { FlightWeatherAgent } from './server.js';
const threadId = '4711';
const url = 'https://...';
const agent = new HttpAgent({ url, threadId });
const userMessage = {
id: 'msg-user-1',
role: 'user' as const,
content: 'What is the flight weather in Frankfurt?',
};
agent.addMessage(userMessage);
await agent.runAgent({ runId: '0815' }, subscriber);
Die Methode addMessage fügt die userMessage zunächst nur zu einem clientseitigen Array hinzu. Erst wenn runAgent den nächsten Run startet, werden die lokal gesammelten Nachrichten übermittelt. Sobald die Antworten vom Agent via SSE eintreffen, ruft runAgent die entsprechenden Handler im übergebenen AgentSubscriber auf.
Je nachdem, ob sich der Agent den Gesprächsverlauf zwischen den einzelnen Runs merkt, übersendet der Client entweder sämtliche bisher ausgetauschten Nachrichten oder nur die neu hinzugefügten.
Agentic UI with Angular
Mehr zu diesem Thema in meinem eBook: Baue skalierbare Agentic UIs mit Angular – mit AG-UI, A2UI und MCP Apps, offen und ohne Vendor Lock-in.
Server-seitiges Tool Calling mit dem AG-UI SDK
Bisher hat unsere Demo lediglich einfache Texte versendet. Das Anfordern von Tool Calls durch den Agent verläuft jedoch sehr ähnlich. Auch die hierfür zu versendenden Informationen kommen typischerweise vom Sprachmodell der Wahl und werden vom Agent AG-UI-konform aufbereitet. Zur Vereinfachung nehmen wir in unserer Demo jedoch nach wie vor mit ein paar hartcodierten Nachrichten vorlieb:
// Server Code
// Step 1: Tool Call
observer.next({
type: EventType.TOOL_CALL_START,
toolCallId: '2001',
toolCallName: 'loadFlightWeather',
});
observer.next({
type: EventType.TOOL_CALL_ARGS,
toolCallId: '2001',
delta: '{"city":"Frankfurt"}',
});
observer.next({
type: EventType.TOOL_CALL_END,
toolCallId: '2001',
});
// Step 2: Execute Server-side Tool
const weatherResult = [...];
// Step 3: Answer Tool Call
observer.next({
type: EventType.TOOL_CALL_RESULT,
toolCallId: '2001',
messageId: '3001',
role: 'tool',
content: JSON.stringify(weatherResult),
});
Mit den ersten drei Nachrichten zeigt der Agent an, dass das LLM einen Tool Call angefordert hat. Da es sich um ein serverseitiges Tool handelt, führt er es selbst aus und gibt das Ergebnis dem Sprachmodell zurück. Außerdem protokolliert er das Ergebnis über eine weitere AG-UI-Nachricht.
Der Client kann den Benutzer aufgrund dieser Nachrichten über den Tool Call informieren. Das schafft Transparenz und verhindert einen längeren Stillstand in der UI.
Client-seitiges Tool Calling mit dem AG-UI SDK
Die Ausführung von Client-Tools erfolgt ähnlich wie bei Server-Tools. Auch hier informiert der Agent über den Aufruf sowie über die Parameter mit den betrachteten Tool-Call-Nachrichten. Der Unterschied ist, dass der Client nun auf diese Nachrichten reagiert, indem er ein Tool ausführt und das Ergebnis im nächsten Run zurücksendet.
Hierbei ergibt sich jedoch zunächst eine Herausforderung: Das Sprachmodell muss über die verfügbaren Client-Tools informiert werden. Auch bei dieser Aufgabe kommt uns das TypeScript SDK zur Hilfe. Es gibt uns einen Datentyp Tool, mit dem sich die Client-Tools definieren lassen. Neben einem Namen und einer textuellen Beschreibung nimmt dieser Typ auch Informationen über die erwarteten Parameter auf:
// Client Code
import { Tool } from '@ag-ui/client';
import { z } from 'zod';
const weatherSchema = z.object({
condition: z.string().describe('e.g., sunny, cloudy, rainy.'),
temperature: z.string().describe('e.g., 25°C, 77°F.'),
wind: z.string().describe('e.g., 5 km/h, 3 mph.'),
});
export const showWeatherTool: Tool = {
name: 'showWeather',
description: 'Provide weather data the client can render.',
parameters: z.toJSONSchema(weatherSchema),
};
Die Parameter sind als JSON Schema zu hinterlegen. Unser Beispiel nutzt die populäre Schema-Bibliothek zod, um dieses Schema bereitzustellen. Basierend auf der Beschreibung des Tools und der Parameter kann das LLM entscheiden, wann und wie das Tool aufzurufen ist.
Die Methode runAgent nimmt die Toolbeschreibungen entgegen und übersendet sie dem Agent:
// Client Code
// 1st run
await agent.runAgent({ runId: '0815', tools: [showWeatherTool] }, subscriber);
// Look into received client-side tool calls and perform respective actions
const toolCallResultMessage = [...];
// Add Tool Call Result
agent.addMessage(toolCallResultMessage);
// 2nd run
await agent.runAgent({ runId: '0816', tools: [showWeatherTool] }, subscriber);
Nach jedem Run prüft der Client, ob der AgentSubscriber Anforderungen für client-seitige Tool Calls erhalten hat. Falls dem so ist, führt er jene Tools aus und sendet die Resultate im Rahmen eines weiteren Runs an den Agent zurück.
AG-UI, der Nachrichtenverlauf und Client Tools
Das Übersenden der über addMessage hinterlegten Nachrichten sowie der Informationen zu den Client-Tools erfolgt über die Payload des HTTP-Requests, der den nächsten Run anstößt. Im Gegensatz zu den besprochenen Nachrichten definiert das AG-UI-Protokoll den Aufbau dieser Payload nicht. Es handelt sich dabei lediglich um ein Implementierungsdetail des AG-UI-SDKs. Aufgrund des offiziellen Charakters dieses SDKs ist zu hoffen, dass sich andere Implementierungen daran anlehnen.
Infos über Client-Tools am Server auslesen
Wie weiter oben erwähnt, leiten die Adapter der einzelnen Agent-Frameworks die empfangenen Informationen zu Client-Tools an das LLM weiter. Wer hingegen mit diesen Informationen im Agent manuell hantieren möchte, findet sie in der Eigenschaft tools des an run übergebenen Parameterobjekts:
class FlightWeatherAgent extends AbstractAgent {
run(input: RunAgentInput): Observable<BaseEvent> {
return new Observable((observer) => {
console.log('tools', input.tools);
[...]
});
}
}
Es handelt sich dabei um ein JSON Schema mit den Parameterbeschreibungen und den textuellen Informationen, die der Client mittels Zod festgelegt hat:

Zusammenfassung
Das TypeScript-SDK nimmt einem einen großen Teil der Integrationsarbeit für AG-UI ab. Es kümmert sich um die korrekte Darstellung und Verarbeitung von Nachrichten. Außerdem bietet es Event-Handler, damit der Client auf eingehende Nachrichten reagieren kann.
Das SDK ist bewusst low-level gehalten. Für einen komfortablen Einsatz in einem Framework wie Angular braucht es daher eine zusätzliche Abstraktion. Als zweiter Teil der Serie liefert dieser Artikel die technische Grundlage dafür. Im nächsten Teil zeigen wir, wie sich AG-UI idiomatisch in Angular-Anwendungen integrieren lässt.
FAQ
Was macht das AG-UI SDK für TypeScript?
Das SDK stellt Bausteine bereit, um AG-UI-Nachrichten am Server und im Browser zu erzeugen, zu versenden und zu verarbeiten. Dazu gehören unter anderem Agentenklassen, Subscriber für eingehende Events sowie Datentypen für Client-Tools.
Unterstützt das AG-UI SDK Server-sent Events?
Das SDK arbeitet gut mit Server-sent Events zusammen, kümmert sich aber nicht selbst um die Transportanbindung. Das eigentliche Versenden von SSE über HTTP muss die Anwendung also zusätzlich über Gluecode oder ein Framework abbilden.
Worin unterscheiden sich serverseitige und clientseitige Tool Calls?
Serverseitige Tools führt der Agent selbst aus und meldet ihre Ergebnisse an das Modell zurück. Clientseitige Tools werden hingegen vom Agent angefordert, im Browser ausgeführt und ihre Resultate im nächsten Run an den Agent übermittelt.
Warum ist das TypeScript-SDK trotzdem noch low-level?
Das SDK bildet die AG-UI-Kommunikation sauber ab, lässt aber viele Komfortfunktionen bewusst offen. In Frameworks wie Angular ist deshalb meist eine weitere Abstraktion sinnvoll, die Boilerplate reduziert und die Integration idiomatischer macht.
