import asyncio import hashlib from openai import AsyncOpenAI from typing import Dict, List, Any from app.core.config import settings from app.core.cache import ai_cache, cache_response import json class AIService: def __init__(self): self.client = AsyncOpenAI(api_key=settings.OPENAI_API_KEY) self._semaphore = asyncio.Semaphore(5) # Limit concurrent AI calls def _create_cache_key(self, text: str, operation: str) -> str: """Create a cache key for AI operations""" text_hash = hashlib.md5(text.encode()).hexdigest() return f"{operation}:{text_hash}" async def analyze_resume(self, resume_text: str) -> Dict[str, Any]: """Extract structured data from resume text using AI with caching""" # Check cache first cache_key = self._create_cache_key(resume_text, "analyze_resume") cached_result = ai_cache.get(cache_key) if cached_result: return cached_result # Rate limiting with semaphore async with self._semaphore: prompt = f""" Analyze the following resume text and extract structured information: {resume_text[:4000]} # Limit text length for faster processing Please return a JSON object with the following structure: {{ "skills": ["skill1", "skill2", ...], "experience_years": number, "education_level": "string", "work_experience": [ {{ "company": "string", "position": "string", "duration": "string", "description": "string" }} ], "education": [ {{ "institution": "string", "degree": "string", "field": "string", "year": "string" }} ], "contact_info": {{ "email": "string", "phone": "string", "location": "string" }} }} """ try: response = await self.client.chat.completions.create( model="gpt-3.5-turbo", messages=[ {"role": "system", "content": "You are an expert resume analyzer. Return only valid JSON."}, {"role": "user", "content": prompt} ], temperature=0.1, max_tokens=1500, # Limit response length timeout=30 # 30 second timeout ) result = response.choices[0].message.content parsed_result = json.loads(result) # Cache the result for 1 hour ai_cache.set(cache_key, parsed_result, ttl=3600) return parsed_result except Exception as e: print(f"Error analyzing resume: {e}") # Return cached empty result to avoid repeated failures empty_result = {} ai_cache.set(cache_key, empty_result, ttl=300) # Cache for 5 minutes return empty_result async def analyze_job_description(self, job_description: str) -> Dict[str, Any]: """Extract structured data from job description using AI with caching""" # Check cache first cache_key = self._create_cache_key(job_description, "analyze_job") cached_result = ai_cache.get(cache_key) if cached_result: return cached_result async with self._semaphore: prompt = f""" Analyze the following job description and extract structured information: {job_description[:3000]} # Limit text length Please return a JSON object with the following structure: {{ "required_skills": ["skill1", "skill2", ...], "preferred_skills": ["skill1", "skill2", ...], "experience_level": "entry/mid/senior", "education_requirement": "string", "key_responsibilities": ["resp1", "resp2", ...], "company_benefits": ["benefit1", "benefit2", ...], "job_type": "full-time/part-time/contract", "remote_option": "yes/no/hybrid" }} """ try: response = await self.client.chat.completions.create( model="gpt-3.5-turbo", messages=[ {"role": "system", "content": "You are an expert job description analyzer. Return only valid JSON."}, {"role": "user", "content": prompt} ], temperature=0.1, max_tokens=1000, timeout=30 ) result = response.choices[0].message.content parsed_result = json.loads(result) # Cache for 1 hour ai_cache.set(cache_key, parsed_result, ttl=3600) return parsed_result except Exception as e: print(f"Error analyzing job description: {e}") empty_result = {} ai_cache.set(cache_key, empty_result, ttl=300) return empty_result async def calculate_match_score( self, resume_data: Dict[str, Any], job_data: Dict[str, Any] ) -> Dict[str, Any]: """Calculate match score between resume and job description with caching""" # Create cache key from both resume and job data combined_data = f"{json.dumps(resume_data, sort_keys=True)}{json.dumps(job_data, sort_keys=True)}" cache_key = self._create_cache_key(combined_data, "match_score") cached_result = ai_cache.get(cache_key) if cached_result: return cached_result async with self._semaphore: # Limit data size for faster processing limited_resume = {k: v for k, v in resume_data.items() if k in ["skills", "experience_years", "education_level"]} limited_job = {k: v for k, v in job_data.items() if k in ["required_skills", "preferred_skills", "experience_level", "education_requirement"]} prompt = f""" Calculate a match score between this resume and job description: RESUME: {json.dumps(limited_resume)} JOB: {json.dumps(limited_job)} Return JSON: {{ "overall_score": number (0-100), "skill_match_score": number (0-100), "experience_match_score": number (0-100), "education_match_score": number (0-100), "missing_skills": [ {{"skill": "string", "importance": "required/preferred", "suggestion": "string"}} ], "strengths": ["strength1", "strength2"], "weaknesses": ["weakness1", "weakness2"], "overall_feedback": "brief feedback" }} """ try: response = await self.client.chat.completions.create( model="gpt-3.5-turbo", messages=[ {"role": "system", "content": "You are an expert HR analyst. Provide accurate match scoring. Be concise."}, {"role": "user", "content": prompt} ], temperature=0.2, max_tokens=1500, timeout=30 ) result = response.choices[0].message.content parsed_result = json.loads(result) # Cache for 30 minutes ai_cache.set(cache_key, parsed_result, ttl=1800) return parsed_result except Exception as e: print(f"Error calculating match score: {e}") default_result = {"overall_score": 0, "skill_match_score": 0, "experience_match_score": 0, "education_match_score": 0} ai_cache.set(cache_key, default_result, ttl=300) return default_result async def generate_resume_suggestions( self, resume_data: Dict[str, Any], job_data: Dict[str, Any], match_analysis: Dict[str, Any] ) -> List[Dict[str, str]]: """Generate suggestions for improving resume based on job requirements with caching""" # Create cache key from all input data combined_data = f"{json.dumps(resume_data, sort_keys=True)}{json.dumps(job_data, sort_keys=True)}{json.dumps(match_analysis, sort_keys=True)}" cache_key = self._create_cache_key(combined_data, "resume_suggestions") cached_result = ai_cache.get(cache_key) if cached_result: return cached_result async with self._semaphore: # Use only essential data for faster processing limited_data = { "skills": resume_data.get("skills", []), "missing_skills": match_analysis.get("missing_skills", []), "weaknesses": match_analysis.get("weaknesses", []) } prompt = f""" Provide 3-5 specific resume improvement suggestions based on this analysis: DATA: {json.dumps(limited_data)} Return JSON array: [ {{ "section": "skills/experience/education/summary", "suggestion": "specific actionable suggestion", "priority": "high/medium/low", "impact": "brief explanation" }} ] """ try: response = await self.client.chat.completions.create( model="gpt-3.5-turbo", messages=[ {"role": "system", "content": "You are an expert resume coach. Be concise and actionable."}, {"role": "user", "content": prompt} ], temperature=0.3, max_tokens=800, timeout=30 ) result = response.choices[0].message.content parsed_result = json.loads(result) # Cache for 1 hour ai_cache.set(cache_key, parsed_result, ttl=3600) return parsed_result except Exception as e: print(f"Error generating resume suggestions: {e}") empty_result = [] ai_cache.set(cache_key, empty_result, ttl=300) return empty_result async def generate_cover_letter( self, resume_data: Dict[str, Any], job_data: Dict[str, Any], user_name: str ) -> str: """Generate a personalized cover letter with caching""" # Create cache key from resume, job, and user name combined_data = f"{json.dumps(resume_data, sort_keys=True)}{json.dumps(job_data, sort_keys=True)}{user_name}" cache_key = self._create_cache_key(combined_data, "cover_letter") cached_result = ai_cache.get(cache_key) if cached_result: return cached_result async with self._semaphore: # Use essential data only essential_resume = { "skills": resume_data.get("skills", []), "work_experience": resume_data.get("work_experience", [])[:2] # Only first 2 jobs } essential_job = { "title": job_data.get("title", ""), "company": job_data.get("company", ""), "required_skills": job_data.get("required_skills", [])[:5] # Top 5 skills } prompt = f""" Write a professional cover letter for {user_name}: RESUME: {json.dumps(essential_resume)} JOB: {json.dumps(essential_job)} Requirements: - 3 paragraphs - Professional tone - Highlight relevant skills - Show enthusiasm """ try: response = await self.client.chat.completions.create( model="gpt-3.5-turbo", messages=[ {"role": "system", "content": "You are an expert cover letter writer. Write compelling, concise cover letters."}, {"role": "user", "content": prompt} ], temperature=0.4, max_tokens=600, timeout=30 ) result = response.choices[0].message.content # Cache for 30 minutes ai_cache.set(cache_key, result, ttl=1800) return result except Exception as e: print(f"Error generating cover letter: {e}") error_msg = "Unable to generate cover letter at this time." ai_cache.set(cache_key, error_msg, ttl=300) return error_msg