File size: 10,758 Bytes
6c9a9ea e403cba 833f869 e403cba 833f869 6c9a9ea e403cba 6c9a9ea e403cba 833f869 e403cba 6c9a9ea e403cba 6c9a9ea e403cba 6c9a9ea e403cba 6c9a9ea e403cba 833f869 6c9a9ea e403cba 6c9a9ea e403cba 6c9a9ea e403cba 6c9a9ea e403cba 6c9a9ea e403cba 6c9a9ea e403cba 6c9a9ea e403cba 6c9a9ea 833f869 6c9a9ea e403cba 6c9a9ea 833f869 6c9a9ea e403cba 6c9a9ea e403cba 6c9a9ea e403cba 6c9a9ea e403cba 6c9a9ea e403cba 6c9a9ea e403cba 2a885ce 6c9a9ea |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 |
"""
Module pour le parsing de CV avec CrewAI
"""
import os
import json
import logging
logger = logging.getLogger(__name__)
# Gestion des imports avec fallback
try:
from src.crew.crew_pool import analyse_cv
CREW_POOL_AVAILABLE = True
logger.info("✅ crew_pool importé avec succès")
except ImportError as e:
logger.error(f"❌ Erreur import crew_pool: {e}")
CREW_POOL_AVAILABLE = False
analyse_cv = None
try:
from src.config import load_pdf
CONFIG_AVAILABLE = True
logger.info("✅ config importé avec succès")
except ImportError as e:
logger.error(f"❌ Erreur import config: {e}")
CONFIG_AVAILABLE = False
load_pdf = None
def clean_dict_keys(data):
"""
Nettoie les clés d'un dictionnaire en les convertissant en string.
Args:
data: Données à nettoyer (dict, list, ou autre)
Returns:
Données nettoyées avec des clés string
"""
if isinstance(data, dict):
return {str(key): clean_dict_keys(value) for key, value in data.items()}
elif isinstance(data, list):
return [clean_dict_keys(element) for element in data]
else:
return data
class CvParserAgent:
"""
Agent de parsing de CV utilisant CrewAI.
Cette classe traite un fichier PDF de CV et en extrait les informations
structurées (compétences, expériences, formations, etc.)
"""
def __init__(self, pdf_path: str):
"""
Initialise l'agent de parsing de CV.
Args:
pdf_path (str): Chemin vers le fichier PDF à traiter
Raises:
ValueError: Si le chemin du fichier est invalide
ImportError: Si les dépendances nécessaires ne sont pas disponibles
"""
if not pdf_path or not isinstance(pdf_path, str):
raise ValueError("Le chemin du fichier PDF doit être une chaîne non vide")
self.pdf_path = pdf_path
# Vérifier que les dépendances sont disponibles
if not CREW_POOL_AVAILABLE:
logger.warning("CrewAI crew_pool non disponible - mode dégradé")
if not CONFIG_AVAILABLE:
logger.warning("Module config non disponible - mode dégradé")
def process(self) -> dict:
"""
Traite le fichier PDF pour en extraire le contenu sous forme de JSON.
Returns:
dict: Dictionnaire contenant les données extraites du CV,
ou données de fallback en cas d'erreur
"""
logger.info(f"Début du traitement du CV : {self.pdf_path}")
# Vérifier que le fichier existe
if not os.path.exists(self.pdf_path):
logger.error(f"Fichier PDF non trouvé: {self.pdf_path}")
return self._create_fallback_data()
# Vérifier les dépendances
if not CREW_POOL_AVAILABLE or not CONFIG_AVAILABLE:
logger.error("Dépendances manquantes pour le traitement complet")
return self._create_fallback_data()
try:
# Charger le contenu du PDF
cv_text_content = load_pdf(self.pdf_path)
if not cv_text_content or not cv_text_content.strip():
logger.error("Le PDF semble vide ou illisible")
return self._create_fallback_data()
logger.info(f"PDF chargé, {len(cv_text_content)} caractères extraits")
# Analyser avec CrewAI
crew_output = analyse_cv(cv_text_content)
if not crew_output or not hasattr(crew_output, 'raw') or not crew_output.raw.strip():
logger.error("L'analyse par le crew n'a pas retourné de résultat.")
return self._create_fallback_data()
raw_string = crew_output.raw
logger.info(f"Résultat brut du crew: {raw_string[:200]}...")
# Nettoyer le JSON si nécessaire
json_string_cleaned = self._clean_json_string(raw_string)
# Parser le JSON
profile_data = json.loads(json_string_cleaned)
logger.info("Parsing JSON réussi")
return clean_dict_keys(profile_data)
except json.JSONDecodeError as e:
logger.error(f"Erreur de décodage JSON : {e}")
if 'crew_output' in locals():
logger.error(f"Données brutes reçues : {crew_output.raw}")
return self._create_fallback_data()
except Exception as e:
logger.error(f"Erreur inattendue dans CvParserAgent : {e}", exc_info=True)
return self._create_fallback_data()
def _clean_json_string(self, raw_string: str) -> str:
"""
Nettoie une chaîne JSON brute en supprimant les blocs de code markdown.
Args:
raw_string (str): Chaîne brute à nettoyer
Returns:
str: Chaîne JSON nettoyée
"""
json_string_cleaned = raw_string.strip()
# Supprimer les blocs de code markdown si présents
if '```' in raw_string:
try:
# Chercher le bloc json
if '```json' in raw_string:
json_part = raw_string.split('```json')[1].split('```')[0]
json_string_cleaned = json_part.strip()
else:
# Prendre le premier bloc de code
parts = raw_string.split('```')
if len(parts) >= 3:
json_string_cleaned = parts[1].strip()
except IndexError:
logger.warning("Format de code block détecté mais mal formé")
return json_string_cleaned
def _create_fallback_data(self) -> dict:
"""
Crée des données de CV de fallback en cas d'erreur de traitement.
Returns:
dict: Structure de données de CV par défaut
"""
logger.info("Création de données de fallback pour le CV")
return {
"candidat": {
"informations_personnelles": {
"nom": "Candidat Test",
"email": "[email protected]",
"numero_de_telephone": "Non spécifié",
"localisation": "Non spécifiée"
},
"compétences": {
"hard_skills": ["Python", "FastAPI", "Data Analysis"],
"soft_skills": ["Communication", "Travail d'équipe", "Adaptabilité"]
},
"expériences": [
{
"Poste": "Développeur",
"Entreprise": "Entreprise Test",
"start_date": "2022",
"end_date": "Aujourd'hui",
"responsabilités": ["Développement d'applications", "Maintenance du code"]
}
],
"projets": {
"professional": [
{
"title": "Projet Test",
"role": "Développeur principal",
"technologies": ["Python", "FastAPI"],
"outcomes": ["Application fonctionnelle"]
}
],
"personal": []
},
"formations": [
{
"degree": "Formation en Informatique",
"institution": "École Test",
"start_date": "2020",
"end_date": "2022"
}
],
"reconversion": {
"is_reconversion": False,
"analysis": "Pas de reconversion détectée - données de test"
}
}
}
# Fonction utilitaire pour créer des données de fallback
def create_fallback_cv_data(pdf_path: str = None) -> dict:
"""
Fonction utilitaire pour créer des données de CV de fallback.
Args:
pdf_path (str, optional): Chemin du fichier PDF (non utilisé dans le fallback)
Returns:
dict: Structure de données de CV par défaut
"""
return {
"candidat": {
"informations_personnelles": {
"nom": "Candidat Test",
"email": "[email protected]",
"numero_de_telephone": "Non spécifié",
"localisation": "Non spécifiée"
},
"compétences": {
"hard_skills": ["Python", "FastAPI", "Data Analysis"],
"soft_skills": ["Communication", "Travail d'équipe", "Adaptabilité"]
},
"expériences": [
{
"Poste": "Développeur",
"Entreprise": "Entreprise Test",
"start_date": "2022",
"end_date": "Aujourd'hui",
"responsabilités": ["Développement d'applications", "Maintenance du code"]
}
],
"projets": {
"professional": [
{
"title": "Projet Test",
"role": "Développeur principal",
"technologies": ["Python", "FastAPI"],
"outcomes": ["Application fonctionnelle"]
}
],
"personal": []
},
"formations": [
{
"degree": "Formation en Informatique",
"institution": "École Test",
"start_date": "2020",
"end_date": "2022"
}
],
"reconversion": {
"is_reconversion": False,
"analysis": "Pas de reconversion détectée - données de test"
}
}
}
# Test des imports au chargement du module
if __name__ == "__main__":
logger.info("Test du module cv_parsing_agents")
logger.info(f"CREW_POOL_AVAILABLE: {CREW_POOL_AVAILABLE}")
logger.info(f"CONFIG_AVAILABLE: {CONFIG_AVAILABLE}")
# Test de création d'une instance
try:
agent = CvParserAgent("/tmp/test.pdf")
logger.info("✅ CvParserAgent créé avec succès")
except Exception as e:
logger.error(f"❌ Erreur création CvParserAgent: {e}")
# Test des données de fallback
fallback_data = create_fallback_cv_data()
logger.info(f"✅ Données de fallback créées: {len(fallback_data)} clés") |