Skip to content

AG-UI

The Agent-User Interaction (AG-UI) Protocol is an open standard introduced by the CopilotKit team that standardises how frontend applications communicate with AI agents, with support for streaming, frontend tools, shared state, and custom events.

Installation

The only dependencies are:

You can install Pydantic AI with the ag-ui extra to ensure you have all the required AG-UI dependencies:

Terminal
pip install 'pydantic-ai-slim[ag-ui]'

To run the examples you’ll also need:

  • uvicorn or another ASGI compatible server
Terminal
pip install uvicorn

Usage

There are three ways to run a Pydantic AI agent based on AG-UI run input with streamed AG-UI events as output, from most to least flexible. If you’re using a Starlette-based web framework like FastAPI, you’ll typically want to use the second method.

  1. The AGUIAdapter.run_stream() method, when called on an AGUIAdapter instantiated with an agent and an AG-UI RunAgentInput object, will run the agent and return a stream of AG-UI events. It also takes optional Agent.iter() arguments including deps. Use this if you’re using a web framework not based on Starlette (e.g. Django or Flask) or want to modify the input or output some way.
  2. The AGUIAdapter.dispatch_request() class method takes an agent and a Starlette request (e.g. from FastAPI) coming from an AG-UI frontend, and returns a streaming Starlette response of AG-UI events that you can return directly from your endpoint. It also takes optional Agent.iter() arguments including deps, that you can vary for each request (e.g. based on the authenticated user). This is a convenience method that combines AGUIAdapter.from_request(), AGUIAdapter.run_stream(), and AGUIAdapter.streaming_response().
  3. AGUIApp represents an ASGI application that handles every AG-UI request by running the agent. It also takes optional Agent.iter() arguments including deps, but these will be the same for each request, with the exception of the AG-UI state that’s injected as described under state management. This ASGI app can be mounted at a given path in an existing FastAPI app.

Handle run input and output directly

This example uses AGUIAdapter.run_stream() and performs its own request parsing and response generation. This can be modified to work with any web framework.

run_ag_ui.py
import json
from http import HTTPStatus

from fastapi import FastAPI
from fastapi.requests import Request
from fastapi.responses import Response, StreamingResponse
from pydantic import ValidationError

from pydantic_ai import Agent
from pydantic_ai.ui import SSE_CONTENT_TYPE
from pydantic_ai.ui.ag_ui import AGUIAdapter

agent = Agent('openai:gpt-5.2', instructions='Be fun!')

app = FastAPI()


@app.post('/')
async def run_agent(request: Request) -> Response:
    accept = request.headers.get('accept', SSE_CONTENT_TYPE)
    try:
        run_input = AGUIAdapter.build_run_input(await request.body())  # (1)
    except ValidationError as e:
        return Response(
            content=json.dumps(e.json()),
            media_type='application/json',
            status_code=HTTPStatus.UNPROCESSABLE_ENTITY,
        )

    adapter = AGUIAdapter(agent=agent, run_input=run_input, accept=accept)
    event_stream = adapter.run_stream() # (2)

    sse_event_stream = adapter.encode_stream(event_stream)
    return StreamingResponse(sse_event_stream, media_type=accept) # (3)
  1. AGUIAdapter.build_run_input() takes the request body as bytes and returns an AG-UI RunAgentInput object. You can also use the AGUIAdapter.from_request() class method to build an adapter directly from a request.
  2. AGUIAdapter.run_stream() runs the agent and returns a stream of AG-UI events. It supports the same optional arguments as Agent.run_stream_events(), including deps. You can also use AGUIAdapter.run_stream_native() to run the agent and return a stream of Pydantic AI events instead, which can then be transformed into AG-UI events using AGUIAdapter.transform_stream().
  3. AGUIAdapter.encode_stream() encodes the stream of AG-UI events as strings according to the accept header value. You can also use AGUIAdapter.streaming_response() to generate a streaming response directly from the AG-UI event stream returned by run_stream().

Since app is an ASGI application, it can be used with any ASGI server:

Terminal
uvicorn run_ag_ui:app

This will expose the agent as an AG-UI server, and your frontend can start sending requests to it.

Handle a Starlette request

This example uses AGUIAdapter.dispatch_request() to directly handle a FastAPI request and return a response. Something analogous to this will work with any Starlette-based web framework.

handle_ag_ui_request.py
from fastapi import FastAPI
from starlette.requests import Request
from starlette.responses import Response

