QuentinL52's picture
Update src/agents/cv_agents.py
159ea4c verified
import json
import logging
from typing import Dict, Any, List
from crewai import Agent, Task, Crew, Process
logger = logging.getLogger(__name__)
class CVAgentOrchestrator:
def __init__(self, llm):
self.llm = llm
self._create_agents()
def _create_agents(self):
self.section_splitter = Agent(
role="Analyseur de Structure de CV",
goal="Découper intelligemment un CV en sections thématiques",
backstory="Expert en analyse documentaire spécialisé dans la reconnaissance de structures de CV.",
verbose=False,
llm=self.llm
)
self.contact_extractor = Agent(
role="Extracteur d'informations de contact",
goal="Extraire les coordonnées du candidat",
backstory="Expert en extraction d'informations de contact avec précision.",
verbose=False,
llm=self.llm
)
self.skills_extractor = Agent(
role="Extracteur de compétences",
goal="Identifier hard skills et soft skills",
backstory="Spécialiste en identification de compétences techniques et comportementales.",
verbose=False,
llm=self.llm
)
self.experience_extractor = Agent(
role="Extracteur d'expériences",
goal="Extraire les expériences professionnelles",
backstory="Expert en analyse de parcours professionnels.",
verbose=False,
llm=self.llm
)
self.project_extractor = Agent(
role="Extracteur de projets",
goal="Identifier projets professionnels et personnels",
backstory="Spécialiste en identification de projets significatifs.",
verbose=False,
llm=self.llm
)
self.education_extractor = Agent(
role="Extracteur de formations",
goal="Extraire formations et diplômes",
backstory="Expert en analyse de parcours académiques.",
verbose=False,
llm=self.llm
)
self.reconversion_detector = Agent(
role="Détecteur de reconversion",
goal="Analyser les changements de carrière",
backstory="Conseiller d'orientation expert en transitions de carrière.",
verbose=False,
llm=self.llm
)
self.profile_builder = Agent(
role="Constructeur de profil",
goal="Assembler le profil candidat final",
backstory="Expert en structuration de données JSON.",
verbose=False,
llm=self.llm
)
def split_cv_sections(self, cv_content: str) -> Dict[str, str]:
task = Task(
description=f"Analyser ce CV et l'organiser en sections: {cv_content}",
expected_output="""JSON avec sections: contact, experiences, projects, education, skills, other""",
agent=self.section_splitter
)
crew = Crew(
agents=[self.section_splitter],
tasks=[task],
process=Process.sequential,
verbose=False,
telemetry=False
)
result = crew.kickoff()
return self._parse_sections_result(result)
def extract_all_sections(self, sections: Dict[str, str]) -> Dict[str, Any]:
# Créer les tâches avec les sections en input
tasks = self._create_extraction_tasks(sections)
crew = Crew(
agents=[
self.contact_extractor,
self.skills_extractor,
self.experience_extractor,
self.project_extractor,
self.education_extractor,
self.reconversion_detector,
self.profile_builder
],
tasks=tasks,
process=Process.sequential,
verbose=True, # Activer pour debug
telemetry=False
)
# Passer les sections comme inputs
inputs = {
"contact": sections.get("contact", ""),
"experiences": sections.get("experiences", ""),
"projects": sections.get("projects", ""),
"education": sections.get("education", ""),
"skills": sections.get("skills", ""),
"other": sections.get("other", "")
}
logger.info(f"Starting crew with inputs: {list(inputs.keys())}")
result = crew.kickoff(inputs=inputs)
logger.info(f"Crew completed. Raw result: {result.raw if hasattr(result, 'raw') else str(result)[:200]}...")
return self._parse_final_result(result)
def _create_extraction_tasks(self, sections: Dict[str, str]) -> List[Task]:
contact_task = Task(
description=(
"Voici la section contact du CV : {contact}\n"
"Extraire précisément le nom, email, téléphone et localisation du candidat."
),
expected_output='{"nom": "...", "email": "...", "numero_de_telephone": "...", "localisation": "..."}',
agent=self.contact_extractor
)
skills_task = Task(
description=(
"Voici les sections pertinentes du CV :\n"
"Expériences: {experiences}\n"
"Projets: {projects}\n"
"Compétences: {skills}\n"
"Extraire toutes les compétences techniques (hard skills) et comportementales (soft skills) mentionnées."
),
expected_output='{"hard_skills": ["compétence1", "compétence2"], "soft_skills": ["compétence1", "compétence2"]}',
agent=self.skills_extractor
)
experience_task = Task(
description=(
"Voici la section expériences du CV : {experiences}\n"
"Extraire toutes les expériences professionnelles avec poste, entreprise, dates et responsabilités."
),
expected_output='[{"Poste": "titre", "Entreprise": "nom", "start_date": "date", "end_date": "date", "responsabilités": ["resp1", "resp2"]}]',
agent=self.experience_extractor
)
project_task = Task(
description=(
"Voici les sections projets et expériences du CV :\n"
"Projets: {projects}\n"
"Identifier et extraire les projets professionnels et personnels distincts des responsabilités générales."
),
expected_output='{"professional": [{"title": "titre", "technologies": ["tech1"], "outcomes": ["résultat1"]}], "personal": []}',
agent=self.project_extractor
)
education_task = Task(
description=(
"Voici la section formations du CV : {education}\n"
"Extraire toutes les formations, diplômes et certifications avec institution et dates."
),
expected_output='[{"degree": "diplôme", "institution": "établissement", "start_date": "date", "end_date": "date"}]',
agent=self.education_extractor
)
reconversion_task = Task(
description=(
"En analysant les expériences extraites précédemment, déterminer si le candidat est en reconversion professionnelle. "
"Chercher des changements de secteur, de type de poste ou des transitions significatives."
),
expected_output='{"reconversion_analysis": {"is_reconversion": true, "analysis": "Explication détaillée..."}}',
agent=self.reconversion_detector,
context=[experience_task]
)
profile_task = Task(
description=(
"Assembler toutes les informations extraites des tâches précédentes en un profil candidat complet. "
"Créer un JSON valide avec une clé 'candidat' contenant toutes les sections."
),
expected_output=(
'{"candidat": {'
'"informations_personnelles": {...}, '
'"compétences": {...}, '
'"expériences": [...], '
'"projets": {...}, '
'"formations": [...], '
'"reconversion": {...}'
'}}'
),
agent=self.profile_builder,
context=[contact_task, skills_task, experience_task, project_task, education_task, reconversion_task]
)
return [contact_task, skills_task, experience_task, project_task, education_task, reconversion_task, profile_task]
def _parse_sections_result(self, result) -> Dict[str, str]:
result_str = result.raw if hasattr(result, 'raw') else str(result)
if '```json' in result_str:
result_str = result_str.split('```json')[1].split('```')[0].strip()
elif '```' in result_str:
parts = result_str.split('```')
if len(parts) >= 3:
result_str = parts[1].strip()
parsed = json.loads(result_str)
# Assurer que toutes les sections nécessaires existent
default_sections = {
"contact": "",
"experiences": "",
"projects": "",
"education": "",
"skills": "",
"other": ""
}
for key in default_sections:
if key not in parsed:
parsed[key] = default_sections[key]
return parsed
def _parse_final_result(self, result) -> Dict[str, Any]:
result_str = result.raw if hasattr(result, 'raw') else str(result)
if '```json' in result_str:
result_str = result_str.split('```json')[1].split('```')[0].strip()
elif '```' in result_str:
parts = result_str.split('```')
if len(parts) >= 3:
result_str = parts[1].strip()
return json.loads(result_str)