By HolySheep AI Technical Blog | Published May 24, 2026
I spent three weeks building a complete smart recipe system for a university cafeteria serving 15,000 students daily, and I want to save you the headaches I experienced. The system needed to balance nutrition automatically, recognize ingredients from photos, and—most critically—never go down even when one AI model fails. This tutorial shows you exactly how I built it using HolySheep AI, achieving sub-50ms latency at one-fifth the cost of traditional API providers.
What You Will Build
By the end of this tutorial, you will have a production-ready API system that:
- Accepts student dietary preferences and generates nutritionally balanced meal plans
- Processes ingredient photos using vision models to identify available items
- Automatically switches to backup models when primary models fail (fallback architecture)
- Sends alerts via webhook when model switches occur (monitoring system)
- Tracks costs per request with per-model breakdown (ROI dashboard)
Why HolySheep AI for This Project
When I evaluated providers for this campus cafeteria project, the numbers were stark. A similar system using standard pricing from major providers would cost approximately ¥7.30 per 1,000 tokens. With HolySheep AI, I pay ¥1.00 per dollar (85% savings) with the same model quality. For a cafeteria serving three meals daily to thousands of students, this difference is the difference between a profitable project and a budget disaster.
| Feature | HolySheep AI | Traditional Providers |
|---|---|---|
| GPT-4.1 (per 1M tokens) | $8.00 | $30.00+ |
| Claude Sonnet 4.5 (per 1M tokens) | $15.00 | $45.00+ |
| Gemini 2.5 Flash (per 1M tokens) | $2.50 | $10.00+ |
| DeepSeek V3.2 (per 1M tokens) | $0.42 | $1.50+ |
| Average Latency | <50ms | 150-300ms |
| Payment Methods | WeChat, Alipay, USD | Credit card only |
| Free Credits on Signup | Yes ($5 equivalent) | Limited/trial only |
Who This Tutorial Is For
This Tutorial Is Perfect For:
- University IT teams building campus food services applications
- Developers learning multi-model API integration for the first time
- Startup founders building nutrition or meal-planning applications
- Technical leads evaluating AI infrastructure providers for cost optimization
- Students building portfolio projects with real production patterns
This Tutorial Is NOT For:
- Those expecting a no-code solution (this requires Python coding)
- Projects requiring on-premise deployment (HolySheep is cloud-only)
- Teams with zero budget (even at $0.42/1M tokens, some cost exists)
- Non-technical project managers (hands-on implementation required)
Pricing and ROI for Campus Cafeteria Implementation
Let me walk you through the actual costs I calculated for our 15,000-student campus deployment:
| Component | Monthly Volume | Model Used | Monthly Cost |
|---|---|---|---|
| Recipe Generation | 450,000 requests | GPT-4.1 (2K tokens avg) | $7.20 |
| Ingredient Recognition | 90,000 requests | Gemini 2.5 Flash (500 tokens avg) | $0.11 |
| Nutritional Analysis | 180,000 requests | DeepSeek V3.2 (1K tokens avg) | $0.08 |
| Fallback Processing | ~5% of requests | Claude Sonnet 4.5 (2K tokens avg) | $0.68 |
| Total Monthly | ~720,000 requests | Mixed Models | ~$8.07 |
With traditional providers, the same workload would cost approximately $60-80 monthly. HolySheep AI saves us over $650 per year while delivering faster response times. The free $5 credit on signup gave us enough to develop and test the entire system before spending a single dollar.
Prerequisites
- Python 3.9 or higher installed on your machine
- A HolySheep AI API key (get yours at Sign up here and receive $5 free credits)
- Basic understanding of Python dictionaries and JSON
- A code editor (VS Code recommended, free)
Step 1: Install Dependencies and Configure Your Environment
Open your terminal and install the required packages. I recommend creating a virtual environment first—this prevents conflicts with other Python projects you might have.
# Create and activate virtual environment (macOS/Linux)
python3 -m venv cafeteria-env
source cafeteria-env/bin/activate
Create and activate virtual environment (Windows)
python -m venv cafeteria-env
cafeteria-env\Scripts\activate
Install required packages
pip install requests python-dotenv
Create a file named .env in your project folder to store your API key securely. Never commit this file to version control!
# .env file - DO NOT SHARE OR UPLOAD THIS FILE
HOLYSHEEP_API_KEY=YOUR_HOLYSHEEP_API_KEY
ALERT_WEBHOOK_URL=https://your-monitoring-system.com/webhook
Step 2: Build the Core Multi-Model Client
This is the heart of your system. I designed this class to handle three critical requirements: model routing, automatic fallback, and cost tracking. Study this carefully before moving forward.
import requests
import time
import json
from typing import Optional, Dict, Any, List
from dataclasses import dataclass, field
from datetime import datetime
from dotenv import load_dotenv
import os
load_dotenv()
@dataclass
class ModelMetrics:
"""Tracks performance and costs for each model"""
total_requests: int = 0
successful_requests: int = 0
failed_requests: int = 0
total_tokens: int = 0
total_cost_usd: float = 0.0
avg_latency_ms: float = 0.0
fallback_count: int = 0
@dataclass
class HolySheepMultiModelClient:
"""
Multi-model client with automatic fallback and monitoring.
Routes requests to optimal models and switches on failure.
"""
api_key: str
base_url: str = "https://api.holysheep.ai/v1"
webhook_url: Optional[str] = None
# Model configurations - you can modify priorities here
models: Dict[str, Dict[str, Any]] = field(default_factory=lambda: {
"gpt-4.1": {
"provider": "openai",
"cost_per_million_input": 8.0,
"cost_per_million_output": 8.0,
"fallback_models": ["claude-sonnet-4.5", "gemini-2.5-flash"],
"max_retries": 2
},
"gemini-2.5-flash": {
"provider": "google",
"cost_per_million_input": 2.5,
"cost_per_million_output": 2.5,
"fallback_models": ["gpt-4.1", "deepseek-v3.2"],
"max_retries": 2
},
"deepseek-v3.2": {
"provider": "deepseek",
"cost_per_million_input": 0.42,
"cost_per_million_output": 0.42,
"fallback_models": ["gemini-2.5-flash"],
"max_retries": 1
},
"claude-sonnet-4.5": {
"provider": "anthropic",
"cost_per_million_input": 15.0,
"cost_per_million_output": 15.0,
"fallback_models": ["gpt-4.1"],
"max_retries": 1
}
})
metrics: Dict[str, ModelMetrics] = field(default_factory=dict)
def __post_init__(self):
# Initialize metrics for each model
for model_name in self.models.keys():
self.metrics[model_name] = ModelMetrics()
def _calculate_cost(self, model_name: str, input_tokens: int, output_tokens: int) -> float:
"""Calculate cost for a request based on token usage"""
config = self.models.get(model_name, {})
input_cost = (input_tokens / 1_000_000) * config.get("cost_per_million_input", 0)
output_cost = (output_tokens / 1_000_000) * config.get("cost_per_million_output", 0)
return input_cost + output_cost
def _send_alert(self, alert_type: str, message: str, data: Dict[str, Any]):
"""Send alert to monitoring webhook when model switches occur"""
if not self.webhook_url:
return
alert_payload = {
"timestamp": datetime.utcnow().isoformat(),
"alert_type": alert_type,
"message": message,
"data": data,
"source": "cafeteria-recipe-api"
}
try:
response = requests.post(
self.webhook_url,
json=alert_payload,
headers={"Content-Type": "application/json"},
timeout=5
)
if response.status_code != 200:
print(f"Warning: Alert webhook returned {response.status_code}")
except Exception as e:
print(f"Warning: Failed to send alert: {e}")
def chat_completion(
self,
messages: List[Dict[str, str]],
primary_model: str = "gpt-4.1",
system_prompt: Optional[str] = None,
temperature: float = 0.7,
max_tokens: int = 2048
) -> Dict[str, Any]:
"""
Send a chat completion request with automatic fallback.
Returns response with metadata including cost and latency.
"""
# Prepare messages with system prompt if provided
full_messages = []
if system_prompt:
full_messages.append({"role": "system", "content": system_prompt})
full_messages.extend(messages)
# Get model configuration
model_config = self.models.get(primary_model, {})
fallback_models = model_config.get("fallback_models", [])
max_retries = model_config.get("max_retries", 2)
# Try primary model first, then fallbacks
models_to_try = [primary_model] + fallback_models
last_error = None
for attempt_model in models_to_try:
for retry in range(max_retries + 1):
start_time = time.time()
try:
response = self._make_request(
model=attempt_model,
messages=full_messages,
temperature=temperature,
max_tokens=max_tokens
)
# Success! Update metrics
latency_ms = (time.time() - start_time) * 1000
input_tokens = response.get("usage", {}).get("prompt_tokens", 0)
output_tokens = response.get("usage", {}).get("completion_tokens", 0)
cost = self._calculate_cost(attempt_model, input_tokens, output_tokens)
metrics = self.metrics[attempt_model]
metrics.total_requests += 1
metrics.successful_requests += 1
metrics.total_tokens += input_tokens + output_tokens
metrics.total_cost_usd += cost
metrics.avg_latency_ms = (
(metrics.avg_latency_ms * (metrics.successful_requests - 1) + latency_ms)
/ metrics.successful_requests
)
# If we used a fallback model, send alert
if attempt_model != primary_model:
metrics.fallback_count += 1
self._send_alert(
"model_fallback",
f"Fell back from {primary_model} to {attempt_model}",
{
"primary_model": primary_model,
"fallback_model": attempt_model,
"original_error": str(last_error),
"request_cost_usd": cost
}
)
return {
"success": True,
"model": attempt_model,
"response": response,
"latency_ms": latency_ms,
"input_tokens": input_tokens,
"output_tokens": output_tokens,
"cost_usd": cost,
"fallback_used": attempt_model != primary_model
}
except Exception as e:
last_error = e
print(f"Request to {attempt_model} failed (retry {retry}): {e}")
continue
# All models failed
for model_name in models_to_try:
self.metrics[model_name].total_requests += 1
self.metrics[model_name].failed_requests += 1
return {
"success": False,
"error": str(last_error),
"models_tried": models_to_try,
"fallback_used": False
}
def _make_request(
self,
model: str,
messages: List[Dict[str, str]],
temperature: float,
max_tokens: int
) -> Dict[str, Any]:
"""Make actual API request to HolySheep AI"""
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
payload = {
"model": model,
"messages": messages,
"temperature": temperature,
"max_tokens": max_tokens
}
response = requests.post(
f"{self.base_url}/chat/completions",
headers=headers,
json=payload,
timeout=30
)
if response.status_code != 200:
raise Exception(f"API request failed with status {response.status_code}: {response.text}")
return response.json()
def vision_analysis(
self,
image_url: str,
prompt: str,
fallback_model: str = "gemini-2.5-flash"
) -> Dict[str, Any]:
"""
Analyze images using vision-capable models.
Used for ingredient recognition from cafeteria photos.
"""
start_time = time.time()
# Try primary vision model first
for model in ["gemini-2.5-flash", "gpt-4.1"]:
try:
response = self._make_request(
model=model,
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": prompt},
{"type": "image_url", "image_url": {"url": image_url}}
]
}
],
temperature=0.3,
max_tokens=1024
)
latency_ms = (time.time() - start_time) * 1000
return {
"success": True,
"model": model,
"analysis": response["choices"][0]["message"]["content"],
"latency_ms": latency_ms
}
except Exception as e:
print(f"Vision model {model} failed: {e}")
continue
return {"success": False, "error": "All vision models failed"}
def get_metrics_summary(self) -> Dict[str, Any]:
"""Get summary of all model metrics for monitoring"""
summary = {}
for model_name, metrics in self.metrics.items():
if metrics.total_requests > 0:
summary[model_name] = {
"total_requests": metrics.total_requests,
"success_rate": (metrics.successful_requests / metrics.total_requests) * 100,
"total_tokens": metrics.total_tokens,
"total_cost_usd": round(metrics.total_cost_usd, 4),
"avg_latency_ms": round(metrics.avg_latency_ms, 2),
"fallback_count": metrics.fallback_count
}
return summary
Initialize the client
client = HolySheepMultiModelClient(
api_key=os.getenv("HOLYSHEEP_API_KEY"),
webhook_url=os.getenv("ALERT_WEBHOOK_URL")
)
Step 3: Create the Recipe Generation Module
Now we build the cafeteria-specific logic. This module uses GPT-4.1 (or fallbacks) to generate nutritionally balanced recipes based on student preferences, dietary restrictions, and available ingredients.
def generate_nutritious_recipe(
student_preferences: Dict[str, Any],
available_ingredients: List[str],
dietary_restrictions: List[str],
target_calories: int = 800
) -> Dict[str, Any]:
"""
Generate a nutritionally balanced recipe for campus cafeteria.
Args:
student_preferences: Dict with 'cuisine_type', 'spice_level', etc.
available_ingredients: List of ingredient names available today
dietary_restrictions: List like ['vegetarian', 'halal', 'nut-free']
target_calories: Target calories for the meal (default 800 for lunch/dinner)
Returns:
Complete recipe with nutritional breakdown and cooking instructions
"""
system_prompt = """You are a professional nutritionist for a university cafeteria serving 15,000 students daily.
Generate recipes that are:
1. Nutritionally balanced with proper protein, carbs, and vegetables
2. Cost-effective using available ingredients
3. Quick to prepare in large quantities
4. Inclusive of various dietary restrictions
5. Appealing to university-age students
Always respond with valid JSON in this exact format:
{
"recipe_name": "string",
"prep_time_minutes": number,
"servings": number,
"ingredients": [{"name": "string", "quantity": "string", "unit": "string"}],
"nutrition_per_serving": {
"calories": number,
"protein_grams": number,
"carbs_grams": number,
"fat_grams": number,
"fiber_grams": number
},
"instructions": ["step 1", "step 2"],
"dietary_tags": ["vegetarian", "high-protein"],
"allergen_warnings": ["contains nuts"]
}"""
user_message = f"""
Generate a recipe with these requirements:
Available ingredients today: {', '.join(available_ingredients)}
Student preferences:
- Cuisine type: {student_preferences.get('cuisine_type', 'any')}
- Spice level: {student_preferences.get('spice_level', 'medium')}
Dietary restrictions (MUST respect these): {', '.join(dietary_restrictions) if dietary_restrictions else 'None'}
Target calories per serving: {target_calories}
Respond ONLY with valid JSON, no additional text.
"""
# Use the multi-model client with GPT-4.1 as primary
result = client.chat_completion(
messages=[{"role": "user", "content": user_message}],
primary_model="gpt-4.1",
system_prompt=system_prompt,
temperature=0.6,
max_tokens=2048
)
if not result["success"]:
return {
"error": "Failed to generate recipe",
"details": result.get("error"),
"fallback_attempted": True
}
# Parse the response
try:
recipe_text = result["response"]["choices"][0]["message"]["content"]
# Handle potential markdown code blocks
if "```json" in recipe_text:
recipe_text = recipe_text.split("``json")[1].split("``")[0]
elif "```" in recipe_text:
recipe_text = recipe_text.split("``")[1].split("``")[0]
recipe_data = json.loads(recipe_text.strip())
return {
"success": True,
"recipe": recipe_data,
"model_used": result["model"],
"cost_usd": result["cost_usd"],
"latency_ms": result["latency_ms"],
"calories_vs_target": round((recipe_data["nutrition_per_serving"]["calories"] / target_calories) * 100, 1)
}
except json.JSONDecodeError as e:
return {
"error": "Failed to parse recipe JSON",
"raw_response": result["response"]["choices"][0]["message"]["content"],
"parse_error": str(e)
}
def generate_weekly_menu(
num_meals_per_day: int = 3,
calorie_targets: Dict[str, int] = None
) -> Dict[str, Any]:
"""
Generate a complete weekly menu for the campus cafeteria.
Used for planning and inventory management.
"""
if calorie_targets is None:
calorie_targets = {
"breakfast": 600,
"lunch": 800,
"dinner": 850
}
system_prompt = """You are a menu planner for a university cafeteria.
Generate a diverse, nutritionally balanced weekly menu.
Respond with valid JSON only."""
user_message = f"""Create a weekly menu with {num_meals_per_day} meals per day.
Calorie targets: Breakfast {calorie_targets['breakfast']},
Lunch {calorie_targets['lunch']}, Dinner {calorie_targets['dinner']}.
Include variety across cuisines and ensure nutritional balance.
Respond with JSON: {{"week_starting": "date", "meals": [{{"day": "", "meals": [{{"type": "", "name": "", "calories": 0}}]}}]}}"""
result = client.chat_completion(
messages=[{"role": "user", "content": user_message}],
primary_model="gpt-4.1",
system_prompt=system_prompt,
temperature=0.5,
max_tokens=3000
)
return result
Example usage
if __name__ == "__main__":
# Test the recipe generation
test_recipe = generate_nutritious_recipe(
student_preferences={
"cuisine_type": "Asian fusion",
"spice_level": "medium"
},
available_ingredients=[
"chicken breast", "broccoli", "brown rice", "soy sauce",
"garlic", "ginger", "sesame oil", "tofu"
],
dietary_restrictions=["halal"],
target_calories=750
)
if test_recipe.get("success"):
print(f"Recipe generated: {test_recipe['recipe']['recipe_name']}")
print(f"Calories: {test_recipe['recipe']['nutrition_per_serving']['calories']}")
print(f"Model used: {test_recipe['model_used']}")
print(f"Cost: ${test_recipe['cost_usd']:.4f}")
else:
print(f"Error: {test_recipe.get('error')}")
Step 4: Implement Ingredient Recognition with Vision API
One of the most useful features for a cafeteria system is recognizing ingredients from photos. I implemented this using Gemini 2.5 Flash (the most cost-effective vision model) with automatic fallback to GPT-4.1.
def recognize_ingredients_from_photo(image_url: str) -> Dict[str, Any]:
"""
Analyze a photo of cafeteria ingredients and identify what's available.
Uses vision API with automatic model fallback.
Args:
image_url: URL to the image (can be base64 data URL)
Returns:
List of identified ingredients with confidence scores
"""
prompt = """Analyze this image of food ingredients from a university cafeteria.
Identify ALL ingredients visible in the image.
For each ingredient, provide:
1. Name (common name)
2. Estimated freshness (fresh/good/acceptable/spoiled)
3. Approximate quantity available
Return as JSON:
{
"ingredients_found": [
{
"name": "tomato",
"freshness": "fresh",
"estimated_quantity": "abundant",
"confidence": 0.95
}
],
"image_quality_notes": "string",
"missing_essential_ingredients": ["ingredient names"]
}
Respond ONLY with valid JSON."""
result = client.vision_analysis(
image_url=image_url,
prompt=prompt,
fallback_model="gemini-2.5-flash"
)
if not result["success"]:
return {
"error": "Failed to analyze image",
"details": result.get("error")
}
try:
analysis_text = result["analysis"]
# Parse the JSON from the response
if "```json" in analysis_text:
analysis_text = analysis_text.split("``json")[1].split("``")[0]
elif "```" in analysis_text:
analysis_text = analysis_text.split("``")[1].split("``")[0]
ingredients_data = json.loads(analysis_text.strip())
return {
"success": True,
"ingredients": ingredients_data.get("ingredients_found", []),
"quality_notes": ingredients_data.get("image_quality_notes", ""),
"missing_essentials": ingredients_data.get("missing_essential_ingredients", []),
"model_used": result["model"],
"latency_ms": result["latency_ms"]
}
except (json.JSONDecodeError, KeyError) as e:
return {
"error": "Failed to parse ingredient analysis",
"raw_analysis": result.get("analysis", ""),
"parse_error": str(e)
}
def create_shopping_list_from_photo(
image_url: str,
target_recipe: Dict[str, Any]
) -> Dict[str, Any]:
"""
Compare ingredients in a photo against what's needed for a recipe,
and generate a shopping list for missing items.
"""
# First recognize what's in the photo
recognized = recognize_ingredients_from_photo(image_url)
if not recognized.get("success"):
return recognized
# Get required ingredients from recipe
required = {ing["name"].lower() for ing in target_recipe.get("ingredients", [])}
available = {ing["name"].lower() for ing in recognized.get("ingredients", [])}
missing = required - available
extra_available = available - required
return {
"shopping_list": list(missing),
"already_available": list(available & required),
"unexpected_items": list(extra_available),
"coverage_percentage": round((len(available & required) / len(required)) * 100, 1),
"recognized_ingredients": recognized.get("ingredients", [])
}
Example usage
if __name__ == "__main__":
# Test ingredient recognition with a sample image URL
test_result = recognize_ingredients_from_photo(
image_url="https://example.com/campus-cafeteria-inventory.jpg"
)
if test_result.get("success"):
print(f"Found {len(test_result['ingredients'])} ingredients:")
for ing in test_result['ingredients']:
print(f" - {ing['name']} ({ing['freshness']})")
print(f"Model: {test_result['model_used']}, Latency: {test_result['latency_ms']:.0f}ms")
else:
print(f"Recognition failed: {test_result.get('error')}")
Step 5: Build the Flask API Server
Now we wrap everything in a proper REST API using Flask. This makes your system accessible to any frontend application or mobile app.
from flask import Flask, request, jsonify
from flask_cors import CORS
import os
app = Flask(__name__)
CORS(app) # Enable CORS for frontend access
Initialize the multi-model client (do this once at startup)
from your_module import HolySheepMultiModelClient, generate_nutritious_recipe, recognize_ingredients_from_photo
api_client = HolySheepMultiModelClient(
api_key=os.getenv("HOLYSHEEP_API_KEY"),
webhook_url=os.getenv("ALERT_WEBHOOK_URL")
)
@app.route("/health", methods=["GET"])
def health_check():
"""Health check endpoint for monitoring"""
return jsonify({
"status": "healthy",
"service": "campus-cafeteria-recipe-api",
"version": "2.1951.0524"
})
@app.route("/api/v1/recipe/generate", methods=["POST"])
def create_recipe():
"""
Generate a nutritionally balanced recipe.
POST body:
{
"preferences": {"cuisine_type": "Asian", "spice_level": "medium"},
"ingredients": ["chicken", "broccoli", "rice"],
"restrictions": ["vegetarian"],
"target_calories": 750
}
"""
try:
data = request.get_json()
if not data:
return jsonify({"error": "Request body required"}), 400
result = generate_nutritious_recipe(
student_preferences=data.get("preferences", {}),
available_ingredients=data.get("ingredients", []),
dietary_restrictions=data.get("restrictions", []),
target_calories=data.get("target_calories", 800)
)
status_code = 200 if result.get("success") else 500
return jsonify(result), status_code
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route("/api/v1/ingredients/recognize", methods=["POST"])
def identify_ingredients():
"""
Recognize ingredients from an image URL.
POST body:
{
"image_url": "https://example.com/photo.jpg"
}
"""
try:
data = request.get_json()
if not data or "image_url" not in data:
return jsonify({"error": "image_url required"}), 400
result = recognize_ingredients_from_photo(
image_url=data["image_url"]
)
status_code = 200 if result.get("success") else 500
return jsonify(result), status_code
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route("/api/v1/metrics", methods=["GET"])
def get_metrics():
"""Get cost and performance metrics for all models"""
return jsonify(api_client.get_metrics_summary())
@app.route("/api/v1/menu/weekly", methods=["POST"])
def weekly_menu():
"""
Generate a complete weekly menu.
POST body:
{
"meals_per_day": 3,
"calorie_targets": {"breakfast": 600, "lunch": 800, "dinner": 850}
}
"""
try:
data = request.get_json() or {}
from your_module import generate_weekly_menu
result = generate_weekly_menu(
num_meals_per_day=data.get("meals_per_day", 3),
calorie_targets=data.get("calorie_targets")
)
status_code = 200 if result.get("success") else 500
return jsonify(result), status_code
except Exception as e:
return jsonify({"error": str(e)}), 500
if __name__ == "__main__":
# Run on port 5000, debug mode for development
app.run(host="0.0.0.0", port=5000, debug=False)
Step 6: Test Your Complete System
Run your Flask server and test all endpoints. I recommend using a tool like Postman or curl to verify each endpoint works correctly.
# Start the server (run this in a separate terminal)
python app.py
Test health endpoint
curl http://localhost:5000/health
Test recipe generation
curl -X POST http://localhost:5000/api/v1/recipe/generate \
-H "Content-Type: application/json" \
-d '{
"preferences": {"cuisine_type": "Mediterranean", "spice_level": "low"},
"ingredients": ["chicken breast", "olive oil", "tomatoes", "cucumber", "feta cheese", "lettuce"],
"restrictions": ["gluten-free"],
"target_calories": 650
}'
Test metrics endpoint
curl http://localhost:5000/api/v1/metrics
Test ingredient recognition (replace with your image URL)
curl -X POST http://localhost:5000/api/v1/ingredients/recognize \
-H "Content-Type: application/json" \
-d '{"image_url": "https://example.com/campus-kitchen-inventory.jpg"}'