Hinweis: Dieses Video wurde mithilfe von KI erstellt auf Grundlage der ursprünglichen Inhalten sowie den technischen Erkenntnissen des Autors des Blogartikels.
Ein KI-Assistent kann zur Laufzeit aus Hunderten von Aktionen auswählen – doch das Frontend kann nicht für jede mögliche Kombination ein eigenes Bedienfeld bereitstellen. Der Artikel zeigt eine deklarative Architektur für Generative UI, bei der eine gemeinsame Aktionsdefinition sowohl dem LLM als auch der UI-Ebene vorgibt, welche Komponenten benötigt werden und wie Aktionsparameter und UI-Zustand ineinander überführt werden.
Wir befinden uns mitten in einem grundlegenden Wandel darin, wie Anwender mit Software interagieren. Jahrzehntelang war das Paradigma statisch: Designer und Entwickler legten gemeinsam das Layout eines Bedienfelds fest. Anschließend banden die Entwickler dessen Steuerelemente an ein View Model, das wiederum mit dem Document Model kommunizierte. Dieses Vorgehen funktioniert hervorragend, solange Funktionsumfang und Workflows weitgehend im Voraus bekannt sind. KI-Assistenten durchbrechen dieses Modell jedoch. Anwender beschreiben in natürlicher Sprache, was sie erreichen möchten, und das System entscheidet selbst, welche Bedienelemente angezeigt werden sollen.
Dadurch entsteht eine architektonische Herausforderung, die sich mit bestehenden Frontend-Mustern nicht lösen lässt. Wie kann eine UI-Ebene die jeweils vom LLM ausgewählten Steuerelemente dynamisch zusammenstellen, ohne dass jede mögliche Kombination von Hand programmiert werden muss?
Dieser Artikel beleuchtet eine Architektur, mit der sich dieses Problem skalierbar lösen lässt. Wir betrachten
-
warum Benutzeroberflächen für KI-Assistenten grundlegend anders sind als statische Bedienfelder.
-
ein Muster mit doppeltem Verwendungszweck für Aktionsdefinitionen, das sowohl den Anforderungen des LLM als auch des UI gerecht wird.
-
ein typsicheres deklaratives Schema für dynamische Formulare.
-
die Laufzeitmechanismen von Lazy Loading, Replay und der Komposition mehrerer Aktionen.
-
Muster, die Sie in Ihren eigenen KI-gestützten Anwendungen einsetzen können.
Mit anderen Worten: Wenn sich nicht jede denkbare Benutzeroberfläche im Voraus erstellen lässt, entwickeln Sie ein System, das Benutzeroberflächen selbst erzeugt.
KI-Assistenten stellen die Grundannahmen statischer Benutzeroberflächen infrage
Bei einem Assistenten im Stil von Adobe Express kann das System Hunderte von Aktionen vorschlagen: „Deckkraft erhöhen“, „Hintergrund austauschen“, „Schlagschatten hinzufügen“, „diese Gruppe animieren“ oder „ein filmisches Intro-Preset anwenden“. Entscheidend ist dabei nicht nur die Anzahl der Aktionen, sondern auch, dass deren Kombination und Reihenfolge zur Entwurfszeit unbekannt sind. Der Assistent könnte beispielsweise Folgendes anzeigen:
-
einen einzelnen Slider für die Deckkraft
-
ein zusammengesetztes Formular mit Color Picker, Slider für die Rahmenstärke und einem Toggle Auf alle Seiten anwenden
-
einen Preset-Editor aus einem Remote-Service, der ein JSON-Schema zur Beschreibung der Steuerelemente zurückliefert
Nehmen wir an, ein LLM antwortet auf die Anweisung „Gib diesem Bild einen roten Rahmen und mache es leicht transparent“:
{
"actions": [
{ "action": "setBorderStyle", "parameters": { "color": "#ff0000", "size": 30 } },
{ "action": "setOpacity", "parameters": { "opacity": 0.8 } }
]
}
Der Assistent muss anschließend Folgendes anzeigen:
-
Einen Color Picker für die Rahmenfarbe
-
Einen Slider für die Rahmenstärke
-
Einen Slider für die Deckkraft
Diese Steuerelemente müssen gemeinsam in einem dynamisch zusammengesetzten UI erscheinen, das vollständig aus der Antwort des LLM abgeleitet wird. Gleichzeitig kann es mehr als hundert unterschiedliche Aktionen geben, von denen jede eigene Anforderungen an die Benutzeroberfläche stellt. Wenn jede neue Aktion ein individuell entwickeltes Bedienfeld, spezielle Bindings und eine separate Anbindung an das Document Model erfordert, ist die Plattform für KI-Assistenten praktisch nicht skalierbar. Das Ergebnis ist eine fehleranfällige UI-Schicht, die einschränkt, welche Vorschläge die KI überhaupt machen kann.
Eine generative Benutzeroberfläche muss genau diese Lücke schließen. Der Assistent erzeugt oder lädt Aktionsspezifikationen, die der Client deterministisch in eine sichere, konsistente und barrierefreie Benutzeroberfläche überführt.
Was „Generative UI“ wirklich bedeutet
Generative UI bedeutet nicht, dass Chatbots Text streamen. Gemeint sind Benutzeroberflächen, die zur Laufzeit entstehen, auf Grundlage dessen, wie die KI das Anliegen des Anwenders interpretiert. Die zentrale Erkenntnis lautet: Für jede Aktion sollte deklarativ festgelegt sein, welche Bedienelemente sie benötigt. Nicht: „Hier ist ein Bedienfeld mit einem Slider“, sondern: „Ich benötige einen Slider mit Minimum 0, Maximum 100 und aktuellem Wert 50.“ (Listing 1).
Listing 1
const SetOpacityAction = {
// For the LLM
actionId: "setOpacity",
description: "Changes transparency of selected elements",
parameters: { type: "number", min: 0, max: 1 },
// For the UI
uiSettings: {
componentType: "Slider",
generatePayload: (inputs) => ({
min: 0,
max: 100,
value: inputs.opacity * 100,
label: "Opacity",
}),
parsePayload: (payload) => ({
opacity: payload.value / 100,
}),
},
};
Damit kehrt sich die traditionelle Beziehung um. Statt eines UI, das Aktionen kennt, gibt es Aktionen, die ihre UI-Anforderungen beschreiben. Dieselbe Definition dient beiden Seiten als Grundlage:
-
Das LLM versteht, was die Aktion bewirkt und wie sie aufgerufen wird.
-
Die UI-Ebene kann daraus exakt ableiten, welches Steuerelement gerendert werden muss und wie der Zustand der Benutzeroberfläche den Aktionsparametern zugeordnet wird.
Die meisten KI-Integrationen behandeln diese Aspekte getrennt: Sie verwenden ein Schema für das LLM und separaten UI-Code für jede Funktion. Das funktioniert in kleineren Anwendungen, führt jedoch mit einer wachsenden Zahl von Aktionen zu erheblichem Wartungsaufwand. Ein deutlich besser skalierbarer Ansatz besteht in einer einzigen AssistantAction-Definition, die LLM und UI gleichermaßen dient (Listing 2).
Listing 2
interface AssistantAction {
// === LLM-facing properties ===
// The ID the LLM uses to invoke this action
actionId: string;
// Natural language description for the LLM (max 256 chars)
// Try to complete: "This action..."
description: string;
// Categories for organizational context
categories: string[];
// Parameter schema the LLM uses to construct invocations
parameters: ParameterSchema;
// Few-shot examples for the LLM
examples?: string[];
// === UI-facing properties ===
// What types of elements this applies to
applicableTo?: string[];
// Whether this action can be undone
isUndoable: boolean;
// The declarative UI specification
uiSettings?: {
componentType: ComponentType;
generatePayload: (inputs: ActionInputs) => ComponentPayload;
parsePayload: (payload: ComponentPayload) => ActionInputs;
};
}
Das LLM nutzt actionId, description, categories, parameters und examples, um zu verstehen, was die Aktion bewirkt und wie sie mit den richtigen Parametern aufgerufen wird. Die UI-Ebene nutzt uiSettings. Diese Eigenschaft legt fest, welche Steuerelemente gerendert werden und wie die Transformation zwischen Aktionsparametern und UI-Zustand erfolgt.
Für jede Aktion werden beide Perspektiven in derselben Datei definiert. Wird eine neue Aktion hinzugefügt, definieren Sie sowohl ihre LLM-Schnittstelle als auch ihre Benutzeroberfläche an einer zentralen Stelle.
Deklaratives Schema und bidirektionale Transformationen
Die Grundidee ist der Wechsel von ‚UI als Code‘ zu ‚UI als Daten‘. Dazu definieren wir ein deklaratives Framework für Formulare. Darin beschreibt ein parametrisierter Typ jedes Steuerelement einschließlich seiner Payload und der zugehörigen Callbacks. Zur Laufzeit entscheidet der Assistent oder ein anderer Service, welche Steuerelemente angezeigt werden. Dazu übermittelt die jeweilige Instanz lediglich deren Beschreibung.
Das Framework basiert auf bidirektionalen Transformationen zwischen Aktions- und UI-Zustand. Jede Aktion mit UI deklariert:
-
componentType – welche Art von Steuerelement verwendet wird (Slider, ColorPicker, Picker, Flex)
-
generatePayload – konvertiert Aktionsparameter → Component Props
-
parsePayload – konvertiert Komponentenstatus → Aktionsparameter
In Listing 3 sehen Sie eine BorderAction mit mehreren Steuerelementen. Die Aktion kann damit ein zusammengesetztes UI beschreiben: eine Flex-Spalte mit einem Slider und einem ColorPicker. Dieses UI wird automatisch aus den Eingabewerten der Aktion erzeugt.
Listing 3
const SetBorderAction = {
actionId: "setBorderStyle",
uiSettings: {
componentType: "Flex",
generatePayload: (inputs) => ({
direction: "column",
children: [
inputs.size !== undefined && {
key: "size",
componentType: "Slider",
label: "Thickness",
min: 0,
max: 150,
value: inputs.size,
},
inputs.color !== undefined && {
key: "color",
componentType: "ColorPicker",
label: "Color",
value: inputs.color,
},
].filter(Boolean),
}),
parsePayload: (payload) =>
payload.children.reduce((acc, child) => {
acc[child.key] = child.value;
return acc;
}, {} as Record<string, unknown>),
},
};
Praktische Fullstack-Architektur: von der API bis zur Benutzeroberfläche
Erlebe das neue Bootcamp vom 15. – 16. Oktober 2026
Typsichere Zuordnung von Komponenten
Bei mehr als hundert Aktionen möchten Sie, dass der Compiler Fehler erkennt – nicht Ihre Anwender. Sie können Component Payloads als Mapping modellieren (Listing 4). Definieren Sie anschließend einen Hilfstyp, der anhand von componentType automatisch den passenden Payload-Typ auswählt (Listing 5).
Listing 4
interface ComponentPayloadMap {
Slider: { value: number; min: number; max: number; label: string };
ColorPicker: { value: string; label?: string };
Picker: {
value: string;
label: string;
options: { value: string; label: string }[];
};
Flex: {
direction: "row" | "column";
children: ChildPayload[];
};
}
Listing 5
type UISettings<TInputs> = {
[K in keyof ComponentPayloadMap]: {
componentType: K;
generatePayload: (inputs: TInputs) => ComponentPayloadMap[K];
parsePayload: (payload: ComponentPayloadMap[K]) => Partial<TInputs>;
};
}[keyof ComponentPayloadMap];
Wenn Sie nun componentType: “Slider” schreiben, weiß TypeScript automatisch:
-
generatePayload muss { value: number; min: number; max: number; label: string } zurückgeben.
-
parsePayload erhält exakt dieselbe Struktur.
-
Falscher Payload-Typ? Compilerfehler.
Teams können das Mapping durch Declaration Merging um eigene Komponenten erweitern. (Listing 6). Ihr Framework für Generative UI bleibt dadurch offen für Erweiterungen und wahrt gleichzeitig die Typsicherheit sämtlicher Komponenten.
Listing 6
declare module "./types" {
interface ComponentPayloadMap {
ShadowControl: {
offsetX: number;
offsetY: number;
blur: number;
color: string;
};
}
}
Lazy Loading: Komponenten erst bei Bedarf laden
Es wäre ineffizient, hundert Komponenten vorab zu importieren, obwohl die KI nur einen Teil davon tatsächlich verwendet. Ein Registry-Muster lädt eine Komponente erst dann, wenn die KI tatsächlich eine Aktion vorschlägt, die eine bestimmte Komponente benötigt (Listing 7).
Listing 7
class ComponentRegistry {
private cache = new Map<string, any>();
private loaders = new Map<string, () => Promise<any>>();
register(type: string, loader: () => Promise<any>) {
this.loaders.set(type, loader);
}
async get(type: string) {
if (!this.cache.has(type)) {
const loader = this.loaders.get(type);
if (!loader) {
throw new Error(`No loader registered for component type "${type}"`);
}
const component = await loader();
this.cache.set(type, component);
}
return this.cache.get(type);
}
}
// Registration
registry.register("Slider", () => import("./Slider"));
registry.register("ColorPicker", () => import("./ColorPicker"));
Schlägt die KI erstmals vor, die Deckkraft zu ändern, wird die Slider-Komponente geladen. Alle weiteren Aufrufe verwenden den Cache. Komponenten, die für keine vorgeschlagene Aktion benötigt werden, werden überhaupt nicht geladen.
Dynamische UI-Komponenten aus Aktionsspezifikationen rendern
Listing 8 zeigt, wie die einzelnen Bausteine zusammenspielen und die KI-Ausgabe in interaktive Bedienelemente überführen.
Listing 8
async function renderActionUI(invocation: ActionInvocation, actionDef: AssistantAction) {
const { componentType, generatePayload, parsePayload } = actionDef.uiSettings!;
// Get component (lazy loads if needed)
const component = await registry.get(componentType);
// Generate initial payload from action inputs
const payload = generatePayload(invocation.inputs);
// Render with callbacks
return component.render(payload, {
onInput: (newPayload: ComponentPayload) => {
// Real-time feedback while dragging
replayAction(invocation, parsePayload(newPayload), { preview: true });
},
onChange: (newPayload: ComponentPayload) => {
// Commit when released
replayAction(invocation, parsePayload(newPayload), { commit: true });
},
});
}
Entscheidend ist dabei das Replay-Muster: Interaktionen mit der Benutzeroberfläche wirken sich nicht direkt auf den Dokumentzustand aus. Stattdessen führen sie die Aktion mit aktualisierten Eingabewerten erneut aus. Die Aktion bleibt damit die maßgebliche Instanz: Sie bestimmt sowohl die Änderungen am Dokument als auch deren Speicherung im Verlauf und im Rückgängig-Verlauf.
Mehrere Aktionen gemeinsam darstellen
Wenn die KI mehrere Aktionen gleichzeitig vorschlägt, sollten deren UI-Komponenten in einer gemeinsamen Oberfläche erscheinen. Durch die Deduplizierung werden doppelte Bedienelemente vermieden. Schlägt die KI beispielsweise zwei Änderungen der Deckkraft für dasselbe Element vor, wird Anwendern nur ein Slider statt zweier Slider angezeigt (Listing 9).
Listing 9
async function renderAllActionUIs(invocations: ActionInvocation[]) {
// Deduplicate: same action on same element = one UI
const deduped = deduplicateByActionAndTarget(invocations);
// Render each
const elements = await Promise.all(
deduped
.filter((inv) => inv.actionDef.uiSettings)
.map((inv) => renderActionUI(inv, inv.actionDef)),
);
// Compose into container
return elements;
}
Praxisbeispiele
Die Listings 10 bis 13 zeigen typische Anwendungsbeispiele.
Listing 10: Slider – Steuerung der Deckkraft
uiSettings: {
componentType: "Slider",
generatePayload: (inputs) => ({
min: 0,
max: 100,
step: 1,
value: (inputs.opacity ?? 1) * 100,
label: "Opacity",
}),
parsePayload: (p) => ({ opacity: p.value / 100 }),
}
Listing 11: ColorPicker – Rahmenfarbe
uiSettings: {
componentType: "ColorPicker",
generatePayload: (inputs) => ({
value: inputs.color ?? "#000000",
label: "Border color",
}),
parsePayload: (p) => ({ color: p.value }),
}
Listing 12: Picker für den Rahmenstil
uiSettings: {
componentType: "Picker",
generatePayload: (inputs) => ({
value: inputs.style ?? "solid",
label: "Style",
options: [
{ value: "solid", label: "Solid" },
{ value: "dashed", label: "Dashed" },
{ value: "dotted", label: "Dotted" },
],
}),
parsePayload: (p) => ({ style: p.value }),
}
Listing 13: Zusammengesetztes UI – mehrere Steuerelemente in einem Flex-Container
uiSettings: {
componentType: "Flex",
generatePayload: (inputs) => ({
direction: "column",
children: [
{
key: "size",
componentType: "Slider",
label: "Size",
min: 0,
max: 150,
value: inputs.size ?? 50,
},
{
key: "color",
componentType: "ColorPicker",
label: "Color",
value: inputs.color ?? "#ff0000",
},
{
key: "style",
componentType: "Picker",
label: "Style",
value: inputs.style ?? "solid",
options: [
{ value: "solid", label: "Solid" },
{ value: "dashed", label: "Dashed" },
{ value: "dotted", label: "Dotted" },
],
},
],
}),
parsePayload: (p) =>
Object.fromEntries(p.children.map((c: any) => [c.key, c.value])),
}
Von der LLM-Antwort zur interaktiven Benutzeroberfläche
Der Ablauf lässt sich wie folgt skizzieren:
-
Benutzer: „Füge einen dicken roten Rahmen hinzu.“
-
Das LLM liefert: { action: “setBorderStyle”, params: { color: “#ff0000”, size: 50 } }
-
Die Aktion wird ausgeführt, und der Rahmen erscheint auf der Arbeitsfläche.
-
Die generative UI-Ebene rendert anschließend die erforderlichen Bedienelemente: Slider und ColorPicker werden anhand der uiSettings automatisch gerendert.
-
Der Benutzer bewegt den Slider → parsePayload → die Aktion wird mit den aktualisierten Eingabewerten erneut ausgeführt → der Rahmen wird in Echtzeit aktualisiert.
-
Der Benutzer lässt den Slider los → die Änderung wird im Undo-Stack gespeichert.
Diese konkrete Zusammenstellung von Bedienelementen existierte zuvor nicht. Sie entstand erst, als die KI sie für die Anfrage des Benutzers auswählte. Das System erzeugte sie aus einer leichtgewichtigen Spezifikation, verknüpfte sie mit dem reaktiven Anwendungszustand und entfernte sie wieder, sobald sie nicht mehr benötigt wurde.
Bleib informiert
Brancheninsights im Newsletter erhalten:
Fazit
Der Übergang von statischen Bedienfeldern zu Generative UI ist mehr als eine rein technische Umstrukturierung. Er bedeutet ein grundsätzliches Umdenken darüber, wie und durch welche Instanz die Benutzeroberfläche festgelegt wird. In einer klassischen Anwendung legen Designer und Entwickler eine feste Schnittstelle fest. In einer KI-gestützten Anwendung bestimmt dagegen der Assistent, welche Oberfläche zur Laufzeit entsteht. Die UI-Schicht muss die von ihm erzeugten Aktionsspezifikationen zuverlässig verarbeiten.
Die beschriebene Architektur löst dieses Problem, indem sie eine langjährige Grundannahme umkehrt. Anstelle eines UI, das Aktionen kennt, gibt es Aktionen, die ihre UI-Anforderungen selbst beschreiben. Jede Aktion bringt eine Spezifikation mit. Sie legt fest, welches Steuerelement gerendert wird, wie es anhand der Aktionsparameter initialisiert wird und wie Benutzerinteraktionen zurück in Aktionsparameter übersetzt werden. Das LLM und die UI-Ebene erhalten aus derselben zentralen Definition jeweils genau die Informationen, die sie benötigen.
Das ist das richtige Denkmodell für skalierbare, konsequent auf KI ausgerichtete Benutzeroberflächen. Die Zukunft liegt nicht in einer Reduzierung der Benutzeroberfläche. Sie besteht aus Benutzeroberflächen, deren Zusammensetzung davon abhängt, wie die KI das Anliegen des Benutzers interpretiert. Wenn sich nicht jede denkbare Oberfläche im Voraus erstellen lässt, entwickeln Sie ein System, das Benutzeroberflächen erzeugt. Genau darin liegt der Kern deklarativer Benutzeroberflächen für KI-Assistenten.
Author
🔍 Frequently Asked Questions (FAQ)
1. Was ist Generative UI?
Generative UI bezeichnet Benutzeroberflächen, die zur Laufzeit entstehen, abhängig davon, wie eine KI die Anfrage eines Anwenders interpretiert. Statt alle möglichen Oberflächen vorab fest zu programmieren, werden benötigte UI-Komponenten aus Aktionsspezifikationen abgeleitet.
2. Wie funktioniert Generative UI?
Bei Generative UI beschreibt eine Aktion deklarativ, welche UI-Komponenten sie benötigt und wie deren Zustand mit den Aktionsparametern verbunden wird. Ein KI-Assistent kann dadurch passende Aktionen auswählen, aus deren Definition die Benutzeroberfläche anschließend dynamisch zusammengestellt wird.
3. Was sind dynamische Benutzeroberflächen?
Dynamische Benutzeroberflächen werden nicht vollständig im Voraus festgelegt, sondern abhängig von der jeweiligen Situation zur Laufzeit zusammengestellt. Bei KI-Assistenten können beispielsweise Slider, Color Picker oder mehrere kombinierte Steuerelemente genau dann angezeigt werden, wenn sie für eine ausgewählte Aktion benötigt werden.
4. Was sind Aktionsdefinitionen bei Generative UI?
Aktionsdefinitionen beschreiben sowohl die Funktion einer Aktion für das LLM als auch die benötigte Benutzeroberfläche. Sie enthalten unter anderem eine Action-ID, eine Beschreibung, Parameter und optionale Beispiele für das LLM sowie UI-Einstellungen für die Darstellung der Aktion.
5. Was bedeutet deklarative UI bei Generative UI?
Deklarative UI bedeutet, dass eine Benutzeroberfläche als Beschreibung von Daten und benötigten Komponenten definiert wird, anstatt ihre konkrete Darstellung vollständig als festen UI-Code vorzugeben. Bei Generative UI können Aktionsdefinitionen beispielsweise festlegen, dass ein Slider oder Color Picker benötigt wird und wie dessen Werte verarbeitet werden.
6. Warum ist Lazy Loading bei dynamischen UI-Komponenten sinnvoll?
Lazy Loading verhindert, dass alle möglichen UI-Komponenten bereits beim Start der Anwendung geladen werden. Eine Komponente wird erst geladen, wenn eine KI-Aktion sie tatsächlich benötigt. Bereits geladene Komponenten können anschließend aus einem Cache wiederverwendet werden.
7. Wie werden KI-generierte Aktionen mit der Benutzeroberfläche verbunden?
Das LLM liefert eine Aktion mit den entsprechenden Parametern. Die Generative-UI-Ebene verwendet anschließend die UI-Spezifikation dieser Aktion, um die benötigten Komponenten zu rendern. Änderungen an der Oberfläche werden über parsePayload wieder in Aktionsparameter übersetzt und die Aktion kann mit den neuen Werten erneut ausgeführt werden.




