QuentinL52 commited on
Commit
722b70a
·
verified ·
1 Parent(s): e075a63

Update src/agents/cv_agents.py

Browse files
Files changed (1) hide show
  1. src/agents/cv_agents.py +251 -250
src/agents/cv_agents.py CHANGED
@@ -1,251 +1,252 @@
1
- import json
2
- import logging
3
- from typing import Dict, Any, List
4
- from crewai import Agent, Task, Crew, Process
5
-
6
- logger = logging.getLogger(__name__)
7
-
8
- class CVAgentOrchestrator:
9
- def __init__(self, llm):
10
- self.llm = llm
11
- self._create_agents()
12
-
13
- def _create_agents(self):
14
- self.section_splitter = Agent(
15
- role="Analyseur de Structure de CV",
16
- goal="Découper intelligemment un CV en sections thématiques",
17
- backstory="Expert en analyse documentaire spécialisé dans la reconnaissance de structures de CV.",
18
- verbose=False,
19
- llm=self.llm
20
- )
21
-
22
- self.contact_extractor = Agent(
23
- role="Extracteur d'informations de contact",
24
- goal="Extraire les coordonnées du candidat",
25
- backstory="Expert en extraction d'informations de contact avec précision.",
26
- verbose=False,
27
- llm=self.llm
28
- )
29
-
30
- self.skills_extractor = Agent(
31
- role="Extracteur de compétences",
32
- goal="Identifier hard skills et soft skills",
33
- backstory="Spécialiste en identification de compétences techniques et comportementales.",
34
- verbose=False,
35
- llm=self.llm
36
- )
37
-
38
- self.experience_extractor = Agent(
39
- role="Extracteur d'expériences",
40
- goal="Extraire les expériences professionnelles",
41
- backstory="Expert en analyse de parcours professionnels.",
42
- verbose=False,
43
- llm=self.llm
44
- )
45
-
46
- self.project_extractor = Agent(
47
- role="Extracteur de projets",
48
- goal="Identifier projets professionnels et personnels",
49
- backstory="Spécialiste en identification de projets significatifs.",
50
- verbose=False,
51
- llm=self.llm
52
- )
53
-
54
- self.education_extractor = Agent(
55
- role="Extracteur de formations",
56
- goal="Extraire formations et diplômes",
57
- backstory="Expert en analyse de parcours académiques.",
58
- verbose=False,
59
- llm=self.llm
60
- )
61
-
62
- self.reconversion_detector = Agent(
63
- role="Détecteur de reconversion",
64
- goal="Analyser les changements de carrière",
65
- backstory="Conseiller d'orientation expert en transitions de carrière.",
66
- verbose=False,
67
- llm=self.llm
68
- )
69
-
70
- self.profile_builder = Agent(
71
- role="Constructeur de profil",
72
- goal="Assembler le profil candidat final",
73
- backstory="Expert en structuration de données JSON.",
74
- verbose=False,
75
- llm=self.llm
76
- )
77
-
78
- def split_cv_sections(self, cv_content: str) -> Dict[str, str]:
79
- task = Task(
80
- description=f"Analyser ce CV et l'organiser en sections: {cv_content}",
81
- expected_output="""JSON avec sections: contact, experiences, projects, education, skills, other""",
82
- agent=self.section_splitter
83
- )
84
-
85
- crew = Crew(
86
- agents=[self.section_splitter],
87
- tasks=[task],
88
- process=Process.sequential,
89
- verbose=False,
90
- telemetry=False
91
- )
92
-
93
- result = crew.kickoff()
94
- return self._parse_sections_result(result)
95
-
96
- def extract_all_sections(self, sections: Dict[str, str]) -> Dict[str, Any]:
97
- # Créer les tâches avec les sections en input
98
- tasks = self._create_extraction_tasks(sections)
99
-
100
- crew = Crew(
101
- agents=[
102
- self.contact_extractor,
103
- self.skills_extractor,
104
- self.experience_extractor,
105
- self.project_extractor,
106
- self.education_extractor,
107
- self.reconversion_detector,
108
- self.profile_builder
109
- ],
110
- tasks=tasks,
111
- process=Process.sequential,
112
- verbose=True, # Activer pour debug
113
- telemetry=False
114
- )
115
-
116
- # Passer les sections comme inputs
117
- inputs = {
118
- "contact": sections.get("contact", ""),
119
- "experiences": sections.get("experiences", ""),
120
- "projects": sections.get("projects", ""),
121
- "education": sections.get("education", ""),
122
- "skills": sections.get("skills", ""),
123
- "other": sections.get("other", "")
124
- }
125
-
126
- logger.info(f"Starting crew with inputs: {list(inputs.keys())}")
127
- result = crew.kickoff(inputs=inputs)
128
- logger.info(f"Crew completed. Raw result: {result.raw if hasattr(result, 'raw') else str(result)[:200]}...")
129
-
130
- return self._parse_final_result(result)
131
-
132
- def _create_extraction_tasks(self, sections: Dict[str, str]) -> List[Task]:
133
- contact_task = Task(
134
- description=(
135
- "Voici la section contact du CV : {contact}\n"
136
- "Extraire précisément le nom, email, téléphone et localisation du candidat."
137
- ),
138
- expected_output='{"nom": "...", "email": "...", "numero_de_telephone": "...", "localisation": "..."}',
139
- agent=self.contact_extractor
140
- )
141
-
142
- skills_task = Task(
143
- description=(
144
- "Voici les sections pertinentes du CV :\n"
145
- "Expériences: {experiences}\n"
146
- "Projets: {projects}\n"
147
- "Compétences: {skills}\n"
148
- "Extraire toutes les compétences techniques (hard skills) et comportementales (soft skills) mentionnées."
149
- ),
150
- expected_output='{"hard_skills": ["compétence1", "compétence2"], "soft_skills": ["compétence1", "compétence2"]}',
151
- agent=self.skills_extractor
152
- )
153
-
154
- experience_task = Task(
155
- description=(
156
- "Voici la section expériences du CV : {experiences}\n"
157
- "Extraire toutes les expériences professionnelles avec poste, entreprise, dates et responsabilités."
158
- ),
159
- expected_output='[{"Poste": "titre", "Entreprise": "nom", "start_date": "date", "end_date": "date", "responsabilités": ["resp1", "resp2"]}]',
160
- agent=self.experience_extractor
161
- )
162
-
163
- project_task = Task(
164
- description=(
165
- "Voici les sections projets et expériences du CV :\n"
166
- "Projets: {projects}\n"
167
- "Identifier et extraire les projets professionnels et personnels distincts des responsabilités générales."
168
- ),
169
- expected_output='{"professional": [{"title": "titre", "technologies": ["tech1"], "outcomes": ["résultat1"]}], "personal": []}',
170
- agent=self.project_extractor
171
- )
172
-
173
- education_task = Task(
174
- description=(
175
- "Voici la section formations du CV : {education}\n"
176
- "Extraire toutes les formations, diplômes et certifications avec institution et dates."
177
- ),
178
- expected_output='[{"degree": "diplôme", "institution": "établissement", "start_date": "date", "end_date": "date"}]',
179
- agent=self.education_extractor
180
- )
181
-
182
- reconversion_task = Task(
183
- description=(
184
- "En analysant les expériences extraites précédemment, déterminer si le candidat est en reconversion professionnelle. "
185
- "Chercher des changements de secteur, de type de poste ou des transitions significatives."
186
- ),
187
- expected_output='{"reconversion_analysis": {"is_reconversion": true, "analysis": "Explication détaillée..."}}',
188
- agent=self.reconversion_detector,
189
- context=[experience_task]
190
- )
191
-
192
- profile_task = Task(
193
- description=(
194
- "Assembler toutes les informations extraites des tâches précédentes en un profil candidat complet. "
195
- "Créer un JSON valide avec une clé 'candidat' contenant toutes les sections."
196
- ),
197
- expected_output=(
198
- '{"candidat": {'
199
- '"informations_personnelles": {...}, '
200
- '"compétences": {...}, '
201
- '"expériences": [...], '
202
- '"projets": {...}, '
203
- '"formations": [...], '
204
- '"reconversion": {...}'
205
- '}}'
206
- ),
207
- agent=self.profile_builder,
208
- context=[contact_task, skills_task, experience_task, project_task, education_task, reconversion_task]
209
- )
210
-
211
- return [contact_task, skills_task, experience_task, project_task, education_task, reconversion_task, profile_task]
212
-
213
- def _parse_sections_result(self, result) -> Dict[str, str]:
214
- result_str = result.raw if hasattr(result, 'raw') else str(result)
215
-
216
- if '```json' in result_str:
217
- result_str = result_str.split('```json')[1].split('```')[0].strip()
218
- elif '```' in result_str:
219
- parts = result_str.split('```')
220
- if len(parts) >= 3:
221
- result_str = parts[1].strip()
222
-
223
- parsed = json.loads(result_str)
224
-
225
- # Assurer que toutes les sections nécessaires existent
226
- default_sections = {
227
- "contact": "",
228
- "experiences": "",
229
- "projects": "",
230
- "education": "",
231
- "skills": "",
232
- "other": ""
233
- }
234
-
235
- for key in default_sections:
236
- if key not in parsed:
237
- parsed[key] = default_sections[key]
238
-
239
- return parsed
240
-
241
- def _parse_final_result(self, result) -> Dict[str, Any]:
242
- result_str = result.raw if hasattr(result, 'raw') else str(result)
243
-
244
- if '```json' in result_str:
245
- result_str = result_str.split('```json')[1].split('```')[0].strip()
246
- elif '```' in result_str:
247
- parts = result_str.split('```')
248
- if len(parts) >= 3:
249
- result_str = parts[1].strip()
250
-
 
