akshit7093 commited on
Commit
e38b21e
·
verified ·
1 Parent(s): a6f4740

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +493 -0
app.py ADDED
@@ -0,0 +1,493 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import streamlit as st
3
+ import ollama
4
+ import json
5
+ import csv
6
+ from datetime import datetime
7
+
8
+
9
+ # Streamlit UI
10
+ st.title("TalentScout Hiring Assistant Chatbot")
11
+
12
+ # Initialize session state for conversation history and candidate information
13
+ if "messages" not in st.session_state:
14
+ st.session_state.messages = []
15
+ if "candidate_info" not in st.session_state:
16
+ st.session_state.candidate_info = {
17
+ "full_name": None,
18
+ "email": None,
19
+ "phone": None,
20
+ "years_of_experience": None,
21
+ "desired_position": None,
22
+ "current_location": None,
23
+ "tech_stack": None,
24
+ }
25
+ if "info_collected" not in st.session_state:
26
+ st.session_state.info_collected = False
27
+ if "technical_questions" not in st.session_state:
28
+ st.session_state.technical_questions = []
29
+ if "current_question_index" not in st.session_state:
30
+ st.session_state.current_question_index = 0
31
+ if "answers" not in st.session_state:
32
+ st.session_state.answers = []
33
+ if "submitted" not in st.session_state:
34
+ st.session_state.submitted = False
35
+ if "conversation_ended" not in st.session_state:
36
+ st.session_state.conversation_ended = False
37
+
38
+ # Function to generate responses using Ollama
39
+ # Function to generate responses using Ollama
40
+ def generate_response(prompt):
41
+ try:
42
+ response = ollama.generate(
43
+ model="llama3.1", # You can use other models like "llama3" or "mistral"
44
+ prompt=prompt,
45
+ )
46
+ return response["response"]
47
+ except Exception as e:
48
+ return f"Error generating response: {str(e)}"
49
+
50
+ def generate_technical_questions(tech_stack):
51
+ """
52
+ Generates 3-5 technical questions based on the candidate's tech stack.
53
+ """
54
+ if not tech_stack:
55
+ return []
56
+
57
+ prompt = f"""
58
+ Generate exactly 4 technical interview questions for a candidate with experience in: {', '.join(tech_stack)}
59
+
60
+ Return only a JSON array in this exact format:
61
+ [
62
+ {{"question": "Your first question here", "type": "text"}},
63
+ {{"question": "Your second question here", "type": "code"}},
64
+ {{"question": "Your third question here", "type": "text"}},
65
+ {{"question": "Your fourth question here", "type": "code"}}
66
+ ]
67
+ """
68
+
69
+ try:
70
+ response = ollama.generate(
71
+ model="llama3.1",
72
+ prompt=prompt,
73
+ )
74
+ response_text = response["response"].strip()
75
+
76
+ # Extract the JSON array from the response
77
+ start_idx = response_text.find('[')
78
+ end_idx = response_text.rfind(']') + 1
79
+
80
+ if start_idx >= 0 and end_idx > start_idx:
81
+ json_str = response_text[start_idx:end_idx]
82
+ questions = json.loads(json_str)
83
+
84
+ # Validate question format
85
+ if len(questions) >= 3 and all(isinstance(q, dict) and 'question' in q and 'type' in q for q in questions):
86
+ return questions
87
+
88
+ raise ValueError("Invalid question format received")
89
+
90
+ except Exception as e:
91
+ st.error(f"Error generating questions: {str(e)}")
92
+ return []
93
+
94
+
95
+ # Function to evaluate answers and calculate score
96
+ def evaluate_answers(questions, answers):
97
+ """
98
+ Strictly evaluates the candidate's answers and calculates a score.
99
+ Points are only awarded for correct answers.
100
+ """
101
+ score = 0
102
+ feedback = []
103
+
104
+ for i, question in enumerate(questions):
105
+ if question["type"] == "text":
106
+ prompt = f"""
107
+ You are a strict technical interviewer. Evaluate this answer with high standards.
108
+
109
+ Question: {question["question"]}
110
+ Candidate's Answer: {answers[i]}
111
+
112
+ Evaluate if the answer demonstrates clear understanding and technical accuracy.
113
+ First line must be exactly "CORRECT" or "INCORRECT"
114
+ Then provide a brief explanation of why.
115
+ """
116
+ try:
117
+ response = ollama.generate(
118
+ model="llama3.1",
119
+ prompt=prompt,
120
+ )
121
+ evaluation = response["response"].strip().split('\n')[0].upper()
122
+ explanation = ' '.join(response["response"].strip().split('\n')[1:])
123
+
124
+ if evaluation == "CORRECT":
125
+ score += 1
126
+ feedback.append(f"Question {i + 1}: Correct (1 point) - {explanation}")
127
+ else:
128
+ feedback.append(f"Question {i + 1}: Incorrect (0 points) - {explanation}")
129
+ except Exception as e:
130
+ feedback.append(f"Question {i + 1}: Evaluation failed (0 points)")
131
+
132
+ elif question["type"] == "code":
133
+ prompt = f"""
134
+ You are a strict technical interviewer evaluating code.
135
+
136
+ Coding Question: {question["question"]}
137
+ Submitted Code: {answers[i]}
138
+
139
+ Evaluate for:
140
+ 1. Correctness
141
+ 2. Proper syntax
142
+ 3. Efficiency
143
+ 4. Error handling
144
+
145
+ First line must be exactly "CORRECT" or "INCORRECT"
146
+ Then provide specific technical feedback.
147
+ """
148
+ try:
149
+ response = ollama.generate(
150
+ model="llama3.1",
151
+ prompt=prompt,
152
+ )
153
+ evaluation = response["response"].strip().split('\n')[0].upper()
154
+ explanation = ' '.join(response["response"].strip().split('\n')[1:])
155
+
156
+ if evaluation == "CORRECT":
157
+ score += 2
158
+ feedback.append(f"Question {i + 1}: Correct (2 points) - {explanation}")
159
+ else:
160
+ feedback.append(f"Question {i + 1}: Incorrect (0 points) - {explanation}")
161
+ except Exception as e:
162
+ feedback.append(f"Question {i + 1}: Evaluation failed (0 points)")
163
+
164
+ return score, feedback
165
+
166
+
167
+
168
+ # Function to save data to CSV
169
+ def save_to_csv(candidate_info, questions, answers, score, feedback):
170
+ """
171
+ Saves interview data to a CSV file
172
+ """
173
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
174
+ filename = "interview_responses.csv"
175
+
176
+ # Prepare the data row
177
+ row_data = {
178
+ 'Timestamp': timestamp,
179
+ 'Full Name': candidate_info['full_name'],
180
+ 'Email': candidate_info['email'],
181
+ 'Phone': candidate_info['phone'],
182
+ 'Years of Experience': candidate_info['years_of_experience'],
183
+ 'Desired Position': candidate_info['desired_position'],
184
+ 'Current Location': candidate_info['current_location'],
185
+ 'Tech Stack': ', '.join(candidate_info['tech_stack']),
186
+ 'Total Score': score
187
+ }
188
+
189
+ # Add questions and answers
190
+ for i, (question, answer) in enumerate(zip(questions, answers)):
191
+ row_data[f'Question_{i+1}'] = question['question']
192
+ row_data[f'Answer_{i+1}'] = answer
193
+ row_data[f'Feedback_{i+1}'] = feedback[i]
194
+
195
+ # Check if file exists to write headers
196
+ file_exists = os.path.isfile(filename)
197
+
198
+ with open(filename, mode='a', newline='', encoding='utf-8') as file:
199
+ writer = csv.DictWriter(file, fieldnames=list(row_data.keys()))
200
+
201
+ if not file_exists:
202
+ writer.writeheader()
203
+
204
+ writer.writerow(row_data)
205
+
206
+
207
+
208
+ # Function to handle fallback mechanism
209
+ def fallback_response():
210
+ """
211
+ Provides a meaningful response when the chatbot does not understand the input.
212
+ """
213
+ return "I'm sorry, I didn't understand that. Could you please rephrase or ask something else?"
214
+
215
+ # Function to end the conversation gracefully
216
+ def end_conversation():
217
+ """
218
+ Gracefully concludes the conversation.
219
+ """
220
+ st.session_state.conversation_ended = True
221
+ return "Thank you for your time! We will review your responses and get back to you shortly. Have a great day!"
222
+
223
+ # Display a short greeting message at the beginning
224
+ if "greeting_displayed" not in st.session_state:
225
+ st.write("Hi! I'm the Hiring Assistant from TalentScout. Let's get started by filling out some details.")
226
+ st.session_state.greeting_displayed = True
227
+
228
+ # Step 1: Collect candidate information
229
+ if not st.session_state.info_collected and not st.session_state.conversation_ended:
230
+ with st.form("candidate_details_form"):
231
+ st.write("### Step 1: Provide Your Details")
232
+ full_name = st.text_input("Full Name")
233
+ email = st.text_input("Email Address")
234
+ phone = st.text_input("Phone Number")
235
+ years_of_experience = st.number_input("Years of Experience", min_value=0, max_value=50, step=1)
236
+ desired_position = st.text_input("Desired Position (e.g., Software Engineer, Data Scientist)")
237
+ current_location = st.text_input("Current Location")
238
+ tech_stack = st.multiselect(
239
+ "Tech Stack (Select all that apply)",
240
+ ["Python", "Java", "JavaScript", "Django", "React", "PostgreSQL", "AWS", "Machine Learning"]
241
+ )
242
+ submitted = st.form_submit_button("Submit")
243
+
244
+ if submitted:
245
+ # Save candidate information to session state
246
+ st.session_state.candidate_info = {
247
+ "full_name": full_name,
248
+ "email": email,
249
+ "phone": phone,
250
+ "years_of_experience": years_of_experience,
251
+ "desired_position": desired_position,
252
+ "current_location": current_location,
253
+ "tech_stack": tech_stack,
254
+ }
255
+ st.session_state.info_collected = True
256
+ st.session_state.technical_questions = generate_technical_questions(tech_stack)
257
+ if not st.session_state.technical_questions:
258
+ st.error("Failed to generate technical questions. Please try again.")
259
+ st.session_state.info_collected = False
260
+ else:
261
+ st.session_state.answers = [""] * len(st.session_state.technical_questions)
262
+ st.rerun() # Use st.rerun() instead of st.experimental_rerun()
263
+
264
+ # Step 2: Question-Answer Session
265
+ if st.session_state.info_collected and not st.session_state.submitted and not st.session_state.conversation_ended:
266
+ if not st.session_state.technical_questions:
267
+ st.error("No technical questions were generated. Please restart the session.")
268
+ else:
269
+ st.write("### Step 2: Answer the Questions")
270
+ current_question = st.session_state.technical_questions[st.session_state.current_question_index]
271
+ st.write(f"#### Question {st.session_state.current_question_index + 1}")
272
+ st.write(current_question["question"])
273
+
274
+ # Input field for the answer
275
+ if current_question["type"] == "text":
276
+ answer = st.text_area("Your Answer", value=st.session_state.answers[st.session_state.current_question_index])
277
+ elif current_question["type"] == "code":
278
+ answer = st.text_area("Write your code here", value=st.session_state.answers[st.session_state.current_question_index])
279
+
280
+ # Save the answer
281
+ st.session_state.answers[st.session_state.current_question_index] = answer
282
+
283
+ # Navigation buttons
284
+ col1, col2 = st.columns(2)
285
+ with col1:
286
+ if st.session_state.current_question_index > 0:
287
+ if st.button("Previous"):
288
+ st.session_state.current_question_index -= 1
289
+ st.rerun() # Use st.rerun() instead of st.experimental_rerun()
290
+ with col2:
291
+ if st.session_state.current_question_index < len(st.session_state.technical_questions) - 1:
292
+ if st.button("Next"):
293
+ st.session_state.current_question_index += 1
294
+ st.rerun() # Use st.rerun() instead of st.experimental_rerun()
295
+ else:
296
+ if st.button("Submit Answers"):
297
+ st.session_state.submitted = True
298
+ st.rerun() # Use st.rerun() instead of st.experimental_rerun()
299
+
300
+ # Step 3: Evaluate Answers
301
+ if st.session_state.submitted and not st.session_state.conversation_ended:
302
+ st.write("### Evaluation Results")
303
+ score, feedback = evaluate_answers(st.session_state.technical_questions, st.session_state.answers)
304
+ st.write(f"#### Your Score: {score}/{len(st.session_state.technical_questions) * 2}")
305
+
306
+ for fb in feedback:
307
+ st.write(fb)
308
+
309
+ # Save responses to CSV
310
+ save_to_csv(
311
+ st.session_state.candidate_info,
312
+ st.session_state.technical_questions,
313
+ st.session_state.answers,
314
+ score,
315
+ feedback
316
+ )
317
+
318
+ # Provide next steps
319
+ st.write("### Next Steps")
320
+ st.write("We will review your responses and get back to you shortly.")
321
+
322
+
323
+ # Option to restart or end the session
324
+ col1, col2 = st.columns(2)
325
+ with col1:
326
+ if st.button("Restart"):
327
+ st.session_state.info_collected = False
328
+ st.session_state.technical_questions = []
329
+ st.session_state.answers = []
330
+ st.session_state.submitted = False
331
+ st.session_state.current_question_index = 0
332
+ st.rerun() # Restart the session
333
+ with col2:
334
+ if st.button("End Session"):
335
+ st.session_state.conversation_ended = True
336
+ st.write("Thank you for participating! Have a great day!")
337
+
338
+ # Step 4: Professional Discussion and Technical Guidance
339
+ def generate_candidate_report(candidate_info, questions, answers, score, chat_history):
340
+ """
341
+ Generates a detailed technical assessment report for the candidate
342
+ """
343
+ total_possible_score = len(questions) * 2
344
+ score_percentage = (score / total_possible_score) * 100
345
+
346
+ prompt = f"""
347
+ Generate a strict technical assessment report for this candidate:
348
+
349
+ CANDIDATE PROFILE:
350
+ - Role: {candidate_info['desired_position']}
351
+ - Experience: {candidate_info['years_of_experience']} years
352
+ - Tech Stack: {', '.join(candidate_info['tech_stack'])}
353
+ - Score: {score}/{total_possible_score} ({score_percentage:.1f}%)
354
+
355
+ TECHNICAL ASSESSMENT:
356
+ Questions and Answers:
357
+ {' '.join([f'Q{i+1}: {q["question"]} | A: {a}' for i, (q, a) in enumerate(zip(questions, answers))])}
358
+
359
+ Based on the above:
360
+ 1. Evaluate technical proficiency considering years of experience
361
+ 2. Analyze answer quality and depth
362
+ 3. Assess role fit
363
+ 4. Provide clear HIRE/NO HIRE recommendation
364
+ 5. List key strengths and areas for improvement
365
+
366
+ Format the report professionally with clear sections and bullet points.
367
+ Be strict and objective in evaluation.
368
+ """
369
+
370
+ report = generate_response(prompt)
371
+ return report
372
+
373
+ if st.session_state.submitted and not st.session_state.conversation_ended:
374
+ st.write("### Step 4: Professional Discussion")
375
+ st.markdown("""
376
+ Let's discuss your technical expertise and career fit at TalentScout. You can:
377
+ - 🎯 Get feedback on your technical assessment
378
+ - 💼 Learn about role requirements and expectations
379
+ - 📈 Explore growth opportunities in your tech stack
380
+ - 🔍 Understand next steps in the hiring process
381
+ """)
382
+
383
+ # Initialize chat history
384
+ if "chat_history" not in st.session_state:
385
+ st.session_state.chat_history = []
386
+
387
+ # Display chat history
388
+ for message in st.session_state.chat_history:
389
+ with st.chat_message(message["role"]):
390
+ st.write(message["content"])
391
+
392
+ # Chat input
393
+ user_message = st.chat_input("Discuss your technical career path...")
394
+
395
+ if user_message:
396
+ with st.chat_message("user"):
397
+ st.write(user_message)
398
+
399
+ # Enhanced context-aware prompt
400
+ chat_prompt = f"""
401
+ You are TalentScout's Technical Hiring Assistant. Focus on:
402
+
403
+ Context:
404
+ - Role: {st.session_state.candidate_info['desired_position']}
405
+ - Technologies: {', '.join(st.session_state.candidate_info['tech_stack'])}
406
+ - Technical Assessment Score: {score}/{len(st.session_state.technical_questions) * 2}
407
+ - Experience Level: {st.session_state.candidate_info['years_of_experience']} years
408
+
409
+ Current Question: {user_message}
410
+
411
+ Provide responses that:
412
+ 1. Stay focused on technical recruitment and assessment
413
+ 2. Give specific feedback on technical skills
414
+ 3. Explain role-specific requirements
415
+ 4. Suggest concrete improvement paths in their tech stack
416
+ 5. Maintain professional recruitment context
417
+ 6. Include next steps in the hiring process when relevant
418
+
419
+ Keep responses concise, technical, and recruitment-focused.
420
+ """
421
+
422
+ response = generate_response(chat_prompt)
423
+
424
+ with st.chat_message("assistant"):
425
+ st.write(response)
426
+
427
+ st.session_state.chat_history.append({"role": "user", "content": user_message})
428
+ st.session_state.chat_history.append({"role": "assistant", "content": response})
429
+
430
+ # Professional exit options
431
+ st.write("---")
432
+ col1, col2 = st.columns(2)
433
+ with col1:
434
+ # Modify the Complete Interview Process button section:
435
+ if st.button("Complete Interview Process"):
436
+ st.session_state.conversation_ended = True
437
+
438
+ # Generate and display report
439
+ st.write("### Technical Assessment Report")
440
+ report = generate_candidate_report(
441
+ st.session_state.candidate_info,
442
+ st.session_state.technical_questions,
443
+ st.session_state.answers,
444
+ score,
445
+ st.session_state.chat_history
446
+ )
447
+
448
+ # Display report in a structured format
449
+ st.markdown(report)
450
+
451
+ # Save report to CSV along with other data
452
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
453
+ report_data = {
454
+ 'Timestamp': timestamp,
455
+ 'Candidate_Name': st.session_state.candidate_info['full_name'],
456
+ 'Position': st.session_state.candidate_info['desired_position'],
457
+ 'Experience': st.session_state.candidate_info['years_of_experience'],
458
+ 'Tech_Stack': ', '.join(st.session_state.candidate_info['tech_stack']),
459
+ 'Score': score,
460
+ 'Technical_Report': report
461
+ }
462
+
463
+ # Save to CSV
464
+ report_filename = "technical_assessment_reports.csv"
465
+ report_exists = os.path.isfile(report_filename)
466
+
467
+ with open(report_filename, mode='a', newline='', encoding='utf-8') as file:
468
+ writer = csv.DictWriter(file, fieldnames=list(report_data.keys()))
469
+ if not report_exists:
470
+ writer.writeheader()
471
+ writer.writerow(report_data)
472
+
473
+ st.write("---")
474
+ st.write("Thank you for completing the technical screening process. Our recruitment team will review your profile and contact you soon.")
475
+ with col2:
476
+ if st.button("Start New Application"):
477
+ st.session_state.clear()
478
+ st.rerun()
479
+
480
+
481
+ # Fallback mechanism for unexpected inputs
482
+ if st.session_state.info_collected and not st.session_state.conversation_ended:
483
+ if st.session_state.messages and st.session_state.messages[-1]["role"] == "user":
484
+ last_user_message = st.session_state.messages[-1]["content"]
485
+ if "unable to generate" in st.session_state.messages[-1]["content"].lower():
486
+ bot_response = fallback_response()
487
+ st.session_state.messages.append({"role": "assistant", "content": bot_response})
488
+ with st.chat_message("assistant"):
489
+ st.markdown(bot_response)
490
+
491
+ # Display collected candidate information (for debugging purposes)
492
+ st.sidebar.title("Collected Candidate Information")
493
+ st.sidebar.json(st.session_state.candidate_info)