from pydantic_ai import Agent
from pydantic_ai.ui.ag_ui import AGUIAdapter

agent = Agent('openai:gpt-5.2', instructions='Be fun!')

app = FastAPI()

@app.post('/')
async def run_agent(request: Request) -> Response:
    return await AGUIAdapter.dispatch_request(request, agent=agent) # (1)
  1. This method essentially does the same as the previous example, but it’s more convenient to use when you’re already using a Starlette/FastAPI app.

Since app is an ASGI application, it can be used with any ASGI server:

Terminal
uvicorn handle_ag_ui_request:app

This will expose the agent as an AG-UI server, and your frontend can start sending requests to it.

Stand-alone ASGI app

This example uses AGUIApp to turn the agent into a stand-alone ASGI application:

ag_ui_app.py
from pydantic_ai import Agent
from pydantic_ai.ui.ag_ui.app import AGUIApp

agent = Agent('openai:gpt-5.2', instructions='Be fun!')
app = AGUIApp(agent)

Since app is an ASGI application, it can be used with any ASGI server:

Terminal
uvicorn ag_ui_app:app

This will expose the agent as an AG-UI server, and your frontend can start sending requests to it.

Design

The Pydantic AI AG-UI integration supports all features of the spec:

The integration receives messages in the form of a RunAgentInput object that describes the details of the requested agent run including message history, state, and available tools.

These are converted to Pydantic AI types and passed to the agent’s run method. Events from the agent, including tool calls, are converted to AG-UI events and streamed back to the caller as Server-Sent Events (SSE).

A user request may require multiple round trips between client UI and Pydantic AI server, depending on the tools and events needed.

Features

State management

The integration provides full support for AG-UI state management, which enables real-time synchronization between agents and frontend applications.

In the example below we have document state which is shared between the UI and server using the StateDeps dependencies type that can be used to automatically validate state contained in RunAgentInput.state using a Pydantic BaseModel specified as a generic parameter.

ag_ui_state.py
from pydantic import BaseModel

from pydantic_ai import Agent
from pydantic_ai.ui import StateDeps
from pydantic_ai.ui.ag_ui.app import AGUIApp


class DocumentState(BaseModel):
    """State for the document being written."""

    document: str = ''


agent = Agent(
    'openai:gpt-5.2',
    instructions='Be fun!',
    deps_type=StateDeps[DocumentState],
)
app = AGUIApp(agent, deps=StateDeps(DocumentState()))

Since app is an ASGI application, it can be used with any ASGI server:

Terminal
uvicorn ag_ui_state:app --host 0.0.0.0 --port 9000

Tools

AG-UI frontend tools are seamlessly provided to the Pydantic AI agent, enabling rich user experiences with frontend user interfaces.

Events

Pydantic AI tools can send AG-UI events simply by returning a ToolReturn object with a BaseEvent (or a list of events) as metadata, which allows for custom events and state updates.

ag_ui_tool_events.py
from ag_ui.core import CustomEvent, EventType, StateSnapshotEvent
from pydantic import BaseModel

from pydantic_ai import Agent, RunContext, ToolReturn
from pydantic_ai.ui import StateDeps
from pydantic_ai.ui.ag_ui.app import AGUIApp


class DocumentState(BaseModel):
    """State for the document being written."""

    document: str = ''


agent = Agent(
    'openai:gpt-5.2',
    instructions='Be fun!',
    deps_type=StateDeps[DocumentState],
)
app = AGUIApp(agent, deps=StateDeps(DocumentState()))


@agent.tool
async def update_state(ctx: RunContext[StateDeps[DocumentState]]) -> ToolReturn:
    return ToolReturn(
        return_value='State updated',
        metadata=[
            StateSnapshotEvent(
                type=EventType.STATE_SNAPSHOT,
                snapshot=ctx.deps.state,
            ),
        ],
    )


@agent.tool_plain
async def custom_events() -> ToolReturn:
    return ToolReturn(
        return_value='Count events sent',
        metadata=[
            CustomEvent(
                type=EventType.CUSTOM,
                name='count',
                value=1,
            ),
            CustomEvent(
                type=EventType.CUSTOM,
                name='count',
                value=2,
            ),
        ]
    )

Since app is an ASGI application, it can be used with any ASGI server:

Terminal
uvicorn ag_ui_tool_events:app --host 0.0.0.0 --port 9000

Examples

For more examples of how to use AGUIApp see pydantic_ai_examples.ag_ui, which includes a server for use with the AG-UI Dojo.