File size: 9,934 Bytes
44078fc |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 |
import os
import time
import tempfile
import gradio as gr
import warnings
from pathlib import Path
import PyPDF2
import markdown
from datetime import datetime, timedelta
from collections import defaultdict
import threading
warnings.filterwarnings('ignore')
# Rate limiting
class RateLimiter:
def __init__(self, max_requests=5, time_window=60):
self.max_requests = max_requests
self.time_window = time_window
self.requests = defaultdict(list)
self.lock = threading.Lock()
def is_allowed(self, user_id):
with self.lock:
now = datetime.now()
# Clean old requests
self.requests[user_id] = [
req_time for req_time in self.requests[user_id]
if now - req_time < timedelta(seconds=self.time_window)
]
if len(self.requests[user_id]) >= self.max_requests:
return False
self.requests[user_id].append(now)
return True
# Global rate limiter
rate_limiter = RateLimiter(max_requests=3, time_window=300) # 3 requests per 5 minutes
def extract_text_from_pdf(pdf_file):
"""Extract text from uploaded PDF file."""
try:
reader = PyPDF2.PdfReader(pdf_file)
text = ""
for page in reader.pages:
text += page.extract_text() + "\n"
return text.strip()
except Exception as e:
return f"Error reading PDF: {str(e)}"
def setup_crewai():
"""Initialize CrewAI components."""
try:
from crewai import Agent, Task, Crew
from crewai_tools import ScrapeWebsiteTool, SerperDevTool
from langchain_openai import ChatOpenAI
# Initialize tools
search_tool = SerperDevTool()
scrape_tool = ScrapeWebsiteTool()
# Initialize LLM
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.3)
# Create agents
researcher = Agent(
role="Job Requirements Analyst",
goal="Extract and analyze key job requirements efficiently",
tools=[scrape_tool, search_tool],
verbose=False,
backstory="Expert at quickly identifying essential job requirements and qualifications from job postings.",
llm=llm,
)
resume_strategist = Agent(
role="Resume Enhancement Specialist",
goal="Optimize resumes to match job requirements effectively",
tools=[],
verbose=False,
backstory="Skilled at tailoring resumes to highlight relevant experience and skills for specific job applications.",
llm=llm,
)
return researcher, resume_strategist, llm
except ImportError:
raise Exception("CrewAI not installed. Please install required packages.")
def create_tasks(researcher, resume_strategist, job_url, resume_text):
"""Create optimized tasks for the crew."""
from crewai import Task
# Research task - focused and efficient
research_task = Task(
description=f"""
Analyze the job posting at {job_url} and extract the top 10 most important:
1. Required skills and technologies
2. Key qualifications and experience levels
3. Preferred background and certifications
Focus on the most critical requirements only.
""",
expected_output="A concise list of the top 10 most important job requirements.",
agent=researcher,
)
# Resume optimization task
resume_task = Task(
description=f"""
Using the job requirements from the research task, optimize this resume:
{resume_text}
Instructions:
1. Rewrite the professional summary to align with the job
2. Highlight relevant experience and skills
3. Adjust technical skills section to match requirements
4. Ensure ATS-friendly formatting
5. Keep the same factual information but present it strategically
Return the complete optimized resume in markdown format.
""",
expected_output="A complete, optimized resume in markdown format tailored to the job requirements.",
agent=resume_strategist,
context=[research_task]
)
return research_task, resume_task
def process_application(pdf_file, job_url, user_session):
"""Main processing function with rate limiting."""
# Rate limiting check
if not rate_limiter.is_allowed(user_session):
return "β οΈ Rate limit exceeded. Please wait 5 minutes before submitting another request.", ""
if not pdf_file or not job_url:
return "β Please provide both a PDF resume and job URL.", ""
try:
# Extract text from PDF
with gr.Progress() as progress:
progress(0.1, desc="Extracting text from PDF...")
resume_text = extract_text_from_pdf(pdf_file)
if "Error reading PDF" in resume_text:
return f"β {resume_text}", ""
progress(0.3, desc="Setting up AI agents...")
researcher, resume_strategist, llm = setup_crewai()
progress(0.5, desc="Creating optimization tasks...")
research_task, resume_task = create_tasks(researcher, resume_strategist, job_url, resume_text)
progress(0.7, desc="Analyzing job requirements...")
# Execute tasks
from crewai import Crew
crew = Crew(
agents=[researcher, resume_strategist],
tasks=[research_task, resume_task],
verbose=False
)
progress(0.9, desc="Generating tailored resume...")
result = crew.kickoff()
progress(1.0, desc="Complete!")
# Convert markdown to HTML for better display
html_result = markdown.markdown(str(result))
return "β
Resume successfully tailored!", html_result
except Exception as e:
return f"β Error processing your request: {str(e)}", ""
def create_interface():
"""Create the Gradio interface."""
with gr.Blocks(
title="CV Tailor - AI Resume Optimizer",
theme=gr.themes.Soft(),
css="""
.gradio-container {
max-width: 1200px;
margin: auto;
}
.header {
text-align: center;
margin-bottom: 30px;
}
.rate-limit-info {
background-color: #f0f8ff;
padding: 10px;
border-radius: 5px;
margin-bottom: 20px;
}
"""
) as app:
gr.HTML("""
<div class="header">
<h1>π― CV Tailor - AI Resume Optimizer</h1>
<p>Upload your PDF resume and job URL to get an AI-tailored resume that matches the job requirements!</p>
</div>
""")
gr.HTML("""
<div class="rate-limit-info">
<strong>β‘ Rate Limit:</strong> 3 requests per 5 minutes to manage API costs.
Please be patient and make each request count!
</div>
""")
with gr.Row():
with gr.Column(scale=1):
pdf_input = gr.File(
label="π Upload Your Resume (PDF)",
file_types=[".pdf"],
file_count="single"
)
job_url_input = gr.Textbox(
label="π Job Posting URL",
placeholder="https://company.com/jobs/position",
lines=1
)
submit_btn = gr.Button(
"π Generate Tailored Resume",
variant="primary",
size="lg"
)
# Examples
gr.Examples(
examples=[
["https://jobs.lever.co/example-company/software-engineer"],
["https://www.linkedin.com/jobs/view/example-job-id"],
["https://careers.google.com/jobs/results/example-position"]
],
inputs=job_url_input,
label="π Example Job URLs"
)
with gr.Column(scale=2):
status_output = gr.Textbox(
label="π Status",
interactive=False,
lines=1
)
result_output = gr.HTML(
label="π Tailored Resume",
value="Your optimized resume will appear here..."
)
# Event handlers
submit_btn.click(
fn=process_application,
inputs=[pdf_input, job_url_input, gr.State(lambda: str(time.time()))],
outputs=[status_output, result_output]
)
# Footer
gr.HTML("""
<div style="text-align: center; margin-top: 50px; color: #666;">
<p>Powered by CrewAI & OpenAI GPT-4o Mini |
<a href="https://github.com/joaomdmoura/crewAI" target="_blank">CrewAI</a> |
Built with β€οΈ using Gradio</p>
</div>
""")
return app
# Create the app
if __name__ == "__main__":
# Set up environment variables
# In Hugging Face Spaces, set these in the Settings > Variables section
if not os.getenv("OPENAI_API_KEY"):
print("β οΈ Warning: OPENAI_API_KEY not found in environment variables")
app = create_interface()
app.launch(
server_name="0.0.0.0",
server_port=7860,
show_api=False,
share=False
) |