Skip to content
Learn Agentic AI
Learn Agentic AI14 min read17 views

Build a Recipe Finder Agent: Ingredient Matching, Dietary Filters, and Cooking Instructions

Build an AI-powered recipe finder agent that matches recipes to available ingredients, respects dietary restrictions, provides step-by-step cooking instructions, and suggests ingredient substitutions.

The Problem With Finding Recipes

You open the fridge, see half a dozen ingredients, and then spend twenty minutes scrolling through recipe websites filled with ads trying to find something that uses what you already have. A recipe finder agent solves this by taking your available ingredients, applying dietary filters, and returning matching recipes with full cooking instructions — all through a single conversational prompt.

This tutorial builds a complete recipe finder agent with an in-memory recipe database, fuzzy ingredient matching, dietary filtering, substitution suggestions, and step-by-step guidance.

Project Structure

mkdir recipe-agent && cd recipe-agent
python -m venv venv && source venv/bin/activate
pip install openai-agents pydantic
mkdir -p src
touch src/__init__.py src/recipes_db.py src/matcher.py src/agent.py

Step 1: Build the Recipe Database

We store recipes as structured Pydantic models with ingredients, tags for dietary info, and ordered cooking steps.

flowchart LR
    INPUT(["User intent"])
    PARSE["Parse plus<br/>classify"]
    PLAN["Plan and tool<br/>selection"]
    AGENT["Agent loop<br/>LLM plus tools"]
    GUARD{"Guardrails<br/>and policy"}
    EXEC["Execute and<br/>verify result"]
    OBS[("Trace and metrics")]
    OUT(["Outcome plus<br/>next action"])
    INPUT --> PARSE --> PLAN --> AGENT --> GUARD
    GUARD -->|Pass| EXEC --> OUT
    GUARD -->|Fail| AGENT
    AGENT --> OBS
    style AGENT fill:#4f46e5,stroke:#4338ca,color:#fff
    style GUARD fill:#f59e0b,stroke:#d97706,color:#1f2937
    style OBS fill:#ede9fe,stroke:#7c3aed,color:#1e1b4b
    style OUT fill:#059669,stroke:#047857,color:#fff
# src/recipes_db.py
from pydantic import BaseModel

class Ingredient(BaseModel):
    name: str
    amount: str
    unit: str
    optional: bool = False

class Recipe(BaseModel):
    id: str
    title: str
    tags: list[str]  # e.g., ["vegetarian", "gluten-free"]
    prep_time: int    # minutes
    cook_time: int
    servings: int
    ingredients: list[Ingredient]
    steps: list[str]
    substitutions: dict[str, str]  # ingredient -> substitute

RECIPE_DB: list[Recipe] = [
    Recipe(
        id="r001",
        title="Garlic Butter Pasta",
        tags=["vegetarian"],
        prep_time=5, cook_time=15, servings=2,
        ingredients=[
            Ingredient(name="spaghetti", amount="200", unit="g"),
            Ingredient(name="garlic", amount="4", unit="cloves"),
            Ingredient(name="butter", amount="3", unit="tbsp"),
            Ingredient(name="parmesan", amount="50", unit="g"),
            Ingredient(
                name="red pepper flakes",
                amount="1", unit="tsp", optional=True,
            ),
        ],
        steps=[
            "Boil salted water and cook spaghetti until al dente.",
            "Mince garlic and saute in butter over medium heat.",
            "Toss drained pasta with garlic butter.",
            "Top with grated parmesan and pepper flakes.",
        ],
        substitutions={
            "butter": "olive oil for dairy-free",
            "parmesan": "nutritional yeast for vegan",
            "spaghetti": "gluten-free pasta",
        },
    ),
    Recipe(
        id="r002",
        title="Chicken Stir Fry",
        tags=["gluten-free", "high-protein"],
        prep_time=10, cook_time=12, servings=3,
        ingredients=[
            Ingredient(name="chicken breast", amount="400", unit="g"),
            Ingredient(name="broccoli", amount="2", unit="cups"),
            Ingredient(name="soy sauce", amount="3", unit="tbsp"),
            Ingredient(name="garlic", amount="3", unit="cloves"),
            Ingredient(name="ginger", amount="1", unit="tbsp"),
            Ingredient(name="sesame oil", amount="1", unit="tbsp"),
        ],
        steps=[
            "Slice chicken into thin strips and season with salt.",
            "Heat sesame oil in a wok over high heat.",
            "Stir-fry chicken until golden, about 5 minutes.",
            "Add broccoli, garlic, and ginger; cook 4 minutes.",
            "Pour soy sauce over everything and toss to coat.",
        ],
        substitutions={
            "chicken breast": "tofu for vegetarian",
            "soy sauce": "coconut aminos for soy-free",
        },
    ),
    Recipe(
        id="r003",
        title="Black Bean Tacos",
        tags=["vegan", "gluten-free"],
        prep_time=10, cook_time=10, servings=4,
        ingredients=[
            Ingredient(name="black beans", amount="400", unit="g"),
            Ingredient(name="corn tortillas", amount="8", unit="pieces"),
            Ingredient(name="avocado", amount="2", unit="whole"),
            Ingredient(name="lime", amount="2", unit="whole"),
            Ingredient(name="cumin", amount="1", unit="tsp"),
            Ingredient(name="salsa", amount="1", unit="cup"),
        ],
        steps=[
            "Drain and rinse black beans, heat in a pan with cumin.",
            "Warm corn tortillas in a dry skillet.",
            "Mash avocado with lime juice and salt.",
            "Assemble tacos with beans, guacamole, and salsa.",
        ],
        substitutions={
            "corn tortillas": "flour tortillas (not gluten-free)",
            "black beans": "pinto beans or lentils",
        },
    ),
]

