222 lines
8.1 KiB
Python
222 lines
8.1 KiB
Python
"""
|
|
AI Service for Job Forge - Handles document generation and AI processing
|
|
"""
|
|
import structlog
|
|
from typing import Dict, Optional
|
|
import anthropic
|
|
import openai
|
|
from ..core.config import settings
|
|
|
|
logger = structlog.get_logger()
|
|
|
|
class AIService:
|
|
def __init__(self):
|
|
self.claude_client = None
|
|
self.openai_client = None
|
|
|
|
# Initialize Claude client if API key is available
|
|
if settings.claude_api_key:
|
|
self.claude_client = anthropic.Anthropic(api_key=settings.claude_api_key)
|
|
|
|
# Initialize OpenAI client if API key is available
|
|
if settings.openai_api_key:
|
|
self.openai_client = openai.AsyncOpenAI(api_key=settings.openai_api_key)
|
|
|
|
async def generate_cover_letter(
|
|
self,
|
|
job_description: str,
|
|
company_name: str,
|
|
role_title: str,
|
|
user_name: str,
|
|
user_resume: Optional[str] = None
|
|
) -> Dict[str, str]:
|
|
"""
|
|
Generate a personalized cover letter using AI
|
|
"""
|
|
try:
|
|
# Construct the prompt
|
|
prompt = f"""
|
|
You are a professional career coach helping someone write a compelling cover letter.
|
|
|
|
JOB DETAILS:
|
|
- Company: {company_name}
|
|
- Role: {role_title}
|
|
- Job Description: {job_description}
|
|
|
|
USER INFORMATION:
|
|
- Name: {user_name}
|
|
{f"- Resume/Background: {user_resume[:1000]}..." if user_resume else ""}
|
|
|
|
TASK:
|
|
Write a professional, personalized cover letter that:
|
|
1. Shows genuine interest in the specific role and company
|
|
2. Highlights relevant skills from the job description
|
|
3. Demonstrates understanding of the company's needs
|
|
4. Uses a professional but engaging tone
|
|
5. Is 3-4 paragraphs long
|
|
6. Includes a strong opening and closing
|
|
|
|
Format the response as a complete cover letter without any meta-commentary.
|
|
"""
|
|
|
|
# Try Claude first, fallback to OpenAI
|
|
if self.claude_client:
|
|
logger.info("Generating cover letter with Claude")
|
|
response = self.claude_client.messages.create(
|
|
model="claude-3-haiku-20240307",
|
|
max_tokens=1000,
|
|
messages=[
|
|
{"role": "user", "content": prompt}
|
|
]
|
|
)
|
|
content = response.content[0].text
|
|
model_used = "claude-3-haiku"
|
|
|
|
elif self.openai_client:
|
|
logger.info("Generating cover letter with OpenAI")
|
|
response = await self.openai_client.chat.completions.create(
|
|
model="gpt-3.5-turbo",
|
|
messages=[
|
|
{"role": "system", "content": "You are a professional career coach helping write cover letters."},
|
|
{"role": "user", "content": prompt}
|
|
],
|
|
max_tokens=1000,
|
|
temperature=0.7
|
|
)
|
|
content = response.choices[0].message.content
|
|
model_used = "gpt-3.5-turbo"
|
|
|
|
else:
|
|
# Fallback to template-based generation
|
|
logger.warning("No AI API keys available, using template")
|
|
content = self._generate_template_cover_letter(
|
|
company_name, role_title, user_name, job_description
|
|
)
|
|
model_used = "template"
|
|
|
|
return {
|
|
"content": content,
|
|
"model_used": model_used,
|
|
"prompt": prompt[:500] + "..." if len(prompt) > 500 else prompt
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error("AI cover letter generation failed", error=str(e))
|
|
# Fallback to template
|
|
content = self._generate_template_cover_letter(
|
|
company_name, role_title, user_name, job_description
|
|
)
|
|
return {
|
|
"content": content,
|
|
"model_used": "template-fallback",
|
|
"prompt": "Template fallback due to AI service error"
|
|
}
|
|
|
|
async def generate_resume_optimization(
|
|
self,
|
|
current_resume: str,
|
|
job_description: str,
|
|
role_title: str
|
|
) -> Dict[str, str]:
|
|
"""
|
|
Optimize resume for specific job requirements
|
|
"""
|
|
try:
|
|
prompt = f"""
|
|
You are an expert resume writer helping optimize a resume for a specific job.
|
|
|
|
CURRENT RESUME:
|
|
{current_resume}
|
|
|
|
TARGET JOB:
|
|
- Role: {role_title}
|
|
- Job Description: {job_description}
|
|
|
|
TASK:
|
|
Optimize this resume by:
|
|
1. Highlighting relevant skills mentioned in the job description
|
|
2. Reordering sections to emphasize most relevant experience
|
|
3. Using keywords from the job posting
|
|
4. Maintaining truthfulness - only reorganize/reword existing content
|
|
5. Keeping the same general structure and format
|
|
|
|
Return the optimized resume without meta-commentary.
|
|
"""
|
|
|
|
if self.claude_client:
|
|
response = self.claude_client.messages.create(
|
|
model="claude-3-haiku-20240307",
|
|
max_tokens=2000,
|
|
messages=[
|
|
{"role": "user", "content": prompt}
|
|
]
|
|
)
|
|
content = response.content[0].text
|
|
model_used = "claude-3-haiku"
|
|
|
|
elif self.openai_client:
|
|
response = await self.openai_client.chat.completions.create(
|
|
model="gpt-3.5-turbo",
|
|
messages=[
|
|
{"role": "system", "content": "You are an expert resume writer."},
|
|
{"role": "user", "content": prompt}
|
|
],
|
|
max_tokens=2000,
|
|
temperature=0.5
|
|
)
|
|
content = response.choices[0].message.content
|
|
model_used = "gpt-3.5-turbo"
|
|
|
|
else:
|
|
content = f"Resume optimization for {role_title}\n\n{current_resume}\n\n[AI optimization would be applied here with API keys configured]"
|
|
model_used = "template"
|
|
|
|
return {
|
|
"content": content,
|
|
"model_used": model_used,
|
|
"prompt": prompt[:500] + "..." if len(prompt) > 500 else prompt
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error("Resume optimization failed", error=str(e))
|
|
return {
|
|
"content": f"Optimized Resume for {role_title}\n\n{current_resume}",
|
|
"model_used": "template-fallback",
|
|
"prompt": "Template fallback due to AI service error"
|
|
}
|
|
|
|
def _generate_template_cover_letter(
|
|
self,
|
|
company_name: str,
|
|
role_title: str,
|
|
user_name: str,
|
|
job_description: str
|
|
) -> str:
|
|
"""
|
|
Generate a basic template cover letter when AI services are unavailable
|
|
"""
|
|
# Extract a few keywords from job description
|
|
keywords = []
|
|
common_skills = ["python", "javascript", "react", "sql", "aws", "docker", "git", "api", "database"]
|
|
for skill in common_skills:
|
|
if skill.lower() in job_description.lower():
|
|
keywords.append(skill.title())
|
|
|
|
skills_text = f" with expertise in {', '.join(keywords[:3])}" if keywords else ""
|
|
|
|
return f"""Dear Hiring Manager,
|
|
|
|
I am writing to express my strong interest in the {role_title} position at {company_name}. Based on the job description, I am excited about the opportunity to contribute to your team{skills_text}.
|
|
|
|
Your requirements align well with my background and experience. I am particularly drawn to this role because it represents an excellent opportunity to apply my skills in a dynamic environment while contributing to {company_name}'s continued success.
|
|
|
|
I would welcome the opportunity to discuss how my experience and enthusiasm can benefit your team. Thank you for considering my application, and I look forward to hearing from you.
|
|
|
|
Best regards,
|
|
{user_name}
|
|
|
|
---
|
|
[Generated by Job Forge AI Assistant - Configure API keys for enhanced personalization]"""
|
|
|
|
# Create a singleton instance
|
|
ai_service = AIService() |