Spaces:
Paused
Paused
| """ | |
| LLM-powered explanation generator for RewardPilot recommendations. | |
| Uses Hugging Face Inference API with Llama 3.2 for natural language explanations. | |
| """ | |
| from huggingface_hub import InferenceClient | |
| import os | |
| from typing import Dict, List, Optional | |
| import logging | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| class LLMExplainer: | |
| """Generate natural language explanations for credit card recommendations using LLM""" | |
| def __init__(self, model: str = "meta-llama/Llama-3.2-3B-Instruct"): | |
| """ | |
| Initialize LLM explainer with Hugging Face Inference API | |
| Args: | |
| model: HuggingFace model ID to use for generation | |
| """ | |
| self.model = model | |
| self.client = None | |
| # Try to initialize with token | |
| hf_token = os.getenv("HF_TOKEN", "") | |
| if hf_token: | |
| try: | |
| self.client = InferenceClient(token=hf_token) | |
| # Test the connection | |
| logger.info(f" LLM Explainer initialized with model: {model}") | |
| except Exception as e: | |
| logger.warning(f" Could not initialize HF client: {e}") | |
| self.client = None | |
| else: | |
| logger.warning(" No HF_TOKEN found. LLM explanations will use fallback mode.") | |
| def explain_recommendation( | |
| self, | |
| card: str, | |
| rewards: float, | |
| rewards_rate: str, | |
| merchant: str, | |
| category: str, | |
| amount: float, | |
| warnings: Optional[List[str]] = None, | |
| annual_potential: Optional[float] = None, | |
| alternatives: Optional[List[Dict]] = None | |
| ) -> str: | |
| """ | |
| Generate natural language explanation for a card recommendation | |
| Args: | |
| card: Recommended card name | |
| rewards: Rewards earned for this transaction | |
| rewards_rate: Rewards rate (e.g., "4x points") | |
| merchant: Merchant name | |
| category: Transaction category | |
| amount: Transaction amount | |
| warnings: List of warning messages | |
| annual_potential: Annual rewards potential | |
| alternatives: Alternative card options | |
| Returns: | |
| Natural language explanation string | |
| """ | |
| # Fallback if LLM not available | |
| if not self.client: | |
| return self._generate_fallback_explanation( | |
| card, rewards, rewards_rate, merchant, category, amount, warnings | |
| ) | |
| # Build context-aware prompt | |
| prompt = self._build_prompt( | |
| card, rewards, rewards_rate, merchant, category, amount, | |
| warnings, annual_potential, alternatives | |
| ) | |
| try: | |
| # Generate explanation using LLM with correct API | |
| messages = [ | |
| { | |
| "role": "system", | |
| "content": "You are a friendly credit card rewards expert who provides concise, helpful explanations." | |
| }, | |
| { | |
| "role": "user", | |
| "content": prompt | |
| } | |
| ] | |
| response = self.client.chat_completion( | |
| messages=messages, | |
| model=self.model, | |
| max_tokens=200, | |
| temperature=0.7, | |
| top_p=0.9 | |
| ) | |
| # Extract response text | |
| explanation = response.choices[0].message.content.strip() | |
| logger.info(f" Generated LLM explanation for {card}") | |
| return explanation | |
| except Exception as e: | |
| logger.error(f" LLM generation failed: {e}") | |
| return self._generate_fallback_explanation( | |
| card, rewards, rewards_rate, merchant, category, amount, warnings | |
| ) | |
| def _build_prompt( | |
| self, | |
| card: str, | |
| rewards: float, | |
| rewards_rate: str, | |
| merchant: str, | |
| category: str, | |
| amount: float, | |
| warnings: Optional[List[str]], | |
| annual_potential: Optional[float], | |
| alternatives: Optional[List[Dict]] | |
| ) -> str: | |
| """Build optimized prompt for LLM""" | |
| prompt = f"""Explain why this credit card is the best choice for this purchase. | |
| Transaction Details: | |
| - Merchant: {merchant} | |
| - Category: {category} | |
| - Amount: ${amount:.2f} | |
| Recommendation: | |
| - Best Card: {card} | |
| - Rewards Earned: ${rewards:.2f} ({rewards_rate}) | |
| """ | |
| if annual_potential: | |
| prompt += f"- Annual Potential: ${annual_potential:.2f} in this category\n" | |
| if warnings: | |
| prompt += f"- Important Warning: {warnings[0]}\n" | |
| if alternatives and len(alternatives) > 0: | |
| alt_text = ", ".join([f"{alt['card']} (${alt['rewards']:.2f})" for alt in alternatives[:2]]) | |
| prompt += f"- Alternatives: {alt_text}\n" | |
| prompt += """ | |
| Provide a friendly, concise explanation (2-3 sentences) that: | |
| 1. Explains why this card is the best choice | |
| 2. Highlights the key benefit | |
| 3. Mentions any important warnings if present | |
| Keep it conversational and helpful.""" | |
| return prompt | |
| def _generate_fallback_explanation( | |
| self, | |
| card: str, | |
| rewards: float, | |
| rewards_rate: str, | |
| merchant: str, | |
| category: str, | |
| amount: float, | |
| warnings: Optional[List[str]] | |
| ) -> str: | |
| """Generate rule-based explanation when LLM is unavailable""" | |
| explanation = f"The **{card}** is your best choice for this {category.lower()} purchase at {merchant}. " | |
| explanation += f"You'll earn **{rewards_rate}**, which gives you the highest rewards rate among your cards. " | |
| if warnings: | |
| explanation += f"\n\n **Note:** {warnings[0]}" | |
| else: | |
| explanation += "This optimizes your rewards while staying within spending caps." | |
| return explanation | |
| def generate_spending_insights( | |
| self, | |
| user_id: str, | |
| total_spending: float, | |
| total_rewards: float, | |
| optimization_score: int, | |
| top_categories: List[Dict], | |
| recommendations_count: int | |
| ) -> str: | |
| """ | |
| Generate personalized spending insights for analytics dashboard | |
| Args: | |
| user_id: User identifier | |
| total_spending: Total spending amount | |
| total_rewards: Total rewards earned | |
| optimization_score: Optimization score (0-100) | |
| top_categories: List of top spending categories | |
| recommendations_count: Number of optimized transactions | |
| Returns: | |
| Personalized insights text | |
| """ | |
| if not self.client: | |
| return self._generate_fallback_insights( | |
| total_spending, total_rewards, optimization_score | |
| ) | |
| prompt = f"""Analyze this user's credit card spending and provide personalized insights. | |
| User Spending Summary: | |
| - Total Spending: ${total_spending:.2f} | |
| - Total Rewards: ${total_rewards:.2f} | |
| - Optimization Score: {optimization_score}/100 | |
| - Optimized Transactions: {recommendations_count} | |
| - Top Categories: {', '.join([cat['category'] for cat in top_categories[:3]])} | |
| Provide 2-3 actionable insights about: | |
| 1. Their optimization performance | |
| 2. Opportunities to earn more rewards | |
| 3. One specific tip to improve their score | |
| Be encouraging and specific. Keep it under 100 words.""" | |
| try: | |
| messages = [ | |
| { | |
| "role": "system", | |
| "content": "You are a financial advisor specializing in credit card rewards optimization." | |
| }, | |
| { | |
| "role": "user", | |
| "content": prompt | |
| } | |
| ] | |
| response = self.client.chat_completion( | |
| messages=messages, | |
| model=self.model, | |
| max_tokens=150, | |
| temperature=0.8 | |
| ) | |
| return response.choices[0].message.content.strip() | |
| except Exception as e: | |
| logger.error(f" Insights generation failed: {e}") | |
| return self._generate_fallback_insights( | |
| total_spending, total_rewards, optimization_score | |
| ) | |
| def _generate_fallback_insights( | |
| self, | |
| total_spending: float, | |
| total_rewards: float, | |
| optimization_score: int | |
| ) -> str: | |
| """Generate rule-based insights when LLM unavailable""" | |
| rewards_rate = (total_rewards / total_spending * 100) if total_spending > 0 else 0 | |
| insights = f"You're earning **${total_rewards:.2f}** in rewards on **${total_spending:.2f}** of spending " | |
| insights += f"(**{rewards_rate:.1f}%** effective rate). " | |
| if optimization_score >= 80: | |
| insights += " **Excellent optimization!** You're maximizing your rewards effectively. " | |
| elif optimization_score >= 60: | |
| insights += " **Good progress!** Consider using our recommendations more consistently. " | |
| else: | |
| insights += " **Room for improvement!** Follow our card suggestions to boost your rewards. " | |
| insights += "Keep tracking your spending to identify new optimization opportunities." | |
| return insights | |
| def chat_response( | |
| self, | |
| user_message: str, | |
| user_context: Dict, | |
| chat_history: List[tuple] = None | |
| ) -> str: | |
| """ | |
| Generate conversational response for chat interface | |
| Args: | |
| user_message: User's question/message | |
| user_context: User's spending data and card portfolio | |
| chat_history: Previous conversation history | |
| Returns: | |
| AI assistant response | |
| """ | |
| if not self.client: | |
| return self._generate_fallback_chat(user_message, user_context) | |
| # Build context from user data | |
| context = f"""User Profile: | |
| - Cards: {', '.join(user_context.get('cards', ['Unknown']))} | |
| - Monthly Spending: ${user_context.get('monthly_spending', 0):.2f} | |
| - Top Category: {user_context.get('top_category', 'Unknown')} | |
| """ | |
| # Build messages with history | |
| messages = [ | |
| { | |
| "role": "system", | |
| "content": f"You are RewardPilot AI, a helpful credit card rewards assistant.\n\n{context}" | |
| } | |
| ] | |
| # Add chat history | |
| if chat_history: | |
| for user_msg, assistant_msg in chat_history[-3:]: # Last 3 exchanges | |
| messages.append({"role": "user", "content": user_msg}) | |
| messages.append({"role": "assistant", "content": assistant_msg}) | |
| # Add current message | |
| messages.append({"role": "user", "content": user_message}) | |
| try: | |
| response = self.client.chat_completion( | |
| messages=messages, | |
| model=self.model, | |
| max_tokens=200, | |
| temperature=0.8 | |
| ) | |
| return response.choices[0].message.content.strip() | |
| except Exception as e: | |
| logger.error(f" Chat response failed: {e}") | |
| return self._generate_fallback_chat(user_message, user_context) | |
| def _generate_fallback_chat(self, user_message: str, user_context: Dict) -> str: | |
| """Generate rule-based chat response when LLM unavailable""" | |
| message_lower = user_message.lower() | |
| # Greeting | |
| if any(word in message_lower for word in ['hello', 'hi', 'hey', 'greetings']): | |
| return "Hello! I'm RewardPilot AI. I can help you choose the best credit card for any purchase. What would you like to know?" | |
| # Card-specific questions | |
| if 'amex gold' in message_lower or 'american express gold' in message_lower: | |
| return "The **Amex Gold** is excellent for dining and groceries, earning **4x points** in both categories. It has a $250 annual fee but comes with dining credits. Best for foodies! " | |
| if 'chase sapphire' in message_lower: | |
| return "The **Chase Sapphire Reserve** is a premium travel card earning **3x points** on travel and dining. It has a $550 annual fee but offers travel credits and lounge access. Perfect for frequent travelers! " | |
| if 'costco' in message_lower: | |
| return "The **Costco Anywhere Visa** offers **4% cashback** on gas (up to $7,000/year), 3% on restaurants and travel, 2% at Costco, and 1% elsewhere. No annual fee beyond Costco membership! " | |
| # Category questions | |
| if 'grocery' in message_lower or 'groceries' in message_lower: | |
| return "For groceries, I recommend:\n\n1. **Amex Gold** - 4x points\n2. **Blue Cash Preferred** - 6% cashback (up to $6,000/year)\n3. **Chase Freedom Flex** - 5% in rotating categories\n\nWhich sounds best for you? " | |
| if 'dining' in message_lower or 'restaurant' in message_lower: | |
| return "For dining, top choices are:\n\n1. **Capital One Savor** - 4% cashback\n2. **Amex Gold** - 4x points\n3. **Chase Sapphire Preferred** - 3x points\n\nAll great options! " | |
| if 'travel' in message_lower: | |
| return "For travel, consider:\n\n1. **Chase Sapphire Reserve** - 3x points\n2. **Amex Platinum** - 5x points on flights\n3. **Capital One Venture X** - 2x miles everywhere\n\nDepends on your travel style! " | |
| if 'gas' in message_lower: | |
| return "For gas stations:\n\n1. **Costco Visa** - 4% cashback\n2. **BofA Customized Cash** - 3% in your choice category\n3. **Citi Custom Cash** - 5% on top category (up to $500/month)\n\nSave at the pump! " | |
| # Optimization | |
| if 'optimize' in message_lower or 'maximize' in message_lower: | |
| return "To optimize your rewards:\n\n1. Use category-specific cards\n2. Have a 2% cashback baseline card\n3. Track spending caps\n4. Consider annual fees vs. rewards\n\nUse the 'Get Recommendation' tab for personalized advice!" | |
| # Help | |
| if 'help' in message_lower or 'what can you do' in message_lower: | |
| return "I can help you with:\n\n Choosing the best card for specific merchants\n Comparing card benefits\n Understanding rewards rates\n Optimizing your wallet strategy\n\nWhat would you like to know?" | |
| # Default response | |
| return "I can help you find the best credit card for any purchase! Try asking:\n\n• 'Which card for groceries?'\n• 'Tell me about Chase Sapphire Reserve'\n• 'How can I maximize rewards?'\n\nWhat would you like to know? " | |
| # Singleton instance | |
| _llm_explainer = None | |
| def get_llm_explainer() -> LLMExplainer: | |
| """Get or create singleton LLM explainer instance""" | |
| global _llm_explainer | |
| if _llm_explainer is None: | |
| _llm_explainer = LLMExplainer() | |
| return _llm_explainer |