|
|
import gradio as gr |
|
|
import os |
|
|
import docx2txt |
|
|
import PyPDF2 |
|
|
import json |
|
|
from openai import OpenAI |
|
|
|
|
|
|
|
|
client = OpenAI( |
|
|
base_url="https://api.aimlapi.com/v1", |
|
|
api_key=os.getenv("AIML_API_KEY") |
|
|
) |
|
|
|
|
|
|
|
|
def extract_text_from_pdf(file): |
|
|
try: |
|
|
reader = PyPDF2.PdfReader(file) |
|
|
return "\n".join([page.extract_text() or "" for page in reader.pages]) |
|
|
except Exception: |
|
|
return "" |
|
|
|
|
|
def extract_text_from_docx(file): |
|
|
try: |
|
|
return docx2txt.process(file) |
|
|
except Exception: |
|
|
return "" |
|
|
|
|
|
def parse_resume(file): |
|
|
ext = os.path.splitext(file.name)[1].lower() |
|
|
if ext == ".pdf": |
|
|
return extract_text_from_pdf(file) |
|
|
elif ext == ".docx": |
|
|
return extract_text_from_docx(file) |
|
|
else: |
|
|
return None |
|
|
|
|
|
|
|
|
def call_ai_screening_agent(resume_texts, job_description, min_experience): |
|
|
prompt = f""" |
|
|
You are an AI resume screening assistant. Compare each candidate's resume to the job description below. |
|
|
Only include candidates who meet the minimum required years of experience: {min_experience}. |
|
|
For each candidate, return: |
|
|
- name (from filename or parsed text) |
|
|
- strengths (2–5 key strengths) |
|
|
- risks (0–3 potential risks) |
|
|
- score (from 1 to 10) |
|
|
|
|
|
Job Description: |
|
|
\"\"\"{job_description}\"\"\" |
|
|
|
|
|
Resumes: |
|
|
{resume_texts} |
|
|
|
|
|
Return a JSON list of candidates as described. |
|
|
""" |
|
|
try: |
|
|
response = client.chat.completions.create( |
|
|
model="gpt-4-turbo", |
|
|
messages=[{"role": "user", "content": prompt}], |
|
|
temperature=0.2 |
|
|
) |
|
|
return json.loads(response.choices[0].message.content) |
|
|
except Exception as e: |
|
|
return {"error": str(e)} |
|
|
|
|
|
|
|
|
def process_resumes(files, job_description, min_experience): |
|
|
if not files: |
|
|
return "⚠️ Please upload at least one resume.", None |
|
|
if not job_description.strip(): |
|
|
return "⚠️ Job description cannot be empty.", None |
|
|
|
|
|
parsed_resumes = [] |
|
|
for file in files: |
|
|
content = parse_resume(file) |
|
|
if not content or len(content.strip()) == 0: |
|
|
continue |
|
|
parsed_resumes.append({"name": os.path.basename(file.name), "text": content}) |
|
|
|
|
|
if not parsed_resumes: |
|
|
return "⚠️ Could not parse any valid resumes.", None |
|
|
|
|
|
resume_texts = "\n\n".join([f"{r['name']}:\n{r['text']}" for r in parsed_resumes]) |
|
|
results = call_ai_screening_agent(resume_texts, job_description, min_experience) |
|
|
|
|
|
if isinstance(results, dict) and "error" in results: |
|
|
return f"⚠️ API Error: {results['error']}", None |
|
|
|
|
|
|
|
|
top_candidates = sorted(results, key=lambda x: x["score"], reverse=True)[:3] |
|
|
display_md = "" |
|
|
for candidate in top_candidates: |
|
|
display_md += f"### {candidate['name']} \n" |
|
|
display_md += f"⭐ **Score**: {candidate['score']} \n" |
|
|
display_md += "✅ **Strengths**:\n" |
|
|
for s in candidate['strengths']: |
|
|
display_md += f"- ✅ {s}\n" |
|
|
if candidate['risks']: |
|
|
display_md += "⚠️ **Risks**:\n" |
|
|
for r in candidate['risks']: |
|
|
display_md += f"- ⚠️ {r}\n" |
|
|
display_md += "\n---\n" |
|
|
|
|
|
return None, display_md |
|
|
|
|
|
|
|
|
with gr.Blocks(title="SmartHire - AI Job Screening Assistant") as demo: |
|
|
gr.Markdown("# 🤖 SmartHire — AI Job Screening Assistant") |
|
|
gr.Markdown("Upload resumes and paste a job description to find the best candidates automatically!") |
|
|
|
|
|
with gr.Row(): |
|
|
resume_input = gr.File(file_types=[".pdf", ".docx"], file_count="multiple", label="Upload Resumes") |
|
|
experience_input = gr.Number(label="Minimum Years of Experience", value=0) |
|
|
|
|
|
job_desc_input = gr.Textbox(lines=8, placeholder="Paste the job description here...", label="Job Description") |
|
|
process_button = gr.Button("Run Screening") |
|
|
|
|
|
error_output = gr.Textbox(label="Warnings / Errors", visible=False) |
|
|
output_md = gr.Markdown() |
|
|
|
|
|
process_button.click( |
|
|
fn=process_resumes, |
|
|
inputs=[resume_input, job_desc_input, experience_input], |
|
|
outputs=[error_output, output_md] |
|
|
) |
|
|
|
|
|
demo.launch() |
|
|
|