Spaces:
Sleeping
Sleeping
Jonah Ramponi
commited on
Commit
•
98eaa40
1
Parent(s):
8983fcf
total restructure
Browse files- app.py +134 -72
- backend.py +24 -18
- utils/format.py +142 -0
- utils/gpt.py +18 -0
- utils/prompts.py +128 -93
app.py
CHANGED
@@ -3,13 +3,16 @@
|
|
3 |
"""
|
4 |
|
5 |
import json
|
|
|
6 |
import concurrent.futures
|
7 |
|
8 |
import streamlit as st
|
9 |
|
|
|
|
|
10 |
from utils.process_doc import parse_docx, parse_pdf
|
11 |
-
from
|
12 |
-
from utils.
|
13 |
|
14 |
st.set_page_config(layout="wide")
|
15 |
|
@@ -21,7 +24,7 @@ with st.sidebar:
|
|
21 |
)
|
22 |
|
23 |
if "state" not in st.session_state:
|
24 |
-
st.session_state.state = {"
|
25 |
|
26 |
STATE = st.session_state.state
|
27 |
|
@@ -38,93 +41,152 @@ cv_upload_box = st.file_uploader(
|
|
38 |
)
|
39 |
job_posting_upload_box = st.text_area(
|
40 |
"Job Description Upload Box",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
41 |
placeholder="Copy and Paste a job post you are interested in. Make sure to include the full post! More information is better.",
|
42 |
help="In this box, please dump text content for a job description you are interested in. This could easily be setup to work directly with a webpage (we'd simply need to scrape said page) however I do not want to do that on HF spaces.",
|
43 |
)
|
44 |
|
45 |
if cv_upload_box and job_posting_upload_box != "":
|
|
|
46 |
|
47 |
-
|
48 |
-
|
49 |
-
if process_files:
|
50 |
-
if test_api_key(COHERE_API_KEY):
|
51 |
-
|
52 |
-
# Process our two uploaded files into state variables
|
53 |
-
STATE["job_posting"] = job_posting_upload_box
|
54 |
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
|
64 |
-
|
|
|
|
|
65 |
|
66 |
-
#
|
67 |
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
68 |
|
69 |
-
future1 = executor.submit(process_cv, STATE["cv"], COHERE_API_KEY)
|
70 |
future2 = executor.submit(
|
71 |
-
|
|
|
|
|
|
|
|
|
72 |
)
|
73 |
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
job_posting_json_text = (
|
81 |
-
"{"
|
82 |
-
+ job_posting_json_text.lstrip().lstrip("{").rstrip().rstrip("}")
|
83 |
-
+ "}"
|
84 |
-
)
|
85 |
-
try:
|
86 |
-
STATE["cv_json"] = json.loads(cv_json_text)
|
87 |
-
except json.JSONDecodeError as e:
|
88 |
-
print(
|
89 |
-
f"Error parsing JSON Output for CV: {e}. Response content: {cv_json_text}"
|
90 |
)
|
91 |
-
STATE["cv_json"] = {"name": "Failed"}
|
92 |
|
93 |
-
|
94 |
-
|
|
|
95 |
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
|
|
|
|
|
|
101 |
|
102 |
-
|
103 |
-
|
104 |
-
st.error(
|
105 |
-
"You entered an invalid Cohere API Key. Please enter a valid API key in the sidebar."
|
106 |
-
)
|
107 |
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
)
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
126 |
)
|
127 |
|
128 |
-
|
129 |
-
["Role Specific CV Critique", "Practice Interview", "General CV Critique"]
|
130 |
-
)
|
|
|
3 |
"""
|
4 |
|
5 |
import json
|
6 |
+
import random
|
7 |
import concurrent.futures
|
8 |
|
9 |
import streamlit as st
|
10 |
|
11 |
+
from backend import paired_critique
|
12 |
+
|
13 |
from utils.process_doc import parse_docx, parse_pdf
|
14 |
+
from utils.gpt import test_api_key, gpt_stream_response_chat_history
|
15 |
+
from utils.format import extract_json, generate_markdown_report
|
16 |
|
17 |
st.set_page_config(layout="wide")
|
18 |
|
|
|
24 |
)
|
25 |
|
26 |
if "state" not in st.session_state:
|
27 |
+
st.session_state.state = {"successful_report_flag": True, "paired_report": {}}
|
28 |
|
29 |
STATE = st.session_state.state
|
30 |
|
|
|
41 |
)
|
42 |
job_posting_upload_box = st.text_area(
|
43 |
"Job Description Upload Box",
|
44 |
+
value="""Job description
|
45 |
+
As a Data Scientist at Meta, you will shape the future of people-facing and business-facing products we build across our entire family of applications (Facebook, Instagram, Messenger, WhatsApp, Oculus). By applying your technical skills, analytical mindset, and product intuition to one of the richest data sets in the world, you will help define the experiences we build for billions of people and hundreds of millions of businesses around the world. You will collaborate on a wide array of product and business problems with a diverse set of cross-functional partners across Product, Engineering, Research, Data Engineering, Marketing, Sales, Finance and others. You will use data and analysis to identify and solve product development’s biggest challenges. You will influence product strategy and investment decisions with data, be focused on impact, and collaborate with other teams. By joining Meta, you will become part of a world-class analytics community dedicated to skill development and career growth in analytics and beyond.Product leadership: You will use data to shape product development, quantify new opportunities, identify upcoming challenges, and ensure the products we build bring value to people, businesses, and Meta. You will help your partner teams prioritize what to build, set goals, and understand their product’s ecosystem.Analytics: You will guide teams using data and insights. You will focus on developing hypotheses and employ a diverse toolkit of rigorous analytical approaches, different methodologies, frameworks, and technical approaches to test them.Communication and influence: You won’t simply present data, but tell data-driven stories. You will convince and influence your partners using clear insights and recommendations. You will build credibility through structure and clarity, and be a trusted strategic partner.
|
46 |
+
|
47 |
+
Data Scientist, Product Analytics Responsibilities:
|
48 |
+
|
49 |
+
Work with large and complex data sets to solve a wide array of challenging problems using different analytical and statistical approaches.
|
50 |
+
Apply technical expertise with quantitative analysis, experimentation, data mining, and the presentation of data to develop strategies for our products that serve billions of people and hundreds of millions of businesses.
|
51 |
+
Identify and measure success of product efforts through goal setting, forecasting, and monitoring of key product metrics to understand trends.
|
52 |
+
Define, understand, and test opportunities and levers to improve the product, and drive roadmaps through your insights and recommendations.
|
53 |
+
Partner with Product, Engineering, and cross-functional teams to inform, influence, support, and execute product strategy and investment decisions.
|
54 |
+
|
55 |
+
Minimum Qualifications:
|
56 |
+
|
57 |
+
A minimum of 6 years of work experience in analytics (minimum of 4 years with a Ph.D.).
|
58 |
+
Bachelor's degree in Mathematics, Statistics, a relevant technical field, or equivalent practical experience.
|
59 |
+
Experience with data querying languages (e.g. SQL), scripting languages (e.g. Python), and/or statistical/mathematical software (e.g. R).
|
60 |
+
|
61 |
+
Preferred Qualifications:
|
62 |
+
|
63 |
+
Masters or Ph.D. Degree in a quantitative field.
|
64 |
+
|
65 |
+
About Meta:
|
66 |
+
|
67 |
+
Meta builds technologies that help people connect, find communities, and grow businesses. When Facebook launched in 2004, it changed the way people connect. Apps like Messenger, Instagram and WhatsApp further empowered billions around the world. Now, Meta is moving beyond 2D screens toward immersive experiences like augmented and virtual reality to help build the next evolution in social technology. People who choose to build their careers by building with us at Meta help shape a future that will take us beyond what digital connection makes possible today—beyond the constraints of screens, the limits of distance, and even the rules of physics.
|
68 |
+
|
69 |
+
Individual compensation is determined by skills, qualifications, experience, and location. Compensation details listed in this posting reflect the base hourly rate, monthly rate, or annual salary only, and do not include bonus, equity or sales incentives, if applicable. In addition to base compensation, Meta offers benefits. Learn more about benefits at Meta.""",
|
70 |
placeholder="Copy and Paste a job post you are interested in. Make sure to include the full post! More information is better.",
|
71 |
help="In this box, please dump text content for a job description you are interested in. This could easily be setup to work directly with a webpage (we'd simply need to scrape said page) however I do not want to do that on HF spaces.",
|
72 |
)
|
73 |
|
74 |
if cv_upload_box and job_posting_upload_box != "":
|
75 |
+
STATE["job_posting"] = job_posting_upload_box
|
76 |
|
77 |
+
cv_filetype = cv_upload_box.name.split(".")[-1]
|
78 |
+
cv_file_contents = cv_upload_box.getvalue()
|
|
|
|
|
|
|
|
|
|
|
79 |
|
80 |
+
STATE["cv"] = (
|
81 |
+
parse_docx(cv_file_contents)
|
82 |
+
if cv_filetype == "docx"
|
83 |
+
else parse_pdf(cv_file_contents)
|
84 |
+
)
|
85 |
+
cv_critique, practice_interview, general_cv_critique = st.tabs(
|
86 |
+
["Role Specific CV Critique", "Practice Interview", "General CV Critique"]
|
87 |
+
)
|
88 |
|
89 |
+
with cv_critique:
|
90 |
+
produce_report = st.button("Produce Suitability Report")
|
91 |
+
if produce_report:
|
92 |
|
93 |
+
# Make 3 calls in parallel
|
94 |
with concurrent.futures.ThreadPoolExecutor() as executor:
|
95 |
+
future1 = executor.submit(
|
96 |
+
paired_critique,
|
97 |
+
STATE["cv"],
|
98 |
+
STATE["job_posting"],
|
99 |
+
"basic",
|
100 |
+
COHERE_API_KEY,
|
101 |
+
)
|
102 |
|
|
|
103 |
future2 = executor.submit(
|
104 |
+
paired_critique,
|
105 |
+
STATE["cv"],
|
106 |
+
STATE["job_posting"],
|
107 |
+
"general",
|
108 |
+
COHERE_API_KEY,
|
109 |
)
|
110 |
|
111 |
+
future3 = executor.submit(
|
112 |
+
paired_critique,
|
113 |
+
STATE["cv"],
|
114 |
+
STATE["job_posting"],
|
115 |
+
"specific",
|
116 |
+
COHERE_API_KEY,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
117 |
)
|
|
|
118 |
|
119 |
+
basic_details_out = future1.result()
|
120 |
+
general_details_out = future2.result()
|
121 |
+
specific_details_out = future3.result()
|
122 |
|
123 |
+
# merge the outputs
|
124 |
+
resultsDict = {}
|
125 |
+
for jsonText in [
|
126 |
+
basic_details_out,
|
127 |
+
general_details_out,
|
128 |
+
specific_details_out,
|
129 |
+
]:
|
130 |
+
valid_json_flag, output_report_json = extract_json(jsonText)
|
131 |
|
132 |
+
if not valid_json_flag:
|
133 |
+
STATE["successful_report_flag"] = False
|
|
|
|
|
|
|
134 |
|
135 |
+
resultsDict.update(output_report_json)
|
136 |
+
|
137 |
+
STATE["paired_report"] = resultsDict
|
138 |
+
|
139 |
+
if STATE["successful_report_flag"] and STATE["paired_report"]:
|
140 |
+
|
141 |
+
paired_report = STATE["paired_report"]
|
142 |
+
|
143 |
+
name = paired_report.get("personName", "MissingPersonName")
|
144 |
+
job_title = paired_report.get("jobTitle", "MissingTitle")
|
145 |
+
company_name = paired_report.get("companyName", "MissingCompany")
|
146 |
+
|
147 |
+
with cv_critique:
|
148 |
+
st.markdown(generate_markdown_report(STATE["paired_report"]))
|
149 |
+
|
150 |
+
st.download_button(
|
151 |
+
label="Download Report JSON",
|
152 |
+
data=json.dumps(STATE["paired_report"], indent=4),
|
153 |
+
file_name=f"{name}_{job_title}_{company_name}.json",
|
154 |
+
mime="application/json",
|
155 |
+
use_container_width=True,
|
156 |
+
)
|
157 |
+
|
158 |
+
# Streaming Chatbot !!!
|
159 |
+
with practice_interview:
|
160 |
+
initial_questions = [
|
161 |
+
"What do you think is the biggest reason you're unsuitable for the role?",
|
162 |
+
"Why are you interested in this role specifically?",
|
163 |
+
"What do you know about the company?",
|
164 |
+
]
|
165 |
+
|
166 |
+
if "messages" not in st.session_state:
|
167 |
+
st.session_state["messages"] = [
|
168 |
+
{"role": "assistant", "message": random.choice(initial_questions)}
|
169 |
+
]
|
170 |
+
|
171 |
+
# Populate the chat with historic messages
|
172 |
+
for msg in st.session_state.messages:
|
173 |
+
st.chat_message(msg["role"]).write(msg["message"])
|
174 |
+
|
175 |
+
if prompt := st.chat_input():
|
176 |
+
st.session_state.messages.append({"role": "user", "message": prompt})
|
177 |
+
st.chat_message("user").write(prompt)
|
178 |
+
|
179 |
+
assistant_message = st.chat_message("assistant")
|
180 |
+
|
181 |
+
response = assistant_message.write_stream(
|
182 |
+
gpt_stream_response_chat_history(
|
183 |
+
st.session_state.messages,
|
184 |
+
background_info={
|
185 |
+
"cv": STATE["cv"],
|
186 |
+
"job_posting": STATE["job_posting"],
|
187 |
+
},
|
188 |
+
api_key=COHERE_API_KEY,
|
189 |
+
)
|
190 |
)
|
191 |
|
192 |
+
st.session_state.messages.append({"role": "assistant", "message": response})
|
|
|
|
backend.py
CHANGED
@@ -1,31 +1,31 @@
|
|
1 |
from utils.prompts import (
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
job_posting_format,
|
6 |
)
|
7 |
|
8 |
from utils.gpt import gpt_response
|
9 |
|
|
|
|
|
|
|
|
|
|
|
10 |
|
11 |
-
|
|
|
|
|
|
|
12 |
"""Process CV contents, using Cohere"""
|
13 |
|
14 |
-
prompt
|
|
|
15 |
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
)
|
20 |
|
21 |
-
return response
|
22 |
-
|
23 |
-
|
24 |
-
def process_job_posting(job_post_contents: str, API_KEY: str) -> str:
|
25 |
-
"""Process a job posting, using Cohere"""
|
26 |
-
|
27 |
-
prompt = job_posting_extract_prompt.replace("<job-posting>", job_post_contents)
|
28 |
-
|
29 |
response = gpt_response(
|
30 |
prompt=prompt,
|
31 |
api_key=API_KEY,
|
@@ -38,4 +38,10 @@ if __name__ == "__main__":
|
|
38 |
with open("sample_data/meta_job.txt", "r") as file:
|
39 |
post_contents = file.read()
|
40 |
|
41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
from utils.prompts import (
|
2 |
+
basic_details_extraction_prompt,
|
3 |
+
general_skils_extraction_prompt,
|
4 |
+
specific_skills_comparison_prompt,
|
|
|
5 |
)
|
6 |
|
7 |
from utils.gpt import gpt_response
|
8 |
|
9 |
+
prompt_mapping = {
|
10 |
+
"basic": basic_details_extraction_prompt,
|
11 |
+
"general": general_skils_extraction_prompt,
|
12 |
+
"specific": specific_skills_comparison_prompt,
|
13 |
+
}
|
14 |
|
15 |
+
|
16 |
+
def paired_critique(
|
17 |
+
cv_contents: str, job_post_contents: str, response_type: str, API_KEY: str
|
18 |
+
) -> str:
|
19 |
"""Process CV contents, using Cohere"""
|
20 |
|
21 |
+
# First, get the prompt from the prompt dict
|
22 |
+
prompt = prompt_mapping.get(response_type)
|
23 |
|
24 |
+
# Now, populate with the contents of the CV and job posting
|
25 |
+
prompt = prompt.replace("<cv>", cv_contents).replace(
|
26 |
+
"<job-posting>", job_post_contents
|
27 |
)
|
28 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
29 |
response = gpt_response(
|
30 |
prompt=prompt,
|
31 |
api_key=API_KEY,
|
|
|
38 |
with open("sample_data/meta_job.txt", "r") as file:
|
39 |
post_contents = file.read()
|
40 |
|
41 |
+
with open("sample_data/example_cv.txt", "r") as file:
|
42 |
+
cv_contents = file.read()
|
43 |
+
|
44 |
+
COHERE_API_KEY = ""
|
45 |
+
|
46 |
+
output = paired_critique(post_contents, cv_contents, "specific", COHERE_API_KEY)
|
47 |
+
print(output)
|
utils/format.py
ADDED
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
|
3 |
+
|
4 |
+
def extract_json(input_string: str) -> dict:
|
5 |
+
"""String to Json function"""
|
6 |
+
|
7 |
+
# First, ensure we remove json wrapper
|
8 |
+
input_string = input_string.replace("```json", "```").replace("```", "")
|
9 |
+
|
10 |
+
# Now, ensure we have stripped everything so it is just json
|
11 |
+
input_string_formatted = input_string.lstrip("{").rstrip("}")
|
12 |
+
|
13 |
+
# Ensure we do not have the weird \_ behaviour that models sometimes include
|
14 |
+
input_string_formatted = input_string_formatted.replace("\_", "_")
|
15 |
+
|
16 |
+
try:
|
17 |
+
return True, json.loads(input_string_formatted)
|
18 |
+
|
19 |
+
except json.JSONDecodeError as e:
|
20 |
+
print(f"Error parsing JSON Output: {input_string}. Error: {e}")
|
21 |
+
return False, {}
|
22 |
+
|
23 |
+
|
24 |
+
def generate_markdown_report(data):
|
25 |
+
# Header
|
26 |
+
report = f"# CV Analysis Report\n\n"
|
27 |
+
report += f"**Name:** {data.get('personName', 'Unknown')} \n"
|
28 |
+
report += f"**Job:** {data.get('jobTitle', 'N/A')} at {data.get('companyName', 'N/A')} \n"
|
29 |
+
report += (
|
30 |
+
f"**Job Description:** {data.get('jobDesc', 'No description available.')}\n\n"
|
31 |
+
)
|
32 |
+
report += "---\n\n"
|
33 |
+
report += "## Key Findings\n\n"
|
34 |
+
|
35 |
+
experiences = data.get("experience", [])
|
36 |
+
if experiences:
|
37 |
+
|
38 |
+
report += "### Experience\n\n"
|
39 |
+
report += (
|
40 |
+
"| Job Posting Requirement | CV Details | Explanation | Impact Score |\n"
|
41 |
+
)
|
42 |
+
report += (
|
43 |
+
"| ----------------------- | ---------- | ----------- | -------------- |\n"
|
44 |
+
)
|
45 |
+
for exp in experiences:
|
46 |
+
report += f"| {exp.get('jobPostingDetails', 'N/A')} | {exp.get('cvDetails', 'N/A')} | {exp.get('explanation', '')} | **{exp.get('severityScore', 0)}** |\n"
|
47 |
+
report += "\n"
|
48 |
+
|
49 |
+
education = data.get("education", [])
|
50 |
+
if education:
|
51 |
+
report += "### Education\n\n"
|
52 |
+
report += (
|
53 |
+
"| Job Posting Requirement | CV Details | Explanation | Impact Score |\n"
|
54 |
+
)
|
55 |
+
report += (
|
56 |
+
"| ----------------------- | ---------- | ----------- | -------------- |\n"
|
57 |
+
)
|
58 |
+
for edu in education:
|
59 |
+
report += f"| {edu.get('jobPostingDetails', 'N/A')} | {edu.get('cvDetails', 'N/A')} | {edu.get('explanation', '')} | **{edu.get('severityScore', 0)}** |\n"
|
60 |
+
report += "\n"
|
61 |
+
|
62 |
+
responsibilities = data.get("responsibilities", [])
|
63 |
+
if responsibilities:
|
64 |
+
report += "### Responsibilities\n\n"
|
65 |
+
report += (
|
66 |
+
"| Job Posting Requirement | CV Details | Explanation | Impact Score |\n"
|
67 |
+
)
|
68 |
+
report += (
|
69 |
+
"| ----------------------- | ---------- | ----------- | -------------- |\n"
|
70 |
+
)
|
71 |
+
for resp in responsibilities:
|
72 |
+
report += f"| {resp.get('jobPostingDetails', 'N/A')} | {resp.get('cvDetails', 'N/A')} | {resp.get('explanation', '')} | **{resp.get('severityScore', 0)}** |\n"
|
73 |
+
report += "\n"
|
74 |
+
|
75 |
+
languages = data.get("languages", [])
|
76 |
+
if languages:
|
77 |
+
report += "### Languages\n\n"
|
78 |
+
report += (
|
79 |
+
"| Job Posting Requirement | CV Details | Explanation | Impact Score |\n"
|
80 |
+
)
|
81 |
+
report += (
|
82 |
+
"| ----------------------- | ---------- | ----------- | -------------- |\n"
|
83 |
+
)
|
84 |
+
for lang in languages:
|
85 |
+
report += f"| {lang.get('jobPostingDetails', 'N/A')} | {lang.get('cvDetails', 'N/A')} | {lang.get('explanation', '')} | **{lang.get('severityScore', 0)}** |\n"
|
86 |
+
report += "\n"
|
87 |
+
|
88 |
+
# Tools
|
89 |
+
tools = data.get("tools", [])
|
90 |
+
if tools:
|
91 |
+
report += "### Tools\n\n"
|
92 |
+
report += (
|
93 |
+
"| Job Posting Requirement | CV Details | Explanation | Impact Score |\n"
|
94 |
+
)
|
95 |
+
report += (
|
96 |
+
"| ----------------------- | ---------- | ----------- | -------------- |\n"
|
97 |
+
)
|
98 |
+
for tool in tools:
|
99 |
+
report += f"| {tool.get('jobPostingDetails', 'N/A')} | {tool.get('cvDetails', 'N/A')} | {tool.get('explanation', '')} | **{tool.get('severityScore', 0)}** |\n"
|
100 |
+
report += "\n"
|
101 |
+
|
102 |
+
# Closing
|
103 |
+
report += "---\n"
|
104 |
+
|
105 |
+
return report
|
106 |
+
|
107 |
+
|
108 |
+
def format_chat_history_cohere(chat_history: list, background_info: dict) -> list:
|
109 |
+
"""Takes streamlit chat history, and converts to cohere format"""
|
110 |
+
|
111 |
+
# Could use cohere to track history, maybe for the future
|
112 |
+
new_output = [
|
113 |
+
{
|
114 |
+
"role": "USER",
|
115 |
+
"message": f"Hi there! Here is my CV! {background_info['cv']}.\n\n I'd like you to act as a senior technical recruiter, recruiting for a role at a specific company. I want you to ask highly specific questions about the role, and critique my CV and its' suitability for the role. Please also ask general interview questions.",
|
116 |
+
},
|
117 |
+
{
|
118 |
+
"role": "CHATBOT",
|
119 |
+
"message": f"Thanks. Can you send me the job posting?",
|
120 |
+
},
|
121 |
+
{
|
122 |
+
"role": "USER",
|
123 |
+
"message": f"Here is the job posting: {background_info['job_posting']}",
|
124 |
+
},
|
125 |
+
]
|
126 |
+
|
127 |
+
for item in chat_history:
|
128 |
+
new_output.append(
|
129 |
+
{
|
130 |
+
"role": "USER" if item["role"] == "user" else "CHATBOT",
|
131 |
+
"message": item["message"],
|
132 |
+
}
|
133 |
+
)
|
134 |
+
|
135 |
+
return new_output
|
136 |
+
|
137 |
+
|
138 |
+
if __name__ == "__main__":
|
139 |
+
example_json = """
|
140 |
+
|
141 |
+
"""
|
142 |
+
extract_json(example_json)
|
utils/gpt.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1 |
import cohere
|
|
|
2 |
|
3 |
|
4 |
def test_api_key(api_key: str):
|
@@ -14,6 +15,23 @@ def test_api_key(api_key: str):
|
|
14 |
return False
|
15 |
|
16 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
def gpt_stream_response(prompt: str, api_key: str):
|
18 |
"""Get response from Cohere and stream response"""
|
19 |
|
|
|
1 |
import cohere
|
2 |
+
from utils.format import format_chat_history_cohere
|
3 |
|
4 |
|
5 |
def test_api_key(api_key: str):
|
|
|
15 |
return False
|
16 |
|
17 |
|
18 |
+
def gpt_stream_response_chat_history(chat_history, background_info, api_key):
|
19 |
+
"""Get response from Cohere and stream response"""
|
20 |
+
co = cohere.Client(
|
21 |
+
api_key=api_key,
|
22 |
+
)
|
23 |
+
|
24 |
+
cohere_history = format_chat_history_cohere(chat_history, background_info)
|
25 |
+
|
26 |
+
stream = co.chat_stream(
|
27 |
+
chat_history=cohere_history[:-1], message=cohere_history[-1]["message"]
|
28 |
+
)
|
29 |
+
|
30 |
+
for event in stream:
|
31 |
+
if event.event_type == "text-generation":
|
32 |
+
yield event.text
|
33 |
+
|
34 |
+
|
35 |
def gpt_stream_response(prompt: str, api_key: str):
|
36 |
"""Get response from Cohere and stream response"""
|
37 |
|
utils/prompts.py
CHANGED
@@ -1,124 +1,159 @@
|
|
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 |
**Goal**
|
32 |
-
|
33 |
|
34 |
-
**
|
35 |
-
|
|
|
|
|
|
|
|
|
|
|
36 |
|
37 |
**Output Format**
|
38 |
-
You will
|
39 |
-
|
40 |
|
|
|
41 |
{
|
42 |
-
"
|
43 |
-
"
|
44 |
-
"
|
45 |
-
"
|
46 |
-
"packages": [],
|
47 |
-
"tools": [],
|
48 |
-
"qualifications": [] ,
|
49 |
-
"responsibilities": [],
|
50 |
}
|
51 |
|
52 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
53 |
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
- qualifications: of form [{"type": , "grade": ,"location": }] where type is the qualification type identified. Available Education Levels are: bsc,msc,phd. grade should be the grade achieved (number between 0 and 100. Make relevant conversions, if no number is given, assume 60). location is the location of where the education was taken.
|
58 |
-
- responsibilities: an extensive list of the responsibilities demonstrated in the CV.
|
59 |
|
60 |
-
|
|
|
|
|
|
|
|
|
61 |
"""
|
62 |
|
63 |
-
job_posting_format = {
|
64 |
-
"type": "object",
|
65 |
-
"required": [
|
66 |
-
"companyName",
|
67 |
-
"roleShortDesc",
|
68 |
-
"roleLongDesc",
|
69 |
-
"requiredExperience",
|
70 |
-
"languages",
|
71 |
-
"packages",
|
72 |
-
"tools",
|
73 |
-
"qualifications",
|
74 |
-
"responsibilities",
|
75 |
-
],
|
76 |
-
"properties": {
|
77 |
-
"name": {"type": "string"},
|
78 |
-
"roleShortDesc": {"type": "string"},
|
79 |
-
"roleLongDesc": {"type": "string"},
|
80 |
-
"requiredExperience": {"type": "list"},
|
81 |
-
"languages": {"type": "list"},
|
82 |
-
"packages": {"type": "list"},
|
83 |
-
"tools": {"type": "list"},
|
84 |
-
"qualifications": {"type": "list"},
|
85 |
-
"responsibilities": {"type": "list"},
|
86 |
-
},
|
87 |
-
}
|
88 |
|
89 |
-
|
90 |
-
You are an expert at
|
|
|
91 |
|
92 |
**Goal**
|
93 |
-
|
94 |
|
95 |
-
**
|
|
|
96 |
<job-posting>
|
97 |
|
|
|
|
|
|
|
|
|
98 |
**Output Format**
|
99 |
-
You will respond with a json object, in the form given.
|
100 |
-
You will ensure that you are concise.
|
101 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
102 |
{
|
103 |
-
"companyName": ,
|
104 |
-
"roleShortDesc": ,
|
105 |
-
"roleLongDesc": ,
|
106 |
-
"requiredExperience": [],
|
107 |
"languages": [],
|
108 |
"packages": [],
|
109 |
-
"tools": []
|
110 |
-
"qualifications": [] ,
|
111 |
-
"responsibilities": [],
|
112 |
}
|
113 |
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
122 |
|
123 |
-
|
124 |
"""
|
|
|
1 |
+
basic_details_extraction_prompt = """
|
2 |
+
You are an expert at understanding how suitable a given CV is for a given job posting.
|
3 |
+
You will focus on evaluating the candidates experience quality
|
4 |
+
|
5 |
+
**Goal**
|
6 |
+
To summarize the information provided in a clear form
|
7 |
+
|
8 |
+
**Job Posting**
|
9 |
+
Here are the Job Posting contents:
|
10 |
+
<job-posting>
|
11 |
+
|
12 |
+
**CV**
|
13 |
+
Here are the CV contents:
|
14 |
+
<cv>
|
15 |
+
|
16 |
+
**Output Format**
|
17 |
+
|
18 |
+
You will produce a json, summarizing the input data.
|
19 |
+
|
20 |
+
Here is the overall structure of your output:
|
21 |
+
{
|
22 |
+
"jobTitle": str,
|
23 |
+
"companyName": str,
|
24 |
+
"personName": str,
|
25 |
+
"jobDesc": str
|
26 |
}
|
27 |
|
28 |
+
* jobTitle: at most 5 words giving role title
|
29 |
+
* companyName: name of company in job posting
|
30 |
+
* personName: name of person who submitted CV (if no name, put Example Name)
|
31 |
+
* jobDesc: a short description of the job
|
32 |
+
|
33 |
+
Respond only with a json, wrapped in ```json.
|
34 |
|
35 |
+
If you wish to leave a field blank in a given json, use "". You must never use null as this will be loaded using json.loads!
|
36 |
+
|
37 |
+
Now respond with your professional, concise, answer.
|
38 |
+
"""
|
39 |
+
|
40 |
+
general_skils_extraction_prompt = """
|
41 |
+
You are an expert at understanding how suitable a given CV is for a given job posting.
|
42 |
+
You will focus on evaluating the candidates experience quality
|
43 |
|
44 |
**Goal**
|
45 |
+
To evaluate the candidate, against the job posting, given set criteria
|
46 |
|
47 |
+
**Job Posting**
|
48 |
+
Here are the Job Posting contents:
|
49 |
+
<job-posting>
|
50 |
+
|
51 |
+
**CV**
|
52 |
+
Here are the CV contents:
|
53 |
+
<cv>
|
54 |
|
55 |
**Output Format**
|
56 |
+
You will produce a json, summarizing the candidates suitability.
|
57 |
+
The fields will all be lists, each item in those lists will be a dictionary with 4 keys.
|
58 |
|
59 |
+
That required form is
|
60 |
{
|
61 |
+
"jobPostingDetails": str,
|
62 |
+
"cvDetails": str,
|
63 |
+
"explanation": str,
|
64 |
+
"SeverityScore": float
|
|
|
|
|
|
|
|
|
65 |
}
|
66 |
|
67 |
+
* SeverityScore is between 0 and 10, marking how much of a critical miss the given thing is.
|
68 |
+
* Explanation will contain a brief explanation of what the problem was
|
69 |
+
|
70 |
+
For instance
|
71 |
+
(3 Years using R, No Mention of R, Missing Language Experience, 4)
|
72 |
+
|
73 |
+
Here is the overall structure of your output:
|
74 |
+
{
|
75 |
+
"experience": [],
|
76 |
+
"education": [],
|
77 |
+
"responsibilities": []
|
78 |
+
}
|
79 |
|
80 |
+
* experience should look at the desired experiences in the job posting. For each experience listed, it should be understood if the user's CV showed that trait.
|
81 |
+
* education should compare required / desired education levels with the user's CV's education levels
|
82 |
+
* responsibilities refers to the list of responsibilities in the job posting, which should each be compared with the user's CV
|
|
|
|
|
83 |
|
84 |
+
Respond only with a json, wrapped in ```json.
|
85 |
+
|
86 |
+
If you wish to leave a field blank in a given json, use "". You must never use null as this will be loaded using json.loads!
|
87 |
+
|
88 |
+
Now respond with your professional, concise, answer.
|
89 |
"""
|
90 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
91 |
|
92 |
+
specific_skills_comparison_prompt = """
|
93 |
+
You are an expert at understanding how suitable a given CV is for a given job posting.
|
94 |
+
You will focus on evaluating the candidates experience quality
|
95 |
|
96 |
**Goal**
|
97 |
+
To evaluate the candidate, against the job posting, given set criteria
|
98 |
|
99 |
+
**Job Posting**
|
100 |
+
Here are the Job Posting contents:
|
101 |
<job-posting>
|
102 |
|
103 |
+
**CV**
|
104 |
+
Here are the CV contents:
|
105 |
+
<cv>
|
106 |
+
|
107 |
**Output Format**
|
|
|
|
|
108 |
|
109 |
+
You will produce a json, summarizing the candidates suitability.
|
110 |
+
The fields will all be lists, each item in those lists will be a dictionary with 4 keys.
|
111 |
+
|
112 |
+
That required form is
|
113 |
+
{
|
114 |
+
"jobPostingDetails": str,
|
115 |
+
"cvDetails": str,
|
116 |
+
"explanation": str,
|
117 |
+
"SeverityScore": float
|
118 |
+
}
|
119 |
+
|
120 |
+
* SeverityScore is between 0 and 10, marking how much of a critical miss the given thing is.
|
121 |
+
* Explanation will contain a brief explanation of what the problem was
|
122 |
+
|
123 |
+
For instance this could look like: (3 Years using R, No Mention of R, Missing Language Experience, 4)
|
124 |
+
|
125 |
+
Here is the overall structure of your output:
|
126 |
{
|
|
|
|
|
|
|
|
|
127 |
"languages": [],
|
128 |
"packages": [],
|
129 |
+
"tools": []
|
|
|
|
|
130 |
}
|
131 |
|
132 |
+
* languages should compare either programming, or regular language, requirements
|
133 |
+
* packages refers specifically to packages of programming languages. Leave blank if job posting gives no details on specific packages.
|
134 |
+
* tools refers to specific tools referenced in the job posting, leave blank if job posting gives no details on tools.
|
135 |
+
|
136 |
+
Be incredibly careful that you do not confuse the content of the CV with the content of the Job Posting.
|
137 |
+
|
138 |
+
Respond only with a json, wrapped in ```json.
|
139 |
+
|
140 |
+
You must never use null as this will be loaded using json.loads! If there is nothing relevant, leave the list empty. If a field is missing from the dictionary of 4, then just put "".
|
141 |
+
|
142 |
+
Now respond with your professional, concise, answer.
|
143 |
+
"""
|
144 |
+
|
145 |
+
interviewer_prompt = """
|
146 |
+
**Goal**
|
147 |
+
You are an interviewer, and will carry out a natural interview
|
148 |
+
|
149 |
+
**Background Information**
|
150 |
+
The user has the following CV:
|
151 |
+
<cv>
|
152 |
+
|
153 |
+
The user is interested in the following job:
|
154 |
+
<job-posting>
|
155 |
+
|
156 |
+
You will act like an interviewer for that job, critiquing the user's CV and asking them questions. Follow the conversation naturally.
|
157 |
|
158 |
+
Ask either STAR reasoning, or technical questions, based on the job posting, or general job title.
|
159 |
"""
|