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
- Status: Draft
- Author(s): Markus Ecker (mail@mme.xyz)
Challenges and Limitations
Tool Description Length
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
flowchart TD
A[Agent needs UI] --> B["Step 1: <b>What?</b> <br/> Agent calls generateUserInterface <br/>(description, data, output)"]
B --> C["Step 2: <b>How?</b> <br/> Secondary generator builds actual UI <br/>(JSON Schema, React, etc.)"]
C --> D[Rendered UI shown to user]
D --> E[Validated user input returned to Agent]
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, andoutputto 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"
}
}
Example Output: ReactFormHookGenerator
// ----- 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
generateUserInterfacetool 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
Dynamic Forms
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