developed files
This commit is contained in:
222
src/backend/services/ai_service.py
Normal file
222
src/backend/services/ai_service.py
Normal file
@@ -0,0 +1,222 @@
|
||||
"""
|
||||
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()
|
||||
Reference in New Issue
Block a user