pydantic-ai-examples 0.1.2__py3-none-any.whl → 1.12.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pydantic_ai_examples/ag_ui/__init__.py +41 -0
- pydantic_ai_examples/ag_ui/__main__.py +9 -0
- pydantic_ai_examples/ag_ui/api/__init__.py +19 -0
- pydantic_ai_examples/ag_ui/api/agentic_chat.py +28 -0
- pydantic_ai_examples/ag_ui/api/agentic_generative_ui.py +120 -0
- pydantic_ai_examples/ag_ui/api/human_in_the_loop.py +27 -0
- pydantic_ai_examples/ag_ui/api/predictive_state_updates.py +78 -0
- pydantic_ai_examples/ag_ui/api/shared_state.py +139 -0
- pydantic_ai_examples/ag_ui/api/tool_based_generative_ui.py +12 -0
- pydantic_ai_examples/bank_support.py +16 -10
- pydantic_ai_examples/chat_app.py +8 -7
- pydantic_ai_examples/data_analyst.py +107 -0
- pydantic_ai_examples/evals/agent.py +1 -1
- pydantic_ai_examples/evals/custom_evaluators.py +5 -6
- pydantic_ai_examples/evals/example_01_generate_dataset.py +1 -2
- pydantic_ai_examples/evals/example_02_add_custom_evaluators.py +1 -2
- pydantic_ai_examples/evals/example_03_unit_testing.py +4 -2
- pydantic_ai_examples/evals/example_04_compare_models.py +3 -3
- pydantic_ai_examples/flight_booking.py +15 -10
- pydantic_ai_examples/py.typed +0 -0
- pydantic_ai_examples/pydantic_model.py +4 -3
- pydantic_ai_examples/question_graph.py +5 -5
- pydantic_ai_examples/rag.py +8 -7
- pydantic_ai_examples/roulette_wheel.py +1 -2
- pydantic_ai_examples/slack_lead_qualifier/__init__.py +0 -0
- pydantic_ai_examples/slack_lead_qualifier/agent.py +47 -0
- pydantic_ai_examples/slack_lead_qualifier/app.py +36 -0
- pydantic_ai_examples/slack_lead_qualifier/functions.py +85 -0
- pydantic_ai_examples/slack_lead_qualifier/modal.py +66 -0
- pydantic_ai_examples/slack_lead_qualifier/models.py +46 -0
- pydantic_ai_examples/slack_lead_qualifier/slack.py +30 -0
- pydantic_ai_examples/slack_lead_qualifier/store.py +31 -0
- pydantic_ai_examples/sql_gen.py +6 -7
- pydantic_ai_examples/stream_markdown.py +5 -4
- pydantic_ai_examples/stream_whales.py +4 -16
- pydantic_ai_examples/weather_agent.py +36 -88
- pydantic_ai_examples/weather_agent_gradio.py +10 -15
- {pydantic_ai_examples-0.1.2.dist-info → pydantic_ai_examples-1.12.0.dist-info}/METADATA +17 -13
- pydantic_ai_examples-1.12.0.dist-info/RECORD +50 -0
- pydantic_ai_examples-1.12.0.dist-info/licenses/LICENSE +21 -0
- pydantic_ai_examples-0.1.2.dist-info/RECORD +0 -30
- {pydantic_ai_examples-0.1.2.dist-info → pydantic_ai_examples-1.12.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Example usage of the AG-UI adapter for Pydantic AI.
|
|
2
|
+
|
|
3
|
+
This provides a FastAPI application that demonstrates how to use the
|
|
4
|
+
Pydantic AI agent with the AG-UI protocol. It includes examples for
|
|
5
|
+
each of the AG-UI dojo features:
|
|
6
|
+
- Agentic Chat
|
|
7
|
+
- Human in the Loop
|
|
8
|
+
- Agentic Generative UI
|
|
9
|
+
- Tool Based Generative UI
|
|
10
|
+
- Shared State
|
|
11
|
+
- Predictive State Updates
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from fastapi import FastAPI
|
|
17
|
+
|
|
18
|
+
from .api import (
|
|
19
|
+
agentic_chat_app,
|
|
20
|
+
agentic_generative_ui_app,
|
|
21
|
+
human_in_the_loop_app,
|
|
22
|
+
predictive_state_updates_app,
|
|
23
|
+
shared_state_app,
|
|
24
|
+
tool_based_generative_ui_app,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
app = FastAPI(title='Pydantic AI AG-UI server')
|
|
28
|
+
app.mount('/agentic_chat', agentic_chat_app, 'Agentic Chat')
|
|
29
|
+
app.mount('/agentic_generative_ui', agentic_generative_ui_app, 'Agentic Generative UI')
|
|
30
|
+
app.mount('/human_in_the_loop', human_in_the_loop_app, 'Human in the Loop')
|
|
31
|
+
app.mount(
|
|
32
|
+
'/predictive_state_updates',
|
|
33
|
+
predictive_state_updates_app,
|
|
34
|
+
'Predictive State Updates',
|
|
35
|
+
)
|
|
36
|
+
app.mount('/shared_state', shared_state_app, 'Shared State')
|
|
37
|
+
app.mount(
|
|
38
|
+
'/tool_based_generative_ui',
|
|
39
|
+
tool_based_generative_ui_app,
|
|
40
|
+
'Tool Based Generative UI',
|
|
41
|
+
)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Example API for a AG-UI compatible Pydantic AI Agent UI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
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
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
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',
|
|
19
|
+
]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Agentic Chat feature."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from zoneinfo import ZoneInfo
|
|
7
|
+
|
|
8
|
+
from pydantic_ai import Agent
|
|
9
|
+
from pydantic_ai.ui.ag_ui.app import AGUIApp
|
|
10
|
+
|
|
11
|
+
agent = Agent('openai:gpt-5-mini')
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@agent.tool_plain
|
|
15
|
+
async def current_time(timezone: str = 'UTC') -> str:
|
|
16
|
+
"""Get the current time in ISO format.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
timezone: The timezone to use.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
The current time in ISO format string.
|
|
23
|
+
"""
|
|
24
|
+
tz: ZoneInfo = ZoneInfo(timezone)
|
|
25
|
+
return datetime.now(tz=tz).isoformat()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
app = AGUIApp(agent)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Agentic Generative UI feature."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from textwrap import dedent
|
|
6
|
+
from typing import Any, Literal
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
from ag_ui.core import EventType, StateDeltaEvent, StateSnapshotEvent
|
|
11
|
+
from pydantic_ai import Agent
|
|
12
|
+
from pydantic_ai.ui.ag_ui.app import AGUIApp
|
|
13
|
+
|
|
14
|
+
StepStatus = Literal['pending', 'completed']
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Step(BaseModel):
|
|
18
|
+
"""Represents a step in a plan."""
|
|
19
|
+
|
|
20
|
+
description: str = Field(description='The description of the step')
|
|
21
|
+
status: StepStatus = Field(
|
|
22
|
+
default='pending',
|
|
23
|
+
description='The status of the step (e.g., pending, completed)',
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Plan(BaseModel):
|
|
28
|
+
"""Represents a plan with multiple steps."""
|
|
29
|
+
|
|
30
|
+
steps: list[Step] = Field(default_factory=list, description='The steps in the plan')
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class JSONPatchOp(BaseModel):
|
|
34
|
+
"""A class representing a JSON Patch operation (RFC 6902)."""
|
|
35
|
+
|
|
36
|
+
op: Literal['add', 'remove', 'replace', 'move', 'copy', 'test'] = Field(
|
|
37
|
+
description='The operation to perform: add, remove, replace, move, copy, or test',
|
|
38
|
+
)
|
|
39
|
+
path: str = Field(description='JSON Pointer (RFC 6901) to the target location')
|
|
40
|
+
value: Any = Field(
|
|
41
|
+
default=None,
|
|
42
|
+
description='The value to apply (for add, replace operations)',
|
|
43
|
+
)
|
|
44
|
+
from_: str | None = Field(
|
|
45
|
+
default=None,
|
|
46
|
+
alias='from',
|
|
47
|
+
description='Source path (for move, copy operations)',
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
agent = Agent(
|
|
52
|
+
'openai:gpt-5-mini',
|
|
53
|
+
instructions=dedent(
|
|
54
|
+
"""
|
|
55
|
+
When planning use tools only, without any other messages.
|
|
56
|
+
IMPORTANT:
|
|
57
|
+
- Use the `create_plan` tool to set the initial state of the steps
|
|
58
|
+
- Use the `update_plan_step` tool to update the status of each step
|
|
59
|
+
- Do NOT repeat the plan or summarise it in a message
|
|
60
|
+
- Do NOT confirm the creation or updates in a message
|
|
61
|
+
- Do NOT ask the user for additional information or next steps
|
|
62
|
+
|
|
63
|
+
Only one plan can be active at a time, so do not call the `create_plan` tool
|
|
64
|
+
again until all the steps in current plan are completed.
|
|
65
|
+
"""
|
|
66
|
+
),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@agent.tool_plain
|
|
71
|
+
async def create_plan(steps: list[str]) -> StateSnapshotEvent:
|
|
72
|
+
"""Create a plan with multiple steps.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
steps: List of step descriptions to create the plan.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
StateSnapshotEvent containing the initial state of the steps.
|
|
79
|
+
"""
|
|
80
|
+
plan: Plan = Plan(
|
|
81
|
+
steps=[Step(description=step) for step in steps],
|
|
82
|
+
)
|
|
83
|
+
return StateSnapshotEvent(
|
|
84
|
+
type=EventType.STATE_SNAPSHOT,
|
|
85
|
+
snapshot=plan.model_dump(),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@agent.tool_plain
|
|
90
|
+
async def update_plan_step(
|
|
91
|
+
index: int, description: str | None = None, status: StepStatus | None = None
|
|
92
|
+
) -> StateDeltaEvent:
|
|
93
|
+
"""Update the plan with new steps or changes.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
index: The index of the step to update.
|
|
97
|
+
description: The new description for the step.
|
|
98
|
+
status: The new status for the step.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
StateDeltaEvent containing the changes made to the plan.
|
|
102
|
+
"""
|
|
103
|
+
changes: list[JSONPatchOp] = []
|
|
104
|
+
if description is not None:
|
|
105
|
+
changes.append(
|
|
106
|
+
JSONPatchOp(
|
|
107
|
+
op='replace', path=f'/steps/{index}/description', value=description
|
|
108
|
+
)
|
|
109
|
+
)
|
|
110
|
+
if status is not None:
|
|
111
|
+
changes.append(
|
|
112
|
+
JSONPatchOp(op='replace', path=f'/steps/{index}/status', value=status)
|
|
113
|
+
)
|
|
114
|
+
return StateDeltaEvent(
|
|
115
|
+
type=EventType.STATE_DELTA,
|
|
116
|
+
delta=changes,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
app = AGUIApp(agent)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Human in the Loop Feature.
|
|
2
|
+
|
|
3
|
+
No special handling is required for this feature.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from textwrap import dedent
|
|
9
|
+
|
|
10
|
+
from pydantic_ai import Agent
|
|
11
|
+
from pydantic_ai.ui.ag_ui.app import AGUIApp
|
|
12
|
+
|
|
13
|
+
agent = Agent(
|
|
14
|
+
'openai:gpt-5-mini',
|
|
15
|
+
instructions=dedent(
|
|
16
|
+
"""
|
|
17
|
+
When planning tasks use tools only, without any other messages.
|
|
18
|
+
IMPORTANT:
|
|
19
|
+
- Use the `generate_task_steps` tool to display the suggested steps to the user
|
|
20
|
+
- Never repeat the plan, or send a message detailing steps
|
|
21
|
+
- If accepted, confirm the creation of the plan and the number of selected (enabled) steps only
|
|
22
|
+
- If not accepted, ask the user for more information, DO NOT use the `generate_task_steps` tool again
|
|
23
|
+
"""
|
|
24
|
+
),
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
app = AGUIApp(agent)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Predictive State feature."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from textwrap import dedent
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
|
|
9
|
+
from ag_ui.core import CustomEvent, EventType
|
|
10
|
+
from pydantic_ai import Agent, RunContext
|
|
11
|
+
from pydantic_ai.ui import StateDeps
|
|
12
|
+
from pydantic_ai.ui.ag_ui.app import AGUIApp
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DocumentState(BaseModel):
|
|
16
|
+
"""State for the document being written."""
|
|
17
|
+
|
|
18
|
+
document: str = ''
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
agent = Agent('openai:gpt-5-mini', deps_type=StateDeps[DocumentState])
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Tools which return AG-UI events will be sent to the client as part of the
|
|
25
|
+
# event stream, single events and iterables of events are supported.
|
|
26
|
+
@agent.tool_plain
|
|
27
|
+
async def document_predict_state() -> list[CustomEvent]:
|
|
28
|
+
"""Enable document state prediction.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
CustomEvent containing the event to enable state prediction.
|
|
32
|
+
"""
|
|
33
|
+
return [
|
|
34
|
+
CustomEvent(
|
|
35
|
+
type=EventType.CUSTOM,
|
|
36
|
+
name='PredictState',
|
|
37
|
+
value=[
|
|
38
|
+
{
|
|
39
|
+
'state_key': 'document',
|
|
40
|
+
'tool': 'write_document',
|
|
41
|
+
'tool_argument': 'document',
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
),
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@agent.instructions()
|
|
49
|
+
async def story_instructions(ctx: RunContext[StateDeps[DocumentState]]) -> str:
|
|
50
|
+
"""Provide instructions for writing document if present.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
ctx: The run context containing document state information.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Instructions string for the document writing agent.
|
|
57
|
+
"""
|
|
58
|
+
return dedent(
|
|
59
|
+
f"""You are a helpful assistant for writing documents.
|
|
60
|
+
|
|
61
|
+
Before you start writing, you MUST call the `document_predict_state`
|
|
62
|
+
tool to enable state prediction.
|
|
63
|
+
|
|
64
|
+
To present the document to the user for review, you MUST use the
|
|
65
|
+
`write_document` tool.
|
|
66
|
+
|
|
67
|
+
When you have written the document, DO NOT repeat it as a message.
|
|
68
|
+
If accepted briefly summarize the changes you made, 2 sentences
|
|
69
|
+
max, otherwise ask the user to clarify what they want to change.
|
|
70
|
+
|
|
71
|
+
This is the current document:
|
|
72
|
+
|
|
73
|
+
{ctx.deps.state.document}
|
|
74
|
+
"""
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
app = AGUIApp(agent, deps=StateDeps(DocumentState()))
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""Shared State feature."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from textwrap import dedent
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
from ag_ui.core import EventType, StateSnapshotEvent
|
|
11
|
+
from pydantic_ai import Agent, RunContext
|
|
12
|
+
from pydantic_ai.ui import StateDeps
|
|
13
|
+
from pydantic_ai.ui.ag_ui.app import AGUIApp
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SkillLevel(str, Enum):
|
|
17
|
+
"""The level of skill required for the recipe."""
|
|
18
|
+
|
|
19
|
+
BEGINNER = 'Beginner'
|
|
20
|
+
INTERMEDIATE = 'Intermediate'
|
|
21
|
+
ADVANCED = 'Advanced'
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class SpecialPreferences(str, Enum):
|
|
25
|
+
"""Special preferences for the recipe."""
|
|
26
|
+
|
|
27
|
+
HIGH_PROTEIN = 'High Protein'
|
|
28
|
+
LOW_CARB = 'Low Carb'
|
|
29
|
+
SPICY = 'Spicy'
|
|
30
|
+
BUDGET_FRIENDLY = 'Budget-Friendly'
|
|
31
|
+
ONE_POT_MEAL = 'One-Pot Meal'
|
|
32
|
+
VEGETARIAN = 'Vegetarian'
|
|
33
|
+
VEGAN = 'Vegan'
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class CookingTime(str, Enum):
|
|
37
|
+
"""The cooking time of the recipe."""
|
|
38
|
+
|
|
39
|
+
FIVE_MIN = '5 min'
|
|
40
|
+
FIFTEEN_MIN = '15 min'
|
|
41
|
+
THIRTY_MIN = '30 min'
|
|
42
|
+
FORTY_FIVE_MIN = '45 min'
|
|
43
|
+
SIXTY_PLUS_MIN = '60+ min'
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class Ingredient(BaseModel):
|
|
47
|
+
"""A class representing an ingredient in a recipe."""
|
|
48
|
+
|
|
49
|
+
icon: str = Field(
|
|
50
|
+
default='ingredient',
|
|
51
|
+
description="The icon emoji (not emoji code like '\x1f35e', but the actual emoji like 🥕) of the ingredient",
|
|
52
|
+
)
|
|
53
|
+
name: str
|
|
54
|
+
amount: str
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class Recipe(BaseModel):
|
|
58
|
+
"""A class representing a recipe."""
|
|
59
|
+
|
|
60
|
+
skill_level: SkillLevel = Field(
|
|
61
|
+
default=SkillLevel.BEGINNER,
|
|
62
|
+
description='The skill level required for the recipe',
|
|
63
|
+
)
|
|
64
|
+
special_preferences: list[SpecialPreferences] = Field(
|
|
65
|
+
default_factory=list,
|
|
66
|
+
description='Any special preferences for the recipe',
|
|
67
|
+
)
|
|
68
|
+
cooking_time: CookingTime = Field(
|
|
69
|
+
default=CookingTime.FIVE_MIN, description='The cooking time of the recipe'
|
|
70
|
+
)
|
|
71
|
+
ingredients: list[Ingredient] = Field(
|
|
72
|
+
default_factory=list,
|
|
73
|
+
description='Ingredients for the recipe',
|
|
74
|
+
)
|
|
75
|
+
instructions: list[str] = Field(
|
|
76
|
+
default_factory=list, description='Instructions for the recipe'
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class RecipeSnapshot(BaseModel):
|
|
81
|
+
"""A class representing the state of the recipe."""
|
|
82
|
+
|
|
83
|
+
recipe: Recipe = Field(
|
|
84
|
+
default_factory=Recipe, description='The current state of the recipe'
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
agent = Agent('openai:gpt-5-mini', deps_type=StateDeps[RecipeSnapshot])
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@agent.tool_plain
|
|
92
|
+
async def display_recipe(recipe: Recipe) -> StateSnapshotEvent:
|
|
93
|
+
"""Display the recipe to the user.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
recipe: The recipe to display.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
StateSnapshotEvent containing the recipe snapshot.
|
|
100
|
+
"""
|
|
101
|
+
return StateSnapshotEvent(
|
|
102
|
+
type=EventType.STATE_SNAPSHOT,
|
|
103
|
+
snapshot={'recipe': recipe},
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@agent.instructions
|
|
108
|
+
async def recipe_instructions(ctx: RunContext[StateDeps[RecipeSnapshot]]) -> str:
|
|
109
|
+
"""Instructions for the recipe generation agent.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
ctx: The run context containing recipe state information.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Instructions string for the recipe generation agent.
|
|
116
|
+
"""
|
|
117
|
+
return dedent(
|
|
118
|
+
f"""
|
|
119
|
+
You are a helpful assistant for creating recipes.
|
|
120
|
+
|
|
121
|
+
IMPORTANT:
|
|
122
|
+
- Create a complete recipe using the existing ingredients
|
|
123
|
+
- Append new ingredients to the existing ones
|
|
124
|
+
- Use the `display_recipe` tool to present the recipe to the user
|
|
125
|
+
- Do NOT repeat the recipe in the message, use the tool instead
|
|
126
|
+
- Do NOT run the `display_recipe` tool multiple times in a row
|
|
127
|
+
|
|
128
|
+
Once you have created the updated recipe and displayed it to the user,
|
|
129
|
+
summarise the changes in one sentence, don't describe the recipe in
|
|
130
|
+
detail or send it as a message to the user.
|
|
131
|
+
|
|
132
|
+
The current state of the recipe is:
|
|
133
|
+
|
|
134
|
+
{ctx.deps.state.recipe.model_dump_json(indent=2)}
|
|
135
|
+
""",
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
app = AGUIApp(agent, deps=StateDeps(RecipeSnapshot()))
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Tool Based Generative UI feature.
|
|
2
|
+
|
|
3
|
+
No special handling is required for this feature.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from pydantic_ai import Agent
|
|
9
|
+
from pydantic_ai.ui.ag_ui.app import AGUIApp
|
|
10
|
+
|
|
11
|
+
agent = Agent('openai:gpt-5-mini')
|
|
12
|
+
app = AGUIApp(agent)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Small but complete example of using
|
|
1
|
+
"""Small but complete example of using Pydantic AI to build a support agent for a bank.
|
|
2
2
|
|
|
3
3
|
Run with:
|
|
4
4
|
|
|
@@ -7,7 +7,7 @@ Run with:
|
|
|
7
7
|
|
|
8
8
|
from dataclasses import dataclass
|
|
9
9
|
|
|
10
|
-
from pydantic import BaseModel
|
|
10
|
+
from pydantic import BaseModel
|
|
11
11
|
|
|
12
12
|
from pydantic_ai import Agent, RunContext
|
|
13
13
|
|
|
@@ -26,8 +26,11 @@ class DatabaseConn:
|
|
|
26
26
|
|
|
27
27
|
@classmethod
|
|
28
28
|
async def customer_balance(cls, *, id: int, include_pending: bool) -> float:
|
|
29
|
-
if id == 123
|
|
30
|
-
|
|
29
|
+
if id == 123:
|
|
30
|
+
if include_pending:
|
|
31
|
+
return 123.45
|
|
32
|
+
else:
|
|
33
|
+
return 100.00
|
|
31
34
|
else:
|
|
32
35
|
raise ValueError('Customer not found')
|
|
33
36
|
|
|
@@ -39,16 +42,19 @@ class SupportDependencies:
|
|
|
39
42
|
|
|
40
43
|
|
|
41
44
|
class SupportOutput(BaseModel):
|
|
42
|
-
support_advice: str
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
support_advice: str
|
|
46
|
+
"""Advice returned to the customer"""
|
|
47
|
+
block_card: bool
|
|
48
|
+
"""Whether to block their card or not"""
|
|
49
|
+
risk: int
|
|
50
|
+
"""Risk level of query"""
|
|
45
51
|
|
|
46
52
|
|
|
47
53
|
support_agent = Agent(
|
|
48
|
-
'openai:gpt-
|
|
54
|
+
'openai:gpt-5',
|
|
49
55
|
deps_type=SupportDependencies,
|
|
50
56
|
output_type=SupportOutput,
|
|
51
|
-
|
|
57
|
+
instructions=(
|
|
52
58
|
'You are a support agent in our bank, give the '
|
|
53
59
|
'customer support and judge the risk level of their query. '
|
|
54
60
|
"Reply using the customer's name."
|
|
@@ -56,7 +62,7 @@ support_agent = Agent(
|
|
|
56
62
|
)
|
|
57
63
|
|
|
58
64
|
|
|
59
|
-
@support_agent.
|
|
65
|
+
@support_agent.instructions
|
|
60
66
|
async def add_customer_name(ctx: RunContext[SupportDependencies]) -> str:
|
|
61
67
|
customer_name = await ctx.deps.db.customer_name(id=ctx.deps.customer_id)
|
|
62
68
|
return f"The customer's name is {customer_name!r}"
|
pydantic_ai_examples/chat_app.py
CHANGED
|
@@ -10,14 +10,14 @@ from __future__ import annotations as _annotations
|
|
|
10
10
|
import asyncio
|
|
11
11
|
import json
|
|
12
12
|
import sqlite3
|
|
13
|
-
from collections.abc import AsyncIterator
|
|
13
|
+
from collections.abc import AsyncIterator, Callable
|
|
14
14
|
from concurrent.futures.thread import ThreadPoolExecutor
|
|
15
15
|
from contextlib import asynccontextmanager
|
|
16
16
|
from dataclasses import dataclass
|
|
17
17
|
from datetime import datetime, timezone
|
|
18
18
|
from functools import partial
|
|
19
19
|
from pathlib import Path
|
|
20
|
-
from typing import Annotated, Any,
|
|
20
|
+
from typing import Annotated, Any, Literal, TypeVar
|
|
21
21
|
|
|
22
22
|
import fastapi
|
|
23
23
|
import logfire
|
|
@@ -25,21 +25,22 @@ from fastapi import Depends, Request
|
|
|
25
25
|
from fastapi.responses import FileResponse, Response, StreamingResponse
|
|
26
26
|
from typing_extensions import LiteralString, ParamSpec, TypedDict
|
|
27
27
|
|
|
28
|
-
from pydantic_ai import
|
|
29
|
-
|
|
30
|
-
from pydantic_ai.messages import (
|
|
28
|
+
from pydantic_ai import (
|
|
29
|
+
Agent,
|
|
31
30
|
ModelMessage,
|
|
32
31
|
ModelMessagesTypeAdapter,
|
|
33
32
|
ModelRequest,
|
|
34
33
|
ModelResponse,
|
|
35
34
|
TextPart,
|
|
35
|
+
UnexpectedModelBehavior,
|
|
36
36
|
UserPromptPart,
|
|
37
37
|
)
|
|
38
38
|
|
|
39
39
|
# 'if-token-present' means nothing will be sent (and the example will work) if you don't have logfire configured
|
|
40
40
|
logfire.configure(send_to_logfire='if-token-present')
|
|
41
|
+
logfire.instrument_pydantic_ai()
|
|
41
42
|
|
|
42
|
-
agent = Agent('openai:gpt-
|
|
43
|
+
agent = Agent('openai:gpt-5')
|
|
43
44
|
THIS_DIR = Path(__file__).parent
|
|
44
45
|
|
|
45
46
|
|
|
@@ -126,7 +127,7 @@ async def post_chat(
|
|
|
126
127
|
messages = await database.get_messages()
|
|
127
128
|
# run the agent with the user prompt and the chat history
|
|
128
129
|
async with agent.run_stream(prompt, message_history=messages) as result:
|
|
129
|
-
async for text in result.
|
|
130
|
+
async for text in result.stream_output(debounce_by=0.01):
|
|
130
131
|
# text here is a `str` and the frontend wants
|
|
131
132
|
# JSON encoded ModelResponse, so we create one
|
|
132
133
|
m = ModelResponse(parts=[TextPart(text)], timestamp=result.timestamp())
|