Fixed Schema A2UI

Pre-defined A2UI schema with dynamic data. The fastest approach — no LLM schema generation needed.


In the fixed-schema approach, you design the UI schema once (in a JSON file or using the A2UI Composer) and your agent tool only provides the data. The surface appears instantly when the tool returns.

How it works#

  1. Schema is loaded from a JSON file at startup
  2. Agent tool receives data from the LLM (e.g., flight search results)
  3. Tool returns a2ui.render() with createSurface + updateComponents + updateDataModel
  4. The A2UI middleware intercepts the tool result and renders the surface

Implementation#

Create the A2UI schema#

Design your schema using the A2UI Composer or write it by hand. Save it as a JSON file:

apps/agent/src/a2ui/schemas/flight_schema.json

Define the agent tool (Python)#

apps/agent/src/a2ui_fixed_schema.py
from copilotkit import a2ui
from langchain.tools import tool
from pathlib import Path
from typing import TypedDict

class Flight(TypedDict):
    id: str
    airline: str
    airlineLogo: str
    flightNumber: str
    origin: str
    destination: str
    date: str
    departureTime: str
    arrivalTime: str
    duration: str
    status: str
    statusIcon: str
    price: str

SURFACE_ID = "flight-search-results"
FLIGHT_SCHEMA = a2ui.load_schema(
    Path(__file__).parent / "a2ui" / "schemas" / "flight_schema.json"
)


@tool
def search_flights(flights: list[Flight]) -> str:
    """Search for flights and display results as rich cards."""
    return a2ui.render(
        operations=[
            a2ui.create_surface(SURFACE_ID),
            a2ui.update_components(SURFACE_ID, FLIGHT_SCHEMA),
            a2ui.update_data_model(SURFACE_ID, {"flights": flights}),
        ],
    )

Key points:

  • The Flight TypedDict is essential — LangChain serializes it into the tool's JSON schema, which is what the LLM sees when deciding what data to generate.
  • The Python SDK's a2ui.render does not yet support the action_handlers= keyword, so the example keeps the button schema but does not declare server-side handlers. Button clicks are forwarded to the agent, but this example has no server-side handler for them.
  • "book_flight" is the action name used by the schema button and can be handled with the frontend APIs in the Advanced — Action Handlers guide.

Register the tool#

apps/agent/main.py
from deepagents import create_deep_agent
from src.a2ui_fixed_schema import search_flights

agent = create_deep_agent(
    tools=[search_flights, ...],
    ...
)

Configure the runtime (TypeScript)#

Enable A2UI in your CopilotRuntime:

app/api/copilotkit/route.ts
const runtime = new CopilotRuntime({
  agents: { default: myAgent },
  a2ui: {
    injectA2UITool: true,
  },
});

Action handler details#

The current Python SDK does not support the action_handlers= option. The button schema can still define the action context used by frontend handlers. Here's how the schema side looks:

Button with action context#

In your flight_schema.json, buttons declare an action with data-bound context fields. When clicked, the values are resolved from that specific card's data:

{
  "id": "book-button",
  "component": "Button",
  "child": "book-label",
  "variant": "primary",
  "action": {
    "event": {
      "name": "book_flight",
      "context": {
        "flightNumber": { "path": "flightNumber" },
        "price": { "path": "price" }
      }
    }
  }
}

When this button is clicked on a card showing flight AA100 at $350, frontend action handling receives context: { flightNumber: "AA100", price: "$350" }. The Python action_handlers= path is not yet supported.

For custom frontend handling with createA2UIMessageRenderer and its onAction option, see the Advanced — Action Handlers guide.