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 an a2ui_operations container 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)#

src/agents/a2ui_fixed.py
import json
from pathlib import Path
from typing import Annotated

from agent_framework import tool
from pydantic import Field

CATALOG_ID = "copilotkit://flight-fixed-catalog"
SURFACE_ID = "flight-fixed-schema"

FLIGHT_SCHEMA = json.load(
    open(Path(__file__).parent / "a2ui_schemas" / "flight_schema.json")
)


@tool(name="display_flight", description="Show a flight card for the given trip.")
def display_flight(
    origin: Annotated[str, Field(description="3-letter origin code, e.g. 'SFO'.")],
    destination: Annotated[str, Field(description="3-letter destination code.")],
    airline: Annotated[str, Field(description="Airline name.")],
    price: Annotated[str, Field(description="Price string, e.g. '$289'.")],
) -> str:
    """Show a flight card for the given trip."""
    ops = [
        {"version": "v0.9", "createSurface": {"surfaceId": SURFACE_ID, "catalogId": CATALOG_ID}},
        {"version": "v0.9", "updateComponents": {"surfaceId": SURFACE_ID, "components": FLIGHT_SCHEMA}},
        {"version": "v0.9", "updateDataModel": {"surfaceId": SURFACE_ID, "path": "/", "value": {"origin": origin, "destination": destination, "airline": airline, "price": price}}},
    ]
    return json.dumps({"a2ui_operations": ops})

Key points:

  • The Annotated + Field parameters are essential — Microsoft Agent Framework serializes them into the tool's JSON schema, which is what the LLM sees when deciding what data to generate.
  • The tool builds the a2ui_operations container by hand and does not declare server-side action 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#

src/agents/a2ui_fixed.py
from agent_framework import Agent
from agent_framework_ag_ui import AgentFrameworkAgent

base_agent = Agent(
    client=chat_client,
    name="a2ui_fixed_agent",
    instructions="You help users find flights. Call `display_flight` with origin, destination, airline, and price.",
    tools=[display_flight],
)

agent = AgentFrameworkAgent(agent=base_agent)

Configure the runtime (TypeScript)#

Enable A2UI in your CopilotRuntime. The middleware auto-detects A2UI operations in any tool result, so no tool injection is needed here — the agent's display_flight tool returns them directly.

app/api/copilotkit-a2ui-fixed-schema/route.ts
const runtime = new CopilotRuntime({
  agents: { "a2ui-fixed-schema": fixedSchemaAgent },
  a2ui: {
    injectA2UITool: false,
  },
});

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.