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.
# 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.
# 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.
See AI Voice Agents Handle Real Calls
Book a free demo or calculate how much you can save with AI voice automation.
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.
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
CallSphere Team
Expert insights on AI voice agents and customer communication automation.
Try CallSphere AI Voice Agents
See how AI voice agents work for your industry. Live demo available -- no signup required.