Spaces:
Running
Running
| import modal | |
| import json | |
| from typing import Dict, Any, Optional | |
| from datetime import datetime | |
| import os | |
| import urllib.request | |
| import urllib.parse | |
| # Create Modal app | |
| app = modal.App("moneymate-backend") | |
| # Create image with required dependencies | |
| image = modal.Image.debian_slim(python_version="3.11").pip_install([ | |
| "fastapi", | |
| "pydantic" | |
| ]) | |
| # vLLM server configuration | |
| # TODO: Replace with your actual vLLM server URL after deployment | |
| VLLM_SERVER_URL = "https://kaustubhme0--example-vllm-openai-compatible-serve.modal.run" | |
| # This API key should match the API_KEY in your vLLM inference script (paste.txt) | |
| # It's YOUR custom key, not from any external service | |
| VLLM_API_KEY = "super-secret-key" | |
| # Financial advisor system prompt | |
| SYSTEM_PROMPT = """You are MoneyMate, a friendly and knowledgeable financial advisor specifically designed for young Indian professionals who are new to managing their salaries and finances. | |
| PERSONALITY & TONE: | |
| - Warm, approachable, and encouraging | |
| - Use simple, jargon-free language | |
| - Be supportive and non-judgmental | |
| - Add appropriate emojis to make advice engaging | |
| - Safety-first approach - always emphasize careful consideration | |
| EXPERTISE AREAS: | |
| - Salary allocation and budgeting | |
| - Indian investment options (SIP, ELSS, PPF, NPS, etc.) | |
| - Tax-saving strategies under Indian tax law | |
| - Emergency fund planning | |
| - Goal-based financial planning | |
| - Expense optimization | |
| RESPONSE FORMAT: | |
| - Use markdown formatting for better readability | |
| - Include practical examples with Indian Rupee amounts | |
| - Provide step-by-step actionable advice | |
| - Always include safety disclaimers for investment advice | |
| - Suggest starting small and learning gradually | |
| CULTURAL CONTEXT: | |
| - Understand Indian financial instruments and regulations | |
| - Consider family financial responsibilities common in India | |
| - Be aware of Indian tax implications (80C, LTCG, etc.) | |
| - Reference Indian banks, mutual fund companies, and financial platforms | |
| SAFETY GUIDELINES: | |
| - Never guarantee returns or specific outcomes | |
| - Always recommend consulting financial advisors for major decisions | |
| - Emphasize the importance of emergency funds before investing | |
| - Warn about risks associated with investments | |
| - Promote financial literacy and gradual learning | |
| Remember: You're helping someone who may be handling their first salary. Be patient, educational, and encouraging while prioritizing their financial safety and security. | |
| """ | |
| def call_vllm_api(messages: list, max_tokens: int = 1500, temperature: float = 0.7) -> str: | |
| """Call the vLLM server with OpenAI-compatible API""" | |
| headers = { | |
| "Authorization": f"Bearer {VLLM_API_KEY}", | |
| "Content-Type": "application/json", | |
| } | |
| payload = { | |
| "messages": messages, | |
| "model": "neuralmagic/Meta-Llama-3.1-8B-Instruct-quantized.w4a16", | |
| "max_tokens": max_tokens, | |
| "temperature": temperature | |
| } | |
| try: | |
| req = urllib.request.Request( | |
| f"{VLLM_SERVER_URL}/v1/chat/completions", | |
| data=json.dumps(payload).encode("utf-8"), | |
| headers=headers, | |
| method="POST", | |
| ) | |
| # Increased timeout to 180 seconds (3 minutes) for LLM responses | |
| with urllib.request.urlopen(req, timeout=180) as response: | |
| if response.getcode() == 200: | |
| result = json.loads(response.read().decode()) | |
| return result["choices"][0]["message"]["content"] | |
| else: | |
| response_text = response.read().decode() | |
| raise Exception(f"API call failed with status {response.getcode()}: {response_text}") | |
| except urllib.error.URLError as e: | |
| if "timeout" in str(e).lower(): | |
| raise Exception("Request timed out. The vLLM server might be taking too long to respond or might be cold starting.") | |
| else: | |
| raise Exception(f"Network error calling vLLM API: {str(e)}") | |
| except Exception as e: | |
| raise Exception(f"Error calling vLLM API: {str(e)}") | |
| def get_financial_advice(user_input: str, context: Dict[str, Any] = None) -> str: | |
| """Generate financial advice using local vLLM server""" | |
| try: | |
| # Prepare context information | |
| context_str = "" | |
| if context: | |
| salary = context.get("salary", 0) | |
| expenses = context.get("expenses", {}) | |
| total_expenses = context.get("total_expenses", 0) | |
| remaining_salary = context.get("remaining_salary", 0) | |
| savings_goal = context.get("savings_goal", "") | |
| context_str = f""" | |
| CURRENT FINANCIAL SITUATION: | |
| - Monthly Salary: ₹{salary:,.0f} | |
| - Total Monthly Expenses: ₹{total_expenses:,.0f} | |
| - Remaining Amount: ₹{remaining_salary:,.0f} | |
| - Savings Goal: {savings_goal} | |
| EXPENSE BREAKDOWN: | |
| """ | |
| for category, amount in expenses.items(): | |
| if amount > 0: | |
| context_str += f"- {category}: ₹{amount:,.0f}\n" | |
| # Create the full prompt | |
| user_prompt = f""" | |
| {context_str} | |
| USER QUESTION: {user_input} | |
| Please provide personalized financial advice based on this information. Focus on: | |
| 1. Specific recommendations for their situation | |
| 2. Practical next steps they can take | |
| 3. Indian financial instruments and strategies | |
| 4. Risk management and safety | |
| 5. Educational insights to help them learn | |
| """ | |
| # Prepare messages for vLLM | |
| messages = [ | |
| {"role": "system", "content": SYSTEM_PROMPT}, | |
| {"role": "user", "content": user_prompt} | |
| ] | |
| # Call vLLM API | |
| advice = call_vllm_api(messages, max_tokens=1500, temperature=0.7) | |
| # Add timestamp and additional context | |
| current_time = datetime.now().strftime("%B %d, %Y at %I:%M %p") | |
| formatted_advice = f""" | |
| {advice} | |
| --- | |
| 💡 **MoneyMate Tips:** | |
| - Start small and gradually increase your investments | |
| - Always maintain 6-12 months of expenses as emergency fund | |
| - Review and adjust your financial plan quarterly | |
| - Consider consulting a certified financial planner for major decisions | |
| *Advice generated on {current_time}* | |
| """ | |
| return formatted_advice.strip() | |
| except Exception as e: | |
| error_msg = f""" | |
| ## 😔 Sorry, I'm having trouble right now | |
| I encountered an error while processing your request: {str(e)} | |
| **Here are some general tips while I'm unavailable:** | |
| ### 💰 Basic Financial Rules for Young Professionals: | |
| - **50/30/20 Rule**: Allocate 50% for needs, 30% for wants, 20% for savings | |
| - **Emergency Fund**: Build 6-12 months of expenses before investing | |
| - **Start Small**: Begin with SIP of ₹1000-2000 monthly in diversified funds | |
| - **Tax Planning**: Use ELSS funds to save tax under Section 80C | |
| ### 🏦 Safe Investment Options for Beginners: | |
| - **SIP in Index Funds**: Low cost, diversified exposure | |
| - **PPF**: 15-year lock-in, tax-free returns | |
| - **ELSS Funds**: Tax saving with 3-year lock-in | |
| - **Liquid Funds**: For emergency fund parking | |
| Please try again in a few moments, or check your connection to the vLLM server. | |
| """ | |
| return error_msg | |
| def analyze_expenses(expenses: Dict[str, float], salary: float) -> Dict[str, Any]: | |
| """Analyze expense patterns and provide optimization suggestions""" | |
| total_expenses = sum(expenses.values()) | |
| expense_ratio = (total_expenses / salary) * 100 if salary > 0 else 0 | |
| analysis = { | |
| "total_expenses": total_expenses, | |
| "expense_ratio": expense_ratio, | |
| "status": "healthy" if expense_ratio < 70 else "concerning", | |
| "recommendations": [] | |
| } | |
| # Analyze each category | |
| for category, amount in expenses.items(): | |
| category_ratio = (amount / salary) * 100 if salary > 0 else 0 | |
| # Category-specific recommendations | |
| if category.lower() == "rent" and category_ratio > 40: | |
| analysis["recommendations"].append(f"🏠 Rent is high ({category_ratio:.1f}% of salary). Consider relocating or finding roommates.") | |
| elif category.lower() == "food" and category_ratio > 20: | |
| analysis["recommendations"].append(f"🍽️ Food expenses are high ({category_ratio:.1f}% of salary). Try cooking at home more often.") | |
| elif category.lower() == "entertainment" and category_ratio > 15: | |
| analysis["recommendations"].append(f"🎬 Entertainment expenses are high ({category_ratio:.1f}% of salary). Set a monthly budget and stick to it.") | |
| return analysis | |
| def calculate_investment_returns( | |
| monthly_investment: float, | |
| annual_return_rate: float, | |
| years: int | |
| ) -> Dict[str, Any]: | |
| """Calculate SIP returns with compound interest""" | |
| monthly_rate = annual_return_rate / 12 / 100 | |
| total_months = years * 12 | |
| # SIP future value formula | |
| if monthly_rate > 0: | |
| future_value = monthly_investment * (((1 + monthly_rate) ** total_months - 1) / monthly_rate) * (1 + monthly_rate) | |
| else: | |
| future_value = monthly_investment * total_months | |
| total_invested = monthly_investment * total_months | |
| returns = future_value - total_invested | |
| return { | |
| "future_value": round(future_value, 2), | |
| "total_invested": round(total_invested, 2), | |
| "returns": round(returns, 2), | |
| "return_percentage": round((returns / total_invested) * 100, 2) if total_invested > 0 else 0 | |
| } | |
| def health_check_vllm() -> Dict[str, Any]: | |
| """Check if vLLM server is healthy""" | |
| try: | |
| with urllib.request.urlopen(f"{VLLM_SERVER_URL}/health", timeout=30) as response: | |
| if response.getcode() == 200: | |
| return {"vllm_status": "healthy", "server_url": VLLM_SERVER_URL} | |
| else: | |
| return {"vllm_status": "unhealthy", "server_url": VLLM_SERVER_URL, "status_code": response.getcode()} | |
| except Exception as e: | |
| return {"vllm_status": "error", "error": str(e), "server_url": VLLM_SERVER_URL} | |
| # FastAPI app for HTTP endpoints | |
| def fastapi_app(): | |
| from fastapi import FastAPI, HTTPException | |
| from pydantic import BaseModel | |
| from typing import Optional | |
| api = FastAPI(title="MoneyMate Backend API with vLLM") | |
| class FinancialAdviceRequest(BaseModel): | |
| user_input: str | |
| context: Optional[Dict[str, Any]] = None | |
| class ExpenseAnalysisRequest(BaseModel): | |
| expenses: Dict[str, float] | |
| salary: float | |
| class InvestmentCalculationRequest(BaseModel): | |
| monthly_investment: float | |
| annual_return_rate: float | |
| years: int | |
| async def get_advice(request: FinancialAdviceRequest): | |
| """Get AI-powered financial advice using vLLM""" | |
| try: | |
| advice = get_financial_advice.remote(request.user_input, request.context) | |
| return {"advice": advice} | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def analyze_user_expenses(request: ExpenseAnalysisRequest): | |
| """Analyze user's expense patterns""" | |
| try: | |
| analysis = analyze_expenses.remote(request.expenses, request.salary) | |
| return {"analysis": analysis} | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def calculate_returns(request: InvestmentCalculationRequest): | |
| """Calculate SIP investment returns""" | |
| try: | |
| returns = calculate_investment_returns.remote( | |
| request.monthly_investment, | |
| request.annual_return_rate, | |
| request.years | |
| ) | |
| return {"returns": returns} | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def health_check(): | |
| """Health check endpoint including vLLM server status""" | |
| vllm_health = health_check_vllm.remote() | |
| return { | |
| "status": "healthy", | |
| "service": "MoneyMate Backend with vLLM", | |
| "vllm_server": vllm_health | |
| } | |
| async def root(): | |
| """Root endpoint with service information""" | |
| return { | |
| "service": "MoneyMate Backend API with vLLM", | |
| "version": "2.0.0", | |
| "description": "AI-powered financial advice for young Indian professionals using local vLLM server", | |
| "vllm_server": VLLM_SERVER_URL, | |
| "endpoints": [ | |
| "/financial_advice", | |
| "/analyze_expenses", | |
| "/calculate_returns", | |
| "/health" | |
| ] | |
| } | |
| return api | |
| # Test function to verify vLLM integration | |
| def test_vllm_integration(): | |
| """Test the vLLM integration with a sample financial question""" | |
| print(f"Testing vLLM server at: {VLLM_SERVER_URL}") | |
| print("Testing vLLM server health...") | |
| health = health_check_vllm.remote() | |
| print(f"vLLM Health: {health}") | |
| if health.get("vllm_status") == "healthy": | |
| print("\nTesting simple financial advice generation...") | |
| # Start with a very simple question first | |
| try: | |
| advice = get_financial_advice.remote("What is SIP?", None) | |
| print("Generated Advice:") | |
| print("=" * 50) | |
| print(advice) | |
| except Exception as e: | |
| print(f"Error during simple test: {e}") | |
| print("\nTesting with context...") | |
| sample_context = { | |
| "salary": 50000, | |
| "expenses": {"rent": 15000, "food": 8000, "transport": 3000}, | |
| "total_expenses": 26000, | |
| "remaining_salary": 24000, | |
| "savings_goal": "Emergency fund and investments" | |
| } | |
| try: | |
| advice = get_financial_advice.remote( | |
| "How should I invest my remaining salary?", | |
| sample_context | |
| ) | |
| print("Generated Advice with Context:") | |
| print("=" * 50) | |
| print(advice) | |
| except Exception as e: | |
| print(f"Error during context test: {e}") | |
| else: | |
| print("vLLM server is not healthy. Please check:") | |
| print("1. Is your vLLM server deployed and running?") | |
| print("2. Is the VLLM_SERVER_URL correct?") | |
| print("3. Is the API_KEY matching?") | |
| print(f"Current URL: {VLLM_SERVER_URL}") | |
| print(f"Current API Key: {VLLM_API_KEY}") |