FoodGen / app.py
Karthik1610's picture
Update app.py
92f414b verified
Raw
History Blame Contribute Delete
22.1 kB
import pandas as pd
import numpy as np
from sentence_transformers import SentenceTransformer
import faiss
import gradio as gr
from typing import List, Dict, Tuple
import os
from huggingface_hub import InferenceClient
# STEP 1: Enhanced Restaurant Data (100+ items)
def create_enhanced_menu_data():
"""Create a larger, more diverse menu dataset"""
menu_items = [
# Healthy & Protein-Rich Options
{"name": "Grilled Chicken Salad", "description": "High-protein grilled chicken breast with mixed greens, cherry tomatoes, cucumber. 35g protein, 450 calories", "category": "Salad", "cuisine": "Continental", "diet": "Non-Vegetarian", "price": 280},
{"name": "Grilled Salmon with Veggies", "description": "Omega-3 rich grilled salmon fillet with steamed vegetables. Low-carb, high-protein meal. 40g protein", "category": "Main Course", "cuisine": "Continental", "diet": "Non-Vegetarian", "price": 480},
{"name": "Protein Smoothie Bowl", "description": "Thick smoothie bowl with whey protein, banana, berries, topped with nuts and seeds. 30g protein", "category": "Breakfast", "cuisine": "Continental", "diet": "Vegetarian", "price": 250},
{"name": "Tandoori Chicken Platter", "description": "Smoky grilled chicken marinated in yogurt and spices. High-protein, low-fat option. 45g protein", "category": "Starter", "cuisine": "Indian", "diet": "Non-Vegetarian", "price": 350},
{"name": "Chicken Tikka Wrap", "description": "Grilled chicken tikka wrapped in whole wheat roti with veggies. Quick, portable, protein-packed meal. 30g protein", "category": "Snack", "cuisine": "Indian", "diet": "Non-Vegetarian", "price": 200},
{"name": "Greek Yogurt Parfait", "description": "Layered Greek yogurt with fresh berries, granola, honey. High-protein breakfast with 20g protein, 300 calories", "category": "Breakfast", "cuisine": "Continental", "diet": "Vegetarian", "price": 180},
{"name": "Egg White Omelette with Veggies", "description": "Fluffy egg white omelette loaded with vegetables. Low-calorie, high-protein breakfast. 20g protein, 150 calories", "category": "Breakfast", "cuisine": "Continental", "diet": "Non-Vegetarian", "price": 140},
{"name": "Paneer Bhurji with Multigrain Toast", "description": "Scrambled cottage cheese with spices, served with multigrain toast. High-protein vegetarian breakfast. 25g protein", "category": "Breakfast", "cuisine": "Indian", "diet": "Vegetarian", "price": 160},
# Comfort Food
{"name": "Paneer Tikka Masala", "description": "Cottage cheese cubes in creamy tomato gravy with aromatic spices. Rich and indulgent comfort food perfect for cozy evenings", "category": "Main Course", "cuisine": "North Indian", "diet": "Vegetarian", "price": 290},
{"name": "Butter Chicken with Naan", "description": "Tender chicken in rich, creamy tomato gravy. Indulgent comfort food perfect for cheat days", "category": "Main Course", "cuisine": "North Indian", "diet": "Non-Vegetarian", "price": 340},
{"name": "Dal Tadka with Brown Rice", "description": "Yellow lentils tempered with cumin and garlic, served with brown rice. Light, healthy comfort food with balanced nutrition", "category": "Main Course", "cuisine": "North Indian", "diet": "Vegetarian", "price": 180},
{"name": "Mac and Cheese", "description": "Creamy macaroni pasta with three types of cheese. Ultimate comfort food that soothes the soul", "category": "Main Course", "cuisine": "Continental", "diet": "Vegetarian", "price": 260},
{"name": "Lentil Soup with Whole Wheat Bread", "description": "Hearty lentil soup with vegetables, served with whole wheat bread. Warming comfort food, high in fiber and protein", "category": "Soup", "cuisine": "Continental", "diet": "Vegetarian", "price": 160},
{"name": "Spicy Szechuan Noodles", "description": "Fiery noodles tossed in Szechuan sauce with vegetables. Perfect when you're craving something spicy and bold", "category": "Main Course", "cuisine": "Chinese", "diet": "Vegetarian", "price": 220},
# Vegan Options
{"name": "Quinoa Buddha Bowl", "description": "Superfood bowl with quinoa, roasted vegetables, avocado, chickpeas. Vegan-friendly, high in fiber and nutrients. 15g protein, 400 calories", "category": "Salad", "cuisine": "Continental", "diet": "Vegan", "price": 320},
{"name": "Vegan Pad Thai", "description": "Rice noodles stir-fried with tofu, peanuts, bean sprouts in tangy tamarind sauce. Plant-based and flavorful", "category": "Main Course", "cuisine": "Thai", "diet": "Vegan", "price": 300},
{"name": "Vegan Chocolate Cake", "description": "Rich, moist chocolate cake made without dairy or eggs. Guilt-free indulgence for celebrations", "category": "Dessert", "cuisine": "Continental", "diet": "Vegan", "price": 180},
{"name": "Hummus with Pita Bread", "description": "Creamy chickpea hummus served with warm pita bread. Protein-rich vegan appetizer", "category": "Starter", "cuisine": "Mediterranean", "diet": "Vegan", "price": 160},
# Breakfast Items
{"name": "Masala Dosa", "description": "Crispy rice crepe filled with spiced potato masala. Light yet filling breakfast option. Served with sambar and chutney", "category": "Breakfast", "cuisine": "South Indian", "diet": "Vegetarian", "price": 120},
{"name": "Idli Sambar", "description": "Steamed rice cakes served with lentil soup and coconut chutney. Light and healthy breakfast", "category": "Breakfast", "cuisine": "South Indian", "diet": "Vegetarian", "price": 90},
{"name": "Aloo Paratha", "description": "Whole wheat flatbread stuffed with spiced potato filling, served with curd. Hearty North Indian breakfast", "category": "Breakfast", "cuisine": "North Indian", "diet": "Vegetarian", "price": 100},
{"name": "Avocado Toast with Eggs", "description": "Whole grain toast with mashed avocado, poached eggs, cherry tomatoes. Trendy breakfast loaded with healthy fats and protein", "category": "Breakfast", "cuisine": "Continental", "diet": "Vegetarian", "price": 240},
{"name": "Pancakes with Maple Syrup", "description": "Fluffy pancakes served with butter and maple syrup. Classic comfort breakfast", "category": "Breakfast", "cuisine": "Continental", "diet": "Vegetarian", "price": 200},
# Celebration Food
{"name": "Chicken Biryani", "description": "Fragrant basmati rice with tender chicken, aromatic spices, served with raita. A hearty meal perfect for celebrations", "category": "Main Course", "cuisine": "Indian", "diet": "Non-Vegetarian", "price": 320},
{"name": "Mutton Rogan Josh", "description": "Tender mutton cooked in aromatic Kashmiri spices. A royal dish perfect for special occasions", "category": "Main Course", "cuisine": "North Indian", "diet": "Non-Vegetarian", "price": 420},
{"name": "Lobster Thermidor", "description": "Luxurious lobster in creamy sauce, perfect for celebrations and special moments", "category": "Main Course", "cuisine": "French", "diet": "Non-Vegetarian", "price": 980},
# More variety
{"name": "Margherita Pizza", "description": "Classic pizza with fresh mozzarella, tomato sauce, and basil leaves", "category": "Main Course", "cuisine": "Italian", "diet": "Vegetarian", "price": 350},
{"name": "Chicken Wings", "description": "Crispy fried chicken wings tossed in spicy buffalo sauce", "category": "Starter", "cuisine": "Continental", "diet": "Non-Vegetarian", "price": 280},
{"name": "Caesar Salad", "description": "Fresh romaine lettuce, croutons, parmesan cheese, caesar dressing", "category": "Salad", "cuisine": "Continental", "diet": "Vegetarian", "price": 220},
{"name": "Tom Yum Soup", "description": "Hot and sour Thai soup with mushrooms, lemongrass, lime. Light, flavorful, perfect when feeling under the weather", "category": "Soup", "cuisine": "Thai", "diet": "Vegetarian", "price": 180},
{"name": "Fish and Chips", "description": "Crispy battered fish fillet with french fries and tartar sauce", "category": "Main Course", "cuisine": "Continental", "diet": "Non-Vegetarian", "price": 380},
{"name": "Paneer Tikka", "description": "Marinated cottage cheese chunks grilled with bell peppers and onions", "category": "Starter", "cuisine": "Indian", "diet": "Vegetarian", "price": 240},
{"name": "Veg Spring Rolls", "description": "Crispy rolls filled with mixed vegetables and served with sweet chili sauce", "category": "Starter", "cuisine": "Chinese", "diet": "Vegetarian", "price": 140},
{"name": "Chocolate Brownie with Ice Cream", "description": "Rich chocolate brownie with vanilla ice cream. Perfect dessert for celebrations", "category": "Dessert", "cuisine": "Continental", "diet": "Vegetarian", "price": 160},
{"name": "Gulab Jamun", "description": "Soft milk dumplings soaked in sugar syrup. Traditional Indian sweet perfect for festivals", "category": "Dessert", "cuisine": "Indian", "diet": "Vegetarian", "price": 80},
{"name": "Tiramisu", "description": "Italian dessert with coffee-soaked ladyfingers and mascarpone cream", "category": "Dessert", "cuisine": "Italian", "diet": "Vegetarian", "price": 220},
{"name": "Sushi Platter", "description": "Assorted sushi rolls with wasabi and soy sauce. Light and healthy Japanese delicacy", "category": "Main Course", "cuisine": "Japanese", "diet": "Non-Vegetarian", "price": 680},
{"name": "Ramen Bowl", "description": "Japanese noodle soup with rich broth, vegetables, and choice of protein. Comforting and flavorful", "category": "Main Course", "cuisine": "Japanese", "diet": "Non-Vegetarian", "price": 380},
{"name": "Falafel Wrap", "description": "Crispy chickpea falafel in pita with tahini sauce and fresh veggies. Healthy vegan option", "category": "Snack", "cuisine": "Mediterranean", "diet": "Vegan", "price": 180},
]
return pd.DataFrame(menu_items)
def load_restaurant_data():
"""Try loading from Kaggle, fallback to enhanced local data"""
try:
import kagglehub
print("Attempting to download from Kaggle...")
path = kagglehub.dataset_download("graphquest/restaurant-menu-items")
df = pd.read_csv(f"{path}/menu_items.csv")
df = df.dropna(subset=['name', 'description'])
# Ensure required columns
if 'category' not in df.columns:
df['category'] = 'Main Course'
if 'price' not in df.columns:
df['price'] = np.random.randint(100, 500, len(df))
if 'cuisine' not in df.columns:
df['cuisine'] = 'International'
if 'diet' not in df.columns:
df['diet'] = 'Vegetarian'
print(f"βœ… Loaded {len(df)} items from Kaggle!")
return df.head(500)
except Exception as e:
print(f"Kaggle download failed, using local enhanced dataset with 37 items")
return create_enhanced_menu_data()
# STEP 2: Real LLM using Hugging Face
class HuggingFaceReasoningGenerator:
"""Uses Hugging Face Inference API for real LLM generation"""
def __init__(self, hf_token=None):
self.hf_token = hf_token or os.getenv("HF_TOKEN")
if self.hf_token:
# Using a good open-source model
self.client = InferenceClient(token=self.hf_token)
self.model = "mistralai/Mistral-7B-Instruct-v0.2"
print(f"βœ… Using real LLM: {self.model}")
else:
print("⚠️ No HF token provided, will use fallback reasoning")
self.client = None
def generate_reasoning(self, dish_name: str, dish_description: str,
user_query: str, context: Dict) -> str:
"""Generate personalized reasoning using Hugging Face LLM"""
if not self.client:
return self._fallback_reasoning(dish_name, dish_description, context)
prompt = f"""You are a food recommendation assistant. Be concise and friendly.
User Query: "{user_query}"
Dish: {dish_name}
Description: {dish_description}
Context: Situation: {', '.join(context.get('situation', []))}, Dietary: {', '.join(context.get('dietary', []))}
Write ONE short sentence (max 20 words) explaining why this dish is perfect for the user. Be specific and natural.
Reasoning:"""
try:
response = self.client.text_generation(
prompt,
model=self.model,
max_new_tokens=50,
temperature=0.7,
return_full_text=False
)
# Clean up the response
reasoning = response.strip()
if reasoning:
reasoning = reasoning.split('.')[0] + '.'
return reasoning
else:
return self._fallback_reasoning(dish_name, dish_description, context)
except Exception as e:
print(f"LLM Error: {e}")
return self._fallback_reasoning(dish_name, dish_description, context)
def _fallback_reasoning(self, dish_name, dish_description, context):
"""Fallback when LLM fails"""
desc_lower = dish_description.lower()
if 'post_workout' in context.get('situation', []) and 'protein' in desc_lower:
return f"Excellent post-workout choice with high protein for muscle recovery."
if 'celebration' in context.get('situation', []):
return f"Perfect for celebrations with its indulgent and special flavors."
if 'comfort' in context.get('situation', []):
return f"Comforting and satisfying when you need a mood boost."
if 'vegan' in context.get('dietary', []):
return f"Great plant-based option that's both nutritious and delicious."
return f"This dish matches your preferences perfectly."
# STEP 3: Context Extractor
class ContextExtractor:
def __init__(self):
self.context_patterns = {
'post_workout': ['workout', 'gym', 'exercise', 'training', 'fitness'],
'celebration': ['celebration', 'party', 'birthday', 'anniversary', 'special'],
'comfort': ['stressed', 'tired', 'comfort', 'cozy', 'relax', 'bad day'],
'healthy': ['healthy', 'diet', 'nutrition', 'fit', 'wellness'],
}
self.dietary_patterns = {
'vegetarian': ['vegetarian', 'veg', 'no meat'],
'vegan': ['vegan', 'plant-based'],
'protein': ['protein', 'high protein'],
}
def extract_context(self, query: str) -> Dict:
query_lower = query.lower()
context = {'situation': [], 'dietary': []}
for situation, keywords in self.context_patterns.items():
if any(kw in query_lower for kw in keywords):
context['situation'].append(situation)
for diet, keywords in self.dietary_patterns.items():
if any(kw in query_lower for kw in keywords):
context['dietary'].append(diet)
return context
# STEP 4: Neural Search System
class GenAINeuralSearch:
def __init__(self, menu_df: pd.DataFrame, hf_token=None):
print("πŸš€ Initializing GenAI Neural Search System...")
self.model = SentenceTransformer('all-MiniLM-L6-v2')
self.menu_df = menu_df
self.context_extractor = ContextExtractor()
self.llm_generator = HuggingFaceReasoningGenerator(hf_token)
self.menu_df['search_text'] = self.menu_df.apply(
lambda row: f"{row['name']}. {row['description']}. {row['category']}. {row['cuisine']}. {row['diet']}.",
axis=1
)
print("πŸ“Š Creating embeddings...")
self.embeddings = self.model.encode(
self.menu_df['search_text'].tolist(),
show_progress_bar=True
)
print("πŸ” Building FAISS index...")
dimension = self.embeddings.shape[1]
self.index = faiss.IndexFlatIP(dimension)
faiss.normalize_L2(self.embeddings)
self.index.add(self.embeddings)
print(f"βœ… System ready with {len(self.menu_df)} menu items!")
def search_with_genai(self, query: str, top_k: int = 5) -> Tuple[List[Dict], Dict]:
context = self.context_extractor.extract_context(query)
query_embedding = self.model.encode([query])
faiss.normalize_L2(query_embedding)
similarities, indices = self.index.search(query_embedding, top_k)
results = []
for idx, score in zip(indices[0], similarities[0]):
item = self.menu_df.iloc[idx]
# Generate LLM reasoning
llm_reasoning = self.llm_generator.generate_reasoning(
dish_name=item['name'],
dish_description=item['description'],
user_query=query,
context=context
)
results.append({
'name': item['name'],
'description': item['description'],
'category': item['category'],
'cuisine': item['cuisine'],
'diet': item['diet'],
'price': f"β‚Ή{item['price']}",
'similarity_score': float(score),
'ai_reasoning': llm_reasoning
})
return results, context
# STEP 5: Initialize
print("Loading menu data...")
menu_df = load_restaurant_data()
print("Initializing search system...")
search_system = GenAINeuralSearch(menu_df, hf_token=os.getenv("HF_TOKEN"))
# STEP 6: Gradio Interface
def search_with_genai(query, num_results):
if not query.strip():
return "<p style='color: #666;'>Please enter a search query to get started!</p>"
results, context = search_system.search_with_genai(query, top_k=num_results)
# Context display
html_output = "<div style='background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 20px; border-radius: 12px; margin-bottom: 25px; color: white;'>"
html_output += f"<h3 style='margin: 0 0 15px 0; font-size: 24px;'>πŸ” Results for: \"{query}\"</h3>"
if context['situation'] or context['dietary']:
html_output += "<div style='background: rgba(255,255,255,0.2); padding: 12px; border-radius: 8px;'>"
html_output += "<strong>🧠 Context Understanding:</strong><br/>"
if context['situation']:
html_output += f"<span style='margin-right: 10px;'>πŸ“ Situation: {', '.join(context['situation'])}</span>"
if context['dietary']:
html_output += f"<span>πŸ₯— Dietary: {', '.join(context['dietary'])}</span>"
html_output += "</div>"
html_output += "</div>"
# Results
for i, result in enumerate(results, 1):
html_output += f"""
<div style='background: white; border: 2px solid #e0e0e0; padding: 20px; margin: 20px 0; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);'>
<div style='display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;'>
<h4 style='margin: 0; color: #2c3e50; font-size: 20px;'>{i}. {result['name']}</h4>
<span style='color: #27ae60; font-weight: bold; font-size: 18px;'>{result['price']}</span>
</div>
<div style='background: #f0f9ff; border-left: 4px solid #3b82f6; padding: 12px; margin: 15px 0; border-radius: 6px;'>
<strong style='color: #1e40af;'>πŸ€– AI Reasoning:</strong>
<p style='margin: 5px 0 0 0; color: #374151;'>{result['ai_reasoning']}</p>
</div>
<p style='color: #555; line-height: 1.6; margin: 15px 0;'>{result['description']}</p>
<div style='margin-top: 15px;'>
<span style='background: #3b82f6; color: white; padding: 6px 12px; border-radius: 20px; font-size: 13px; margin-right: 8px;'>{result['category']}</span>
<span style='background: #ef4444; color: white; padding: 6px 12px; border-radius: 20px; font-size: 13px; margin-right: 8px;'>{result['cuisine']}</span>
<span style='background: #10b981; color: white; padding: 6px 12px; border-radius: 20px; font-size: 13px;'>{result['diet']}</span>
</div>
<p style='color: #9ca3af; font-size: 12px; margin: 10px 0 0 0;'>Relevance: {result['similarity_score']:.1%}</p>
</div>
"""
return html_output
# Gradio Interface
with gr.Blocks(theme=gr.themes.Soft(), title="Swiggy Neural Search - GenAI") as demo:
gr.Markdown("""
# 🍽️ Swiggy Neural Search - GenAI Edition
**Powered by:** Sentence Transformers + FAISS + Hugging Face LLM (Mistral-7B)
This system understands your context and generates personalized explanations using AI!
""")
with gr.Row():
with gr.Column(scale=2):
query_input = gr.Textbox(
label="What are you looking for?",
placeholder="Try: 'I just finished my workout. Show me healthy lunch options'",
lines=3
)
num_results = gr.Slider(minimum=3, maximum=8, value=5, step=1, label="Number of results")
search_btn = gr.Button("πŸ” Search", variant="primary", size="lg")
output_html = gr.HTML(label="Results")
gr.Examples(
examples=[
["I just finished my workout. Show me healthy lunch options"],
["I'm feeling stressed, need comfort food"],
["It's my birthday! Show me celebration-worthy food"],
["Vegan protein-rich dinner options"],
["Something light for breakfast"],
],
inputs=query_input
)
search_btn.click(
fn=search_with_genai,
inputs=[query_input, num_results],
outputs=output_html
)
if __name__ == "__main__":
demo.launch()