Agent User Interaction (AG-UI) Protocol
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.
Note
The AG-UI integration was originally built by the team at Rocket Science and contributed in collaboration with the Pydantic AI and CopilotKit teams. Thanks Rocket Science!
Installation
The only dependencies are:
- ag-ui-protocol: to provide the AG-UI types and encoder.
- starlette: to handle ASGI requests from a framework like FastAPI.
You can install Pydantic AI with the ag-ui
extra to ensure you have all the
required AG-UI dependencies:
pip install 'pydantic-ai-slim[ag-ui]'
uv add 'pydantic-ai-slim[ag-ui]'
To run the examples you'll also need:
- uvicorn or another ASGI compatible server
pip install uvicorn
uv add 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.
run_ag_ui()
takes an agent and an AG-UIRunAgentInput
object, and returns a stream of AG-UI events encoded as strings. It also takes optionalAgent.iter()
arguments includingdeps
. 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.handle_ag_ui_request()
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 optionalAgent.iter()
arguments includingdeps
, that you can vary for each request (e.g. based on the authenticated user).Agent.to_ag_ui()
returns an ASGI application that handles every AG-UI request by running the agent. It also takes optionalAgent.iter()
arguments includingdeps
, 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 run_ag_ui()
and performs its own request parsing and response generation.
This can be modified to work with any web framework.
from ag_ui.core import RunAgentInput
from fastapi import FastAPI
from http import HTTPStatus
from fastapi.requests import Request
from fastapi.responses import Response, StreamingResponse
from pydantic import ValidationError
import json
from pydantic_ai import Agent
from pydantic_ai.ag_ui import run_ag_ui, SSE_CONTENT_TYPE
agent = Agent('openai:gpt-4.1', 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 = RunAgentInput.model_validate(await request.json())
except ValidationError as e: # pragma: no cover
return Response(
content=json.dumps(e.json()),
media_type='application/json',
status_code=HTTPStatus.UNPROCESSABLE_ENTITY,
)
event_stream = run_ag_ui(agent, run_input, accept=accept)
return StreamingResponse(event_stream, media_type=accept)
Since app
is an ASGI application, it can be used with any ASGI server:
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 handle_ag_ui_request()
to directly handle a FastAPI request and return a response. Something analogous to this will work with any Starlette-based web framework.
from fastapi import FastAPI
from starlette.requests import Request
from starlette.responses import Response
from pydantic_ai import Agent
from pydantic_ai.ag_ui import handle_ag_ui_request
agent = Agent('openai:gpt-4.1', instructions='Be fun!')
app = FastAPI()
@app.post("/")
async def run_agent(request: Request) -> Response:
return await handle_ag_ui_request(agent, request)
Since app
is an ASGI application, it can be used with any ASGI server:
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 Agent.to_ag_ui()
to turn the agent into a stand-alone ASGI application:
from pydantic_ai import Agent
agent = Agent('openai:gpt-4.1', instructions='Be fun!')
app = agent.to_ag_ui()
Since app
is an ASGI application, it can be used with any ASGI server:
uvicorn agent_to_ag_ui: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.
Custom dependencies type with AG-UI state
If you want to use your own dependencies type to hold AG-UI state as well as other things, it needs to implements the
StateHandler
protocol, meaning it needs to be a dataclass with a non-optional state
field. This lets Pydantic AI ensure that state is properly isolated between requests by building a new dependencies object each time.
If the state
field's type is a Pydantic BaseModel
subclass, the raw state dictionary on the request is automatically validated. If not, you can validate the raw value yourself in your dependencies dataclass's __post_init__
method.
from pydantic import BaseModel
from pydantic_ai import Agent
from pydantic_ai.ag_ui import StateDeps
class DocumentState(BaseModel):
"""State for the document being written."""
document: str = ''
agent = Agent(
'openai:gpt-4.1',
instructions='Be fun!',
deps_type=StateDeps[DocumentState],
)
app = agent.to_ag_ui(deps=StateDeps(DocumentState()))
Since app
is an ASGI application, it can be used with any ASGI server:
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 defining a tool
which returns a (subclass of)
BaseEvent
, which allows
for custom events and state updates.
from ag_ui.core import CustomEvent, EventType, StateSnapshotEvent
from pydantic import BaseModel
from pydantic_ai import Agent, RunContext
from pydantic_ai.ag_ui import StateDeps
class DocumentState(BaseModel):
"""State for the document being written."""
document: str = ''
agent = Agent(
'openai:gpt-4.1',
instructions='Be fun!',
deps_type=StateDeps[DocumentState],
)
app = agent.to_ag_ui(deps=StateDeps(DocumentState()))
@agent.tool
async def update_state(ctx: RunContext[StateDeps[DocumentState]]) -> StateSnapshotEvent:
return StateSnapshotEvent(
type=EventType.STATE_SNAPSHOT,
snapshot=ctx.deps.state,
)
@agent.tool_plain
async def custom_events() -> list[CustomEvent]:
return [
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:
uvicorn ag_ui_tool_events:app --host 0.0.0.0 --port 9000
Examples
For more examples of how to use to_ag_ui()
see
pydantic_ai_examples.ag_ui
,
which includes a server for use with the
AG-UI Dojo.