ืืืฉืืง ืืืืงืก ืขืืืื ืืืื ื ืฉืกืืื ืื ืืืืื ืืืืืื ืืืฆืข ืคืขืืืืช ืขื ืืืกื. ืืืื ื ืืงื ืืช ืืจืขืืื ืืื ืฆืขื ืืืื ืงืืืื ืื ืื ื ืืขืจืืช ืขื ืืืฉืง ืืคืื - ืกืืื ืืืืฉืง ืืจืคื. ืืฉืชืืฉืื ืืืืืื ืืขืืื ืขื ืืืขืจืืช ืืื ืืื ืืฉื ื ืืืืฉืงืื ืืฉื ืืื ืชืืื ืืชืืืืื.
โ ืื ืื ืื ื ืืื ืื
ืืืคืืืงืฆืื ืฉื ืื ื ืืื ืืคืืืงืฆืืืช ืฆืืืจ ืืืืกืก ืชืื ืืืช. ืืฉ ืฆืืืจืื ืงืื ืื ืืฆื ืฉืืื ืฉื ืงืฉืช ืืขื ื, ืื ืงืจื, ืืืช, ืืืื, ืขืฅ, ืื, ืขืืืื ืืคืจื ืืคืืืช ืฆืืขืื ืืืืง ืืขืืืื ืฉื ืืขืืื. ืืฉืชืืฉืื ืืืืืื ืืืจืืจ ืชืื ืืช ืืืื ืืขืืืื ืืืจืืื, ืฉื ืื ืืืืื ืืืจืืจ ืืช ืืฆืืจื, ืืฉื ืืช ืื ืืช ืืืืื ืื ืืืืืจ ืื ืฆืืข ืืืจ. ืขื ืคื ืืคืืืงืฆืืืช Front End ืจืืืื ืืืืจื.
ืืืืืืฉ ืืื ืฉืื ืืกืฃ ืืื ืื ืืฉ ืื ืชืืืช ืฉืืื ืขื ืกืืื ืืชืืืช ืืฆื ืืืื ืืช. ืืฉืชืืฉืื ืืืืืื ืืฉืืื ืืช ืืกืืื ืื ืืฆืืืจ ืขื ืืืื, ืืฉืืื ืื ืืขืชื ืขื ืืฆืืืจ ืืื ืืืงืฉ ืฉืื ืืืื ืืืฉื ืืืงืฉ ืืืกืืื ืฉืืืกืืฃ ืืืช, ืื ืงืจื ืื ืฉืืกืืจ ืืช ืืฆืืขืื. ืืกืืื ืืืืฉืชืืฉ ืืฉ ืืืฉื ืืืืืง ืืืืชื ืืคืขืืืืช ืืื ืจืืืื ืชืืื ืืช ืืืชื ืืฆืืืจ. ืื ื ืืฆืจ ืฉืืชืืฃ ืคืขืืื ืืื ืืฉืชืืฉืื ืืกืืื ืื ืืืืื ืฉืืืกืืฃ ืงืกื ืืืขืจืืืช ืฉืื ื.
โ ืืื ืืคืขืืืื
ืงืื ืืืืืื ื ืืฆื ืืชืืงืืืช ืืืืืืืืช:
https://github.com/ynonp/pydanticai-demos/tree/main/06-ai-painter
ืืืื ืืืจืื ืืืคืืืงืฆืืืช Vue ืืืฉืจืช Fastapi ืืคืืืชืื. ืืฉืืื ืืืคืขืื ื ืฆืืจื ืฉื ื ืืืื ืืช. ืืืืื ืืื ืืคืขืืืื ืืช ืืคืืืชืื:
uv run main.py
ืืืืืื ืฉื ื ืืคืขืืืื ืืช ืืคืืืงืฆืืืช ืืคืจืื ืืื ื:
cd frontend
npm run dev
ืืืืจ ืืื ื ืื ืกืื ื localhost:5173 ืืื ืืจืืืช ืืช ืืืืฉืง.
โ ืืื ืื ืขืืื
ืื ืื ืื ืฉืืชืืฃ ืืคืขืืื ืื ืื ืืฉื ื ืืืงืื: ืืจืืฉืื ืืื ืฉืืชืืฃ ืืืืืข ืืืฉื ื ืืื ืืื ืืคืขืืช ืืืืื.
ืืื ืืืืขื ืืฆื ืืืงืื ืืกืืื ืื ื ืืฆืจืคืื ืืืืืขื ืขืืชืง ืฉื ืืื ืืฆืืืจ ืืืืืจ ื Endpoint ืฉื ืืืืขื ืืงืื:
class ChatRequest(BaseModel):
message: str
canvas: CanvasState
# Opaque pydantic-ai message history, exactly as returned by the previous
# /api/chat response. Empty on a fresh page load.
history: list[Any] = Field(default_factory=list)
class CanvasState(BaseModel):
"""The full canvas: its dimensions plus every shape on it."""
width: int = CANVAS_WIDTH
height: int = CANVAS_HEIGHT
shapes: list[Shape] = Field(default_factory=list)
class Shape(BaseModel):
"""A single shape placed on the canvas."""
# Re-validate on assignment so the tools' `shape.size = ...` mutations go
# through the same clamp as construction (keeps server + browser in sync).
model_config = ConfigDict(validate_assignment=True)
id: str
type: ShapeType
x: int = Field(description="Left position in pixels (0 = left edge).")
y: int = Field(description="Top position in pixels (0 = top edge).")
size: int = Field(default=DEFAULT_SHAPE_SIZE, description="Width/height in pixels.")
color: str = Field(default=DEFAULT_SHAPE_COLOR, description="CSS color, e.g. '#ff0000'.")
@field_validator("size")
@classmethod
def _clamp_size(cls, value: int) -> int:
# Mirror the frontend store: never smaller than MIN_SHAPE_SIZE.
return max(MIN_SHAPE_SIZE, value)
ืืื ืืฆืืืจ ืขืืืจ ืืกืืื ืืืืฆืขืืช ืืคืื ืงืฆืื
add_canvas_state ืฉืืืืืจืช ืืงืืืฅ agent.py:@agent.instructions
def add_canvas_state(ctx: RunContext[PainterDeps]) -> str:
canvas = ctx.deps.canvas
return (
f"Canvas size: {canvas.width}x{canvas.height} pixels.\n"
f"Current canvas state (JSON):\n{canvas.model_dump_json(indent=2)}"
)
ืืคืขืืืืช ืฉืกืืื ืืืื ืืขืฉืืช ืขืืืจืืช ืืืืฆืขืืช Tool Calls ืืืืืื ืืฉืืื ืืืืกืืฃ ืฆืืจื ืืกืืื ืืคืขืื ืืช ืืืื:
@agent.tool
async def add_shape(
ctx: RunContext[PainterDeps],
type: ShapeType,
x: int,
y: int,
size: int = DEFAULT_SHAPE_SIZE,
color: str = DEFAULT_SHAPE_COLOR,
) -> str:
"""Add a new shape to the canvas. Returns the new shape's id."""
shape = Shape(id=uuid.uuid4().hex, type=type, x=x, y=y, size=size, color=color)
ctx.deps.canvas.shapes.append(shape)
return f"Added {type} (id={shape.id}) at ({x}, {y})."
ืฉืืื ืื ืฉืืื ืงืืจื ืืฆื ืืฉืจืช ืืคืืืชืื. ืืืกืคืช ืฆืืจื ืืื ืืกื ืืื ืืืกืคื ืฉื ืืืืืงื ืืืขืจื. ืฉืื ืื ืฆืืข ืฉื ืฆืืจื ืืืื ืืกื ืืื ืคืขืืืช ืืฉืื ืืฉืื ืืืืืืงื:
@agent.tool
async def recolor_shape(ctx: RunContext[PainterDeps], id: str, color: str) -> str:
