Spaces:
Sleeping
Sleeping
| import os | |
| import json | |
| import re | |
| import logging | |
| import fitz # PyMuPDF | |
| import google.generativeai as genai | |
| from dotenv import load_dotenv | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| load_dotenv() | |
| api_key = os.getenv("GEMINI_API_KEY") | |
| if not api_key: | |
| raise ValueError("GEMINI_API_KEY is missing.") | |
| genai.configure(api_key=api_key) | |
| def extract_text_from_stream(file_bytes: bytes) -> str: | |
| text = "" | |
| try: | |
| with fitz.open(stream=file_bytes, filetype="pdf") as doc: | |
| for page in doc: | |
| text += page.get_text() | |
| except Exception as e: | |
| logger.error(f"PDF Extraction Error: {e}") | |
| raise ValueError("Failed to extract text from PDF.") | |
| return text | |
| def get_available_model_name(): | |
| """ | |
| Dynamically finds a working model from the user's account. | |
| """ | |
| try: | |
| available_models = [] | |
| for m in genai.list_models(): | |
| if 'generateContent' in m.supported_generation_methods: | |
| available_models.append(m.name) | |
| if not available_models: | |
| logger.error("No models found.") | |
| return None | |
| # Priority list: Try to find these specific powerful models first | |
| preferred_order = [ | |
| "models/gemini-1.5-flash", | |
| "models/gemini-1.5-pro", | |
| "models/gemini-pro", | |
| "models/gemini-1.0-pro" | |
| ] | |
| # 1. Check if any preferred model is in the available list | |
| for preferred in preferred_order: | |
| if preferred in available_models: | |
| logger.info(f"Selected Preferred Model: {preferred}") | |
| return preferred | |
| # 2. If none of the preferred ones exist, take the first available one | |
| fallback = available_models[0] | |
| logger.warning(f"Preferred models missing. Falling back to: {fallback}") | |
| return fallback | |
| except Exception as e: | |
| logger.error(f"Error listing models: {e}") | |
| return None | |
| def analyze_resume(resume_text: str, job_description: str = None) -> dict: | |
| # 1. FIND A WORKING MODEL (The Critical Fix) | |
| model_name = get_available_model_name() | |
| if not model_name: | |
| return {"error": "CRITICAL: No available AI models found for this API Key."} | |
| # 2. CONSTRUCT PROMPT | |
| if job_description: | |
| prompt = f""" | |
| Act as a strict AI Recruiter. Compare the Resume against the Job Description. | |
| RETURN JSON ONLY with this exact structure: | |
| {{ | |
| "candidate": {{ | |
| "name": "string", | |
| "email": "string", | |
| "phone": "string", | |
| "skills": ["list", "of", "candidate", "skills"], | |
| "experience_years": "string or null" | |
| }}, | |
| "match_analysis": {{ | |
| "score": integer_0_to_100, | |
| "reasoning": "brief summary of why this score was given", | |
| "matching_skills": ["skills in both resume and JD"], | |
| "missing_skills": ["skills in JD but NOT in resume"], | |
| "verdict": "Interview" | "Shortlist" | "Reject" | |
| }} | |
| }} | |
| JOB DESCRIPTION: | |
| {job_description[:5000]} | |
| RESUME TEXT: | |
| {resume_text[:10000]} | |
| """ | |
| else: | |
| prompt = f""" | |
| Extract structured data from the resume. Return JSON: | |
| {{ | |
| "candidate": {{ | |
| "name": "string", | |
| "email": "string", | |
| "phone": "string", | |
| "skills": ["list", "of", "skills"], | |
| "summary": "string" | |
| }} | |
| }} | |
| RESUME TEXT: | |
| {resume_text[:10000]} | |
| """ | |
| # 3. GENERATE CONTENT | |
| try: | |
| model = genai.GenerativeModel(model_name) | |
| response = model.generate_content(prompt) | |
| raw = response.text.strip() | |
| clean_json = re.sub(r'```json\s*|```', '', raw, flags=re.MULTILINE).strip() | |
| return json.loads(clean_json) | |
| except Exception as e: | |
| logger.error(f"Analysis failed with model {model_name}: {e}") | |
| return {"error": f"Analysis failed using {model_name}. Detail: {str(e)}"} |