Jonah Ramponi commited on
Commit
0c94c61
1 Parent(s): 98eaa40
.streamlit/config.toml ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ [client]
2
+ showSidebarNavigation = false
CVReview.py ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ CV Review Feature
3
+ """
4
+
5
+ import json
6
+ import concurrent.futures
7
+ from httpx import LocalProtocolError
8
+
9
+ import streamlit as st
10
+ from cohere.core.api_error import ApiError
11
+
12
+ from utils.backend import produce_report
13
+ from utils.format import extract_json
14
+
15
+
16
+ def generate_markdown_report(REPORT_OBJ: dict) -> str:
17
+ """Format the report object dictionary into a markdown readable report"""
18
+
19
+ def add_section(section_title: str, table_contents: list) -> str:
20
+ """Utility function to add a section containing a table to the markdown report"""
21
+
22
+ if not table_contents:
23
+ return ""
24
+
25
+ section = f"### {section_title.title()}\n\n"
26
+ section += (
27
+ "| Job Posting Requirement | CV Details | Explanation | Impact Score |\n"
28
+ "| ----------------------- | ---------- | ----------- | -------------- |\n"
29
+ )
30
+ for table_row in table_contents:
31
+ section += f"| {table_row.get('jobPostingDetails', 'N/A')} | {table_row.get('cvDetails', 'N/A')} | {table_row.get('explanation', '')} | **{table_row.get('severityScore', 0)}** |\n"
32
+ return section + "\n"
33
+
34
+ report = (
35
+ f"# CV Analysis Report\n\n"
36
+ f"**Name:** {REPORT_OBJ.get('personName', 'Unknown')} \n"
37
+ f"**Job:** {REPORT_OBJ.get('jobTitle', 'N/A')} at {REPORT_OBJ.get('companyName', 'N/A')} \n"
38
+ f"**Job Description:** {REPORT_OBJ.get('jobDesc', 'No description available.')}\n\n"
39
+ "---\n\n"
40
+ "## Key Findings\n\n"
41
+ )
42
+
43
+ sections = ["experience", "education", "responsibilities", "languages", "tools"]
44
+
45
+ for section in sections:
46
+ report += add_section(section, REPORT_OBJ.get(section, []))
47
+
48
+ report += "---\n"
49
+ return report
50
+
51
+
52
+ def CVReviewPage():
53
+ """Source Code for CV Review Page"""
54
+
55
+ SHARED_STATE = st.session_state.shared_materials
56
+ API_KEY = st.session_state.api_key
57
+
58
+ produce_report = st.button("Produce Suitability Report")
59
+ if produce_report:
60
+ try:
61
+ results = {}
62
+ # We will make 3 calls in parallel, to get various bits of information efficiently
63
+ with concurrent.futures.ThreadPoolExecutor() as executor:
64
+
65
+ futures = {
66
+ critique_type: executor.submit(
67
+ produce_report,
68
+ SHARED_STATE["cv"],
69
+ SHARED_STATE["job_posting"],
70
+ critique_type,
71
+ API_KEY,
72
+ )
73
+ for critique_type in ["basic", "general", "specific"]
74
+ }
75
+
76
+ for critique_type, future in futures.items():
77
+ results[critique_type] = future.result()
78
+
79
+ except LocalProtocolError:
80
+ st.error("You need to enter a Cohere API Key.")
81
+ except ApiError:
82
+ st.error("You need a valid Cohere API Key")
83
+
84
+ # merge the from our calls, by extracting the json object from the gpt message
85
+ resultsDict = {}
86
+ for jsonText in results.values():
87
+ _, output_report_json = extract_json(jsonText)
88
+
89
+ resultsDict.update(output_report_json)
90
+
91
+ # store this as the report object
92
+ SHARED_STATE["report"] = resultsDict
93
+
94
+ # if the report object exists
95
+ if SHARED_STATE["report"]:
96
+ REPORT = SHARED_STATE["report"]
97
+
98
+ # these are used for file naming
99
+ name = REPORT.get("personName", "MissingPersonName")
100
+ job_title = REPORT.get("jobTitle", "MissingTitle")
101
+ company_name = REPORT.get("companyName", "MissingCompany")
102
+
103
+ # render markdown report
104
+ st.markdown(generate_markdown_report(REPORT))
105
+
106
+ # Downloadable in json form !
107
+ st.download_button(
108
+ label="Download Report JSON",
109
+ data=json.dumps(REPORT, indent=4),
110
+ file_name=f"{name}_{job_title}_{company_name}.json",
111
+ mime="application/json",
112
+ use_container_width=True,
113
+ )
Interview.py ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Simulates an interview, using uploaded CV and Job Description
3
+ """
4
+
5
+ import random
6
+ import streamlit as st
7
+ from httpx import LocalProtocolError
8
+
9
+ from cohere.core.api_error import ApiError
10
+
11
+
12
+ from utils.gpt import stream
13
+
14
+
15
+ def InterviewPage():
16
+ """Source Code for the Interview Simulation Page"""
17
+
18
+ initial_questions = [
19
+ "Ready for me to grill you?",
20
+ "Please let me know when you're ready to begin the interview",
21
+ "Ready to rumble?",
22
+ ]
23
+
24
+ # the initial message will be a random choice, initiating the conversation
25
+ if "messages" not in st.session_state:
26
+ st.session_state["messages"] = [
27
+ {"role": "assistant", "message": random.choice(initial_questions)}
28
+ ]
29
+
30
+ MESSAGES = st.session_state.messages
31
+ SHARED_STATE = st.session_state.shared_materials
32
+ API_KEY = st.session_state.api_key
33
+
34
+ clear_conversation = st.button("Clear Conversation")
35
+
36
+ # Clear conversation will clear message state, and initialize with a new random question
37
+ if clear_conversation:
38
+ st.session_state["messages"] = [
39
+ {"role": "assistant", "message": random.choice(initial_questions)}
40
+ ]
41
+
42
+ if not SHARED_STATE["valid_flag"]:
43
+ st.error("You need to upload a Job Description & CV to use this feature.")
44
+ else:
45
+ try:
46
+ # Populate the chat with historic messages
47
+ for msg in MESSAGES:
48
+ st.chat_message(msg["role"]).write(msg["message"])
49
+
50
+ if user_input := st.chat_input():
51
+
52
+ # Write the user question to UI
53
+ st.chat_message("user").write(user_input)
54
+
55
+ assistant_message = st.chat_message("assistant")
56
+
57
+ # Stream assistant message, using relevant background information
58
+ response = assistant_message.write_stream(
59
+ stream(
60
+ background_info={
61
+ "cv": SHARED_STATE["cv"],
62
+ "job_posting": SHARED_STATE["job_posting"],
63
+ },
64
+ chat_history=MESSAGES,
65
+ api_key=API_KEY,
66
+ )
67
+ )
68
+
69
+ # Append messages to chat history
70
+ MESSAGES.append({"role": "user", "message": user_input})
71
+ MESSAGES.append({"role": "assistant", "message": response})
72
+ except LocalProtocolError:
73
+ st.error("You need to enter a Cohere API Key.")
74
+ except ApiError:
75
+ st.error("You need a valid Cohere API Key")
app.py CHANGED
@@ -2,191 +2,85 @@
2
  For HF, the interface should be called app.py
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
 
