Skip to content

Commit 1d132db

Browse files
committed
chore(ag-ui): address PR feedback
* Update examples to use `to_ag_ui` * Use relative imports where appropriate * Remove `path` and `logger` parameters from `to_ag_ui` and dependencies * Clean up import failure messages * Improve FastAGUI doc comment * Use `PrefixedToolset` eliminating manual tool prefixing * Use CallToolsNode to handle tool calls Also make StateDeps more strict, to avoid runtime failures.
1 parent 4610bb1 commit 1d132db

File tree

12 files changed

+319
-456
lines changed

12 files changed

+319
-456
lines changed

examples/pydantic_ai_ag_ui_examples/api/__init__.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@
22

33
from __future__ import annotations
44

5-
from .agentic_chat import router as agentic_chat_router
6-
from .agentic_generative_ui import router as agentic_generative_ui_router
7-
from .human_in_the_loop import router as human_in_the_loop_router
8-
from .predictive_state_updates import router as predictive_state_updates_router
9-
from .shared_state import router as shared_state_router
10-
from .tool_based_generative_ui import router as tool_based_generative_ui_router
5+
from .agentic_chat import app as agentic_chat_app
6+
from .agentic_generative_ui import app as agentic_generative_ui_app
7+
from .human_in_the_loop import app as human_in_the_loop_app
8+
from .predictive_state_updates import app as predictive_state_updates_app
9+
from .shared_state import app as shared_state_app
10+
from .tool_based_generative_ui import app as tool_based_generative_ui_app
1111

1212
__all__: list[str] = [
13-
'agentic_chat_router',
14-
'agentic_generative_ui_router',
15-
'human_in_the_loop_router',
16-
'predictive_state_updates_router',
17-
'shared_state_router',
18-
'tool_based_generative_ui_router',
13+
'agentic_chat_app',
14+
'agentic_generative_ui_app',
15+
'human_in_the_loop_app',
16+
'predictive_state_updates_app',
17+
'shared_state_app',
18+
'tool_based_generative_ui_app',
1919
]

examples/pydantic_ai_ag_ui_examples/api/agent.py

Lines changed: 25 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,42 +2,34 @@
22

33
from __future__ import annotations
44

5-
from dataclasses import dataclass
6-
from types import NoneType
7-
from typing import Generic
8-
95
from dotenv import load_dotenv
106

117
from pydantic_ai import Agent
12-
from pydantic_ai.ag_ui import Adapter
13-
from pydantic_ai.result import OutputDataT
8+
from pydantic_ai.ag_ui import FastAGUI
149
from pydantic_ai.tools import AgentDepsT
1510

1611

17-
@dataclass(init=False, repr=False)
18-
class AGUIAgent(Generic[AgentDepsT, OutputDataT]):
19-
"""Pydantic AI agent with AG-UI adapter."""
20-
21-
agent: Agent[AgentDepsT, str]
22-
adapter: Adapter[AgentDepsT, str]
23-
instructions: str | None
24-
25-
def __init__(
26-
self, deps_type: type[AgentDepsT] = NoneType, instructions: str | None = None
27-
) -> None:
28-
"""Initialize the API agent with AG-UI adapter.
29-
30-
Args:
31-
deps_type: Type annotation for the agent dependencies.
32-
instructions: Optional instructions for the agent.
33-
"""
34-
# Ensure environment variables are loaded.
35-
load_dotenv()
36-
37-
self.agent = Agent(
38-
'openai:gpt-4o-mini',
39-
output_type=str,
40-
instructions=instructions,
41-
deps_type=deps_type,
42-
)
43-
self.adapter = Adapter(agent=self.agent)
12+
def agent(
13+
model: str = 'openai:gpt-4o-mini',
14+
deps: AgentDepsT = None,
15+
instructions: str | None = None,
16+
) -> FastAGUI[AgentDepsT, str]:
17+
"""Create a Pydantic AI agent with AG-UI adapter.
18+
19+
Args:
20+
model: The model to use for the agent.
21+
deps: Optional dependencies for the agent.
22+
instructions: Optional instructions for the agent.
23+
24+
Returns:
25+
An instance of FastAGUI with the agent and adapter.
26+
"""
27+
# Ensure environment variables are loaded.
28+
load_dotenv()
29+
30+
return Agent(
31+
model,
32+
output_type=str,
33+
instructions=instructions,
34+
deps_type=type(deps),
35+
).to_ag_ui(deps=deps)

examples/pydantic_ai_ag_ui_examples/api/agentic_chat.py

Lines changed: 4 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,16 @@
33
from __future__ import annotations
44

55
from datetime import datetime
6-
from typing import TYPE_CHECKING, Annotated
76
from zoneinfo import ZoneInfo
87

9-
from ag_ui.core import RunAgentInput
10-
from fastapi import APIRouter, Header
11-
from fastapi.responses import StreamingResponse
8+
from pydantic_ai.ag_ui import FastAGUI
129