251
  return json.loads(result_str)
 
1
+ import json
2
+ import logging
3
+ from typing import Dict, Any, List
4
+ from crewai import Agent, Task, Crew, Process
5
+ from src.config import crew_openai
6
+ logger = logging.getLogger(__name__)
7
+
8
+
9
+ class CVAgentOrchestrator:
10
+ def __init__(self, llm):
11
+ self.llm = crew_openai()
12
+ self._create_agents()
13
+
14
+ def _create_agents(self):
15
+ self.section_splitter = Agent(
16
+ role="Analyseur de Structure de CV",
17
+ goal="Découper intelligemment un CV en sections thématiques",
18
+ backstory="Expert en analyse documentaire spécialisé dans la reconnaissance de structures de CV.",
19
+ verbose=False,
20
+ llm=self.llm
21
+ )
22
+
23
+ self.contact_extractor = Agent(
24
+ role="Extracteur d'informations de contact",
25
+ goal="Extraire les coordonnées du candidat",
26
+ backstory="Expert en extraction d'informations de contact avec précision.",
27
+ verbose=False,
28
+ llm=self.llm
29
+ )
30
+
31
+ self.skills_extractor = Agent(
32
+ role="Extracteur de compétences",
33
+ goal="Identifier hard skills et soft skills",
34
+ backstory="Spécialiste en identification de compétences techniques et comportementales.",
35
+ verbose=False,
36
+ llm=self.llm
37
+ )
38
+
39
+ self.experience_extractor = Agent(
40
+ role="Extracteur d'expériences",
41
+ goal="Extraire les expériences professionnelles",
42
+ backstory="Expert en analyse de parcours professionnels.",
43
+ verbose=False,
44
+ llm=self.llm
45
+ )
46
+
47
+ self.project_extractor = Agent(
48
+ role="Extracteur de projets",
49
+ goal="Identifier projets professionnels et personnels",
50
+ backstory="Spécialiste en identification de projets significatifs.",
51
+ verbose=False,
52
+ llm=self.llm
53
+ )
54
+
55
+ self.education_extractor = Agent(
56
+ role="Extracteur de formations",
57
+ goal="Extraire formations et diplômes",
58
+ backstory="Expert en analyse de parcours académiques.",
59
+ verbose=False,
60
+ llm=self.llm
61
+ )
62
+
63
+ self.reconversion_detector = Agent(
64
+ role="Détecteur de reconversion",
65
+ goal="Analyser les changements de carrière",
66
+ backstory="Conseiller d'orientation expert en transitions de carrière.",
67
+ verbose=False,
68
+ llm=self.llm
69
+ )
70
+
71
+ self.profile_builder = Agent(
72
+ role="Constructeur de profil",
73
+ goal="Assembler le profil candidat final",
74
+ backstory="Expert en structuration de données JSON.",
75
+ verbose=False,
76
+ llm=self.llm
77
+ )
78
+
79
+ def split_cv_sections(self, cv_content: str) -> Dict[str, str]:
80
+ task = Task(
81
+ description=f"Analyser ce CV et l'organiser en sections: {cv_content}",
82
+ expected_output="""JSON avec sections: contact, experiences, projects, education, skills, other""",
83
+ agent=self.section_splitter
84
+ )
85
+
86
+ crew = Crew(
87
+ agents=[self.section_splitter],
88
+ tasks=[task],
89
+ process=Process.sequential,
90
+ verbose=False,
91
+ telemetry=False
92
+ )
93
+
94
+ result = crew.kickoff()
95
+ return self._parse_sections_result(result)
96
+
97
+ def extract_all_sections(self, sections: Dict[str, str]) -> Dict[str, Any]:
98
+ # Créer les tâches avec les sections en input
99
+ tasks = self._create_extraction_tasks(sections)
100
+
101
+ crew = Crew(
102
+ agents=[
103
+ self.contact_extractor,
104
+ self.skills_extractor,
105
+ self.experience_extractor,
106
+ self.project_extractor,
107
+ self.education_extractor,
108
+ self.reconversion_detector,
109
+ self.profile_builder
110
+ ],
111
+ tasks=tasks,
112
+ process=Process.sequential,
113
+ verbose=True, # Activer pour debug
114
+ telemetry=False
115
+ )
116
+
117
+ # Passer les sections comme inputs
118
+ inputs = {
119
+ "contact": sections.get("contact", ""),
120
+ "experiences": sections.get("experiences", ""),
121
+ "projects": sections.get("projects", ""),
122
+ "education": sections.get("education", ""),
123
+ "skills": sections.get("skills", ""),
124
+ "other": sections.get("other", "")
125
+ }
126
+
127
+ logger.info(f"Starting crew with inputs: {list(inputs.keys())}")
128
+ result = crew.kickoff(inputs=inputs)
129
+ logger.info(f"Crew completed. Raw result: {result.raw if hasattr(result, 'raw') else str(result)[:200]}...")
130
+
131
+ return self._parse_final_result(result)
132
+
133
+ def _create_extraction_tasks(self, sections: Dict[str, str]) -> List[Task]:
134
+ contact_task = Task(
135
+ description=(
136
+ "Voici la section contact du CV : {contact}\n"
137
+ "Extraire précisément le nom, email, téléphone et localisation du candidat."
138
+ ),
139
+ expected_output='{"nom": "...", "email": "...", "numero_de_telephone": "...", "localisation": "..."}',
140
+ agent=self.contact_extractor
141
+ )
142
+
143
+ skills_task = Task(
144
+ description=(
145
+ "Voici les sections pertinentes du CV :\n"
146
+ "Expériences: {experiences}\n"
147
+ "Projets: {projects}\n"
148
+ "Compétences: {skills}\n"
149
+ "Extraire toutes les compétences techniques (hard skills) et comportementales (soft skills) mentionnées."
150
+ ),
151
+ expected_output='{"hard_skills": ["compétence1", "compétence2"], "soft_skills": ["compétence1", "compétence2"]}',
152
+ agent=self.skills_extractor
153
+ )
154
+
155
+ experience_task = Task(
156
+ description=(
157
+ "Voici la section expériences du CV : {experiences}\n"
158
+ "Extraire toutes les expériences professionnelles avec poste, entreprise, dates et responsabilités."
159
+ ),
160
+ expected_output='[{"Poste": "titre", "Entreprise": "nom", "start_date": "date", "end_date": "date", "responsabilités": ["resp1", "resp2"]}]',
161
+ agent=self.experience_extractor
162
+ )
163
+
164
+ project_task = Task(
165
+ description=(
166
+ "Voici les sections projets et expériences du CV :\n"
167
+ "Projets: {projects}\n"
168
+ "Identifier et extraire les projets professionnels et personnels distincts des responsabilités générales."
169
+ ),
170
+ expected_output='{"professional": [{"title": "titre", "technologies": ["tech1"], "outcomes": ["résultat1"]}], "personal": []}',
171
+ agent=self.project_extractor
172
+ )
173
+
174
+ education_task = Task(
175
+ description=(
176
+ "Voici la section formations du CV : {education}\n"
177
+ "Extraire toutes les formations, diplômes et certifications avec institution et dates."
178
+ ),
179
+ expected_output='[{"degree": "diplôme", "institution": "établissement", "start_date": "date", "end_date": "date"}]',
180
+ agent=self.education_extractor
181
+ )
182
+
183
+ reconversion_task = Task(
184
+ description=(
185
+ "En analysant les expériences extraites précédemment, déterminer si le candidat est en reconversion professionnelle. "
186
+ "Chercher des changements de secteur, de type de poste ou des transitions significatives."
187
+ ),
188
+ expected_output='{"reconversion_analysis": {"is_reconversion": true, "analysis": "Explication détaillée..."}}',
189
+ agent=self.reconversion_detector,
190
+ context=[experience_task]
191
+ )
192
+
193
+ profile_task = Task(
194
+ description=(
195
+ "Assembler toutes les informations extraites des tâches précédentes en un profil candidat complet. "
196
+ "Créer un JSON valide avec une clé 'candidat' contenant toutes les sections."
197
+ ),
198
+ expected_output=(
199
+ '{"candidat": {'
200
+ '"informations_personnelles": {...}, '
201
+ '"compétences": {...}, '
202
+ '"expériences": [...], '
203
+ '"projets": {...}, '
204
+ '"formations": [...], '
205
+ '"reconversion": {...}'
206
+ '}}'
207
+ ),
208
+ agent=self.profile_builder,
209
+ context=[contact_task, skills_task, experience_task, project_task, education_task, reconversion_task]
210
+ )
211
+
212
+ return [contact_task, skills_task, experience_task, project_task, education_task, reconversion_task, profile_task]
213
+
214
+ def _parse_sections_result(self, result) -> Dict[str, str]:
215
+ result_str = result.raw if hasattr(result, 'raw') else str(result)
216
+
217
+ if '```json' in result_str:
218
+ result_str = result_str.split('```json')[1].split('```')[0].strip()
219
+ elif '```' in result_str:
220
+ parts = result_str.split('```')
221
+ if len(parts) >= 3:
222
+ result_str = parts[1].strip()
223
+
224
+ parsed = json.loads(result_str)
225
+
226
+ # Assurer que toutes les sections nécessaires existent
227
+ default_sections = {
228
+ "contact": "",
229
+ "experiences": "",
230
+ "projects": "",
231
+ "education": "",
232
+ "skills": "",
233
+ "other": ""
234
+ }
235
+
236
+ for key in default_sections:
237
+ if key not in parsed:
238
+ parsed[key] = default_sections[key]
239
+
240
+ return parsed
241
+
242
+ def _parse_final_result(self, result) -> Dict[str, Any]:
243
+ result_str = result.raw if hasattr(result, 'raw') else str(result)
244
+
245
+ if '```json' in result_str:
246
+ result_str = result_str.split('```json')[1].split('```')[0].strip()
247
+ elif '```' in result_str:
248
+ parts = result_str.split('```')
249
+ if len(parts) >= 3:
250
+ result_str = parts[1].strip()
251
+
252
  return json.loads(result_str)