ToCode: post #6024 โ€” TG.ME

๐Ÿ“Œ ื™ื•ื 6: ืฆื™ื•ืจ ืขื ืกื•ื›ืŸ

ื‘ืžืฉื—ืง ื”ืื™ืงืก ืขื™ื’ื•ืœ ืœืžื“ื ื• ืฉืกื•ื›ื ื™ื ื—ื›ืžื™ื ื™ื›ื•ืœื™ื ืœื‘ืฆืข ืคืขื•ืœื•ืช ืขืœ ื”ืžืกืš. ื”ื™ื•ื ื ื™ืงื— ืืช ื”ืจืขื™ื•ืŸ ื”ื–ื” ืฆืขื“ ื’ื“ื•ืœ ืงื“ื™ืžื” ื•ื ื‘ื ื” ืžืขืจื›ืช ืขื ืžืžืฉืง ื›ืคื•ืœ - ืกื•ื›ืŸ ื•ืžืžืฉืง ื’ืจืคื™. ืžืฉืชืžืฉื™ื ื™ื›ื•ืœื™ื ืœืขื‘ื•ื“ ืขื ื”ืžืขืจื›ืช ื‘ื›ืœ ืื—ื“ ืžืฉื ื™ ื”ืžืžืฉืงื™ื ื•ืฉื ื™ื”ื ืชืžื™ื“ ืžืชื•ืืžื™ื.

โœ ืžื” ืื ื—ื ื• ื‘ื•ื ื™ื

ื”ืืคืœื™ืงืฆื™ื” ืฉื ื‘ื ื” ื”ื•ื ืืคืœื™ืงืฆื™ื™ืช ืฆื™ื™ืจ ืžื‘ื•ืกืก ืชื‘ื ื™ื•ืช. ื™ืฉ ืฆื™ื•ืจื™ื ืงื˜ื ื™ื ื‘ืฆื“ ืฉืžืืœ ืฉืœ ืงืฉืช ื‘ืขื ืŸ, ื—ื“ ืงืจืŸ, ื‘ื™ืช, ื›ื•ื›ื‘, ืขืฅ, ืœื‘, ืขื™ื’ื•ืœ ื•ืคืจื— ื•ืคืœื˜ืช ืฆื‘ืขื™ื ื‘ื—ืœืง ื”ืขืœื™ื•ืŸ ืฉืœ ื”ืขืžื•ื“. ืžืฉืชืžืฉื™ื ื™ื›ื•ืœื™ื ืœื’ืจื•ืจ ืชื‘ื ื™ืช ืœืœื•ื— ื”ืขื‘ื•ื“ื” ื”ืžืจื›ื–ื™, ืฉื ื”ื ื™ื•ื›ืœื• ืœื’ืจื•ืจ ืืช ื”ืฆื•ืจื”, ืœืฉื ื•ืช ืœื” ืืช ื”ื’ื•ื“ืœ ืื• ืœื‘ื—ื•ืจ ืœื” ืฆื‘ืข ืื—ืจ. ืขื“ ืคื” ืืคืœื™ืงืฆื™ื™ืช 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:
GitHub
pydanticai-demos/06-ai-painter at main ยท ynonp/pydanticai-demos
Contribute to ynonp/pydanticai-demos development by creating an account on GitHub.
August 18, 2026 86 1