Update code via agent code generation

This commit is contained in:
Automated Action 2025-06-27 17:33:33 +00:00
parent 2735438f01
commit 33579e7e0b
3 changed files with 191 additions and 154 deletions

View File

@ -9,8 +9,8 @@ class Settings(BaseSettings):
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30 ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
ALGORITHM: str = "HS256" ALGORITHM: str = "HS256"
# OpenAI Configuration # Cohere Configuration
OPENAI_API_KEY: str = os.getenv("OPENAI_API_KEY", "") COHERE_API_KEY: str = os.getenv("COHERE_API_KEY", "")
# CORS settings # CORS settings
CORS_ORIGINS: list = ["*"] CORS_ORIGINS: list = ["*"]

View File

@ -1,6 +1,6 @@
import asyncio import asyncio
import hashlib import hashlib
from openai import AsyncOpenAI import cohere
from typing import Dict, List, Any from typing import Dict, List, Any
from app.core.config import settings from app.core.config import settings
from app.core.cache import ai_cache, cache_response from app.core.cache import ai_cache, cache_response
@ -9,7 +9,7 @@ import json
class AIService: class AIService:
def __init__(self): def __init__(self):
self.client = AsyncOpenAI(api_key=settings.OPENAI_API_KEY) self.client = cohere.AsyncClient(api_key=settings.COHERE_API_KEY)
self._semaphore = asyncio.Semaphore(5) # Limit concurrent AI calls self._semaphore = asyncio.Semaphore(5) # Limit concurrent AI calls
def _create_cache_key(self, text: str, operation: str) -> str: def _create_cache_key(self, text: str, operation: str) -> str:
@ -18,7 +18,7 @@ class AIService:
return f"{operation}:{text_hash}" return f"{operation}:{text_hash}"
async def analyze_resume(self, resume_text: str) -> Dict[str, Any]: async def analyze_resume(self, resume_text: str) -> Dict[str, Any]:
"""Extract structured data from resume text using AI with caching""" """Extract structured data from resume text using Cohere AI with caching"""
# Check cache first # Check cache first
cache_key = self._create_cache_key(resume_text, "analyze_resume") cache_key = self._create_cache_key(resume_text, "analyze_resume")
cached_result = ai_cache.get(cache_key) cached_result = ai_cache.get(cache_key)
@ -27,54 +27,63 @@ class AIService:
# Rate limiting with semaphore # Rate limiting with semaphore
async with self._semaphore: async with self._semaphore:
prompt = f""" prompt = f"""Analyze this resume and extract structured information. Return only valid JSON.
Analyze the following resume text and extract structured information:
{resume_text[:4000]} # Limit text length for faster processing Resume text:
{resume_text[:4000]}
Please return a JSON object with the following structure: Extract the following information in JSON format:
{{ {{
"skills": ["skill1", "skill2", ...], "skills": ["skill1", "skill2", ...],
"experience_years": number, "experience_years": number,
"education_level": "string", "education_level": "Bachelor's/Master's/PhD/High School/etc",
"work_experience": [ "work_experience": [
{{ {{
"company": "string", "company": "company name",
"position": "string", "position": "job title",
"duration": "string", "duration": "time period",
"description": "string" "description": "brief description"
}} }}
], ],
"education": [ "education": [
{{ {{
"institution": "string", "institution": "school name",
"degree": "string", "degree": "degree type",
"field": "string", "field": "field of study",
"year": "string" "year": "graduation year"
}} }}
], ],
"contact_info": {{ "contact_info": {{
"email": "string", "email": "email address",
"phone": "string", "phone": "phone number",
"location": "string" "location": "location"
}} }}
}} }}
"""
JSON:"""
try: try:
response = await self.client.chat.completions.create( response = await self.client.chat(
model="gpt-3.5-turbo", model="command-r",
messages=[ message=prompt,
{"role": "system", "content": "You are an expert resume analyzer. Return only valid JSON."},
{"role": "user", "content": prompt}
],
temperature=0.1, temperature=0.1,
max_tokens=1500, # Limit response length max_tokens=1500,
timeout=30 # 30 second timeout connectors=[]
) )
result = response.choices[0].message.content result = response.text.strip()
# Try to extract JSON from the response
if result.startswith('{') and result.endswith('}'):
parsed_result = json.loads(result) parsed_result = json.loads(result)
else:
# Try to find JSON in the response
import re
json_match = re.search(r'\{.*\}', result, re.DOTALL)
if json_match:
parsed_result = json.loads(json_match.group())
else:
raise ValueError("No valid JSON found in response")
# Cache the result for 1 hour # Cache the result for 1 hour
ai_cache.set(cache_key, parsed_result, ttl=3600) ai_cache.set(cache_key, parsed_result, ttl=3600)
@ -88,7 +97,7 @@ class AIService:
return empty_result return empty_result
async def analyze_job_description(self, job_description: str) -> Dict[str, Any]: async def analyze_job_description(self, job_description: str) -> Dict[str, Any]:
"""Extract structured data from job description using AI with caching""" """Extract structured data from job description using Cohere AI with caching"""
# Check cache first # Check cache first
cache_key = self._create_cache_key(job_description, "analyze_job") cache_key = self._create_cache_key(job_description, "analyze_job")
cached_result = ai_cache.get(cache_key) cached_result = ai_cache.get(cache_key)
@ -96,38 +105,47 @@ class AIService:
return cached_result return cached_result
async with self._semaphore: async with self._semaphore:
prompt = f""" prompt = f"""Analyze this job description and extract structured information. Return only valid JSON.
Analyze the following job description and extract structured information:
{job_description[:3000]} # Limit text length Job description:
{job_description[:3000]}
Please return a JSON object with the following structure: Extract the following information in JSON format:
{{ {{
"required_skills": ["skill1", "skill2", ...], "required_skills": ["skill1", "skill2", ...],
"preferred_skills": ["skill1", "skill2", ...], "preferred_skills": ["skill1", "skill2", ...],
"experience_level": "entry/mid/senior", "experience_level": "entry/mid/senior",
"education_requirement": "string", "education_requirement": "minimum education required",
"key_responsibilities": ["resp1", "resp2", ...], "key_responsibilities": ["responsibility1", "responsibility2", ...],
"company_benefits": ["benefit1", "benefit2", ...], "company_benefits": ["benefit1", "benefit2", ...],
"job_type": "full-time/part-time/contract", "job_type": "full-time/part-time/contract",
"remote_option": "yes/no/hybrid" "remote_option": "yes/no/hybrid"
}} }}
"""
JSON:"""
try: try:
response = await self.client.chat.completions.create( response = await self.client.chat(
model="gpt-3.5-turbo", model="command-r",
messages=[ message=prompt,
{"role": "system", "content": "You are an expert job description analyzer. Return only valid JSON."},
{"role": "user", "content": prompt}
],
temperature=0.1, temperature=0.1,
max_tokens=1000, max_tokens=1000,
timeout=30 connectors=[]
) )
result = response.choices[0].message.content result = response.text.strip()
# Try to extract JSON from the response
if result.startswith('{') and result.endswith('}'):
parsed_result = json.loads(result) parsed_result = json.loads(result)
else:
# Try to find JSON in the response
import re
json_match = re.search(r'\{.*\}', result, re.DOTALL)
if json_match:
parsed_result = json.loads(json_match.group())
else:
raise ValueError("No valid JSON found in response")
# Cache for 1 hour # Cache for 1 hour
ai_cache.set(cache_key, parsed_result, ttl=3600) ai_cache.set(cache_key, parsed_result, ttl=3600)
@ -142,7 +160,7 @@ class AIService:
async def calculate_match_score( async def calculate_match_score(
self, resume_data: Dict[str, Any], job_data: Dict[str, Any] self, resume_data: Dict[str, Any], job_data: Dict[str, Any]
) -> Dict[str, Any]: ) -> Dict[str, Any]:
"""Calculate match score between resume and job description with caching""" """Calculate match score between resume and job description using Cohere AI with caching"""
# Create cache key from both resume and job data # 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)}" 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") cache_key = self._create_cache_key(combined_data, "match_score")
@ -155,41 +173,52 @@ class AIService:
limited_resume = {k: v for k, v in resume_data.items() if k in ["skills", "experience_years", "education_level"]} 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"]} limited_job = {k: v for k, v in job_data.items() if k in ["required_skills", "preferred_skills", "experience_level", "education_requirement"]}
prompt = f""" prompt = f"""Calculate a match score between this resume and job requirements. Return only valid JSON.
Calculate a match score between this resume and job description:
RESUME: {json.dumps(limited_resume)} RESUME DATA:
JOB: {json.dumps(limited_job)} {json.dumps(limited_resume)}
Return JSON: JOB REQUIREMENTS:
{json.dumps(limited_job)}
Analyze and return a match score in this JSON format:
{{ {{
"overall_score": number (0-100), "overall_score": number_0_to_100,
"skill_match_score": number (0-100), "skill_match_score": number_0_to_100,
"experience_match_score": number (0-100), "experience_match_score": number_0_to_100,
"education_match_score": number (0-100), "education_match_score": number_0_to_100,
"missing_skills": [ "missing_skills": [
{{"skill": "string", "importance": "required/preferred", "suggestion": "string"}} {{"skill": "skill_name", "importance": "required/preferred", "suggestion": "how_to_acquire"}}
], ],
"strengths": ["strength1", "strength2"], "strengths": ["strength1", "strength2"],
"weaknesses": ["weakness1", "weakness2"], "weaknesses": ["weakness1", "weakness2"],
"overall_feedback": "brief feedback" "overall_feedback": "brief_summary"
}} }}
"""
JSON:"""
try: try:
response = await self.client.chat.completions.create( response = await self.client.chat(
model="gpt-3.5-turbo", model="command-r",
messages=[ message=prompt,
{"role": "system", "content": "You are an expert HR analyst. Provide accurate match scoring. Be concise."},
{"role": "user", "content": prompt}
],
temperature=0.2, temperature=0.2,
max_tokens=1500, max_tokens=1500,
timeout=30 connectors=[]
) )
result = response.choices[0].message.content result = response.text.strip()
# Try to extract JSON from the response
if result.startswith('{') and result.endswith('}'):
parsed_result = json.loads(result) parsed_result = json.loads(result)
else:
# Try to find JSON in the response
import re
json_match = re.search(r'\{.*\}', result, re.DOTALL)
if json_match:
parsed_result = json.loads(json_match.group())
else:
raise ValueError("No valid JSON found in response")
# Cache for 30 minutes # Cache for 30 minutes
ai_cache.set(cache_key, parsed_result, ttl=1800) ai_cache.set(cache_key, parsed_result, ttl=1800)
@ -204,7 +233,7 @@ class AIService:
async def generate_resume_suggestions( async def generate_resume_suggestions(
self, resume_data: Dict[str, Any], job_data: Dict[str, Any], match_analysis: Dict[str, Any] self, resume_data: Dict[str, Any], job_data: Dict[str, Any], match_analysis: Dict[str, Any]
) -> List[Dict[str, str]]: ) -> List[Dict[str, str]]:
"""Generate suggestions for improving resume based on job requirements with caching""" """Generate suggestions for improving resume using Cohere AI with caching"""
# Create cache key from all input data # 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)}" 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") cache_key = self._create_cache_key(combined_data, "resume_suggestions")
@ -215,41 +244,50 @@ class AIService:
async with self._semaphore: async with self._semaphore:
# Use only essential data for faster processing # Use only essential data for faster processing
limited_data = { limited_data = {
"skills": resume_data.get("skills", []), "current_skills": resume_data.get("skills", []),
"missing_skills": match_analysis.get("missing_skills", []), "missing_skills": match_analysis.get("missing_skills", []),
"weaknesses": match_analysis.get("weaknesses", []) "weaknesses": match_analysis.get("weaknesses", [])
} }
prompt = f""" prompt = f"""Provide 3-5 specific resume improvement suggestions. Return only valid JSON.
Provide 3-5 specific resume improvement suggestions based on this analysis:
DATA: {json.dumps(limited_data)} Analysis data:
{json.dumps(limited_data)}
Return JSON array: Return suggestions in this JSON array format:
[ [
{{ {{
"section": "skills/experience/education/summary", "section": "skills/experience/education/summary",
"suggestion": "specific actionable suggestion", "suggestion": "specific_actionable_suggestion",
"priority": "high/medium/low", "priority": "high/medium/low",
"impact": "brief explanation" "impact": "brief_explanation"
}} }}
] ]
"""
JSON:"""
try: try:
response = await self.client.chat.completions.create( response = await self.client.chat(
model="gpt-3.5-turbo", model="command-r",
messages=[ message=prompt,
{"role": "system", "content": "You are an expert resume coach. Be concise and actionable."},
{"role": "user", "content": prompt}
],
temperature=0.3, temperature=0.3,
max_tokens=800, max_tokens=800,
timeout=30 connectors=[]
) )
result = response.choices[0].message.content result = response.text.strip()
# Try to extract JSON from the response
if result.startswith('[') and result.endswith(']'):
parsed_result = json.loads(result) parsed_result = json.loads(result)
else:
# Try to find JSON array in the response
import re
json_match = re.search(r'\[.*\]', result, re.DOTALL)
if json_match:
parsed_result = json.loads(json_match.group())
else:
raise ValueError("No valid JSON array found in response")
# Cache for 1 hour # Cache for 1 hour
ai_cache.set(cache_key, parsed_result, ttl=3600) ai_cache.set(cache_key, parsed_result, ttl=3600)
@ -264,7 +302,7 @@ class AIService:
async def generate_cover_letter( async def generate_cover_letter(
self, resume_data: Dict[str, Any], job_data: Dict[str, Any], user_name: str self, resume_data: Dict[str, Any], job_data: Dict[str, Any], user_name: str
) -> str: ) -> str:
"""Generate a personalized cover letter with caching""" """Generate a personalized cover letter using Cohere AI with caching"""
# Create cache key from resume, job, and user name # 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}" 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") cache_key = self._create_cache_key(combined_data, "cover_letter")
@ -275,7 +313,7 @@ class AIService:
async with self._semaphore: async with self._semaphore:
# Use essential data only # Use essential data only
essential_resume = { essential_resume = {
"skills": resume_data.get("skills", []), "skills": resume_data.get("skills", [])[:8], # Top 8 skills
"work_experience": resume_data.get("work_experience", [])[:2] # Only first 2 jobs "work_experience": resume_data.get("work_experience", [])[:2] # Only first 2 jobs
} }
essential_job = { essential_job = {
@ -284,32 +322,31 @@ class AIService:
"required_skills": job_data.get("required_skills", [])[:5] # Top 5 skills "required_skills": job_data.get("required_skills", [])[:5] # Top 5 skills
} }
prompt = f""" prompt = f"""Write a professional cover letter for {user_name} applying to this job.
Write a professional cover letter for {user_name}:
RESUME: {json.dumps(essential_resume)} APPLICANT BACKGROUND:
JOB: {json.dumps(essential_job)} {json.dumps(essential_resume)}
Requirements: JOB DETAILS:
- 3 paragraphs {json.dumps(essential_job)}
- Professional tone
- Highlight relevant skills Write a compelling 3-paragraph cover letter that:
- Show enthusiasm - Opens with enthusiasm for the specific role
""" - Highlights relevant skills and experience
- Closes with a call to action
Keep it professional, concise, and engaging. Do not include placeholders or brackets."""
try: try:
response = await self.client.chat.completions.create( response = await self.client.chat(
model="gpt-3.5-turbo", model="command-r",
messages=[ message=prompt,
{"role": "system", "content": "You are an expert cover letter writer. Write compelling, concise cover letters."},
{"role": "user", "content": prompt}
],
temperature=0.4, temperature=0.4,
max_tokens=600, max_tokens=600,
timeout=30 connectors=[]
) )
result = response.choices[0].message.content result = response.text.strip()
# Cache for 30 minutes # Cache for 30 minutes
ai_cache.set(cache_key, result, ttl=1800) ai_cache.set(cache_key, result, ttl=1800)

View File

@ -9,7 +9,7 @@ python-multipart==0.0.6
python-jose[cryptography]==3.3.0 python-jose[cryptography]==3.3.0
passlib[bcrypt]==1.7.4 passlib[bcrypt]==1.7.4
httpx==0.25.2 httpx==0.25.2
openai>=1.6.1 cohere==4.47
PyPDF2==3.0.1 PyPDF2==3.0.1
python-docx==1.1.0 python-docx==1.1.0
cachetools==5.3.2 cachetools==5.3.2