Nayohn commited on
Commit
2fb26db
·
1 Parent(s): 09498d4

Ajout API job search avec Gradio

Browse files
Files changed (1) hide show
  1. app.py +155 -74
app.py CHANGED
@@ -1,7 +1,118 @@
1
  import gradio as gr
2
  import requests
 
3
 
4
  SERPAPI_KEY = "7aa26c214c77639dc2be2a61cb01ba2811fde874a36d3b04a38b9823655f6706"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
  def search_jobs(job_title="", location="", user_desc=None, salary=None, studies=None, domain=None):
7
  """
@@ -160,112 +271,82 @@ def search_jobs(job_title="", location="", user_desc=None, salary=None, studies=
160
  "jsonrpc": "2.0",
161
  "result": {
162
  "success": False,
163
- "status": "NO_RESULTS_FOUND",
164
- "message": "Aucune offre d'emploi trouvée pour les critères spécifiés",
165
- "search_metadata": {
166
- "original_query": query,
167
  "location": location,
168
- "search_parameters": {
169
- "job_title": job_title,
170
- "location": location,
171
- "user_desc": user_desc,
172
- "salary": salary,
173
- "studies": studies,
174
- "domain": domain
175
- },
176
- "serpapi_parameters": payload,
177
- "search_radius_km": 50,
178
- "fallback_attempted": search_info.get("fallback_query") is not None,
179
- "fallback_query": search_info.get("fallback_query"),
180
- "fallback_results_count": search_info.get("fallback_results", 0)
181
  },
182
  "jobs": {
183
- "total_count": 0,
184
- "displayed_count": 0,
185
  "results": []
186
  },
187
  "suggestions": [
188
- "Essayez avec un terme de métier plus général",
189
- "Vérifiez l'orthographe de la localisation",
190
- "Élargissez la zone géographique",
191
- "Utilisez des synonymes pour le métier recherché"
192
- ],
193
- "debug_information": search_info
194
  },
195
  "id": None
196
  }
197
 
198
- # Retourner la liste de jobs avec informations très détaillées
199
  jobs_list = []
200
  for job in jobs_results:
 
 
 
 
 
201
  job_info = {
202
- "job_id": job.get("job_id", ""),
203
  "title": job.get("title", ""),
204
- "company_name": job.get("company_name", ""),
205
- "company_url": job.get("company_url", ""),
206
  "location": job.get("location", ""),
207
- "via": job.get("via", ""),
208
- "description": job.get("description", ""),
209
- "posted_at": job.get("posted_at", ""),
210
- "schedule_type": job.get("schedule_type", ""),
211
- "salary": job.get("salary", ""),
212
- "job_highlights": job.get("job_highlights", {}),
213
- "related_links": job.get("related_links", []),
214
- "thumbnail": job.get("thumbnail", ""),
215
- "extensions": job.get("extensions", []),
216
- "detected_extensions": job.get("detected_extensions", {}),
217
- "apply_options": job.get("apply_options", []),
218
  "link": job.get("link", ""),
219
- "serpapi_link": job.get("serpapi_link", "")
 
 
 
220
  }
221
  jobs_list.append(job_info)
222
 
223
  total_jobs_found = len(jobs_list)
224
- displayed_jobs = jobs_list # Afficher TOUS les résultats, pas de limite
 
225
 
226
- # Format JSON-RPC 2.0 complet avec toutes les informations
227
- return {
228
  "jsonrpc": "2.0",
229
  "result": {
230
  "success": True,
231
- "status": "JOBS_FOUND_SUCCESSFULLY",
232
- "message": f"Recherche réussie : {total_jobs_found} offres d'emploi trouvées",
233
- "search_metadata": {
234
- "original_query": query,
235
- "processed_keywords": query.split(),
236
  "location": location,
237
- "search_timestamp": None, # Peut être ajouté si nécessaire
238
- "search_parameters": {
239
- "job_title": job_title,
240
- "location": location,
241
- "user_desc": user_desc,
242
- "salary": salary,
243
- "studies": studies,
244
- "domain": domain
245
- },
246
- "serpapi_parameters": payload,
247
- "search_radius_km": 50,
248
- "fallback_attempted": search_info.get("fallback_query") is not None,
249
- "fallback_query": search_info.get("fallback_query"),
250
- "fallback_results_count": search_info.get("fallback_results", 0),
251
- "serpapi_response_keys": search_info.get("serpapi_response_keys", [])
252
  },
253
  "jobs": {
254
- "total_count": total_jobs_found,
255
- "displayed_count": len(displayed_jobs),
256
  "results": displayed_jobs
257
  },
258
- "statistics": {
259
- "jobs_with_salary": len([j for j in displayed_jobs if j.get("salary")]),
260
- "jobs_with_description": len([j for j in displayed_jobs if j.get("description")]),
261
- "jobs_with_company_url": len([j for j in displayed_jobs if j.get("company_url")]),
262
- "unique_companies": len(set([j.get("company_name", "") for j in displayed_jobs if j.get("company_name")])),
263
- "job_sources": list(set([j.get("via", "") for j in displayed_jobs if j.get("via")]))
264
- },
265
- "debug_information": search_info
266
  },
267
  "id": None
268
  }
 
 
 
269
 
270
  except Exception as e:
271
  return {
 
1
  import gradio as gr
2
  import requests
3
+ import json
4
 
5
  SERPAPI_KEY = "7aa26c214c77639dc2be2a61cb01ba2811fde874a36d3b04a38b9823655f6706"
6
+ MIXTRAL_API_KEY = "VhU4tnowxkqtGOIRoyP7qUlhnXj1kjn5" # Remplace par ta clé Hugging Face
7
+ MIXTRAL_API_URL = "https://api-inference.huggingface.co/models/mistralai/Mixtral-8x7B-Instruct-v0.1"
8
+
9
+ def format_jobs_with_mixtral(jobs_data):
10
+ """
11
+ Utilise Mixtral pour reformater les offres d'emploi en format optimal pour le LLM chat.
12
+
13
+ Args:
14
+ jobs_data (dict): Données des offres d'emploi depuis SerpAPI
15
+
16
+ Returns:
17
+ dict: Format optimisé pour le LLM chat
18
+ """
19
+ try:
20
+ # Créer un prompt pour Mixtral
21
+ jobs_json = json.dumps(jobs_data, ensure_ascii=False, indent=2)
22
+
23
+ prompt = f"""Tu es un assistant spécialisé dans la présentation d'offres d'emploi.
24
+ Voici des données JSON d'offres d'emploi que tu dois reformater pour qu'elles soient parfaitement présentables par un LLM chat.
25
+
26
+ DONNÉES JSON:
27
+ {jobs_json}
28
+
29
+ INSTRUCTIONS:
30
+ 1. Crée un format JSON-RPC 2.0 optimisé
31
+ 2. Résume chaque offre de manière claire et concise
32
+ 3. Mets en avant les informations essentielles : titre, entreprise, lieu, salaire, lien
33
+ 4. Garde les descriptions courtes mais informatives (max 150 caractères)
34
+ 5. Assure-toi que le JSON final fait moins de 25000 caractères
35
+ 6. Structure le tout pour que le LLM puisse facilement présenter les résultats à l'utilisateur
36
+
37
+ Réponds UNIQUEMENT avec le JSON formaté, sans explication."""
38
+
39
+ # Appel à l'API Mixtral
40
+ headers = {
41
+ "Authorization": f"Bearer {MIXTRAL_API_KEY}",
42
+ "Content-Type": "application/json"
43
+ }
44
+
45
+ payload = {
46
+ "inputs": prompt,
47
+ "parameters": {
48
+ "max_new_tokens": 2000,
49
+ "temperature": 0.1,
50
+ "return_full_text": False
51
+ }
52
+ }
53
+
54
+ response = requests.post(MIXTRAL_API_URL, headers=headers, json=payload, timeout=30)
55
+
56
+ if response.status_code == 200:
57
+ mixtral_response = response.json()
58
+ if isinstance(mixtral_response, list) and len(mixtral_response) > 0:
59
+ formatted_text = mixtral_response[0].get("generated_text", "")
60
+
61
+ # Essayer de parser le JSON retourné par Mixtral
62
+ try:
63
+ # Nettoyer la réponse (enlever les éventuels markdown)
64
+ if "```json" in formatted_text:
65
+ formatted_text = formatted_text.split("```json")[1].split("```")[0]
66
+ elif "```" in formatted_text:
67
+ formatted_text = formatted_text.split("```")[1].split("```")[0]
68
+
69
+ formatted_json = json.loads(formatted_text.strip())
70
+ return formatted_json
71
+ except json.JSONDecodeError:
72
+ # Si le parsing échoue, retourner le format original simplifié
73
+ pass
74
+
75
+ # Fallback : format simplifié si Mixtral échoue
76
+ return create_simplified_format(jobs_data)
77
+
78
+ except Exception as e:
79
+ # En cas d'erreur avec Mixtral, retourner le format simplifié
80
+ return create_simplified_format(jobs_data)
81
+
82
+ def create_simplified_format(jobs_data):
83
+ """
84
+ Crée un format simplifié en cas d'échec de Mixtral.
85
+ """
86
+ if not jobs_data.get("result", {}).get("jobs", {}).get("results"):
87
+ return jobs_data
88
+
89
+ jobs = jobs_data["result"]["jobs"]["results"]
90
+ simplified_jobs = []
91
+
92
+ for job in jobs[:6]: # Limiter à 6 offres max
93
+ simplified_job = {
94
+ "titre": job.get("title", ""),
95
+ "entreprise": job.get("company", ""),
96
+ "lieu": job.get("location", ""),
97
+ "salaire": job.get("salary", "Non spécifié"),
98
+ "description": job.get("description", "")[:120] + "..." if len(job.get("description", "")) > 120 else job.get("description", ""),
99
+ "lien": job.get("link", "")
100
+ }
101
+ simplified_jobs.append(simplified_job)
102
+
103
+ return {
104
+ "jsonrpc": "2.0",
105
+ "result": {
106
+ "status": "SUCCESS",
107
+ "message": f"✅ {len(simplified_jobs)} offres d'emploi trouvées",
108
+ "offres": simplified_jobs,
109
+ "info": {
110
+ "recherche": jobs_data.get("result", {}).get("search_info", {}).get("query", ""),
111
+ "lieu": jobs_data.get("result", {}).get("search_info", {}).get("location", "")
112
+ }
113
+ },
114
+ "id": None
115
+ }
116
 
117
  def search_jobs(job_title="", location="", user_desc=None, salary=None, studies=None, domain=None):
118
  """
 
