QuentinL52 commited on
Commit
e403cba
·
verified ·
1 Parent(s): 28ace03

Update src/cv_parsing_agents.py

Browse files
Files changed (1) hide show
  1. src/cv_parsing_agents.py +107 -51
src/cv_parsing_agents.py CHANGED
@@ -1,58 +1,114 @@
1
- import torch
2
- from transformers import pipeline
3
- from sentence_transformers import SentenceTransformer, util
4
 
5
- class MultiModelInterviewAnalyzer:
6
- def __init__(self):
7
- self.sentiment_analyzer = pipeline(
8
- "text-classification",
9
- model="astrosbd/french_emotion_camembert",
10
- return_all_scores=True,
11
- device=0 if torch.cuda.is_available() else -1,
12
- )
13
- self.similarity_model = SentenceTransformer('all-MiniLM-L6-v2')
14
- self.intent_classifier = pipeline(
15
- "zero-shot-classification",
16
- model="joeddav/xlm-roberta-large-xnli"
17
- #device=0 if torch.cuda.is_available() else -1,
18
- )
19
 
20
- def analyze_sentiment(self, messages):
21
- user_messages = [msg['content'] for msg in messages if msg['role'] == 'user']
22
- if not user_messages:
23
- return []
24
- sentiments = self.sentiment_analyzer(user_messages)
25
- return sentiments
 
26
 
27
- def compute_semantic_similarity(self, messages, job_requirements):
28
- user_answers = " ".join([msg['content'] for msg in messages if msg['role'] == 'user'])
29
- embedding_answers = self.similarity_model.encode(user_answers, convert_to_tensor=True)
30
- embedding_requirements = self.similarity_model.encode(job_requirements, convert_to_tensor=True)
31
- cosine_score = util.cos_sim(embedding_answers, embedding_requirements)
 
 
 
 
32
 
33
- return cosine_score.max().item()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
- def classify_candidate_intent(self, messages):
36
- user_answers = [msg['content'] for msg in messages if msg['role'] == 'user']
37
- if not user_answers:
38
- return []
39
- candidate_labels = [
40
- "parle de son expérience technique",
41
- "exprime sa motivation",
42
- "pose une question",
43
- "exprime de l’incertitude ou du stress"
44
- ]
45
- classifications = self.intent_classifier(user_answers, candidate_labels, multi_label=False)
46
- return classifications
47
 
48
- def run_full_analysis(self, conversation_history, job_requirements):
49
- sentiment_results = self.analyze_sentiment(conversation_history)
50
- similarity_score = self.compute_semantic_similarity(conversation_history, job_requirements)
51
- intent_results = self.classify_candidate_intent(conversation_history)
52
- analysis_output = {
53
- "overall_similarity_score": round(similarity_score, 2),
54
- "sentiment_analysis": sentiment_results,
55
- "intent_analysis": intent_results,
56
- "raw_transcript": conversation_history
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  }
58
- return analysis_output
 
1
+ import os
2
+ import json
3
+ import logging
4
 
5
+ logger = logging.getLogger(__name__)
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
+ def clean_dict_keys(data):
8
+ if isinstance(data, dict):
9
+ return {str(key): clean_dict_keys(value) for key, value in data.items()}
10
+ elif isinstance(data, list):
11
+ return [clean_dict_keys(element) for element in data]
12
+ else:
13
+ return data
14
 
15
+ class CvParserAgent:
16
+ def __init__(self, pdf_path: str):
17
+ self.pdf_path = pdf_path
18
+
19
+ def process(self) -> dict:
20
+ """
21
+ Version sécurisée pour Cloud Run
22
+ """
23
+ logger.info(f"Début du traitement du CV : {self.pdf_path}")
24
 
25
+ try:
26
+ # Import avec gestion d'erreur
27
+ from src.config import load_pdf
28
+ cv_text_content = load_pdf(self.pdf_path)
29
+ logger.info(f"Contenu extrait : {len(cv_text_content)} caractères")
30
+
31
+ # Import sécurisé de crew_pool
32
+ try:
33
+ from src.crew.crew_pool import analyse_cv
34
+ logger.info("Lancement de l'analyse par le crew...")
35
+ crew_output = analyse_cv(cv_text_content)
36
+ except Exception as crew_error:
37
+ logger.error(f"Erreur de permission : {crew_error}")
38
+ # Fallback en cas d'erreur CrewAI
39
+ return self._create_fallback_response(cv_text_content)
40
+
41
+ # Traitement du résultat
42
+ if not crew_output:
43
+ logger.warning("Crew n'a pas retourné de résultat")
44
+ return self._create_fallback_response(cv_text_content)
45
+
46
+ # Si c'est déjà un dictionnaire (cas d'erreur géré)
47
+ if isinstance(crew_output, dict):
48
+ return clean_dict_keys(crew_output)
49
+
50
+ # Si c'est un objet avec .raw
51
+ if hasattr(crew_output, 'raw') and crew_output.raw:
52
+ raw_string = crew_output.raw.strip()
53
+
54
+ # Nettoyage du JSON si nécessaire
55
+ if '```' in raw_string:
56
+ try:
57
+ json_part = raw_string.split('```json')[1].split('```')[0]
58
+ raw_string = json_part.strip()
59
+ except:
60
+ # Si le parsing échoue, utiliser tel quel
61
+ pass
62
+
63
+ try:
64
+ profile_data = json.loads(raw_string)
65
+ return clean_dict_keys(profile_data)
66
+ except json.JSONDecodeError as e:
67
+ logger.error(f"Erreur JSON : {e}")
68
+ logger.error(f"Raw data: {raw_string[:500]}...")
69
+ return self._create_fallback_response(cv_text_content)
70
+
71
+ # Si aucun format reconnu
72
+ logger.warning("Format de sortie crew non reconnu")
73
+ return self._create_fallback_response(cv_text_content)
74
 
75
+ except Exception as e:
76
+ logger.error(f"Erreur critique dans CvParserAgent : {e}", exc_info=True)
77
+ return self._create_fallback_response("Erreur lors de la lecture du CV")
 
 
 
 
 
 
 
 
 
78
 
79
+ def _create_fallback_response(self, cv_content: str) -> dict:
80
+ """Crée une réponse de fallback en cas d'erreur"""
81
+ return {
82
+ "candidat": {
83
+ "informations_personnelles": {
84
+ "nom": "Extraction automatique échouée",
85
+ "email": "Non extrait",
86
+ "numero_de_telephone": "Non extrait",
87
+ "localisation": "Non extrait"
88
+ },
89
+ "compétences": {
90
+ "hard_skills": ["Analyse manuelle requise"],
91
+ "soft_skills": []
92
+ },
93
+ "expériences": [{
94
+ "Poste": "Analyse manuelle requise",
95
+ "Entreprise": "Voir CV original",
96
+ "start_date": "Non spécifié",
97
+ "end_date": "Non spécifié",
98
+ "responsabilités": ["Consulter le CV original"]
99
+ }],
100
+ "projets": {
101
+ "professional": [],
102
+ "personal": []
103
+ },
104
+ "formations": [{
105
+ "degree": "Analyse manuelle requise",
106
+ "institution": "Voir CV original",
107
+ "start_date": "Non spécifié",
108
+ "end_date": "Non spécifié"
109
+ }],
110
+ "raw_content_length": len(cv_content),
111
+ "status": "fallback_mode",
112
+ "message": "L'extraction automatique a échoué. Analyse manuelle recommandée."
113
+ }
114
  }