Skip to content

Commit da15d13

Browse files
committed
feat: AG-UI adapter
AG-UI adapter enabling integration with the AG-UI protocol via the Agent.to_ag_ui() method. This includes a full example for all features in the AG-UI dojo. Fixes: ag-ui-protocol/ag-ui/issues/5
1 parent 8039c20 commit da15d13

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+3389
-47
lines changed

docs/ag-ui.md

Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
# Agent User Interaction (AG-UI) Protocol
2+
3+
The [Agent User Interaction (AG-UI) Protocol](https://docs.ag-ui.com/introduction)
4+
is an open standard introduced by the
5+
[CopilotKit](https://webflow.copilotkit.ai/blog/introducing-ag-ui-the-protocol-where-agents-meet-users)
6+
team that standardises how frontend applications connect to AI agents through
7+
an open protocol. Think of it as a universal translator for AI-driven systems
8+
no matter what language an agent speaks: AG-UI ensures fluent communication.
9+
10+
The team at [Rocket Science](https://www.rocketscience.gg/), contributed the
11+
[pydantic-ai-ag-ui](#ag-ui-adapter) package to make it easy to implement the
12+
AG-UI protocol with PydanticAI agents.
13+
14+
This also includes an [`Agent.to_ag_ui`][pydantic_ai.Agent.to_ag_ui] convenience
15+
method which simplifies the creation of [`Adapter`][pydantic_ai_ag_ui.Adapter]
16+
for PydanticAI agents, which can then be used by as part of a
17+
[fastapi](https://fastapi.tiangolo.com/) app.
18+
19+
## AG-UI Adapter
20+
21+
The [Adapter][pydantic_ai_ag_ui.Adapter] class is an adapter between
22+
PydanticAI agents and the AG-UI protocol written in Python. It provides support
23+
for all aspects of spec including:
24+
25+
- [Events](https://docs.ag-ui.com/concepts/events)
26+
- [Messages](https://docs.ag-ui.com/concepts/messages)
27+
- [State Management](https://docs.ag-ui.com/concepts/state)
28+
- [Tools](https://docs.ag-ui.com/concepts/tools)
29+
30+
Let's have a quick look at how to use it:
31+
32+
### Installation
33+
34+
[Adapter][pydantic_ai_ag_ui.Adapter] is available on PyPI as
35+
[`pydantic-ai-ag-ui`](https://pypi.org/project/pydantic-ai-ag-ui/) so installation is as
36+
simple as:
37+
38+
```bash
39+
pip/uv-add pydantic-ai-ag-ui
40+
```
41+
42+
The only dependencies are:
43+
44+
- [ag-ui-protocol](https://docs.ag-ui.com/introduction): to provide the AG-UI
45+
types and encoder.
46+
- [pydantic](https://pydantic.dev): to validate the request/response messages
47+
- [pydantic-ai](https://ai.pydantic.dev/): to provide the agent framework
48+
49+
To run the examples you'll also need:
50+
51+
- [fastapi](https://fastapi.tiangolo.com/): to provide ASGI compatible server
52+
53+
```bash
54+
pip/uv-add 'fastapi'
55+
```
56+
57+
You can install PydanticAI with the `ag-ui` extra to include **Adapter**:
58+
59+
```bash
60+
pip/uv-add 'pydantic-ai-slim[ag-ui]'
61+
```
62+
63+
### Quick start
64+
65+
```py {title="agent_to_ag_ui.py" py="3.10" hl_lines="17-28"}
66+
"""Basic example for AG-UI with FastAPI and Pydantic AI."""
67+
68+
from __future__ import annotations
69+
70+
from typing import TYPE_CHECKING, Annotated
71+
72+
from fastapi import FastAPI, Header
73+
from fastapi.responses import StreamingResponse
74+
from pydantic_ai_ag_ui import SSE_CONTENT_TYPE
75+
76+
from pydantic_ai import Agent
77+
78+
if TYPE_CHECKING:
79+
from ag_ui.core import RunAgentInput
80+
81+
agent = Agent('openai:gpt-4.1', instructions='Be fun!')
82+
adapter = agent.to_ag_ui()
83+
app = FastAPI(title='AG-UI Endpoint')
84+
85+
86+
@app.post('/')
87+
async def root(
88+
input_data: RunAgentInput, accept: Annotated[str, Header()] = SSE_CONTENT_TYPE
89+
) -> StreamingResponse:
90+
return StreamingResponse(
91+
adapter.run(input_data, accept),
92+
media_type=SSE_CONTENT_TYPE,
93+
)
94+
```
95+
96+
You can run the example with:
97+
98+
```shell
99+
uvicorn agent_to_ag_ui:app --host 0.0.0.0 --port 8000
100+
```
101+
102+
This will expose the agent as an AG-UI server, and you can start sending
103+
requests to it.
104+
105+
### Design
106+
107+
The adapter receives messages in the form of a
108+
[`RunAgentInput`](https://docs.ag-ui.com/sdk/js/core/types#runagentinput)
109+
which describes the details of a request being passed to the agent including
110+
messages and state. These are then converted to PydanticAI types, passed to the
111+
agent which then process the request.
112+
113+
Results from the agent are converted from PydanticAI types to AG-UI events and
114+
streamed back to the caller as Server-Sent Events (SSE).
115+
116+
A user request may require multiple round trips between client UI and PydanticAI
117+
server, depending on the tools and events needed.
118+
119+
[Adapter][pydantic_ai_ag_ui.Adapter] can be used with any ASGI server.
120+
121+
### Features
122+
123+
To expose a PydanticAI agent as an AG-UI server including state support, you can
124+
use the [`to_ag_ui`][pydantic_ai.agent.Agent.to_ag_ui] method in combination
125+
with [fastapi](https://fastapi.tiangolo.com/).
126+
127+
In the example below we have document state which is shared between the UI and
128+
server using the [`StateDeps`][pydantic_ai_ag_ui.StateDeps] which implements the
129+
[`StateHandler`][pydantic_ai_ag_ui.StateHandler] that can be used to automatically
130+
decode state contained in [`RunAgentInput.state`](https://docs.ag-ui.com/sdk/js/core/types#runagentinput)
131+
when processing requests.
132+
133+
#### State management
134+
135+
The adapter provides full support for
136+
[AG-UI state management](https://docs.ag-ui.com/concepts/state), which enables
137+
real-time synchronization between agents and frontend applications.
138+
139+
```python {title="ag_ui_state.py" py="3.10" hl_lines="18-40"}
140+
"""State example for AG-UI with FastAPI and Pydantic AI."""
141+
142+
from __future__ import annotations
143+
144+
from typing import TYPE_CHECKING, Annotated
145+
146+
from fastapi import FastAPI, Header
147+
from fastapi.responses import StreamingResponse
148+
from pydantic import BaseModel
149+
from pydantic_ai_ag_ui import SSE_CONTENT_TYPE, StateDeps
150+
151+
from pydantic_ai import Agent
152+
153+
if TYPE_CHECKING:
154+
from ag_ui.core import RunAgentInput
155+
156+
157+
class DocumentState(BaseModel):
158+
"""State for the document being written."""
159+
160+
document: str
161+
162+
163+
agent = Agent(
164+
'openai:gpt-4.1',
165+
instructions='Be fun!',
166+
deps_type=StateDeps[DocumentState],
167+
)
168+
adapter = agent.to_ag_ui()
169+
app = FastAPI(title='AG-UI Endpoint')
170+
171+
172+
@app.post('/')
173+
async def root(
174+
input_data: RunAgentInput, accept: Annotated[str, Header()] = SSE_CONTENT_TYPE
175+
) -> StreamingResponse:
176+
return StreamingResponse(
177+
adapter.run(input_data, accept, deps=StateDeps(state_type=DocumentState)),
178+
media_type=SSE_CONTENT_TYPE,
179+
)
180+
```
181+
182+
Since `app` is an ASGI application, it can be used with any ASGI server.
183+
184+
```bash
185+
uvicorn agent_to_ag_ui:app --host 0.0.0.0 --port 8000
186+
```
187+
188+
Since the goal of [`to_ag_ui`][pydantic_ai.agent.Agent.to_ag_ui] is to be a
189+
convenience method, it accepts the same arguments as the
190+
[`Adapter`][pydantic_ai_ag_ui.Adapter] constructor.
191+
192+
#### Tools
193+
194+
AG-UI tools are seamlessly provided to the PydanticAI agent, enabling rich
195+
use experiences with frontend user interfaces.
196+
197+
#### Events
198+
199+
The adapter provides the ability for PydanticAI tools to send
200+
[AG-UI events](https://docs.ag-ui.com/concepts/events) simply by defining a tool
201+
which returns a type based off
202+
[`BaseEvent`](https://docs.ag-ui.com/sdk/js/core/events#baseevent) this allows
203+
for custom events and state updates.
204+
205+
```python {title="ag_ui_tool_events.py" py="3.10" hl_lines="34-55"}
206+
"""Tool events example for AG-UI with FastAPI and Pydantic AI."""
207+
208+
from __future__ import annotations
209+
210+
from typing import TYPE_CHECKING, Annotated
211+
212+
from ag_ui.core import CustomEvent, EventType, StateSnapshotEvent
213+
from fastapi import FastAPI, Header
214+
from fastapi.responses import StreamingResponse
215+
from pydantic import BaseModel
216+
from pydantic_ai_ag_ui import SSE_CONTENT_TYPE, StateDeps
217+
218+
from pydantic_ai import Agent, RunContext
219+
220+
if TYPE_CHECKING:
221+
from ag_ui.core import RunAgentInput
222+
223+
224+
class DocumentState(BaseModel):
225+
"""State for the document being written."""
226+
227+
document: str
228+
229+
230+
agent = Agent(
231+
'openai:gpt-4.1',
232+
instructions='Be fun!',
233+
deps_type=StateDeps[DocumentState],
234+
)
235+
adapter = agent.to_ag_ui()
236+
app = FastAPI(title='AG-UI Endpoint')
237+
238+
239+
@agent.tool
240+
def update_state(ctx: RunContext[StateDeps[DocumentState]]) -> StateSnapshotEvent:
241+
return StateSnapshotEvent(
242+
type=EventType.STATE_SNAPSHOT,
243+
snapshot=ctx.deps.state,
244+
)
245+
246+
247+
@agent.tool_plain
248+
def custom_events() -> list[CustomEvent]:
249+
return [
250+
CustomEvent(
251+
type=EventType.CUSTOM,
252+
name='count',
253+
value=1,
254+
),
255+
CustomEvent(
256+
type=EventType.CUSTOM,
257+
name='count',
258+
value=2,
259+
),
260+
]
261+
262+
263+
@app.post('/')
264+
async def root(
265+
input_data: RunAgentInput, accept: Annotated[str, Header()] = SSE_CONTENT_TYPE
266+
) -> StreamingResponse:
267+
return StreamingResponse(
268+
adapter.run(input_data, accept, deps=StateDeps(state_type=DocumentState)),
269+
media_type=SSE_CONTENT_TYPE,
270+
)
271+
```
272+
273+
### Examples
274+
275+
For more examples of how to use [`Adapter`][pydantic_ai_ag_ui.Adapter] see
276+
[`pydantic_ai_ag_ui_examples`](https://github.com/pydantic/pydantic-ai/tree/main/examples/pydantic_ai_ag_ui_examples),
277+
which includes working server for the with the
278+
[AG-UI Dojo](https://docs.ag-ui.com/tutorials/debugging#the-ag-ui-dojo) which
279+
can be run from a clone of the repo or with the `pydantic-ai-examples` package
280+
installed with either of the following:
281+
282+
```bash
283+
pip/uv-add pydantic-ai-examples
284+
```
285+
286+
Direct, which supports command line flags:
287+
288+
```shell
289+
python -m pydantic_ai_ag_ui_examples.dojo_server --help
290+
usage: dojo_server.py [-h] [--port PORT] [--reload] [--no-reload] [--log-level {critical,error,warning,info,debug,trace}]
291+
292+
PydanticAI AG-UI Dojo server
293+
294+
options:
295+
-h, --help show this help message and exit
296+
--port PORT, -p PORT Port to run the server on (default: 9000)
297+
--reload Enable auto-reload (default: True)
298+
--no-reload Disable auto-reload
299+
--log-level {critical,error,warning,info,debug,trace}
300+
Agent log level (default: info)
301+
```
302+
303+
Run with adapter debug logging:
304+
305+
```shell
306+
python -m pydantic_ai_ag_ui_examples.dojo_server --log-level debug
307+
```
308+
309+
Using uvicorn:
310+
311+
```shell
312+
uvicorn pydantic_ai_ag_ui_examples.dojo_server:app --port 9000
313+
```

docs/api/pydantic_ai_ag_ui.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# `pydantic_ai_ag_ui`
2+
3+
::: pydantic_ai_ag_ui

docs/install.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ pip/uv-add "pydantic-ai-slim[openai]"
5656
* `cohere` - installs `cohere` [PyPI ↗](https://pypi.org/project/cohere){:target="_blank"}
5757
* `duckduckgo` - installs `duckduckgo-search` [PyPI ↗](https://pypi.org/project/duckduckgo-search){:target="_blank"}
5858
* `tavily` - installs `tavily-python` [PyPI ↗](https://pypi.org/project/tavily-python){:target="_blank"}
59+
* `ag-ui` - installs `pydantic-ai-ag-ui` [PyPI ↗](https://pypi.org/project/pydantic-ai-ag-ui){:target="_blank"}
5960

6061
See the [models](models/index.md) documentation for information on which optional dependencies are required for each model.
6162

0 commit comments

Comments
 (0)