---
title: "Recipe Builder"
sidebarTitle: "02 · Recipe Builder"
---

A culinary assistant that rescales a classic carbonara recipe from 2 servings to 6. The Agent is given a custom Python function as a tool and a Pydantic model as its `response_schema`, so the final reply is a fully validated `Recipe` object instead of free-form text.

## What it covers

- Decorating a plain function (`scale_ingredient`) and registering it via `tools=[...]` so the LLM can call it.
- Passing a Pydantic class to `response_schema=` to coerce the model output into a typed object.
- Reading the typed result through `await reply.content(retries=1)` (with a retry on validation failure).

## Primitives covered

- `Agent` with `tools=` and `response_schema=`
- Plain Python `def` as a tool (auto-decorated)
- `pydantic.BaseModel` for structured output
- `reply.content(retries=...)` for schema-validated reads

## Source

```python linenums="1"
"""02 · Recipe builder — tools and structured output

Shows two core Agent features on top of the bare loop:

1. A custom ``@tool`` function the LLM can call (``scale_ingredient``).
2. A Pydantic ``response_schema`` so the final reply is a typed object.

Run::

    .venv-beta/bin/python 02_recipe_builder.py
"""

import asyncio

from pydantic import BaseModel, Field

from autogen.beta import Agent
from autogen.beta.config import GeminiConfig


def section(title: str) -> None:
    print(f"\n── {title} ───")


class Ingredient(BaseModel):
    name: str
    quantity: float
    unit: str


class Recipe(BaseModel):
    title: str = Field(description="Short human title for the recipe.")
    servings: int = Field(description="How many portions this recipe yields.")
    ingredients: list[Ingredient]
    steps: list[str] = Field(description="Ordered preparation steps.")


def scale_ingredient(quantity: float, factor: float) -> float:
    """Return ``quantity`` multiplied by ``factor``, rounded to 2 decimals.

    The model uses this any time it needs to rescale a recipe for a
    different number of servings.
    """
    return round(quantity * factor, 2)


async def main() -> None:
    config = GeminiConfig(model="gemini-3-flash-preview", temperature=0)

    section("Recipe builder — scale an existing dish for 6 servings")

    agent = Agent(
        "chef",
        prompt=(
            "You are a culinary assistant. When asked to rescale a recipe, "
            "use the scale_ingredient tool for every ingredient to compute the "
            "new quantity. Return a complete Recipe object."
        ),
        config=config,
        tools=[scale_ingredient],
        response_schema=Recipe,
    )

    reply = await agent.ask(
        "Start from classic carbonara for 2 servings: 200g spaghetti, 2 eggs, "
        "100g guanciale, 50g pecorino romano. Rescale it for 6 servings and "
        "produce the full Recipe."
    )

    recipe: Recipe | None = await reply.content(retries=1)

    if recipe is None:
        print("Model returned no body — try again.")
        return

    print(f"{recipe.title}  ({recipe.servings} servings)")
    print()
    print("Ingredients:")
    for ing in recipe.ingredients:
        print(f"  - {ing.quantity} {ing.unit} {ing.name}")
    print()
    print("Steps:")
    for i, step in enumerate(recipe.steps, 1):
        print(f"  {i}. {step}")


if __name__ == "__main__":
    asyncio.run(main())
```
