|
|
|
|
|
|
|
|
import google.generativeai as genai |
|
|
from typing import Dict, List, Optional |
|
|
import config |
|
|
import logging |
|
|
|
|
|
logging.basicConfig(level=logging.INFO) |
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
|
|
|
class GeminiExplainer: |
|
|
"""Alternative LLM explainer using Google Gemini""" |
|
|
|
|
|
def __init__(self): |
|
|
self.enabled = False |
|
|
self.model = None |
|
|
|
|
|
|
|
|
if not config.GEMINI_API_KEY: |
|
|
logger.warning("β οΈ GEMINI_API_KEY not found in environment variables") |
|
|
return |
|
|
|
|
|
|
|
|
try: |
|
|
genai.configure(api_key=config.GEMINI_API_KEY) |
|
|
self.model = genai.GenerativeModel(config.GEMINI_MODEL) |
|
|
|
|
|
|
|
|
test_response = self.model.generate_content("Say 'Hello'") |
|
|
if test_response.text: |
|
|
self.enabled = True |
|
|
logger.info(f"β
Gemini explainer initialized successfully with model: {config.GEMINI_MODEL}") |
|
|
else: |
|
|
logger.error("β Gemini test failed: No response text") |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"β Failed to initialize Gemini: {str(e)}") |
|
|
self.enabled = False |
|
|
|
|
|
def explain_recommendation( |
|
|
self, |
|
|
card: str, |
|
|
rewards: float, |
|
|
rewards_rate: str, |
|
|
merchant: str, |
|
|
category: str, |
|
|
amount: float, |
|
|
warnings: Optional[List[str]] = None, |
|
|
annual_potential: float = 0, |
|
|
alternatives: Optional[List[Dict]] = None |
|
|
) -> str: |
|
|
"""Generate explanation using Gemini""" |
|
|
|
|
|
if not self.enabled or not self.model: |
|
|
logger.warning("β οΈ Gemini not enabled, returning fallback explanation") |
|
|
return self._generate_fallback_explanation( |
|
|
card, rewards, rewards_rate, merchant, category, amount |
|
|
) |
|
|
|
|
|
|
|
|
prompt = f"""You are a friendly financial advisor helping everyday consumers optimize their credit card rewards. |
|
|
|
|
|
Transaction Details: |
|
|
- Merchant: {merchant} |
|
|
- Category: {category} |
|
|
- Amount: ${amount:.2f} |
|
|
|
|
|
Recommended Card: {card} |
|
|
Rewards Earned: ${rewards:.2f} ({rewards_rate}) |
|
|
Annual Potential: ${annual_potential:.2f}/year if you use this card for similar purchases |
|
|
|
|
|
Task: Explain in 2-3 simple, conversational sentences why this card is the best choice for this purchase. |
|
|
|
|
|
Guidelines: |
|
|
1. Start with the tangible benefit (e.g., "You'll earn $5.02 back on this purchase") |
|
|
2. Explain the reward rate in simple terms (avoid jargon) |
|
|
3. Add a relatable comparison (e.g., "That's like getting a free coffee!") |
|
|
4. Be encouraging and friendly |
|
|
|
|
|
{"β οΈ Important: Mention this warning - " + warnings[0] if warnings else ""} |
|
|
|
|
|
Keep it under 100 words and use everyday language.""" |
|
|
|
|
|
try: |
|
|
logger.info(f"π€ Calling Gemini for {merchant} recommendation...") |
|
|
|
|
|
response = self.model.generate_content( |
|
|
prompt, |
|
|
generation_config=genai.types.GenerationConfig( |
|
|
temperature=0.7, |
|
|
max_output_tokens=200, |
|
|
) |
|
|
) |
|
|
|
|
|
if response.text: |
|
|
logger.info(f"β
Gemini explanation generated successfully") |
|
|
return response.text.strip() |
|
|
else: |
|
|
logger.warning("β οΈ Gemini returned empty response") |
|
|
return self._generate_fallback_explanation( |
|
|
card, rewards, rewards_rate, merchant, category, amount |
|
|
) |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"β Gemini explanation failed: {str(e)}") |
|
|
return self._generate_fallback_explanation( |
|
|
card, rewards, rewards_rate, merchant, category, amount |
|
|
) |
|
|
|
|
|
def _generate_fallback_explanation( |
|
|
self, |
|
|
card: str, |
|
|
rewards: float, |
|
|
rewards_rate: str, |
|
|
merchant: str, |
|
|
category: str, |
|
|
amount: float |
|
|
) -> str: |
|
|
"""Generate rule-based explanation when Gemini 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 **${rewards:.2f}** back on this transaction. " |
|
|
|
|
|
|
|
|
if rewards >= 5: |
|
|
explanation += "That's like getting a free lunch! π" |
|
|
elif rewards >= 3: |
|
|
explanation += "That's like getting a free coffee! β" |
|
|
else: |
|
|
explanation += "Every bit of savings counts! π°" |
|
|
|
|
|
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 insights using Gemini""" |
|
|
|
|
|
if not self.enabled or not self.model: |
|
|
return self._generate_fallback_insights( |
|
|
total_spending, total_rewards, optimization_score |
|
|
) |
|
|
|
|
|
prompt = f"""You are a personal finance coach reviewing a user's credit card rewards performance. |
|
|
|
|
|
User Stats: |
|
|
- Total Spending: ${total_spending:.2f} |
|
|
- Total Rewards: ${total_rewards:.2f} |
|
|
- Optimization Score: {optimization_score}/100 |
|
|
- Optimized Transactions: {recommendations_count} |
|
|
- Top Categories: {', '.join([c.get('category', 'Unknown') for c in top_categories[:3]])} |
|
|
|
|
|
Task: Provide 3 actionable insights in a friendly, motivating tone. Each insight should be 1 sentence. |
|
|
|
|
|
Guidelines: |
|
|
1. Start with praise for what they're doing well |
|
|
2. Identify their biggest opportunity (highest spending category) |
|
|
3. Give one specific, actionable tip to improve their score |
|
|
4. Use emojis and be encouraging! |
|
|
|
|
|
Keep it under 120 words.""" |
|
|
|
|
|
try: |
|
|
response = self.model.generate_content( |
|
|
prompt, |
|
|
generation_config=genai.types.GenerationConfig( |
|
|
temperature=0.8, |
|
|
max_output_tokens=200, |
|
|
) |
|
|
) |
|
|
|
|
|
if response.text: |
|
|
return response.text.strip() |
|
|
else: |
|
|
return self._generate_fallback_insights( |
|
|
total_spending, total_rewards, optimization_score |
|
|
) |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"β Gemini insights generation failed: {str(e)}") |
|
|
return self._generate_fallback_insights( |
|
|
total_spending, total_rewards, optimization_score |
|
|
) |
|
|
|
|
|
def chat_response(self, message: str, user_context: dict, chat_history: list) -> str: |
|
|
""" |
|
|
Generate conversational response using Gemini |
|
|
|
|
|
Args: |
|
|
message: User's question |
|
|
user_context: User profile data (cards, spending, etc.) |
|
|
chat_history: Previous conversation turns |
|
|
|
|
|
Returns: |
|
|
str: Gemini's response |
|
|
""" |
|
|
if not self.enabled: |
|
|
return "Gemini AI is currently unavailable. Please check your API configuration." |
|
|
|
|
|
try: |
|
|
|
|
|
context_str = f""" |
|
|
You are a helpful credit card rewards expert assistant. You're chatting with a user who has the following profile: |
|
|
|
|
|
**User Profile:** |
|
|
- Cards in wallet: {', '.join(user_context.get('cards', ['Unknown']))} |
|
|
- Monthly spending: ${user_context.get('monthly_spending', 0):.2f} |
|
|
- Top spending category: {user_context.get('top_category', 'Unknown')} |
|
|
- Total rewards earned: ${user_context.get('total_rewards', 0):.2f} |
|
|
- Optimization score: {user_context.get('optimization_score', 0)}/100 |
|
|
|
|
|
**Your role:** |
|
|
- Answer questions about credit cards, rewards, and optimization strategies |
|
|
- Be conversational, friendly, and concise (2-3 paragraphs max) |
|
|
- Reference the user's specific cards and spending when relevant |
|
|
- Provide actionable advice |
|
|
- If asked about a specific card, explain its benefits and best use cases |
|
|
|
|
|
**Conversation history:** |
|
|
""" |
|
|
|
|
|
|
|
|
for user_msg, bot_msg in chat_history[-3:]: |
|
|
context_str += f"\nUser: {user_msg}\nAssistant: {bot_msg}\n" |
|
|
|
|
|
context_str += f"\n**Current question:** {message}\n\nProvide a helpful, personalized response:" |
|
|
|
|
|
|
|
|
response = self.model.generate_content(context_str) |
|
|
|
|
|
if response and response.text: |
|
|
return response.text.strip() |
|
|
else: |
|
|
return "I'm having trouble generating a response. Could you rephrase your question?" |
|
|
|
|
|
except Exception as e: |
|
|
print(f"Gemini chat error: {e}") |
|
|
return "I encountered an error processing your question. Please try asking in a different way." |
|
|
|
|
|
def _generate_fallback_insights( |
|
|
self, |
|
|
total_spending: float, |
|
|
total_rewards: float, |
|
|
optimization_score: int |
|
|
) -> str: |
|
|
"""Generate rule-based insights when Gemini 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 |
|
|
|
|
|
|
|
|
|
|
|
_gemini_explainer = None |
|
|
|
|
|
def get_gemini_explainer() -> GeminiExplainer: |
|
|
"""Get or create singleton Gemini explainer instance""" |
|
|
global _gemini_explainer |
|
|
|
|
|
if _gemini_explainer is None: |
|
|
_gemini_explainer = GeminiExplainer() |
|
|
|
|
|
return _gemini_explainer |