19
- with st.sidebar:
20
- COHERE_API_KEY = st.text_input(
21
- "Cohere API Key Entry",
22
- value="",
23
- placeholder="Enter your Free Tier Cohere API Key",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
-
31
- # Weird Hugging Face display issue, padding fixes it
32
- st.markdown("\n")
33
- st.markdown("\n")
34
- st.markdown("\n")
35
-
36
- cv_upload_box = st.file_uploader(
37
- "CV Upload Box",
38
- help="Upload your CV in .docx or .pdf form. This CV will be parsed, and used to analyse against the given job post.",
39
- type=["docx", "pdf"],
40
- accept_multiple_files=False,
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})
 
2
  For HF, the interface should be called app.py
3
  """
4
 
5
+ import pathlib
 
 
 
6
  import streamlit as st
7
 
 
 
8
  from utils.process_doc import parse_docx, parse_pdf
 
 
 
 
9
 
10
+ from Interview import InterviewPage
11
+ from CVReview import CVReviewPage
12
+
13
+ CURRENT_DIR = pathlib.Path(__file__).parent.resolve()
14
+
15
+
16
+ def main():
17
+ st.set_page_config(layout="wide")
18
+
19
+ if "api_key" not in st.session_state:
20
+ st.session_state.api_key = ""
21
+
22
+ if "report" not in st.session_state:
23
+ st.session_state.report = {}
24
+
25
+ if "shared_materials" not in st.session_state:
26
+ st.session_state.shared_materials = {"valid_flag": False, "report": None}
27
+
28
+ SHARED_MATERIALS = st.session_state.shared_materials
29
+
30
+ with st.sidebar:
31
+ st.session_state["api_key"] = st.text_input(
32
+ "Cohere API Key Entry",
33
+ value="",
34
+ placeholder="Enter your Free Tier Cohere API Key",
35
+ )
36
+
37
+ job_posting_upload_box = st.text_area(
38
+ "Job Description Upload Box",
39
+ value="",
40
+ placeholder="Copy and Paste the contents of a job post you are interested in.",
41
+ 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.",
42
+ )
43
+
44
+ cv_upload_box = st.file_uploader(
45
+ "CV Upload Box",
46
+ help="Upload your CV in .docx or .pdf form. This CV will be parsed, and used to analyse against the given job post.",
47
+ type=["docx", "pdf"],
48
+ accept_multiple_files=False,
49
+ )
50
+
51
+ if cv_upload_box and job_posting_upload_box != "":
52
+ SHARED_MATERIALS["job_posting"] = job_posting_upload_box
53
+
54
+ cv_filetype = cv_upload_box.name.split(".")[-1]
55
+ cv_file_contents = cv_upload_box.getvalue()
56
+
57
+ SHARED_MATERIALS["cv"] = (
58
+ parse_docx(cv_file_contents)
59
+ if cv_filetype == "docx"
60
+ else parse_pdf(cv_file_contents)
61
+ )
62
+
63
+ SHARED_MATERIALS["valid_flag"] = True
64
+
65
+ pg = st.navigation(
66
+ {
67
+ "Job Search AI Tools": [
68
+ st.Page(
69
+ InterviewPage,
70
+ title="Practice Interview",
71
+ icon=":material/chat:",
72
+ ),
73
+ st.Page(
74
+ CVReviewPage,
75
+ title="CV Review",
76
+ icon=":material/description:",
77
+ ),
78
+ ],
79
+ }
80
  )
81
 
82
+ pg.run()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
+ if __name__ == "__main__":
86
+ main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
assets/logo.png ADDED
backend.py → utils/backend.py RENAMED
@@ -1,3 +1,7 @@
 
 
 
 
1
  from utils.prompts import (
2
  basic_details_extraction_prompt,
3
  general_skils_extraction_prompt,
@@ -13,13 +17,15 @@ prompt_mapping = {
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(
@@ -43,5 +49,5 @@ if __name__ == "__main__":
43
 
44
  COHERE_API_KEY = ""
45
 
46
- output = paired_critique(post_contents, cv_contents, "specific", COHERE_API_KEY)
47
  print(output)
 
1
+ """
2
+ Main Backend Handling Function
3
+ """
4
+
5
  from utils.prompts import (
6
  basic_details_extraction_prompt,
7
  general_skils_extraction_prompt,
 
17
  }
18
 
19
 
20
+ def produce_report(
21
+ cv_contents: str, job_post_contents: str, PROMPT_TO_USE: str, API_KEY: str
22
  ) -> str:
23
  """Process CV contents, using Cohere"""
24
 
25
+ # The KEY ARGUMENT here is PROMPT_TO_USE, which controls the prompt to use
26
+
27
  # First, get the prompt from the prompt dict
28
+ prompt = prompt_mapping.get(PROMPT_TO_USE)
29
 
30
  # Now, populate with the contents of the CV and job posting
31
  prompt = prompt.replace("<cv>", cv_contents).replace(
 
49
 
50
  COHERE_API_KEY = ""
51
 
52
+ output = produce_report(post_contents, cv_contents, "specific", COHERE_API_KEY)
53
  print(output)
utils/format.py CHANGED
@@ -1,7 +1,8 @@
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
@@ -21,98 +22,14 @@ def extract_json(input_string: str) -> dict:
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",
@@ -124,6 +41,7 @@ def format_chat_history_cohere(chat_history: list, background_info: dict) -> lis
124
  },
125
  ]
126
 
 
127
  for item in chat_history:
128
  new_output.append(
129
  {
@@ -137,6 +55,9 @@ def format_chat_history_cohere(chat_history: list, background_info: dict) -> lis
137
 
138
  if __name__ == "__main__":
139
  example_json = """
