Spaces:
Sleeping
Sleeping
| 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() |