resume_taylor / utils.py
hongaik's picture
Upload utils.py
bb501ad verified
import streamlit as st
from openai import OpenAI
import os
from crewai import Agent, Task, Crew
from crewai_tools import BaseTool
import hmac
from PyPDF2 import PdfReader
from pydantic import BaseModel
from docx import Document
from markdown import markdown
from bs4 import BeautifulSoup
import io
os.environ['OPENAI_MODEL_NAME'] = "gpt-4o-mini"
# client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
client = OpenAI(api_key=st.secrets["OPENAI_API_KEY"])
# <---------- Default functions ---------->
def get_completion(prompt, model="gpt-4o-mini"):
messages = [{"role": "user", "content": prompt}]
response = client.chat.completions.create(
model=model,
messages=messages,
temperature=0, # this is the degree of randomness of the model's output
)
# print(f"API cost: ${round(compute_cost(response), 2)}")
return response.choices[0].message.content
def check_password():
"""Returns `True` if the user had the correct password."""
def password_entered():
"""Checks whether a password entered by the user is correct."""
# if hmac.compare_digest(st.session_state["password"], os.getenv("APP_PW")):
if hmac.compare_digest(st.session_state["password"], st.secrets["APP_PW"]):
st.session_state["password_correct"] = True
del st.session_state["password"] # Don't store the password.
else:
st.session_state["password_correct"] = False
# Return True if the passward is validated.
if st.session_state.get("password_correct", False):
return True
# Show input for password.
st.text_input(
"Password", type="password", on_change=password_entered, key="password"
)
if "password_correct" in st.session_state:
st.error("πŸ˜• Password incorrect")
return False
# <---------- App functions ---------->
class PDFReaderTool(BaseTool):
name: str="PDF Reader"
description: str = "Reads the contents of a PDF file and returns the text"
def _run(self, pdf_path:str) -> str:
reader = PdfReader(pdf_path)
text = ""
for page in reader.pages:
text += page.extract_text()
return text
# Agents and Tools
profile_builder_agent = Agent(
role="Career profiling expert",
goal="Build up a holistic career profile of a jobseeker",
backstory="""
Equipped with unparalleled analytical prowess, you study a jobseeker's education, work and professional experience and compile a detailed and holistic profile for him/her.
This profile will be used by the resume strategist to craft the perfect resume for the jobseeker to stand out against the crowd.
""",
allow_delegation=False,
verbose=True,
)
jobad_agent = Agent(
role="Job Researcher",
goal="Extract key information from a given job advertisement",
backstory="""
As a Job Researcher, your prowess in navigating and extracting critical information from job advertisements is unmatched.
Your skills help pinpoint the necessary qualifications and skills sought by employers, forming the foundation for effective application tailoring.
This information will be used by the resume strategist to craft the perfect resume for the jobseeker to stand out against the crowd.
""",
allow_delegation=False,
verbose=True,
)
resume_agent = Agent(
role="Resume Writer",
goal="Craft the perfect resume to help jobseeker stand out from the competition",
backstory="""
With a strategic mind and an eye for detail, you excel at refining resumes to highlight the most relevant skills and experiences, ensuring they resonate perfectly with the job's requirements.
However, you never ever make up skills and experiences that the jobseeker does not possess.
""",
allow_delegation=False,
verbose=True,
)
resume_analyst = Agent(
role="Resume Analyst",
goal="Analyse the jobseeker's resume and compare how well it fits to the job advertisement",
backstory="""
As a former HR professional with decades of experience in hiring across all industries and roles, you are well-equipped to perform a deep analysis of how well a resume fits the job requirements
""",
allow_delegation=False,
verbose=True,
)
# Task for Profiler Agent: Compile Comprehensive Profile
profile_task = Task(
description="""\
Compile a detailed professional profile based on the resume.
{profile}""",
expected_output="A comprehensive profile document that includes contact information, skills, work experiences, notable projects and achievements, and education.",
agent=profile_builder_agent,
async_execution=False
)
jobad_task = Task(
description="""/
Analyze the job advertisement provided to extract key skills, experiences, and qualifications required.
Use the tools to gather content and identify and categorize the requirements.
Job advertisement:
{jobad}
""",
expected_output="A structured list of job requirements, including necessary skills, qualifications, and experiences.",
agent=jobad_agent,
async_execution=False
)
# Task for Resume Strategist Agent: Align Resume with Job Requirements
resume_strategy_task = Task(
description="""\
Using the profile and job requirements obtained from previous tasks, craft the perfect resume to highlight the jobseeker's value and how it matches to the job advertisement.
Order of sections: contact information, professional summary, competencies, work experience, relevant projects, significant achievements, education and/or certification and/or languages. Suggest your own suitable headings for each section.
Style, tone and design: Befitting of the job requirements.
Recent skills and experiences should be emphasised.
Even if the profile does not align with the job, never ever make up any skills or experiences that is not stated in the profile.
""",
expected_output=
"A well-crafted resume that effectvely highlights the candidate's qualifications and experiences relevant to the job.",
context=[jobad_task, profile_task],
agent=resume_agent
)
resume_analysis_task = Task(
description="""\
Using the profile and job requirements obtained from previous tasks, perform an in-depth analysis of how compatible is the resume with the job requirements.
You should:
1. Identify areas which are a good fit and areas which are not, citing the relevant texts to support your statements.
2. Provide a score on a scale of 1 to 10 of how well the resume suits the job.
3. Suggest ways on how the jobseeker can improve to become a better fit for the job.
4. Make a recommendation as to whether the jobseeker should apply for this position.
""",
expected_output=
"A comprehensive analysis in Markdown format of how well the jobseeker's profile fit the job requirements",
context=[jobad_task, profile_task],
agent=resume_analyst
)
crew = Crew(
agents=[jobad_agent,
profile_builder_agent,
resume_agent,
resume_analyst],
tasks=[jobad_task,
profile_task,
resume_strategy_task,
resume_analysis_task],
verbose=True
)
# <---------- Chat completion functions ---------->
def create_jobseeker_profile(resume, model="gpt-4o-mini"):
prompt = f"""
<INSTRUCTIONS>
Compile a detailed and holistic career profile for the resume below.
This will help the jobseeker understand his/her strengths and weaknesses and tailor his/her resume to stand out against the crowd.
You should output a comprehensive profile document that includes contact information, skills, work experiences, notable projects and achievements, and education.
</INSTRUCTIONS>
<RESUME>
{resume}
</RESUME>
"""
messages = [{"role": "user", "content": prompt}]
response = client.chat.completions.create(
model=model,
messages=messages,
temperature=0, # this is the degree of randomness of the model's output
)
return response.choices[0].message.content
def create_jobad(jobad, model="gpt-4o-mini"):
prompt = f"""
<INSTRUCTIONS>
Analyze the job_advertisement provided below and extract key skills, experiences, and qualifications required.
You should output a structured list of job requirements, including necessary skills, qualifications, and experiences.
</INSTRUCTIONS>
<JOB_ADVERTISEMENT>
{jobad}
</JOB_ADVERTISEMENT>
"""
messages = [{"role": "user", "content": prompt}]
response = client.chat.completions.create(
model=model,
messages=messages,
temperature=0, # this is the degree of randomness of the model's output
)
return response.choices[0].message.content
def analyze_resume(clean_resume, clean_jobad, model="gpt-4o-mini"):
class TaskOutput(BaseModel):
resume: str
analysis: str
prompt = f"""
<BACKSTORY>
As a former HR professional with decades of experience in hiring across all industries and roles, you are proficient at performing a deep analysis of how well a jobseeker fits the job requirements.
You are also adept at crafting the perfect resume to help jobseeker stand out from the competition.
Always use the two pieces of given information (job_requirements and jobseeker_profile) to complete two tasks.
Do not make up any information that is not provided, even if the jobseeker is unsuitable for the job.
</BACKSTORY>
<TASK_ONE>
<OBJECTIVE>
Extract relevant information from the jobseeker's profile to craft the perfect resume which highlights the jobseeker's value and how it matches to the job requirements. Emphasize more on recent and relevant skills and experiences.
</OBJECTIVE>
<FORMATTING>
1. SEQUENCE OF SECTIONS: contact information, professional summary, competencies, work experience, relevant projects and corresponding hyperlinks, significant achievements, education and/or certification and/or languages.
2. Suggest your own suitable headings for each section, but do not add more sections.
3. Style, tone and design: Befitting of the job.
</FORMATTING>
<INSTRUCTIONS>
Your output should be a ready-for-use resume in Markdown format with no other additional texts.
</INSTRUCTIONS>
</TASK_ONE>
<TASK_TWO>
<OBJECTIVE>
Perform an in-depth analysis of how compatible the jobseeker's profile is with the job requirements.
</OBJECTIVE>
<INSTRUCTIONS>
1. Identify areas which are a good fit and areas which are not, citing the relevant texts to support your statements.
2. Provide a score on a scale of 1 to 10 of how well the resume suits the job.
3. Suggest ways on how the jobseeker can improve to become a better fit for the job.
4. Make a recommendation as to whether the jobseeker should apply for this position.
5. Your output should be an analysis document in Markdown format.
</INSTRUCTIONS>
</TASK_TWO>
<JOB_REQUIREMENTS>
{clean_jobad}
</JOB_REQUIREMENTS>
<JOBSEEKER_PROFILE>
{clean_resume}
</JOBSEEKER_PROFILE>
"""
messages = [{"role": "user", "content": prompt}]
response = client.beta.chat.completions.parse(
model=model,
messages=messages,
response_format=TaskOutput,
temperature=0, # this is the degree of randomness of the model's output
)
return response.choices[0].message.parsed
def markdown_to_docx(md_text):
"""
Converts a Markdown string into a Word document (.docx) and returns it as a bytes buffer.
Args:
md_text (str): The input Markdown content as a string.
Returns:
BytesIO: A Word document file in memory (as a buffer).
"""
# Convert Markdown to HTML
html_text = markdown(md_text)
# Parse the HTML using BeautifulSoup
soup = BeautifulSoup(html_text, "html.parser")
# Create a new Word document
doc = Document()
# Helper function to add paragraphs with formatting
def add_paragraph(text, bold=False, style=None):
p = doc.add_paragraph(style=style)
run = p.add_run(text)
if bold:
run.bold = True
# Traverse through HTML elements and format the Word document
for element in soup.descendants:
if element.name == 'h1': # Heading level 1
add_paragraph(element.get_text(), bold=True, style="Heading1")
elif element.name == 'h2': # Heading level 2
add_paragraph(element.get_text(), bold=True, style="Heading2")
elif element.name == 'h3': # Heading level 3
add_paragraph(element.get_text(), bold=True, style="Heading3")
elif element.name in ['strong', 'b']: # Bold text
add_paragraph(element.get_text(), bold=True)
elif element.name == 'li': # List items
doc.add_paragraph(element.get_text(), style='ListBullet')
elif element.name == 'p': # Paragraphs
add_paragraph(element.get_text())
# Save the document to a BytesIO buffer
buffer = io.BytesIO()
doc.save(buffer)
buffer.seek(0)
return buffer