Spaces:
Running
Running
| import os | |
| import json | |
| import re | |
| from openai import OpenAI | |
| from dotenv import load_dotenv | |
| load_dotenv() | |
| class LLMService: | |
| def __init__(self): | |
| self.api_key = os.getenv("OPENROUTER_API_KEY") | |
| self.client = OpenAI( | |
| base_url="https://openrouter.ai/api/v1", | |
| api_key=self.api_key, | |
| ) | |
| self.model = "deepseek/deepseek-chat-v3.1" | |
| def _clean_json_response(self, content: str): | |
| """Helper to strip Markdown formatting """ | |
| try: | |
| return json.loads(content) | |
| except json.JSONDecodeError: | |
| # Look for markdown code blocks | |
| match = re.search(r"```json\s*(.*)\s*```", content, re.DOTALL) | |
| if match: | |
| return json.loads(match.group(1)) | |
| # Fallback: look for the first { and last } | |
| match = re.search(r"(\{.*\})", content, re.DOTALL) | |
| if match: | |
| return json.loads(match.group(1)) | |
| raise ValueError("Could not parse JSON from LLM") | |
| def analyze_match(self, resume_text: str, jd_text: str) -> dict: | |
| """ | |
| Uses LLM to Extract AND Match skills semantically in one pass. | |
| """ | |
| if not self.api_key: | |
| return { | |
| "score": 0, | |
| "common_skills": [], | |
| "missing_skills": ["API_KEY_MISSING"], | |
| "reasoning": "Please check server secrets." | |
| } | |
| prompt = f""" | |
| You are an expert AI Recruiter. | |
| TASK: | |
| Compare the Candidate's Resume against the Job Description (JD). | |
| 1. Extract the required Technical Skills & Soft Skills from the JD. | |
| 2. Check if the Candidate possesses these skills based on the Resume. | |
| 3. Calculate a Match Score (0-100). | |
| JOB DESCRIPTION: | |
| {jd_text[:]} | |
| CANDIDATE RESUME: | |
| {resume_text[:]} | |
| OUTPUT FORMAT: | |
| Return STRICT JSON with these keys: | |
| - "score": (Integer 0-100) | |
| - "common_skills": (List of strings) | |
| - "missing_skills": (List of strings) | |
| - "reasoning": (Short explanation) | |
| """ | |
| try: | |
| response = self.client.chat.completions.create( | |
| model=self.model, | |
| messages=[ | |
| {"role": "system", "content": "Output strictly valid JSON only."}, | |
| {"role": "user", "content": prompt} | |
| ] | |
| ) | |
| content = response.choices[0].message.content | |
| return self._clean_json_response(content) | |
| except Exception as e: | |
| print(f"LLM MATCH ERROR: {str(e)}") | |
| return { | |
| "score": 0, | |
| "common_skills": [], | |
| "missing_skills": ["Error calculating match"], | |
| "reasoning": "AI Service unavailable." | |
| } | |
| def generate_suggestions(self, resume_text: str, jd_text: str, missing_skills: list) -> dict: | |
| prompt = f""" | |
| You are a Career Coach. | |
| CONTEXT: | |
| Candidate is applying for this job | |
| {jd_text[:1000]} | |
| and this is their resume | |
| {resume_text[:]} | |
| They are missing: {", ".join(missing_skills)} | |
| TASK: | |
| 1. Suggest 3 specific technical side projects to bridge these gaps. | |
| 2. Write a short, professional cover letter. | |
| CRITICAL FORMATTING RULES: | |
| - Project format: Give project title first then description. | |
| - Do not use markdown headers in the project list. | |
| OUTPUT JSON keys: | |
| - "suggested_projects" (List of strings) | |
| - "cover_letter" (String) | |
| """ | |
| try: | |
| response = self.client.chat.completions.create( | |
| model='openrouter/free', | |
| messages=[ | |
| {"role": "system", "content": "Output strictly valid JSON only."}, | |
| {"role": "user", "content": prompt} | |
| ] | |
| ) | |
| content = response.choices[0].message.content | |
| return self._clean_json_response(content) | |
| except Exception as e: | |
| print(f"LLM SUGGESTION ERROR: {str(e)}") | |
| return { | |
| "suggested_projects": ["Error generating projects. Please try again."], | |
| "cover_letter": "Error generating cover letter." | |
| } | |
| llm_service = LLMService() |