Step 2: Build the Ingredient Matcher

The matcher scores recipes by how many of the user's available ingredients overlap with what each recipe needs. It supports partial matching and dietary filtering.

Hear it before you finish reading

Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.

Try Live Demo →
# src/matcher.py
from src.recipes_db import Recipe, RECIPE_DB

def normalize(name: str) -> str:
    return name.lower().strip()

def match_recipes(
    available: list[str],
    dietary: list[str] | None = None,
    max_missing: int = 2,
) -> list[dict]:
    available_set = {normalize(i) for i in available}
    results = []

    for recipe in RECIPE_DB:
        # Dietary filter
        if dietary:
            if not all(
                d.lower() in [t.lower() for t in recipe.tags]
                for d in dietary
            ):
                continue

        required = [
            ing for ing in recipe.ingredients if not ing.optional
        ]
        required_names = {normalize(i.name) for i in required}
        matched = required_names & available_set
        missing = required_names - available_set

        if len(missing) <= max_missing:
            subs = {
                m: recipe.substitutions.get(m, "no substitute known")
                for m in missing
            }
            results.append({
                "recipe": recipe,
                "match_pct": round(
                    len(matched) / len(required_names) * 100, 1
                ),
                "missing": list(missing),
                "substitutions": subs,
            })

    results.sort(key=lambda r: r["match_pct"], reverse=True)
    return results

def format_recipe(recipe: Recipe) -> str:
    lines = [f"# {recipe.title}"]
    lines.append(
        f"Prep: {recipe.prep_time}min | Cook: {recipe.cook_time}min "
        f"| Servings: {recipe.servings}"
    )
    lines.append(f"Tags: {', '.join(recipe.tags)}")
    lines.append("\nIngredients:")
    for ing in recipe.ingredients:
        opt = " (optional)" if ing.optional else ""
        lines.append(f"  - {ing.amount} {ing.unit} {ing.name}{opt}")
    lines.append("\nSteps:")
    for i, step in enumerate(recipe.steps, 1):
        lines.append(f"  {i}. {step}")
    return "\n".join(lines)

Step 3: Build the Agent

# src/agent.py
import asyncio
import json
from agents import Agent, Runner, function_tool
from src.matcher import match_recipes, format_recipe

@function_tool
def find_recipes(
    ingredients: str,
    dietary_filters: str = "",
    max_missing: int = 2,
) -> str:
    """Find recipes matching available ingredients.
    ingredients: comma-separated list of what you have.
    dietary_filters: comma-separated dietary tags.
    """
    avail = [i.strip() for i in ingredients.split(",")]
    dietary = (
        [d.strip() for d in dietary_filters.split(",")]
        if dietary_filters else None
    )
    matches = match_recipes(avail, dietary, max_missing)
    if not matches:
        return "No matching recipes found."
    output = []
    for m in matches:
        output.append(format_recipe(m["recipe"]))
        output.append(f"Match: {m['match_pct']}%")
        if m["missing"]:
            output.append(f"Missing: {', '.join(m['missing'])}")
            sub_lines = [
                f"  {k} -> {v}"
                for k, v in m["substitutions"].items()
            ]
            output.append("Substitutions:\n" + "\n".join(sub_lines))
        output.append("---")
    return "\n".join(output)

