WebashalarForML's picture
Upload 5 files
71c6bbf verified
raw
history blame
No virus
12.6 kB
# mistral.py
import os
import json
import logging
from huggingface_hub import InferenceClient
from huggingface_hub.utils._errors import BadRequestError
from dotenv import load_dotenv
from utils.fileTotext import extract_text_based_on_format
import re
from utils.spacy import Parser_from_model
# Load environment variables from .env file
load_dotenv()
# Authenticate with Hugging Face
HFT = os.getenv('HF_TOKEN')
if not HFT:
raise ValueError("Hugging Face token is not set in environment variables.")
client = InferenceClient(model="mistralai/Mistral-Nemo-Instruct-2407", token=HFT)
# Function to clean model output
def Data_Cleaner(text):
pattern = r".*?format:"
result = re.split(pattern, text, maxsplit=1)
if len(result) > 1:
text_after_format = result[1].strip().strip('`').strip('json')
else:
text_after_format = text.strip().strip('`').strip('json')
return text_after_format
# Function to call Mistral and process output
def Model_ProfessionalDetails_Output(resume, client):
system_role = {
"role": "system",
"content": "You are a skilled resume parser. Your task is to extract professional details from resumes in a structured JSON format defined by the User. Ensure accuracy and completeness while maintaining the format provided and if field are missing just return 'not found'."
}
user_prompt = {
"role": "user",
"content": f'''Act as a resume parser for the following text given in text: {resume}
Extract the text in the following output JSON string as:
{{
"professional": {{
"skills": "Extract and list all technical skills, non-technical skills, programming languages, frameworks, domains, and technologies based on the resume.",
"soft_skills": "Extract non-technical skills, Communication skills, and soft skills based on the resume."
"projects": "Include only the project names, titles, or headers mentioned in the resume. ",
"projects_experience": ["Include overall project Experiences and about project in short mentioned in the resume.] ",
"experience": "Include the total experience in months or years as mentioned in the resume.",
"companies_worked_at": "Include the names of all companies worked at according to the resume. ",
"certification": "Include any certifications obtained based on the resume. ",
"worked_as": "Include the names of roles worked as according to the resume.",
"qualification":"Extract and list the qualifications based on the resume, (qualifications likes B.Tech). If none are found, return 'No education listed'.",
"course": "Extract the name of the Learning Course completed based on the resume. If not found, return 'No Course listed'.",
"university": "Extract the name of the university or Collage or Intitute attended based on the resume. If not found, return 'No university listed'.",
"year_of_graduation": "Extract the year of graduation from the resume. If not found, return 'No year of graduation listed'."
}}
}}
Json Output:
'''
}
response = ""
for message in client.chat_completion(messages=[system_role, user_prompt], max_tokens=3000, stream=True, temperature=0.35):
response += message.choices[0].delta.content
try:
clean_response = Data_Cleaner(response)
parsed_response = json.loads(clean_response)
except json.JSONDecodeError as e:
logging.error(f"JSON Decode Error: {e}")
return {}
return parsed_response
def Model_PersonalDetails_Output(resume, client):
system_role = {
"role": "system",
"content": "You are a skilled resume parser. Your task is to extract professional details from resumes in a structured JSON format defined by the User. Ensure accuracy and completeness while maintaining the format provided and if field are missing just return 'not found'."
}
user_prompt = {
"role": "user",
"content": f'''Act as a resume parser for the following text given in text: {resume}
Extract the text in the following output JSON string as:
{{
"personal": {{
"name": "Extract the full name based on the resume. If not found, return 'No name listed'.",
"contact_number": "Extract the contact number from the resume. If not found, return 'No contact number listed'.",
"email": "Extract the email address from the resume. If not found, return 'No email listed'.",
"Address": "Extract the Address or address from the resume. If not found, return 'No Address listed'.",
"link": "Extract any relevant links (e.g., portfolio, LinkedIn) from the resume. If not found, return 'No link listed'."
}}
}}
output:
'''
}
# Response
response = ""
for message in client.chat_completion(
messages=[system_role, user_prompt],
max_tokens=3000,
stream=True,
temperature=0.35,
):
response += message.choices[0].delta.content
# Handle cases where the response might have formatting issues
try:
#print('The Og response:-->',response)
clean_response=Data_Cleaner(response)
#print("After data cleaning",clean_response)
parsed_response = json.loads(clean_response)
except json.JSONDecodeError as e:
print("JSON Decode Error:", e)
print("Raw Response:", response)
return {}
return parsed_response
# # Fallback to SpaCy if Mistral fails
def process_resume_data(file_path):
resume_text, hyperlinks = extract_text_based_on_format(file_path)
print("Resume converted to text successfully.")
if not resume_text:
return {"error": "Text extraction failed"}
# Attempt to use Mistral model for parsing
try:
# Extract personal details using Mistral
per_data = Model_PersonalDetails_Output(resume_text, client)
# Extract professional details using Mistral
pro_data = Model_ProfessionalDetails_Output(resume_text, client)
# Check if per_data and pro_data have been populated correctly
if not per_data:
logging.warning("Mistral personal data extraction failed.")
per_data = {}
if not pro_data:
logging.warning("Mistral professional data extraction failed.")
pro_data = {}
# Combine both personal and professional details into a structured output
result = {
"personal": {
"name": per_data.get('personal', {}).get('name', 'Not found'),
"contact": per_data.get('personal', {}).get('contact_number', 'Not found'),
"email": per_data.get('personal', {}).get('email', 'Not found'),
"location": per_data.get('personal', {}).get('Address', 'Not found'),
"link": hyperlinks
},
"professional": {
"skills": pro_data.get('professional', {}).get('skills', 'Not found'),
"soft_skills": pro_data.get('professional', {}).get('soft_skills', 'Not found'),
"experience": [
{
"company": pro_data.get('professional', {}).get('companies_worked_at', 'Not found'),
"projects": pro_data.get('professional', {}).get('projects', 'Not found'),
"role": pro_data.get('professional', {}).get('worked_as', 'Not found'),
"years": pro_data.get('professional', {}).get('experience', 'Not found'),
"project_experience": pro_data.get('professional', {}).get('projects_experience', 'Not found')
}
],
"education": [
{
"qualification": pro_data.get('professional', {}).get('qualification', 'Not found'),
"university": pro_data.get('professional', {}).get('university', 'Not found'),
"course": pro_data.get('professional', {}).get('course', 'Not found'),
"certificate": pro_data.get('professional', {}).get('certification', 'Not found')
}
]
}
}
# If Mistral produces valid output, return it
if per_data or pro_data:
print("------Mistral-----")
return result
else:
raise ValueError("Mistral returned no output")
# Handle HuggingFace API or Mistral model errors
except BadRequestError as e:
logging.error(f"HuggingFace API error: {e}. Falling back to SpaCy.")
print(f"HuggingFace API error: {e}. Falling back to SpaCy.")
except Exception as e:
logging.error(f"An error occurred while processing with Mistral: {e}. Falling back to SpaCy.")
print(f"An error occurred while processing with Mistral: {e}. Falling back to SpaCy.")
# Fallback to SpaCy if Mistral fails
logging.warning("Mistral failed, switching to SpaCy.")
print("------Spacy-----")
return Parser_from_model(file_path)
# /////////////////////////////////////////////
# ////////////////Spacy.py/////////////////////
# /////////////////////////////////////////////
import spacy
from spacy.training import Example
from spacy.util import minibatch, compounding
from pathlib import Path
from spacy.tokens import DocBin
import random
# Load the training data from the .spacy file
def load_data_from_spacy_file(file_path):
# Initialize a blank English model to ensure compatibility
nlp = spacy.blank("en")
# Load the DocBin object and get documents
try:
doc_bin = DocBin().from_disk(file_path)
docs = list(doc_bin.get_docs(nlp.vocab))
return docs
except Exception as e:
print(f"Error loading data from .spacy file: {e}")
return []
# Train model function
def train_model(epochs, model_path):
# Initialize a blank English model
nlp = spacy.blank("en")
# Create an NER component and add it to the pipeline
if "ner" not in nlp.pipe_names:
ner = nlp.add_pipe("ner")
nlp.add_pipe("sentencizer")
# Define all possible entity labels
labels = [
"PERSON", "CONTACT", "EMAIL", "ABOUT", "EXPERIENCE", "YEARS_EXPERIENCE",
"UNIVERSITY", "SOFT_SKILL", "INSTITUTE", "LAST_QUALIFICATION_YEAR", "JOB_TITLE",
"COMPANY", "COURSE", "DOB", "HOBBIES", "LINK", "SCHOOL", "QUALIFICATION",
"LANGUAGE", "LOCATION", "PROJECTS", "SKILL", "CERTIFICATE"
]
# Add labels to the NER component
for label in labels:
ner.add_label(label)
# Load the training data
train_data = load_data_from_spacy_file("./data/Spacy_data.spacy")
# Start the training
optimizer = nlp.begin_training()
epoch_losses = []
best_loss = float('inf')
# Training loop
for epoch in range(epochs):
losses = {}
random.shuffle(train_data) # Shuffle data for better training
# Create minibatches
batches = minibatch(train_data, size=compounding(4.0, 32.0, 1.001))
for batch in batches:
texts, annotations = zip(*[(doc.text, {"entities": [(ent.start_char, ent.end_char, ent.label_) for ent in doc.ents]}) for doc in batch])
# Convert to Example objects
examples = [Example.from_dict(nlp.make_doc(text), annotation) for text, annotation in zip(texts, annotations)]
# Update the model
nlp.update(examples, sgd=optimizer, drop=0.35, losses=losses)
current_loss = losses.get("ner", float('inf'))
epoch_losses.append(current_loss)
print(f"Losses at epoch {epoch + 1}: {losses}")
# Stop training if the loss is zero
if current_loss == 0:
break
# Save the best model
if current_loss < best_loss:
best_loss = current_loss
nlp.to_disk(model_path)
# Save the final model
nlp.to_disk(model_path)
return epoch_losses