Skip to content

Commit 3d628b8

Browse files
committed
AG-UI docs
1 parent d834583 commit 3d628b8

File tree

18 files changed

+106
-100
lines changed

18 files changed

+106
-100
lines changed

docs/ag-ui.md

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -33,27 +33,26 @@ pip/uv-add uvicorn
3333

3434
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.
3535

36-
1. [`run_ag_ui()`][pydantic_ai.ag_ui.run_ag_ui] takes an agent and an AG-UI [`RunAgentInput`](https://docs.ag-ui.com/sdk/python/core/types#runagentinput) object, and returns a stream of AG-UI events encoded as strings. It also takes optional [`Agent.iter()`][pydantic_ai.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.
37-
2. [`handle_ag_ui_request()`][pydantic_ai.ag_ui.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 optional [`Agent.iter()`][pydantic_ai.Agent.iter] arguments including `deps`, that you can vary for each request (e.g. based on the authenticated user).
38-
3. [`Agent.to_ag_ui()`][pydantic_ai.agent.AbstractAgent.to_ag_ui] returns an ASGI application that handles every AG-UI request by running the agent. It also takes optional [`Agent.iter()`][pydantic_ai.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](#state-management). This ASGI app can be [mounted](https://fastapi.tiangolo.com/advanced/sub-applications/) at a given path in an existing FastAPI app.
36+
1. The [`AGUIAdapter.run_stream()`][pydantic_ai.ui.ag_ui.AGUIAdapter.run_stream] method, when called on an [`AGUIAdapter`][pydantic_ai.ui.ag_ui.AGUIAdapter] instantiated with an agent and an AG-UI [`RunAgentInput`](https://docs.ag-ui.com/sdk/python/core/types#runagentinput) object, will run the agent and return a stream of AG-UI events. It also takes optional [`Agent.iter()`][pydantic_ai.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.
37+
2. The [`AGUIAdapter.dispatch_request()`][pydantic_ai.ui.ag_ui.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()`][pydantic_ai.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()`][pydantic_ai.ui.ag_ui.AGUIAdapter.from_request], [`AGUIAdapter.run_stream()`][pydantic_ai.ui.ag_ui.AGUIAdapter.run_stream], and [`AGUIAdapter.streaming_response()`][pydantic_ai.ui.ag_ui.AGUIAdapter.streaming_response].
38+
3. [`AGUIApp`][pydantic_ai.ui.ag_ui.app.AGUIApp] represents an ASGI application that handles every AG-UI request by running the agent. It also takes optional [`Agent.iter()`][pydantic_ai.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](#state-management). This ASGI app can be [mounted](https://fastapi.tiangolo.com/advanced/sub-applications/) at a given path in an existing FastAPI app.
3939

4040
### Handle run input and output directly
4141

42-
This example uses [`run_ag_ui()`][pydantic_ai.ag_ui.run_ag_ui] and performs its own request parsing and response generation.
42+
This example uses [`AGUIAdapter.run_stream()`][pydantic_ai.ui.ag_ui.AGUIAdapter.run_stream] and performs its own request parsing and response generation.
4343
This can be modified to work with any web framework.
4444

4545
```py {title="run_ag_ui.py"}
4646
import json
4747
from http import HTTPStatus
4848

49-
from ag_ui.core import RunAgentInput
5049
from fastapi import FastAPI
5150
from fastapi.requests import Request
5251
from fastapi.responses import Response, StreamingResponse
5352
from pydantic import ValidationError
5453

5554
from pydantic_ai import Agent
56-
from pydantic_ai.ag_ui import SSE_CONTENT_TYPE, run_ag_ui
55+
from pydantic_ai.ui.ag_ui import AGUIAdapter, SSE_CONTENT_TYPE
5756

5857
agent = Agent('openai:gpt-4.1', instructions='Be fun!')
5958

@@ -64,19 +63,24 @@ app = FastAPI()
6463
async def run_agent(request: Request) -> Response:
6564
accept = request.headers.get('accept', SSE_CONTENT_TYPE)
6665
try:
67-
run_input = RunAgentInput.model_validate(await request.json())
66+
run_input = AGUIAdapter.build_run_input(await request.json()) # (1)
6867
except ValidationError as e: # pragma: no cover
6968
return Response(
7069
content=json.dumps(e.json()),
7170
media_type='application/json',
7271
status_code=HTTPStatus.UNPROCESSABLE_ENTITY,
7372
)
7473

75-
event_stream = run_ag_ui(agent, run_input, accept=accept)
74+
adapter = AGUIAdapter(agent=agent, run_input=run_input, accept=accept)
75+
events = adapter.run_stream() # (2)
7676

77-
return StreamingResponse(event_stream, media_type=accept)
77+
return StreamingResponse(adapter.encode_stream(events), media_type=accept) # (3)
7878
```
7979

80+
1. You can also use the [`AGUIAdapter.from_request()`][pydantic_ai.ui.ag_ui.AGUIAdapter.from_request] class method to build an adapter directly from a request.
81+
2. You can also use the [`AGUIAdapter.run_stream_native()`][pydantic_ai.ui.ag_ui.AGUIAdapter.run_stream_native] method to run the agent and return a stream of Pydantic AI events instead of AG-UI events. These can then be transformed into AG-UI events using the [`AGUIAdapter.transform_stream()`][pydantic_ai.ui.ag_ui.AGUIAdapter.transform_stream] method.
82+
3. The [`AGUIAdapter.encode_stream()`][pydantic_ai.ui.ag_ui.AGUIAdapter.encode_stream] method encodes the stream of AG-UI events as strings according to the accept header value. You can also use the [`AGUIAdapter.streaming_response()`][pydantic_ai.ui.ag_ui.AGUIAdapter.streaming_response] method to generate a streaming response directly from the AG-UI event stream returned by `run_stream()`.
83+
8084
Since `app` is an ASGI application, it can be used with any ASGI server:
8185

8286
```shell
@@ -87,25 +91,27 @@ This will expose the agent as an AG-UI server, and your frontend can start sendi
8791

8892
### Handle a Starlette request
8993

90-
This example uses [`handle_ag_ui_request()`][pydantic_ai.ag_ui.run_ag_ui] to directly handle a FastAPI request and return a response. Something analogous to this will work with any Starlette-based web framework.
94+
This example uses [`AGUIAdapter.dispatch_request()`][pydantic_ai.ui.ag_ui.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.
9195

9296
```py {title="handle_ag_ui_request.py"}
9397
from fastapi import FastAPI
9498
from starlette.requests import Request
9599
from starlette.responses import Response
96100

97101
from pydantic_ai import Agent
98-
from pydantic_ai.ag_ui import handle_ag_ui_request
102+
from pydantic_ai.ui.ag_ui import AGUIAdapter
99103

100104
agent = Agent('openai:gpt-4.1', instructions='Be fun!')
101105

102106
app = FastAPI()
103107

104108
@app.post('/')
105109
async def run_agent(request: Request) -> Response:
106-
return await handle_ag_ui_request(agent, request)
110+
return await AGUIAdapter.dispatch_request(request, agent=agent) # (1)
107111
```
108112

113+
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.
114+
109115
Since `app` is an ASGI application, it can be used with any ASGI server:
110116

111117
```shell
@@ -116,19 +122,20 @@ This will expose the agent as an AG-UI server, and your frontend can start sendi
116122

117123
### Stand-alone ASGI app
118124

119-
This example uses [`Agent.to_ag_ui()`][pydantic_ai.agent.AbstractAgent.to_ag_ui] to turn the agent into a stand-alone ASGI application:
125+
This example uses [`AGUIApp`][pydantic_ai.ui.ag_ui.app.AGUIApp] to turn the agent into a stand-alone ASGI application:
120126

121-
```py {title="agent_to_ag_ui.py" hl_lines="4"}
127+
```py {title="ag_ui_app.py" hl_lines="4"}
122128
from pydantic_ai import Agent
129+
from pydantic_ai.ui.ag_ui.app import AGUIApp
123130

124131
agent = Agent('openai:gpt-4.1', instructions='Be fun!')
125-
app = agent.to_ag_ui()
132+
app = AGUIApp(agent)
126133
```
127134

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

130137
```shell
131-
uvicorn agent_to_ag_ui:app
138+
uvicorn ag_ui_app:app
132139
```
133140

134141
This will expose the agent as an AG-UI server, and your frontend can start sending requests to it.
@@ -174,7 +181,8 @@ validate state contained in [`RunAgentInput.state`](https://docs.ag-ui.com/sdk/j
174181
from pydantic import BaseModel
175182

176183
from pydantic_ai import Agent
177-
from pydantic_ai.ag_ui import StateDeps
184+
from pydantic_ai.ui import StateDeps
185+
from pydantic_ai.ui.ag_ui import AGUIApp
178186

179187

180188
class DocumentState(BaseModel):
@@ -188,7 +196,7 @@ agent = Agent(
188196
instructions='Be fun!',
189197
deps_type=StateDeps[DocumentState],
190198
)
191-
app = agent.to_ag_ui(deps=StateDeps(DocumentState()))
199+
app = AGUIApp(agent, deps=StateDeps(DocumentState()))
192200
```
193201

194202
Since `app` is an ASGI application, it can be used with any ASGI server:
@@ -214,7 +222,8 @@ from ag_ui.core import CustomEvent, EventType, StateSnapshotEvent
214222
from pydantic import BaseModel
215223

216224
from pydantic_ai import Agent, RunContext, ToolReturn
217-
from pydantic_ai.ag_ui import StateDeps
225+
from pydantic_ai.ui import StateDeps
226+
from pydantic_ai.ui.ag_ui import AGUIApp
218227

219228

220229
class DocumentState(BaseModel):
@@ -228,7 +237,7 @@ agent = Agent(
228237
instructions='Be fun!',
229238
deps_type=StateDeps[DocumentState],
230239
)
231-
app = agent.to_ag_ui(deps=StateDeps(DocumentState()))
240+
app = AGUIApp(agent, deps=StateDeps(DocumentState()))
232241

233242

234243
@agent.tool
@@ -271,7 +280,7 @@ uvicorn ag_ui_tool_events:app --host 0.0.0.0 --port 9000
271280

272281
## Examples
273282

274-
For more examples of how to use [`to_ag_ui()`][pydantic_ai.agent.AbstractAgent.to_ag_ui] see
283+
For more examples of how to use [`AGUIApp`][pydantic_ai.ui.ag_ui.app.AGUIApp] see
275284
[`pydantic_ai_examples.ag_ui`](https://github.com/pydantic/pydantic-ai/tree/main/examples/pydantic_ai_examples/ag_ui),
276285
which includes a server for use with the
277286
[AG-UI Dojo](https://docs.ag-ui.com/tutorials/debugging#the-ag-ui-dojo).

examples/pydantic_ai_examples/ag_ui/api/agentic_chat.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
from zoneinfo import ZoneInfo
77

88
from pydantic_ai import Agent
9+
from pydantic_ai.ui.ag_ui.app import AGUIApp
910

1011
agent = Agent('openai:gpt-4o-mini')
11-
app = agent.to_ag_ui()
1212

1313

1414
@agent.tool_plain
@@ -23,3 +23,6 @@ async def current_time(timezone: str = 'UTC') -> str:
2323
"""
2424
tz: ZoneInfo = ZoneInfo(timezone)
2525
return datetime.now(tz=tz).isoformat()
26+
27+
28+
app = AGUIApp(agent)

examples/pydantic_ai_examples/ag_ui/api/agentic_generative_ui.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from ag_ui.core import EventType, StateDeltaEvent, StateSnapshotEvent
1111
from pydantic_ai import Agent
12+
from pydantic_ai.ui.ag_ui.app import AGUIApp
1213

1314
StepStatus = Literal['pending', 'completed']
1415

@@ -116,4 +117,4 @@ async def update_plan_step(
116117
)
117118

118119

119-
app = agent.to_ag_ui()
120+
app = AGUIApp(agent)

examples/pydantic_ai_examples/ag_ui/api/human_in_the_loop.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from textwrap import dedent
99

1010
from pydantic_ai import Agent
11+
from pydantic_ai.ui.ag_ui.app import AGUIApp
1112

1213
agent = Agent(
1314
'openai:gpt-4o-mini',
@@ -23,4 +24,4 @@
2324
),
2425
)
2526

26-
app = agent.to_ag_ui()
27+
app = AGUIApp(agent)

examples/pydantic_ai_examples/ag_ui/api/predictive_state_updates.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88

99
from ag_ui.core import CustomEvent, EventType
1010
from pydantic_ai import Agent, RunContext
11-
from pydantic_ai.ag_ui import StateDeps
11+
from pydantic_ai.ui import StateDeps
12+
from pydantic_ai.ui.ag_ui.app import AGUIApp
1213

1314

1415
class DocumentState(BaseModel):
@@ -74,4 +75,4 @@ async def story_instructions(ctx: RunContext[StateDeps[DocumentState]]) -> str:
7475
)
7576

7677

77-
app = agent.to_ag_ui(deps=StateDeps(DocumentState()))
78+
app = AGUIApp(agent, deps=StateDeps(DocumentState()))

examples/pydantic_ai_examples/ag_ui/api/shared_state.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99

1010
from ag_ui.core import EventType, StateSnapshotEvent
1111
from pydantic_ai import Agent, RunContext
12-
from pydantic_ai.ag_ui import StateDeps
12+
from pydantic_ai.ui import StateDeps
13+
from pydantic_ai.ui.ag_ui.app import AGUIApp
1314

1415

1516
class SkillLevel(str, Enum):
@@ -135,4 +136,4 @@ async def recipe_instructions(ctx: RunContext[StateDeps[RecipeSnapshot]]) -> str
135136
)
136137

137138

138-
app = agent.to_ag_ui(deps=StateDeps(RecipeSnapshot()))
139+
app = AGUIApp(agent, deps=StateDeps(RecipeSnapshot()))

examples/pydantic_ai_examples/ag_ui/api/tool_based_generative_ui.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from __future__ import annotations
77

88
from pydantic_ai import Agent
9+
from pydantic_ai.ui.ag_ui.app import AGUIApp
910

1011
agent = Agent('openai:gpt-4o-mini')
11-
app = agent.to_ag_ui()
12+
app = AGUIApp(agent)

pydantic_ai_slim/pydantic_ai/ag_ui.py

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
for building interactive AI applications with streaming event-based communication.
55
"""
66

7+
# TODO (v2): Remove this module in favor of `pydantic_ai.ui.ag_ui`
8+
79
from __future__ import annotations
810

911
from collections.abc import AsyncIterator, Sequence
@@ -22,24 +24,15 @@
2224
try:
2325
from ag_ui.core import BaseEvent
2426
from ag_ui.core.types import RunAgentInput
25-
26-
from .ui import SSE_CONTENT_TYPE, OnCompleteFunc, StateDeps, StateHandler
27-
from .ui.ag_ui import (
28-
AGUIAdapter,
29-
AGUIApp,
30-
)
31-
except ImportError as e: # pragma: no cover
32-
raise ImportError(
33-
'Please install the `ag-ui-protocol` package to use `Agent.to_ag_ui()` method, '
34-
'you can use the `ag-ui` optional group — `pip install "pydantic-ai-slim[ag-ui]"`'
35-
) from e
36-
37-
try:
3827
from starlette.requests import Request
3928
from starlette.responses import Response
29+
30+
from .ui import SSE_CONTENT_TYPE, OnCompleteFunc, StateDeps, StateHandler
31+
from .ui.ag_ui import AGUIAdapter
32+
from .ui.ag_ui.app import AGUIApp
4033
except ImportError as e: # pragma: no cover
4134
raise ImportError(
42-
'Please install the `starlette` package to use `Agent.to_ag_ui()` method, '
35+
'Please install the `ag-ui-protocol` and `starlette` packages to use `AGUIAdapter`, '
4336
'you can use the `ag-ui` optional group — `pip install "pydantic-ai-slim[ag-ui]"`'
4437
) from e
4538

pydantic_ai_slim/pydantic_ai/agent/abstract.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
from starlette.routing import BaseRoute, Route
5050
from starlette.types import ExceptionHandler, Lifespan
5151

52-
from ..ag_ui import AGUIApp
52+
from pydantic_ai.ui.ag_ui.app import AGUIApp
5353

5454

5555
T = TypeVar('T')
@@ -1077,7 +1077,7 @@ def to_ag_ui(
10771077
Returns:
10781078
An ASGI application for running Pydantic AI agents with AG-UI protocol support.
10791079
"""
1080-
from ..ag_ui import AGUIApp
1080+
from pydantic_ai.ui.ag_ui.app import AGUIApp
10811081

10821082
return AGUIApp(
10831083
agent=self,

pydantic_ai_slim/pydantic_ai/ui/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from __future__ import annotations
88

99
from .adapter import OnCompleteFunc, StateDeps, StateHandler, UIAdapter
10-
from .app import UIApp
1110
from .event_stream import SSE_CONTENT_TYPE, UIEventStream
1211
from .messages_builder import MessagesBuilder
1312

@@ -18,6 +17,5 @@
1817
'StateDeps',
1918
'StateHandler',
2019
'OnCompleteFunc',
21-
'UIApp',
2220
'MessagesBuilder',
2321
]

0 commit comments

Comments
 (0)