271
  "jsonrpc": "2.0",
272
  "result": {
273
  "success": False,
274
+ "status": "NO_RESULTS",
275
+ "message": "Aucune offre trouvée",
276
+ "search_info": {
277
+ "query": query,
278
  "location": location,
279
+ "fallback_used": search_info.get("fallback_query") is not None
 
 
 
 
 
 
 
 
 
 
 
 
280
  },
281
  "jobs": {
282
+ "total": 0,
 
283
  "results": []
284
  },
285
  "suggestions": [
286
+ "Essayez un métier plus général",
287
+ "Vérifiez l'orthographe de la ville",
288
+ "Élargissez la zone géographique"
289
+ ]
 
 
290
  },
291
  "id": None
292
  }
293
 
294
+ # Retourner la liste de jobs avec informations essentielles (optimisé pour éviter la troncature)
295
  jobs_list = []
296
  for job in jobs_results:
297
+ # Limiter la description pour éviter les JSON trop lourds
298
+ description = job.get("description", "")
299
+ if description and len(description) > 300:
300
+ description = description[:300] + "..."
301
+
302
  job_info = {
 
303
  "title": job.get("title", ""),
304
+ "company": job.get("company_name", ""),
 
305
  "location": job.get("location", ""),
306
+ "salary": job.get("salary", "Non spécifié"),
307
+ "description": description,
308
+ "posted": job.get("posted_at", ""),
309
+ "type": job.get("schedule_type", ""),
310
+ "source": job.get("via", ""),
 
 
 
 
 
 
311
  "link": job.get("link", ""),
312
+ "highlights": {
313
+ "qualifications": job.get("job_highlights", {}).get("Qualifications", [])[:3] if job.get("job_highlights", {}).get("Qualifications") else [],
314
+ "responsibilities": job.get("job_highlights", {}).get("Responsibilities", [])[:3] if job.get("job_highlights", {}).get("Responsibilities") else []
315
+ }
316
  }