recipe_agent = Agent(
    name="Recipe Finder",
    instructions="""You are a helpful cooking assistant.
Use the find_recipes tool to search for recipes based on
the user's available ingredients and dietary needs.
Present results clearly with cooking instructions.
Suggest substitutions for missing ingredients.
Ask clarifying questions about allergies or preferences
if the user hasn't specified them.""",
    tools=[find_recipes],
)

async def main():
    result = await Runner.run(
        recipe_agent,
        "I have spaghetti, garlic, butter, and parmesan. "
        "What can I make? I'm vegetarian.",
    )
    print(result.final_output)

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

Running the Agent

python -m src.agent

The agent identifies the garlic butter pasta as a perfect match, shows the full recipe with steps, and notes that no ingredients are missing.

Extending the Project

Scaling the database. Replace the in-memory list with SQLite or PostgreSQL. Add a search_by_tag tool that queries recipes by cuisine type or cooking method.

Fuzzy matching. Use difflib.SequenceMatcher or the rapidfuzz library to handle misspellings — matching "parmesean" to "parmesan" automatically.

Nutritional info. Add a calories, protein, carbs, and fat field to each recipe and create a get_nutrition tool so the agent can factor macros into recommendations.

Still reading? Stop comparing — try CallSphere live.

CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.

FAQ

How would I add hundreds of recipes without defining them all in code?

Store recipes in a JSON file or database and load them at startup. You can also build a scraper tool that pulls recipes from public APIs like Spoonacular or Edamam and converts them into your Recipe model format. The matcher works the same regardless of how many recipes are in the database.

Can the agent handle ingredient amounts and adjust servings?

Yes. Add a scale_recipe tool that takes a recipe ID and target servings, then multiplies each ingredient amount by the ratio of target to original servings. The agent can call this tool after finding a match to present adjusted quantities.

How do I make substitution suggestions smarter?

Replace the static substitutions dictionary with an LLM-based tool. When an ingredient is missing, the agent can call a suggest_substitution tool that sends the recipe context and missing ingredient to the model, getting back contextually appropriate alternatives based on flavor profiles and cooking chemistry.


#RecipeFinder #AIAgent #Python #IngredientMatching #OpenAIAgentsSDK #AgenticAI #LearnAI #AIEngineering

Share

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.

Related Articles You May Like

Agentic AI

Streaming Agent Responses with OpenAI Agents SDK and LangChain in 2026

How to stream tokens, tool-call deltas, and intermediate steps from an agent — with code for both the OpenAI Agents SDK and LangChain — and the gotchas that bite in production.

Agentic AI

Tool Selection Accuracy: The Eval Most Teams Skip — and Should Not (2026)

Your agent picked the wrong tool 12% of the time and the final answer was still right. That's a latent bug. Here's the eval pipeline that surfaces it.

Agentic AI

Parallel Tool Calling in the OpenAI Agents SDK: When It Helps, When It Hurts (2026)

OpenAI's parallel function calling can cut latency in half — or burn money on dependent calls. The architecture, code, and an eval that proves the win.

Agentic AI

Building Your First Agent with the OpenAI Agents SDK in 2026: A Hands-On Walkthrough

Step-by-step build of a working agent with the OpenAI Agents SDK — Agent class, tools, handoffs, tracing — plus an eval pipeline that catches regressions before merge.

Agentic AI

OpenAI Agents SDK vs Assistants API in 2026: Migration Guide with Eval Parity

Honest principal-engineer comparison of the OpenAI Agents SDK and the legacy Assistants API, with a migration checklist and eval-parity strategy so you don't ship regressions.

Agentic AI

Token-Level Evaluation of Streaming Agents: TTFT, Stream Smoothness, and Mid-Stream Hallucination Detection

Streaming changes the eval game — final-answer correctness isn't enough when users perceive the answer one token at a time. Here's the metric set that matters.