Files
pydantic--pydantic-ai/examples/pydantic_ai_examples/ag_ui/api/shared_state.py
T
wehub-resource-sync 9201ef759e
Harness Compat / harness compat (push) Failing after 0s
CI / test on 3.12 (standard) (push) Has been cancelled
CI / test on 3.13 (standard) (push) Has been cancelled
CI / test on 3.14 (standard) (push) Has been cancelled
CI / test on 3.10 (all-extras) (push) Has been cancelled
CI / test on 3.11 (all-extras) (push) Has been cancelled
CI / test on 3.12 (all-extras) (push) Has been cancelled
CI / test on 3.14 (pydantic-ai-slim) (push) Has been cancelled
CI / test on 3.10 (pydantic-evals) (push) Has been cancelled
CI / test on 3.11 (pydantic-evals) (push) Has been cancelled
CI / test on 3.12 (pydantic-evals) (push) Has been cancelled
CI / deploy-docs-preview (push) Has been cancelled
CI / build release artifacts (push) Has been cancelled
CI / publish to PyPI (push) Has been cancelled
CI / Send tweet (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / mypy (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / test on 3.10 (standard) (push) Has been cancelled
CI / test on 3.11 (standard) (push) Has been cancelled
CI / test on 3.13 (all-extras) (push) Has been cancelled
CI / test on 3.14 (all-extras) (push) Has been cancelled
CI / test on 3.10 (pydantic-ai-slim) (push) Has been cancelled
CI / test on 3.11 (pydantic-ai-slim) (push) Has been cancelled
CI / test on 3.12 (pydantic-ai-slim) (push) Has been cancelled
CI / test on 3.13 (pydantic-ai-slim) (push) Has been cancelled
CI / test on 3.13 (pydantic-evals) (push) Has been cancelled
CI / test on 3.14 (pydantic-evals) (push) Has been cancelled
CI / test on 3.10 (lowest-versions) (push) Has been cancelled
CI / test on 3.11 (lowest-versions) (push) Has been cancelled
CI / test on 3.12 (lowest-versions) (push) Has been cancelled
CI / test on 3.13 (lowest-versions) (push) Has been cancelled
CI / test on 3.14 (lowest-versions) (push) Has been cancelled
CI / test examples on 3.11 (push) Has been cancelled
CI / test examples on 3.12 (push) Has been cancelled
CI / test examples on 3.13 (push) Has been cancelled
CI / test examples on 3.14 (push) Has been cancelled
CI / coverage (push) Has been cancelled
CI / check (push) Has been cancelled
CI / deploy-docs (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:27:52 +08:00

153 lines
4.3 KiB
Python

"""Shared State feature."""
from __future__ import annotations
from dataclasses import replace
from enum import Enum
from textwrap import dedent
from pydantic import BaseModel, Field
from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import Response
from starlette.routing import Route
from ag_ui.core import EventType, StateSnapshotEvent
from pydantic_ai import Agent, RunContext
from pydantic_ai.ui import StateDeps
from pydantic_ai.ui.ag_ui import AGUIAdapter
class SkillLevel(str, Enum):
"""The level of skill required for the recipe."""
BEGINNER = 'Beginner'
INTERMEDIATE = 'Intermediate'
ADVANCED = 'Advanced'
class SpecialPreferences(str, Enum):
"""Special preferences for the recipe."""
HIGH_PROTEIN = 'High Protein'
LOW_CARB = 'Low Carb'
SPICY = 'Spicy'
BUDGET_FRIENDLY = 'Budget-Friendly'
ONE_POT_MEAL = 'One-Pot Meal'
VEGETARIAN = 'Vegetarian'
VEGAN = 'Vegan'
class CookingTime(str, Enum):
"""The cooking time of the recipe."""
FIVE_MIN = '5 min'
FIFTEEN_MIN = '15 min'
THIRTY_MIN = '30 min'
FORTY_FIVE_MIN = '45 min'
SIXTY_PLUS_MIN = '60+ min'
class Ingredient(BaseModel):
"""A class representing an ingredient in a recipe."""
icon: str = Field(
default='ingredient',
description="The icon emoji (not emoji code like '\x1f35e', but the actual emoji like 🥕) of the ingredient",
)
name: str
amount: str
class Recipe(BaseModel):
"""A class representing a recipe."""
skill_level: SkillLevel = Field(
default=SkillLevel.BEGINNER,
description='The skill level required for the recipe',
)
special_preferences: list[SpecialPreferences] = Field(
default_factory=list[SpecialPreferences],
description='Any special preferences for the recipe',
)
cooking_time: CookingTime = Field(
default=CookingTime.FIVE_MIN, description='The cooking time of the recipe'
)
ingredients: list[Ingredient] = Field(
default_factory=list[Ingredient],
description='Ingredients for the recipe',
)
instructions: list[str] = Field(
default_factory=list[str], description='Instructions for the recipe'
)
class RecipeSnapshot(BaseModel):
"""A class representing the state of the recipe."""
recipe: Recipe = Field(
default_factory=Recipe, description='The current state of the recipe'
)
agent = Agent('openai:gpt-5-mini', deps_type=StateDeps[RecipeSnapshot])
@agent.tool_plain
async def display_recipe(recipe: Recipe) -> StateSnapshotEvent:
"""Display the recipe to the user.
Args:
recipe: The recipe to display.
Returns:
StateSnapshotEvent containing the recipe snapshot.
"""
return StateSnapshotEvent(
type=EventType.STATE_SNAPSHOT,
snapshot={'recipe': recipe},
)
@agent.instructions
async def recipe_instructions(ctx: RunContext[StateDeps[RecipeSnapshot]]) -> str:
"""Instructions for the recipe generation agent.
Args:
ctx: The run context containing recipe state information.
Returns:
Instructions string for the recipe generation agent.
"""
return dedent(
f"""
You are a helpful assistant for creating recipes.
IMPORTANT:
- Create a complete recipe using the existing ingredients
- Append new ingredients to the existing ones
- Use the `display_recipe` tool to present the recipe to the user
- Do NOT repeat the recipe in the message, use the tool instead
- Do NOT run the `display_recipe` tool multiple times in a row
Once you have created the updated recipe and displayed it to the user,
summarise the changes in one sentence, don't describe the recipe in
detail or send it as a message to the user.
The current state of the recipe is:
{ctx.deps.state.recipe.model_dump_json(indent=2)}
""",
)
deps = StateDeps(RecipeSnapshot())
async def run_agent(request: Request) -> Response:
# `dispatch_request` mutates `deps.state` from the request, so give each request its own copy.
return await AGUIAdapter.dispatch_request(request, agent=agent, deps=replace(deps))
app = Starlette(routes=[Route('/', run_agent, methods=['POST'])])