Skip to content

Commit 633ee32

Browse files
committed
chore(ag-ui): address PR feedback
* Split out coverage fixes to separate PRs * Rename FastAGUI to AGUIApp * Add thinking part delta test * Remove example agent helper * Use dedent to help create clean instructions * Remove logging from examples * Restore explicit re-raise variable * Eliminate agent_to_ag_ui * Ensure user toolsets are not overwritten incorrectly * Use temporary vars to reduce repetitive indirection * Raise ToolCallNotFoundError if tool result without matching call
1 parent c4f12cc commit 633ee32

File tree

15 files changed

+243
-231
lines changed

15 files changed

+243
-231
lines changed

.github/workflows/ci.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,6 @@ jobs:
204204
enable-cache: true
205205

206206
- run: uv sync --package pydantic-ai-slim --only-dev
207-
- run: rm coverage/.coverage.*-py3.9-* # Exclude 3.9 coverage as it gets the wrong line numbers, causing invalid failures.
208207
- run: uv run coverage combine coverage
209208

210209
- run: uv run coverage html --show-contexts --title "PydanticAI coverage for ${{ github.sha }}"

docs/ag-ui.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ The team at [Rocket Science](https://www.rocketscience.gg/), contributed the
1212
protocol with PydanticAI agents.
1313

1414
This also includes an [`Agent.to_ag_ui`][pydantic_ai.Agent.to_ag_ui] convenience
15-
method which simplifies the creation of [`FastAGUI`][pydantic_ai.ag_ui.FastAGUI]
15+
method which simplifies the creation of [`AGUIApp`][pydantic_ai.ag_ui.AGUIApp]
1616
for PydanticAI agents, which is built on top of [Starlette](https://www.starlette.io/),
1717
meaning it's fully compatible with any ASGI server.
1818

@@ -88,7 +88,7 @@ A user request may require multiple round trips between client UI and PydanticAI
8888
server, depending on the tools and events needed.
8989

9090
In addition to the [Adapter][pydantic_ai.ag_ui.Adapter] there is also
91-
[FastAGUI][pydantic_ai.ag_ui.FastAGUI] which is slim wrapper around
91+
[AGUIApp][pydantic_ai.ag_ui.AGUIApp] which is slim wrapper around
9292
[Starlette](https://www.starlette.io/) providing easy access to run a PydanticAI
9393
server with AG-UI support with any ASGI server.
9494

examples/pydantic_ai_ag_ui_examples/api/agent.py

Lines changed: 0 additions & 35 deletions
This file was deleted.

examples/pydantic_ai_ag_ui_examples/api/agentic_chat.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,23 @@
55
from datetime import datetime
66
from zoneinfo import ZoneInfo
77

8-
from pydantic_ai.ag_ui import FastAGUI
8+
from dotenv import load_dotenv
99

10-
from .agent import agent
10+
from pydantic_ai import Agent
11+
from pydantic_ai.ag_ui import AGUIApp
1112

12-
app: FastAGUI = agent()
13+
# Ensure environment variables are loaded.
14+
load_dotenv()
1315

16+
agent: Agent = Agent(
17+
'openai:gpt-4o-mini',
18+
output_type=str,
19+
)
1420

15-
@app.adapter.agent.tool_plain
21+
app: AGUIApp = agent.to_ag_ui()
22+
23+
24+
@agent.tool_plain
1625
async def current_time(timezone: str = 'UTC') -> str:
1726
"""Get the current time in ISO format.
1827

examples/pydantic_ai_ag_ui_examples/api/agentic_generative_ui.py

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,40 @@
33
from __future__ import annotations
44

55
from enum import StrEnum
6+
from textwrap import dedent
67
from typing import Any, Literal
78

89
from ag_ui.core import EventType, StateDeltaEvent, StateSnapshotEvent
10+
from dotenv import load_dotenv
911
from pydantic import BaseModel, Field
1012

11-
from pydantic_ai.ag_ui import FastAGUI
12-
13-
from .agent import agent
14-
15-
app: FastAGUI = agent(
16-
instructions="""When planning use tools only, without any other messages.
17-
IMPORTANT:
18-
- Use the `create_plan` tool to set the initial state of the steps
19-
- Use the `update_plan_step` tool to update the status of each step
20-
- Do NOT repeat the plan or summarise it in a message
21-
- Do NOT confirm the creation or updates in a message
22-
- Do NOT ask the user for additional information or next steps
23-
24-
Only one plan can be active at a time, so do not call the `create_plan` tool
25-
again until all the steps in current plan are completed.
26-
"""
13+
from pydantic_ai import Agent
14+
from pydantic_ai.ag_ui import AGUIApp
15+
16+
# Ensure environment variables are loaded.
17+
load_dotenv()
18+
19+
agent: Agent = Agent(
20+
'openai:gpt-4o-mini',
21+
output_type=str,
22+
instructions=dedent(
23+
"""
24+
When planning use tools only, without any other messages.
25+
IMPORTANT:
26+
- Use the `create_plan` tool to set the initial state of the steps
27+
- Use the `update_plan_step` tool to update the status of each step
28+
- Do NOT repeat the plan or summarise it in a message
29+
- Do NOT confirm the creation or updates in a message
30+
- Do NOT ask the user for additional information or next steps
31+
32+
Only one plan can be active at a time, so do not call the `create_plan` tool
33+
again until all the steps in current plan are completed.
34+
"""
35+
),
2736
)
2837

38+
app: AGUIApp = agent.to_ag_ui()
39+
2940

3041
class StepStatus(StrEnum):
3142
"""The status of a step in a plan."""
@@ -71,7 +82,7 @@ class JSONPatchOp(BaseModel):
7182
)
7283

7384

74-
@app.adapter.agent.tool_plain
85+
@agent.tool_plain
7586
def create_plan(steps: list[str]) -> StateSnapshotEvent:
7687
"""Create a plan with multiple steps.
7788
@@ -90,7 +101,7 @@ def create_plan(steps: list[str]) -> StateSnapshotEvent:
90101
)
91102

92103

93-
@app.adapter.agent.tool_plain
104+
@agent.tool_plain
94105
def update_plan_step(
95106
index: int, description: str | None = None, status: StepStatus | None = None
96107
) -> StateDeltaEvent:

examples/pydantic_ai_ag_ui_examples/api/human_in_the_loop.py

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,29 @@
55

66
from __future__ import annotations
77

8-
from pydantic_ai.ag_ui import FastAGUI
8+
from textwrap import dedent
99

10-
from .agent import agent
10+
from dotenv import load_dotenv
1111

12-
app: FastAGUI = agent(
13-
instructions="""When planning tasks use tools only, without any other messages.
14-
IMPORTANT:
15-
- Use the `generate_task_steps` tool to display the suggested steps to the user
16-
- Never repeat the plan, or send a message detailing steps
17-
- If accepted, confirm the creation of the plan and the number of selected (enabled) steps only
18-
- If not accepted, ask the user for more information, DO NOT use the `generate_task_steps` tool again
19-
"""
12+
from pydantic_ai import Agent
13+
from pydantic_ai.ag_ui import AGUIApp
14+
15+
# Ensure environment variables are loaded.
16+
load_dotenv()
17+
18+
agent: Agent = Agent(
19+
'openai:gpt-4o-mini',
20+
output_type=str,
21+
instructions=dedent(
22+
"""
23+
When planning tasks use tools only, without any other messages.
24+
IMPORTANT:
25+
- Use the `generate_task_steps` tool to display the suggested steps to the user
26+
- Never repeat the plan, or send a message detailing steps
27+
- If accepted, confirm the creation of the plan and the number of selected (enabled) steps only
28+
- If not accepted, ask the user for more information, DO NOT use the `generate_task_steps` tool again
29+
"""
30+
),
2031
)
32+
33+
app: AGUIApp = agent.to_ag_ui()

examples/pydantic_ai_ag_ui_examples/api/predictive_state_updates.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,42 +2,46 @@
22

33
from __future__ import annotations
44

5-
import logging
65
from typing import TYPE_CHECKING
76

87
from ag_ui.core import CustomEvent, EventType
8+
from dotenv import load_dotenv
99
from pydantic import BaseModel
1010

11-
from pydantic_ai.ag_ui import FastAGUI, StateDeps
12-
13-
from .agent import agent
11+
from pydantic_ai import Agent
12+
from pydantic_ai.ag_ui import AGUIApp, StateDeps
1413

1514
if TYPE_CHECKING: # pragma: no cover
1615
from pydantic_ai import RunContext
1716

1817

19-
_LOGGER: logging.Logger = logging.getLogger(__name__)
20-
21-
2218
class DocumentState(BaseModel):
2319
"""State for the document being written."""
2420

2521
document: str = ''
2622

2723

28-
app: FastAGUI = agent(deps=StateDeps(DocumentState()))
24+
# Ensure environment variables are loaded.
25+
load_dotenv()
26+
27+
agent: Agent = Agent(
28+
'openai:gpt-4o-mini',
29+
output_type=str,
30+
deps_type=StateDeps[DocumentState],
31+
)
32+
33+
app: AGUIApp = agent.to_ag_ui(deps=StateDeps(DocumentState()))
2934

3035

3136
# Tools which return AG-UI events will be sent to the client as part of the
3237
# event stream, single events and iterables of events are supported.
33-
@app.adapter.agent.tool_plain
38+
@agent.tool_plain
3439
def document_predict_state() -> list[CustomEvent]:
3540
"""Enable document state prediction.
3641
3742
Returns:
3843
CustomEvent containing the event to enable state prediction.
3944
"""
40-
_LOGGER.info('enabling document state state prediction')
4145
return [
4246
CustomEvent(
4347
type=EventType.CUSTOM,
@@ -53,7 +57,7 @@ def document_predict_state() -> list[CustomEvent]:
5357
]
5458

5559

56-
@app.adapter.agent.instructions()
60+
@agent.instructions()
5761
def story_instructions(ctx: RunContext[StateDeps[DocumentState]]) -> str:
5862
"""Provide instructions for writing document if present.
5963
@@ -63,8 +67,6 @@ def story_instructions(ctx: RunContext[StateDeps[DocumentState]]) -> str:
6367
Returns:
6468
Instructions string for the document writing agent.
6569
"""
66-
_LOGGER.info('story instructions document=%s', ctx.deps.state.document)
67-
6870
return f"""You are a helpful assistant for writing documents.
6971
7072
Before you start writing, you MUST call the `document_predict_state`

examples/pydantic_ai_ag_ui_examples/api/shared_state.py

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,19 @@
22

33
from __future__ import annotations
44

5-
import json
6-
import logging
75
from enum import StrEnum
86
from typing import TYPE_CHECKING
97

108
from ag_ui.core import EventType, StateSnapshotEvent
9+
from dotenv import load_dotenv
1110
from pydantic import BaseModel, Field
1211

13-
from pydantic_ai.ag_ui import FastAGUI, StateDeps
14-
15-
from .agent import agent
12+
from pydantic_ai import Agent
13+
from pydantic_ai.ag_ui import AGUIApp, StateDeps
1614

1715
if TYPE_CHECKING: # pragma: no cover
1816
from pydantic_ai import RunContext
1917

20-
_LOGGER: logging.Logger = logging.getLogger(__name__)
21-
2218

2319
class SkillLevel(StrEnum):
2420
"""The level of skill required for the recipe."""
@@ -92,10 +88,19 @@ class RecipeSnapshot(BaseModel):
9288
)
9389

9490

95-
app: FastAGUI = agent(deps=StateDeps(RecipeSnapshot()))
91+
# Ensure environment variables are loaded.
92+
load_dotenv()
93+
94+
agent: Agent = Agent(
95+
'openai:gpt-4o-mini',
96+
output_type=str,
97+
deps_type=StateDeps[RecipeSnapshot],
98+
)
99+
100+
app: AGUIApp = agent.to_ag_ui(deps=StateDeps(RecipeSnapshot()))
96101

97102

98-
@app.adapter.agent.tool_plain
103+
@agent.tool_plain
99104
def display_recipe(recipe: Recipe) -> StateSnapshotEvent:
100105
"""Display the recipe to the user.
101106
@@ -111,7 +116,7 @@ def display_recipe(recipe: Recipe) -> StateSnapshotEvent:
111116
)
112117

113118

114-
@app.adapter.agent.instructions
119+
@agent.instructions
115120
def recipe_instructions(ctx: RunContext[StateDeps[RecipeSnapshot]]) -> str:
116121
"""Instructions for the recipe generation agent.
117122
@@ -121,8 +126,6 @@ def recipe_instructions(ctx: RunContext[StateDeps[RecipeSnapshot]]) -> str:
121126
Returns:
122127
Instructions string for the recipe generation agent.
123128
"""
124-
_LOGGER.info('recipe instructions recipe=%s', ctx.deps.state.recipe)
125-
126129
return f"""You are a helpful assistant for creating recipes.
127130
128131
IMPORTANT:
@@ -135,10 +138,6 @@ def recipe_instructions(ctx: RunContext[StateDeps[RecipeSnapshot]]) -> str:
135138
summarise the changes in one sentence, don't describe the recipe in
136139
detail or send it as a message to the user.
137140
138-
The structure of a recipe is as follows:
139-
140-
{json.dumps(Recipe.model_json_schema(), indent=2)}
141-
142141
The current state of the recipe is:
143142
144143
{ctx.deps.state.recipe.model_dump_json(indent=2)}

examples/pydantic_ai_ag_ui_examples/api/tool_based_generative_ui.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,17 @@
55

66
from __future__ import annotations
77

8-
from pydantic_ai.ag_ui import FastAGUI
8+
from dotenv import load_dotenv
99

10-
from .agent import agent
10+
from pydantic_ai import Agent
11+
from pydantic_ai.ag_ui import AGUIApp
1112

12-
app: FastAGUI = agent()
13+
# Ensure environment variables are loaded.
14+
load_dotenv()
15+
16+
agent: Agent = Agent(
17+
'openai:gpt-4o-mini',
18+
output_type=str,
19+
)
20+
21+
app: AGUIApp = agent.to_ag_ui()

pydantic_ai_slim/pydantic_ai/_agent_graph.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -621,7 +621,7 @@ async def process_function_tools( # noqa: C901
621621
result_data = await toolset.call_tool(call, run_context)
622622
except exceptions.UnexpectedModelBehavior as e:
623623
ctx.state.increment_retries(ctx.deps.max_result_retries, e)
624-
raise # pragma: no cover
624+
raise e # pragma: no cover
625625
except ToolRetryError as e:
626626
ctx.state.increment_retries(ctx.deps.max_result_retries, e)
627627
yield _messages.FunctionToolCallEvent(call)

0 commit comments

Comments
 (0)