13-
from pydantic_ai.ag_ui import SSE_CONTENT_TYPE
10+
from .agent import agent
1411

15-
from .agent import AGUIAgent
12+
app: FastAGUI = agent()
1613

17-
if TYPE_CHECKING: # pragma: no cover
18-
from ag_ui.core import RunAgentInput
1914

20-
21-
router: APIRouter = APIRouter(prefix='/agentic_chat')
22-
agui: AGUIAgent = AGUIAgent()
23-
24-
25-
@agui.agent.tool_plain
15+
@app.adapter.agent.tool_plain
2616
async def current_time(timezone: str = 'UTC') -> str:
2717
"""Get the current time in ISO format.
2818
@@ -34,22 +24,3 @@ async def current_time(timezone: str = 'UTC') -> str:
3424
"""
3525
tz: ZoneInfo = ZoneInfo(timezone)
3626
return datetime.now(tz=tz).isoformat()
37-
38-
39-
@router.post('')
40-
async def handler(
41-
input_data: RunAgentInput, accept: Annotated[str, Header()] = SSE_CONTENT_TYPE
42-
) -> StreamingResponse:
43-
"""Endpoint to handle AG-UI protocol requests and stream responses.
44-
45-
Args:
46-
input_data: The AG-UI run input.
47-
accept: The Accept header to specify the response format.
48-
49-
Returns:
50-
A streaming response with event-stream media type.
51-
"""
52-
return StreamingResponse(
53-
agui.adapter.run(input_data, accept),
54-
media_type=SSE_CONTENT_TYPE,
55-
)

examples/pydantic_ai_ag_ui_examples/api/agentic_generative_ui.py

Lines changed: 9 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,17 @@
33
from __future__ import annotations
44

55
from enum import StrEnum
6-
from typing import Annotated, Any, Literal
6+
from typing import Any, Literal
77

8-
from ag_ui.core import EventType, RunAgentInput, StateDeltaEvent, StateSnapshotEvent
9-
from fastapi import APIRouter, Header
10-
from fastapi.responses import StreamingResponse
8+
from ag_ui.core import EventType, StateDeltaEvent, StateSnapshotEvent
119
from pydantic import BaseModel, Field
1210

13-
from pydantic_ai.ag_ui import SSE_CONTENT_TYPE
11+
from pydantic_ai.ag_ui import FastAGUI
1412

15-
from .agent import AGUIAgent
13+
from .agent import agent
1614

17-
router: APIRouter = APIRouter(prefix='/agentic_generative_ui')
18-
instructions: str = """When planning use tools only, without any other messages.
15+
app: FastAGUI = agent(
16+
instructions="""When planning use tools only, without any other messages.
1917
IMPORTANT:
2018
- Use the `create_plan` tool to set the initial state of the steps
2119
- Use the `update_plan_step` tool to update the status of each step
@@ -26,7 +24,7 @@
2624
Only one plan can be active at a time, so do not call the `create_plan` tool
2725
again until all the steps in current plan are completed.
2826
"""
29-
agui: AGUIAgent = AGUIAgent(instructions=instructions)
27+
)
3028

3129

3230
class StepStatus(StrEnum):
@@ -73,7 +71,7 @@ class JSONPatchOp(BaseModel):
7371
)
7472

7573

76-
@agui.agent.tool_plain
74+
@app.adapter.agent.tool_plain
7775
def create_plan(steps: list[str]) -> StateSnapshotEvent:
7876
"""Create a plan with multiple steps.
7977
@@ -92,7 +90,7 @@ def create_plan(steps: list[str]) -> StateSnapshotEvent:
9290
)
9391

9492

95-
@agui.agent.tool_plain
93+
@app.adapter.agent.tool_plain
9694
def update_plan_step(
9795
index: int, description: str | None = None, status: StepStatus | None = None
9896
) -> StateDeltaEvent:
@@ -121,22 +119,3 @@ def update_plan_step(
121119
type=EventType.STATE_DELTA,
122120
delta=changes,
123121
)
124-
125-
126-
@router.post('')
127-
async def handler(
128-
input_data: RunAgentInput, accept: Annotated[str, Header()] = SSE_CONTENT_TYPE
129-
) -> StreamingResponse:
130-
"""Endpoint to handle AG-UI protocol requests and stream responses.
131-
132-
Args:
133-
input_data: The AG-UI run input.
134-
accept: The Accept header to specify the response format.
135-
136-
Returns:
137-
A streaming response with event-stream media type.
138-
"""
139-
return StreamingResponse(
140-
agui.adapter.run(input_data, accept),
141-
media_type=SSE_CONTENT_TYPE,
142-
)

examples/pydantic_ai_ag_ui_examples/api/human_in_the_loop.py

Lines changed: 5 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,45 +5,16 @@
55

66
from __future__ import annotations
77