140
-
 
 
 
141
  """
142
  extract_json(example_json)
 
1
  import json
2
+ from typing import Union
3
 
4
 
5
+ def extract_json(input_string: str) -> Union[bool, dict]:
6
  """String to Json function"""
7
 
8
  # First, ensure we remove json wrapper
 
22
  return False, {}
23
 
24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  def format_chat_history_cohere(chat_history: list, background_info: dict) -> list:
26
  """Takes streamlit chat history, and converts to cohere format"""
27
 
28
+ # TODO: Could use cohere to track history, maybe for the future
29
  new_output = [
30
  {
31
  "role": "USER",
32
+ "message": f"Hi there! Here is my CV! {background_info['cv']}.\n\n I'd like you to act as a senior technical recruiter. Critique my CV and its' suitability for the role. You may only ask me one question at a time. Be as critical of the CV as possible, really focus on the CV and its relevance to the role. Ask about inconsistencies, mistakes in grammar, things which are technically not quite right suggesting a misunderstanding, or ask me to justify claims I make in my CV. You could also ask specific technical details about the architectures I have used.",
33
  },
34
  {
35
  "role": "CHATBOT",
 
41
  },
42
  ]
43
 
44
+ # Lazy approach to format it correctly for cohere input!
45
  for item in chat_history:
46
  new_output.append(
47
  {
 
55
 
56
  if __name__ == "__main__":
57
  example_json = """
