Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -4,7 +4,6 @@ import streamlit as st
|
|
4 |
import docx
|
5 |
import docx2txt
|
6 |
import tempfile
|
7 |
-
import numpy as np
|
8 |
import time
|
9 |
import re
|
10 |
import concurrent.futures
|
@@ -36,8 +35,14 @@ def load_models():
|
|
36 |
# Load smaller summarization model for speed
|
37 |
models['summarizer'] = pipeline("summarization", model="facebook/bart-large-cnn", max_length=130)
|
38 |
|
39 |
-
# Load
|
40 |
-
models['evaluator'] = pipeline(
|
|
|
|
|
|
|
|
|
|
|
|
|
41 |
|
42 |
return models
|
43 |
|
@@ -296,64 +301,86 @@ def summarize_resume_text(resume_text):
|
|
296 |
return formatted_summary, execution_time
|
297 |
|
298 |
#####################################
|
299 |
-
# Function: Evaluate
|
300 |
#####################################
|
301 |
@st.cache_data(show_spinner=False)
|
302 |
-
def
|
303 |
"""
|
304 |
-
Use
|
305 |
-
based on their resume summary and the company requirements.
|
306 |
"""
|
307 |
start_time = time.time()
|
308 |
|
309 |
evaluator = _evaluator or models['evaluator']
|
310 |
|
311 |
-
#
|
312 |
-
prompt = f"""
|
313 |
-
|
314 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
315 |
|
316 |
Candidate Profile:
|
317 |
{candidate_summary}
|
318 |
|
319 |
-
|
320 |
-
{
|
|
|
321 |
|
322 |
-
|
323 |
-
Score: [0-100]
|
324 |
-
Evaluation: [Your brief evaluation]
|
325 |
"""
|
326 |
|
327 |
-
# Generate the
|
328 |
-
|
329 |
|
330 |
-
# Extract the
|
331 |
-
|
332 |
-
|
333 |
-
score = int(score_match.group(1))
|
334 |
-
# Normalize to 0-1 range
|
335 |
-
normalized_score = score / 100
|
336 |
-
else:
|
337 |
-
# Default score if extraction fails
|
338 |
-
normalized_score = 0.5
|
339 |
|
340 |
-
#
|
341 |
-
|
342 |
-
|
343 |
-
|
344 |
-
|
345 |
-
|
346 |
-
|
347 |
-
|
348 |
-
|
349 |
-
|
|
|
|
|
|
|
|
|
350 |
break
|
351 |
else:
|
352 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
353 |
|
354 |
execution_time = time.time() - start_time
|
355 |
|
356 |
-
return normalized_score,
|
357 |
|
358 |
#####################################
|
359 |
# Main Streamlit Interface - with Progress Reporting
|
@@ -364,7 +391,7 @@ st.markdown(
|
|
364 |
Upload your resume file in **.docx**, **.doc**, or **.txt** format. The app performs the following tasks:
|
365 |
1. Extracts text from the resume.
|
366 |
2. Uses AI to generate a structured candidate summary with name, age, expected job industry, previous work experience, and skills.
|
367 |
-
3. Uses
|
368 |
"""
|
369 |
)
|
370 |
|
@@ -402,9 +429,9 @@ if uploaded_file is not None and company_prompt and st.button("Analyze Resume"):
|
|
402 |
st.markdown(summary)
|
403 |
st.info(f"Summary generated in {summarization_time:.2f} seconds")
|
404 |
|
405 |
-
# Step 3: Evaluate
|
406 |
-
status_text.text("Step 3/3: Evaluating candidate
|
407 |
-
suitability_score, evaluation, evaluation_time =
|
408 |
summary, company_prompt, _evaluator=models['evaluator']
|
409 |
)
|
410 |
progress_bar.progress(100)
|
@@ -414,16 +441,20 @@ if uploaded_file is not None and company_prompt and st.button("Analyze Resume"):
|
|
414 |
|
415 |
# Display suitability results
|
416 |
st.subheader("Suitability Assessment")
|
417 |
-
st.markdown(f"**Matching Score:** {suitability_score:.0%}")
|
418 |
|
419 |
-
# Display
|
|
|
420 |
if suitability_score >= 0.85:
|
421 |
-
st.success(f"**
|
422 |
elif suitability_score >= 0.70:
|
423 |
-
st.success(f"**
|
424 |
elif suitability_score >= 0.50:
|
425 |
-
st.warning(f"**
|
426 |
else:
|
427 |
-
st.error(f"**
|
|
|
|
|
|
|
|
|
428 |
|
429 |
-
st.info(f"Evaluation completed in {evaluation_time:.2f} seconds")
|
|
|
4 |
import docx
|
5 |
import docx2txt
|
6 |
import tempfile
|
|
|
7 |
import time
|
8 |
import re
|
9 |
import concurrent.futures
|
|
|
35 |
# Load smaller summarization model for speed
|
36 |
models['summarizer'] = pipeline("summarization", model="facebook/bart-large-cnn", max_length=130)
|
37 |
|
38 |
+
# Load TinyLlama model for evaluation
|
39 |
+
models['evaluator'] = pipeline(
|
40 |
+
"text-generation",
|
41 |
+
model="TinyLlama/TinyLlama-1.1B-Chat-v1.0",
|
42 |
+
max_new_tokens=200,
|
43 |
+
do_sample=True,
|
44 |
+
temperature=0.7
|
45 |
+
)
|
46 |
|
47 |
return models
|
48 |
|
|
|
301 |
return formatted_summary, execution_time
|
302 |
|
303 |
#####################################
|
304 |
+
# Function: Evaluate with TinyLlama
|
305 |
#####################################
|
306 |
@st.cache_data(show_spinner=False)
|
307 |
+
def evaluate_with_tiny_llama(candidate_summary, company_info, _evaluator=None):
|
308 |
"""
|
309 |
+
Use TinyLlama to evaluate the match between a candidate's resume and company requirements.
|
|
|
310 |
"""
|
311 |
start_time = time.time()
|
312 |
|
313 |
evaluator = _evaluator or models['evaluator']
|
314 |
|
315 |
+
# Format the chat prompt for TinyLlama's chat format
|
316 |
+
prompt = f"""<|im_start|>system
|
317 |
+
You are an expert HR recruiter. Your task is to evaluate how well a candidate's profile matches with a company's requirements. Be concise but thorough in your evaluation.
|
318 |
+
<|im_end|>
|
319 |
+
|
320 |
+
<|im_start|>user
|
321 |
+
I need to evaluate a job candidate against company requirements. Please:
|
322 |
+
1. Analyze the match between the candidate and the position
|
323 |
+
2. Give a suitability score from 0-100
|
324 |
+
3. Provide 2-3 sentences explaining your evaluation
|
325 |
+
4. List the top 3 strengths of the candidate for this role
|
326 |
+
5. List 1-2 potential gaps if any
|
327 |
|
328 |
Candidate Profile:
|
329 |
{candidate_summary}
|
330 |
|
331 |
+
Company Requirements:
|
332 |
+
{company_info}
|
333 |
+
<|im_end|>
|
334 |
|
335 |
+
<|im_start|>assistant
|
|
|
|
|
336 |
"""
|
337 |
|
338 |
+
# Generate the response
|
339 |
+
response = evaluator(prompt)[0]['generated_text']
|
340 |
|
341 |
+
# Extract just the assistant's response after the prompt
|
342 |
+
assistant_response_start = response.find("<|im_start|>assistant") + len("<|im_start|>assistant")
|
343 |
+
assistant_response = response[assistant_response_start:].strip()
|
|
|
|
|
|
|
|
|
|
|
|
|
344 |
|
345 |
+
# Remove any trailing tag if present
|
346 |
+
if "<|im_end|>" in assistant_response:
|
347 |
+
assistant_response = assistant_response.split("<|im_end|>")[0].strip()
|
348 |
+
|
349 |
+
# Try to extract the score from the response
|
350 |
+
score_match = re.search(r'(\d{1,3})/100|score:?\s*(\d{1,3})|rating:?\s*(\d{1,3})|suitability:?\s*(\d{1,3})',
|
351 |
+
assistant_response.lower())
|
352 |
+
|
353 |
+
if score_match:
|
354 |
+
# Find the first group that matched and isn't None
|
355 |
+
for group in score_match.groups():
|
356 |
+
if group is not None:
|
357 |
+
score = int(group)
|
358 |
+
normalized_score = min(100, max(0, score)) / 100 # Ensure it's in 0-1 range
|
359 |
break
|
360 |
else:
|
361 |
+
normalized_score = 0.5 # Default if no group was extracted
|
362 |
+
else:
|
363 |
+
# If no explicit score, try to infer from sentiments
|
364 |
+
positive_words = ['excellent', 'perfect', 'outstanding', 'ideal', 'great']
|
365 |
+
negative_words = ['poor', 'inadequate', 'insufficient', 'lacks', 'mismatch']
|
366 |
+
|
367 |
+
positive_count = sum(assistant_response.lower().count(word) for word in positive_words)
|
368 |
+
negative_count = sum(assistant_response.lower().count(word) for word in negative_words)
|
369 |
+
|
370 |
+
if positive_count > negative_count * 2:
|
371 |
+
normalized_score = 0.85
|
372 |
+
elif positive_count > negative_count:
|
373 |
+
normalized_score = 0.7
|
374 |
+
elif negative_count > positive_count * 2:
|
375 |
+
normalized_score = 0.3
|
376 |
+
elif negative_count > positive_count:
|
377 |
+
normalized_score = 0.4
|
378 |
+
else:
|
379 |
+
normalized_score = 0.5
|
380 |
|
381 |
execution_time = time.time() - start_time
|
382 |
|
383 |
+
return normalized_score, assistant_response, execution_time
|
384 |
|
385 |
#####################################
|
386 |
# Main Streamlit Interface - with Progress Reporting
|
|
|
391 |
Upload your resume file in **.docx**, **.doc**, or **.txt** format. The app performs the following tasks:
|
392 |
1. Extracts text from the resume.
|
393 |
2. Uses AI to generate a structured candidate summary with name, age, expected job industry, previous work experience, and skills.
|
394 |
+
3. Uses TinyLlama AI to evaluate the candidate's suitability for the company and provide detailed feedback.
|
395 |
"""
|
396 |
)
|
397 |
|
|
|
429 |
st.markdown(summary)
|
430 |
st.info(f"Summary generated in {summarization_time:.2f} seconds")
|
431 |
|
432 |
+
# Step 3: Evaluate with TinyLlama
|
433 |
+
status_text.text("Step 3/3: Evaluating candidate with TinyLlama...")
|
434 |
+
suitability_score, evaluation, evaluation_time = evaluate_with_tiny_llama(
|
435 |
summary, company_prompt, _evaluator=models['evaluator']
|
436 |
)
|
437 |
progress_bar.progress(100)
|
|
|
441 |
|
442 |
# Display suitability results
|
443 |
st.subheader("Suitability Assessment")
|
|
|
444 |
|
445 |
+
# Display score with appropriate color
|
446 |
+
score_percent = int(suitability_score * 100)
|
447 |
if suitability_score >= 0.85:
|
448 |
+
st.success(f"**Matching Score:** {score_percent}%")
|
449 |
elif suitability_score >= 0.70:
|
450 |
+
st.success(f"**Matching Score:** {score_percent}%")
|
451 |
elif suitability_score >= 0.50:
|
452 |
+
st.warning(f"**Matching Score:** {score_percent}%")
|
453 |
else:
|
454 |
+
st.error(f"**Matching Score:** {score_percent}%")
|
455 |
+
|
456 |
+
# Display the full evaluation
|
457 |
+
st.markdown("### Detailed Evaluation")
|
458 |
+
st.markdown(evaluation)
|
459 |
|
460 |
+
st.info(f"Evaluation completed in {evaluation_time:.2f} seconds using TinyLlama")
|