8-
from typing import TYPE_CHECKING, Annotated
8+
from pydantic_ai.ag_ui import FastAGUI
99

10-
from ag_ui.core import RunAgentInput
11-
from fastapi import APIRouter, Header
12-
from fastapi.responses import StreamingResponse
10+
from .agent import agent
1311

14-
from pydantic_ai.ag_ui import SSE_CONTENT_TYPE
15-
16-
from .agent import AGUIAgent
17-
18-
if TYPE_CHECKING: # pragma: no cover
19-
from ag_ui.core import RunAgentInput
20-
21-
22-
instructions: str = """When planning tasks use tools only, without any other messages.
12+
app: FastAGUI = agent(
13+
instructions="""When planning tasks use tools only, without any other messages.
2314
IMPORTANT:
2415
- Use the `generate_task_steps` tool to display the suggested steps to the user
2516
- Never repeat the plan, or send a message detailing steps
2617
- If accepted, confirm the creation of the plan and the number of selected (enabled) steps only
2718
- If not accepted, ask the user for more information, DO NOT use the `generate_task_steps` tool again
2819
"""
29-
router: APIRouter = APIRouter(prefix='/human_in_the_loop')
30-
agui: AGUIAgent = AGUIAgent(instructions=instructions)
31-
32-
33-
@router.post('')
34-
async def handler(
35-
input_data: RunAgentInput, accept: Annotated[str, Header()] = SSE_CONTENT_TYPE
36-
) -> StreamingResponse:
37-
"""Endpoint to handle AG-UI protocol requests and stream responses.
38-
39-
Args:
40-
input_data: The AG-UI run input.
41-
accept: The Accept header to specify the response format.
42-
43-
Returns:
44-
A streaming response with event-stream media type.
45-
"""
46-
return StreamingResponse(
47-
agui.adapter.run(input_data, accept),
48-
media_type=SSE_CONTENT_TYPE,
49-
)
20+
)

examples/pydantic_ai_ag_ui_examples/api/predictive_state_updates.py

Lines changed: 8 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,14 @@
33
from __future__ import annotations
44

55
import logging
6-
from typing import TYPE_CHECKING, Annotated
6+
from typing import TYPE_CHECKING
77

8-
from ag_ui.core import CustomEvent, EventType, RunAgentInput
9-
from fastapi import APIRouter, Header
10-
from fastapi.responses import StreamingResponse
8+
from ag_ui.core import CustomEvent, EventType
119
from pydantic import BaseModel
1210

13-
from pydantic_ai.ag_ui import SSE_CONTENT_TYPE, StateDeps
11+
from pydantic_ai.ag_ui import FastAGUI, StateDeps
1412

15-
from .agent import AGUIAgent
13+
from .agent import agent
1614

1715
if TYPE_CHECKING: # pragma: no cover
1816
from pydantic_ai import RunContext
@@ -24,18 +22,15 @@
2422
class DocumentState(BaseModel):
2523
"""State for the document being written."""
2624

27-
document: str
25+
document: str = ''
2826

2927

30-
router: APIRouter = APIRouter(prefix='/predictive_state_updates')
31-
agui: AGUIAgent[StateDeps[DocumentState]] = AGUIAgent(
32-
deps_type=StateDeps[DocumentState]
33-
)
28+
app: FastAGUI = agent(deps=StateDeps(DocumentState()))
3429

3530

3631
# Tools which return AG-UI events will be sent to the client as part of the
3732
# event stream, single events and iterables of events are supported.
38-
@agui.agent.tool_plain
33+
@app.adapter.agent.tool_plain
3934
def document_predict_state() -> list[CustomEvent]:
4035
"""Enable document state prediction.
4136
@@ -58,7 +53,7 @@ def document_predict_state() -> list[CustomEvent]:
5853
]
5954

6055

61-
@agui.agent.instructions()
56+
@app.adapter.agent.instructions()
6257
def story_instructions(ctx: RunContext[StateDeps[DocumentState]]) -> str:
6358
"""Provide instructions for writing document if present.
6459
@@ -86,22 +81,3 @@ def story_instructions(ctx: RunContext[StateDeps[DocumentState]]) -> str:
8681
8782
{ctx.deps.state.document}
8883
"""
89-
90-
91-
@router.post('')
92-
async def handler(
93-
input_data: RunAgentInput, accept: Annotated[str, Header()] = SSE_CONTENT_TYPE
94-
) -> StreamingResponse:
95-
"""Endpoint to handle AG-UI protocol requests and stream responses.
96-
97-
Args:
98-
input_data: The AG-UI run input.
99-
accept: The Accept header to specify the response format.
100-
101-
Returns:
102-
A streaming response with event-stream media type.
103-
"""
104-
return StreamingResponse(
105-
agui.adapter.run(input_data, accept, deps=StateDeps(state_type=DocumentState)),
106-
media_type=SSE_CONTENT_TYPE,
107-
)

0 commit comments

Comments
 (0)