58
+ ```json
59
+ {
60
+ "dogs": "are blue?"
61
+ }
62
  """
63
  extract_json(example_json)
utils/gpt.py CHANGED
@@ -1,9 +1,15 @@
 
 
 
 
 
 
1
  import cohere
2
  from utils.format import format_chat_history_cohere
3
 
4
 
5
  def test_api_key(api_key: str):
6
-
7
  try:
8
  # try to just generate 3 tokens
9
  co = cohere.Client(
@@ -15,7 +21,9 @@ 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,
@@ -32,20 +40,6 @@ def gpt_stream_response_chat_history(chat_history, background_info, api_key):
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
-
38
- co = cohere.Client(
39
- api_key=api_key,
40
- )
41
-
42
- stream = co.chat_stream(message=prompt)
43
-
44
- for event in stream:
45
- if event.event_type == "text-generation":
46
- yield event.text
47
-
48
-
49
  def gpt_response(prompt: str, api_key: str) -> str:
50
  """Get response from Cohere, with option to get output in json format"""
51
 
 
1
+ """
2
+ GPT Related Functions
3
+ """
4
+
5
+ from typing import List, Dict, Generator
6
+
7
  import cohere
8
  from utils.format import format_chat_history_cohere
9
 
10
 
11
  def test_api_key(api_key: str):
12
+ """Function to test Cohere API is working"""
13
  try:
14
  # try to just generate 3 tokens
15
  co = cohere.Client(
 
21
  return False
22
 
23
 
24
+ def stream(
25
+ background_info: str, chat_history: List[Dict[str, str]] = [], api_key: str = ""
26
+ ) -> Generator:
27
  """Get response from Cohere and stream response"""
28
  co = cohere.Client(
29
  api_key=api_key,
 
40
  yield event.text
41
 
42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  def gpt_response(prompt: str, api_key: str) -> str:
44
  """Get response from Cohere, with option to get output in json format"""
45
 
utils/process_doc.py CHANGED
@@ -12,10 +12,11 @@ def parse_pdf(pdf_file) -> str:
12
  """Read PDF from Streamlit's file uploader"""
13
 
14
  pdf_document = fitz.open("pdf", pdf_file)
 
15
 
16
  all_text = []
17
 
18
- for page_number in range(len(pdf_document)):
19
 
20
  page = pdf_document.load_page(page_number)
21
 
@@ -26,15 +27,12 @@ def parse_pdf(pdf_file) -> str:
26
  return "\n\n".join(all_text)
27
 
28
 
29
- def parse_docx(docx_file):
30
  """Read in docx file"""
31
  docx_file = io.BytesIO(docx_file)
32
 
33
- document = Document(docx_file)
34
 
35
- all_text = []
36
-
37
- for paragraph in document.paragraphs:
38
- all_text.append(paragraph.text)
39
 
40
  return "\n".join(all_text)
 
12
  """Read PDF from Streamlit's file uploader"""
13
 
14
  pdf_document = fitz.open("pdf", pdf_file)
15
+ n_pages = len(pdf_document)
16
 
17
  all_text = []
18
 
19
+ for page_number in range(n_pages):
20
 
21
  page = pdf_document.load_page(page_number)
22
 
 
27
  return "\n\n".join(all_text)
28
 
29
 
30
+ def parse_docx(docx_file) -> str:
31
  """Read in docx file"""
32
  docx_file = io.BytesIO(docx_file)
33
 
34
+ doc = Document(docx_file)
35
 
36
+ all_text = [para.text for para in doc.paragraphs]
 
 
 
37
 
38
  return "\n".join(all_text)
utils/prompts.py CHANGED
@@ -88,7 +88,6 @@ If you wish to leave a field blank in a given json, use "". You must never use n
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
@@ -141,19 +140,3 @@ You must never use null as this will be loaded using json.loads! If there is not
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
- """
 
88
  Now respond with your professional, concise, answer.
89
  """
90
 
 
91
  specific_skills_comparison_prompt = """
92
  You are an expert at understanding how suitable a given CV is for a given job posting.
93
  You will focus on evaluating the candidates experience quality
 
140
 
141
  Now respond with your professional, concise, answer.
142
  """