Documentation Index
Fetch the complete documentation index at: https://docs.ag-ui.com/llms.txt
Use this file to discover all available pages before exploring further.
Generative User Interfaces
Summary
Problem Statement
Currently, creating custom user interfaces for agent interactions requires
programmers to define specific tool renderers. This limits the flexibility and
adaptability of agent-driven applications.
Motivation
This draft describes an AG-UI extension that addresses generative user
interfaces—interfaces produced directly by artificial intelligence without
requiring a programmer to define custom tool renderers. The key idea is to
leverage our ability to send client-side tools to the agent, thereby enabling
this capability across all agent frameworks supported by AG-UI.
Status
Challenges and Limitations
OpenAI enforces a limit of 1024 characters for tool descriptions. Gemini and
Anthropic impose no such limit.
Arguments JSON Schema Constraints
Classes, nesting, $ref, and oneOf are not reliably supported across LLM
providers.
Context Window Considerations
Injecting a large UI description language into an agent may reduce its
performance. Agents dedicated solely to UI generation perform better than agents
combining UI generation with other tasks.
Detailed Specification
Two-Step Generation Process
Step 1: What to Generate?
Inject a lightweight tool into the agent:
Tool Definition:
- Name:
generateUserInterface
- Arguments:
- description: A high-level description of the UI (e.g., “A form for
entering the user’s address”)
- data: Arbitrary pre-populated data for the generated UI
- output: A description or schema of the data the agent expects the user
to submit back (fields, required/optional, types, constraints)
Example Tool Call:
{
"tool": "generateUserInterface",
"arguments": {
"description": "A form that collects a user's shipping address.",
"data": {
"firstName": "Ada",
"lastName": "Lovelace",
"city": "London"
},
"output": {
"type": "object",
"required": [
"firstName",
"lastName",
"street",
"city",
"postalCode",
"country"
],
"properties": {
"firstName": { "type": "string", "title": "First Name" },
"lastName": { "type": "string", "title": "Last Name" },
"street": { "type": "string", "title": "Street Address" },
"city": { "type": "string", "title": "City" },
"postalCode": { "type": "string", "title": "Postal Code" },
"country": {
"type": "string",
"title": "Country",
"enum": ["GB", "US", "DE", "AT"]
}
}
}
}
}
Step 2: How to Generate?
Delegate UI generation to a secondary LLM or agent:
- The CopilotKit user stays in control: Can make their own generators, add
custom libraries, include additional prompts etc.
- On tool invocation, the secondary model consumes
description, data, and
output to generate the user interface
- This model is focused solely on UI generation, ensuring maximum fidelity and
consistency
- The generation method can be swapped as needed (e.g., JSON, HTML, or other
renderable formats)
- The UI format description is not subject to structural or length constraints,
allowing arbitrarily complex specifications
Implementation Examples
Example Output: UISchemaGenerator
{
"jsonSchema": {
"title": "Shipping Address",
"type": "object",
"required": [
"firstName",
"lastName",
"street",
"city",
"postalCode",
"country"
],
"properties": {
"firstName": { "type": "string", "title": "First name" },
"lastName": { "type": "string", "title": "Last name" },
"street": { "type": "string", "title": "Street address" },
"city": { "type": "string", "title": "City" },
"postalCode": { "type": "string", "title": "Postal code" },
"country": {
"type": "string",
"title": "Country",
"enum": ["GB", "US", "DE", "AT"]
}
}
},
"uiSchema": {
"type": "VerticalLayout",
"elements": [
{
"type": "Group",
"label": "Personal Information",
"elements": [
{ "type": "Control", "scope": "#/properties/firstName" },
{ "type": "Control", "scope": "#/properties/lastName" }
]
},
{
"type": "Group",
"label": "Address",
"elements": [
{ "type": "Control", "scope": "#/properties/street" },
{ "type": "Control", "scope": "#/properties/city" },
{ "type": "Control", "scope": "#/properties/postalCode" },
{ "type": "Control", "scope": "#/properties/country" }
]
}
]
},
"initialData": {
"firstName": "Ada",
"lastName": "Lovelace",
"city": "London",
"country": "GB"
}
}
import React from "react"
import { useForm } from "react-hook-form"
import { z } from "zod"
import { zodResolver } from "@hookform/resolvers/zod"
// ----- Schema (contract) -----
const AddressSchema = z.object({
firstName: z.string().min(1, "Required"),
lastName: z.string().min(1, "Required"),
street: z.string().min(1, "Required"),
city: z.string().min(1, "Required"),
postalCode: z.string().regex(/^[A-Za-z0-9\\-\\s]{3,10}$/, "3–10 chars"),
country: z.enum(["GB", "US", "DE", "AT", "FR", "IT", "ES"]),
})
export type Address = z.infer<typeof AddressSchema>
type Props = {
initialData?: Partial<Address>
meta?: { title?: string; submitLabel?: string }
respond: (data: Address) => void // <-- called on successful submit
}
const COUNTRIES: Address["country"][] = [
"GB",
"US",
"DE",
"AT",
"FR",
"IT",
"ES",
]
export default function AddressForm({ initialData, meta, respond }: Props) {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<Address>({
resolver: zodResolver(AddressSchema),
defaultValues: {
firstName: "",
lastName: "",
street: "",
city: "",
postalCode: "",
country: "GB",
...initialData,
},
})
const onSubmit = (data: Address) => {
// Guaranteed to match AddressSchema
respond(data)
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
{meta?.title && <h2>{meta.title}</h2>}
{/* Section: Personal Information */}
<fieldset>
<legend>Personal Information</legend>
<div>
<label>First name</label>
<input {...register("firstName")} placeholder="Ada" autoFocus />
{errors.firstName && <small>{errors.firstName.message}</small>}
</div>
<div>
<label>Last name</label>
<input {...register("lastName")} placeholder="Lovelace" />
{errors.lastName && <small>{errors.lastName.message}</small>}
</div>
</fieldset>
{/* Section: Address */}
<fieldset>
<legend>Address</legend>
<div>
<label>Street address</label>
<input {...register("street")} />
{errors.street && <small>{errors.street.message}</small>}
</div>
<div>
<label>City</label>
<input {...register("city")} />
{errors.city && <small>{errors.city.message}</small>}
</div>
<div>
<label>Postal code</label>
<input {...register("postalCode")} />
{errors.postalCode && <small>{errors.postalCode.message}</small>}
</div>
<div>
<label>Country</label>
<select {...register("country")}>
{COUNTRIES.map((c) => (
<option key={c} value={c}>
{c}
</option>
))}
</select>
{errors.country && <small>{errors.country.message}</small>}
</div>
</fieldset>
<div>
<button type="submit">{meta?.submitLabel ?? "Submit"}</button>
</div>
</form>
)
}
Implementation Considerations
Client SDK Changes
TypeScript SDK additions:
- New
generateUserInterface tool type
- UI generator registry for pluggable generators
- Validation layer for generated UI schemas
- Response handler for user-submitted data
Python SDK additions:
- Support for UI generation tool invocation
- Schema validation utilities
- Serialization for UI definitions
Integration Impact
- All AG-UI integrations can leverage this capability without modification
- Frameworks emit standard tool calls; client handles UI generation
- Backward compatible with existing tool-based UI approaches
Use Cases
Agents can generate forms on-the-fly based on conversation context without
pre-defined schemas.
Data Visualization
Generate charts, graphs, or tables appropriate to the data being discussed.
Interactive Workflows
Create multi-step wizards or guided processes tailored to user needs.
Adaptive Interfaces
Generate different UI layouts based on user preferences or device capabilities.
Testing Strategy
- Unit tests for tool injection and invocation
- Integration tests with multiple UI generators
- E2E tests demonstrating various UI types
- Performance benchmarks comparing single vs. two-step generation
- Cross-provider compatibility testing
References