317
  jobs_list.append(job_info)
318
 
319
  total_jobs_found = len(jobs_list)
320
+ # Limiter à 8 résultats max pour éviter la troncature
321
+ displayed_jobs = jobs_list[:8]
322
 
323
+ # Format JSON-RPC 2.0 brut pour Mixtral
324
+ raw_result = {
325
  "jsonrpc": "2.0",
326
  "result": {
327
  "success": True,
328
+ "status": "SUCCESS",
329
+ "message": f" {total_jobs_found} offres trouvées ({len(displayed_jobs)} affichées)",
330
+ "search_info": {
331
+ "query": query,
 
332
  "location": location,
333
+ "fallback_used": search_info.get("fallback_query") is not None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
334
  },
335
  "jobs": {
336
+ "total": total_jobs_found,
337
+ "displayed": len(displayed_jobs),
338
  "results": displayed_jobs
339
  },
340
+ "stats": {
341
+ "with_salary": len([j for j in displayed_jobs if j.get("salary") and j.get("salary") != "Non spécifié"]),
342
+ "companies": len(set([j.get("company", "") for j in displayed_jobs if j.get("company")]))
343
+ }
 
 
 
 
344
  },
345
  "id": None
346
  }
347
+
348
+ # Passer par Mixtral pour optimiser le format pour le LLM chat
349
+ return format_jobs_with_mixtral(raw_result)
350
 
351
  except Exception as e:
352
  return {