Bachir00 commited on
Commit
e4bcc27
·
verified ·
1 Parent(s): 04fe1ac

Update agent.py

Browse files
Files changed (1) hide show
  1. agent.py +523 -0
agent.py CHANGED
@@ -0,0 +1,523 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import time
3
+ from datetime import datetime
4
+ from langchain_groq import ChatGroq
5
+ from langchain.tools import tool
6
+ from langgraph.graph import StateGraph, END
7
+ from langchain_core.runnables import RunnableLambda
8
+ from typing import TypedDict
9
+ from arxiv import Search, Client, SortCriterion
10
+ from duckduckgo_search import DDGS
11
+ import requests
12
+ from bs4 import BeautifulSoup
13
+ import re
14
+ import os
15
+ from langchain.tools import WikipediaQueryRun
16
+ from langchain.utilities import WikipediaAPIWrapper
17
+
18
+ @tool
19
+ def wikipedia_search(query: str, language: str = "en") -> str:
20
+ """
21
+ Recherche des informations sur Wikipedia.
22
+ Args:
23
+ query: La requête de recherche
24
+ language: Code de langue (par défaut 'en')
25
+ """
26
+ try:
27
+ wikipedia = WikipediaAPIWrapper(language=language, top_k_results=3)
28
+ tool = WikipediaQueryRun(api_wrapper=wikipedia)
29
+ return tool.run(query)
30
+ except Exception as e:
31
+ return f"Erreur Wikipedia: {str(e)}"
32
+ import pandas as pd
33
+ from dotenv import load_dotenv
34
+
35
+ load_dotenv()
36
+
37
+ # === VOTRE CODE AGENT (copié tel quel) ===
38
+ @tool
39
+ def math_tool(expression: str) -> str:
40
+ """
41
+ Évalue une expression mathématique simple donnée sous forme de chaîne de caractères.
42
+ Exemple : "2 + 5 * 3"
43
+ """
44
+ try:
45
+ allowed_chars = set('0123456789+-*/.() ')
46
+ if not all(c in allowed_chars for c in expression):
47
+ return "Erreur : Expression contient des caractères non autorisés"
48
+ result = eval(expression)
49
+ return f"Résultat : {result}"
50
+ except Exception as e:
51
+ return f"Erreur : {str(e)}"
52
+
53
+ @tool
54
+ def search_arxiv(query: str, max_results: int = 3) -> str:
55
+ """
56
+ Recherche d'articles scientifiques sur arXiv.
57
+ Retourne les titres, auteurs, résumés et liens PDF.
58
+ """
59
+ try:
60
+ if not query.strip():
61
+ return "Erreur : la requête de recherche arXiv est vide."
62
+
63
+ client = Client(num_retries=3)
64
+ search = Search(
65
+ query=query,
66
+ max_results=max_results,
67
+ sort_by=SortCriterion.Relevance
68
+ )
69
+
70
+ response = ""
71
+ for result in client.results(search):
72
+ response += f" Titre : {result.title.strip()}\n"
73
+ response += f" Auteurs : {', '.join([a.name for a in result.authors])}\n"
74
+ response += f" Résumé : {result.summary.strip()[:300]}...\n"
75
+ response += f" Lien : {result.pdf_url}\n\n"
76
+
77
+ return response or "Aucun résultat trouvé sur arXiv."
78
+
79
+ except Exception as e:
80
+ return f"Erreur lors de la recherche sur arXiv : {str(e)}"
81
+
82
+ @tool
83
+ def search_web(query: str, max_results: int = 3) -> str:
84
+ """ Recherche sur le web en utilisant DuckDuckGo.
85
+ Retourne les titres et liens des résultats.
86
+ Si la requête est vide, retourne une erreur.
87
+ """
88
+ if not query.strip():
89
+ return "Erreur : La requête de recherche est vide."
90
+ with DDGS() as ddgs:
91
+ results = ddgs.text(query, max_results=max_results)
92
+ response = ""
93
+ for res in results:
94
+ response += f"- {res['title']}: {res['href']}\n"
95
+ return response or "Aucun résultat trouvé."
96
+
97
+ @tool
98
+ def html_scraper_tool(prompt: str) -> str:
99
+ """
100
+ Extrait une URL depuis un prompt texte et scrappe la page correspondante.
101
+ Ex : "Scrappe-moi le site : www.google.com"
102
+ """
103
+ match = re.search(r'(https?://)?(www\.[^\s]+)', prompt)
104
+ if not match:
105
+ return " Aucune URL valide trouvée dans le prompt."
106
+
107
+ url = match.group(0)
108
+ if not url.startswith("http"):
109
+ url = "https://" + url
110
+
111
+ try:
112
+ response = requests.get(url, timeout=5)
113
+ response.raise_for_status()
114
+ soup = BeautifulSoup(response.text, 'html.parser')
115
+ title = soup.title.string.strip() if soup.title else "Aucun titre trouvé"
116
+ return f" Page title : {title}"
117
+
118
+ except Exception as e:
119
+ return f" Erreur lors du scraping de '{url}' : {str(e)}"
120
+
121
+ @tool
122
+ def wikipedia_search(query: str, language: str = "en") -> str:
123
+ """
124
+ Recherche des informations sur Wikipedia.
125
+ Args:
126
+ query: La requête de recherche
127
+ language: Code de langue (par défaut 'en')
128
+ """
129
+ try:
130
+ print(f"Langue utilisée : {language}")
131
+ print(f"Recherche Wikipedia pour : {query}")
132
+
133
+ wikipedia = WikipediaAPIWrapper(language=language, top_k_results=3)
134
+ print("WikipediaAPIWrapper instancié.")
135
+
136
+ tool = WikipediaQueryRun(api_wrapper=wikipedia)
137
+ print("WikipediaQueryRun instancié.")
138
+
139
+ result = tool.run(query)
140
+ print("Résultat obtenu.")
141
+ return result
142
+ except Exception as e:
143
+ print(f"Erreur Wikipedia : {str(e)}")
144
+ return f"Erreur Wikipedia: {str(e)}"
145
+
146
+ @tool
147
+ def reverse_text(text: str) -> str:
148
+ """
149
+ Inverse une chaîne de caractères.
150
+ """
151
+ return text[::-1]
152
+
153
+ @tool
154
+ def process_excel(file_path: str) -> str:
155
+ """
156
+ Traite un fichier Excel et extrait des informations.
157
+ """
158
+ try:
159
+ df = pd.read_excel(file_path)
160
+ return df.to_string()
161
+ except Exception as e:
162
+ return f"Erreur Excel: {str(e)}"
163
+ from youtube_transcript_api import YouTubeTranscriptApi
164
+
165
+ @tool
166
+ def get_youtube_transcript(video_url: str) -> str:
167
+ """
168
+ Récupère la transcription d'une vidéo YouTube.
169
+ """
170
+ try:
171
+ video_id = video_url.split("watch?v=")[1]
172
+ transcript = YouTubeTranscriptApi.get_transcript(video_id)
173
+ return " ".join([entry['text'] for entry in transcript])
174
+ except Exception as e:
175
+ return f"Erreur YouTube: {str(e)}"
176
+
177
+
178
+
179
+ tools = {
180
+ "math": math_tool,
181
+ "search": search_web,
182
+ "arxiv": search_arxiv,
183
+ "html_scraper": html_scraper_tool,
184
+ "wikipedia": wikipedia_search,
185
+ "reverse_text": reverse_text,
186
+ "process_excel": process_excel,
187
+ "get_youtube_transcript": get_youtube_transcript
188
+ }
189
+
190
+ api_key = os.getenv("API_KEY_GROQ")
191
+ if not api_key:
192
+ raise ValueError("La variable d'environnement 'API_KEY_GROQ' n'est pas définie.")
193
+
194
+ llm = ChatGroq(
195
+ model="llama-3.1-8b-instant",
196
+ temperature=0.7,
197
+ max_tokens=1024,
198
+ api_key=api_key
199
+ )
200
+
201
+ class AgentState(TypedDict):
202
+ input: str
203
+ tool: str
204
+ processed_input: str
205
+ tool_output: str
206
+ final_answer: str
207
+
208
+ def call_llm(state: AgentState) -> AgentState:
209
+ """
210
+ Appelle le LLM pour déterminer l'outil approprié.
211
+ """
212
+ system_prompt = f"""
213
+ Analyze this user request: "{state['input']}"
214
+
215
+ Available tools:
216
+ - 'math': for calculations and mathematical expressions
217
+ - 'search': to perform web searches
218
+ - 'arxiv': to search for scientific papers on arXiv
219
+ - 'html_scraper': to scrape HTML content
220
+ - 'wikipedia': to search for information on Wikipedia
221
+ - 'reverse_text': to reverse a given text
222
+ - 'process_excel': to process Excel files
223
+ - 'get_youtube_transcript': to retrieve YouTube video transcripts
224
+
225
+ Respond **only** with the appropriate tool name.
226
+ NO SENTENCES, just the tool name.
227
+ """
228
+
229
+ try:
230
+ response = llm.invoke(system_prompt)
231
+ tool_name = response.content.strip().lower()
232
+ tool_name = tool_name.strip("'\"")
233
+
234
+ if tool_name in ['math', 'search', 'arxiv', 'html_scraper', 'wikipedia', 'reverse_text', 'process_excel', 'get_youtube_transcript']:
235
+ state['tool'] = tool_name
236
+ return state
237
+ except Exception as e:
238
+ print(f"Erreur lors de l'appel LLM : {e}")
239
+ return state
240
+
241
+ def extract_math_expression(state: AgentState) -> AgentState:
242
+ """
243
+ Extrait l'expression mathématique de l'input pour les demandes de type math.
244
+ """
245
+ if state['tool'] == 'math':
246
+ system_prompt = f"""
247
+ Extrayez uniquement l'expression mathématique de cette question : "{state['input']}"
248
+
249
+ Exemples :
250
+ - "Quel est le résultat de 100 / 4 ?" → "100 / 4"
251
+ - "Calcule 15 + 27 * 3" → "15 + 27 * 3"
252
+ - "Quelle est la racine carrée de 144?" → "144 ** 0.5"
253
+ - "2 plus 3 fois 5" → "2 + 3 * 5"
254
+ - "Combien font 12 * 8 ?" → "12 * 8"
255
+ - "Quel est le résultat de 23 * (5 + 7) ?" → "23 * (5 + 7)"
256
+
257
+ Répondez uniquement par l'expression mathématique, sans explication.
258
+ """
259
+ try:
260
+ response = llm.invoke(system_prompt)
261
+ math_expression = response.content.strip()
262
+ state['processed_input'] = math_expression
263
+ except Exception as e:
264
+ print(f"Erreur extraction math : {e}")
265
+ else:
266
+ state['processed_input'] = state['input']
267
+ return state
268
+
269
+ def generate_response(state: AgentState) -> AgentState:
270
+ """
271
+ Génère la réponse finale pour l'utilisateur.
272
+ """
273
+ system_prompt = f"""
274
+ The tool '{state['tool']}' returned: "{state['tool_output']}"
275
+
276
+ Formulate a clear and natural response for the user .
277
+ Integrate the result smoothly into your reply.
278
+ """
279
+ try:
280
+ response = llm.invoke(system_prompt)
281
+ state['final_answer'] = response.content
282
+ return state
283
+ except Exception as e:
284
+ state['final_answer'] = f"Réponse générée avec succès : {state['tool_output']}"
285
+ return state
286
+
287
+ def create_agent_graph():
288
+ workflow = StateGraph(AgentState)
289
+
290
+ workflow.add_node("llm_decision", call_llm)
291
+ workflow.add_node("process", extract_math_expression)
292
+ workflow.add_node("response_generation", generate_response)
293
+
294
+ workflow.add_node("math_tool", RunnableLambda(lambda state: {
295
+ **state,
296
+ "tool_output": tools["math"].invoke(state["processed_input"])
297
+ }))
298
+
299
+ workflow.add_node("search_tool", RunnableLambda(lambda state: {
300
+ **state,
301
+ "tool_output": tools["search"](state["processed_input"])
302
+ }))
303
+
304
+ workflow.add_node("arxiv_tool", RunnableLambda(lambda state: {
305
+ **state,
306
+ "tool_output": tools["arxiv"](state["processed_input"])
307
+ }))
308
+
309
+ workflow.add_node("html_scraper_tool", RunnableLambda(lambda state: {
310
+ **state,
311
+ "tool_output": tools["html_scraper"](state["processed_input"])
312
+ }))
313
+ workflow.add_node("wikipedia_search", RunnableLambda(lambda state: {
314
+ **state,
315
+ "tool_output": tools["wikipedia"](state["processed_input"])
316
+ }))
317
+ workflow.add_node("reverse_text", RunnableLambda(lambda state: {
318
+ **state,
319
+ "tool_output": tools["reverse_text"](state["processed_input"])
320
+ }))
321
+
322
+ workflow.add_node("process_excel", RunnableLambda(lambda state: {
323
+ **state,
324
+ "tool_output": tools["process_excel"](state["processed_input"])
325
+ }))
326
+
327
+ workflow.add_node("get_youtube_transcript", RunnableLambda(lambda state: {
328
+ **state,
329
+ "tool_output": tools["get_youtube_transcript"](state["processed_input"])
330
+ }))
331
+
332
+
333
+
334
+ workflow.set_entry_point("llm_decision")
335
+ workflow.add_edge("llm_decision", "process")
336
+
337
+ def router(state: AgentState) -> str:
338
+ if state["tool"] == "math":
339
+ return "math_tool"
340
+ elif state["tool"] == "search":
341
+ return "search_tool"
342
+ elif state["tool"] == "arxiv":
343
+ return "arxiv_tool"
344
+ elif state["tool"] == "html_scraper":
345
+ return "html_scraper_tool"
346
+ elif state["tool"] == "wikipedia":
347
+ return "wikipedia_search"
348
+ elif state["tool"] == "reverse_text":
349
+ return "reverse_text"
350
+ elif state["tool"] == "process_excel":
351
+ return "process_excel"
352
+ elif state["tool"] == "get_youtube_transcript":
353
+ return "get_youtube_transcript"
354
+
355
+ workflow.add_conditional_edges("process", router, {
356
+ "math_tool": "math_tool",
357
+ "search_tool": "search_tool",
358
+ "arxiv_tool": "arxiv_tool",
359
+ "html_scraper_tool": "html_scraper_tool",
360
+ "wikipedia_search": "wikipedia_search",
361
+ "reverse_text": "reverse_text",
362
+ "process_excel": "process_excel",
363
+ "get_youtube_transcript": "get_youtube_transcript"
364
+
365
+ })
366
+
367
+ workflow.add_edge("math_tool", "response_generation")
368
+ workflow.add_edge("search_tool", "response_generation")
369
+ workflow.add_edge("arxiv_tool", "response_generation")
370
+ workflow.add_edge("html_scraper_tool", "response_generation")
371
+ workflow.add_edge("wikipedia_search", "response_generation")
372
+ workflow.add_edge("reverse_text", "response_generation")
373
+ workflow.add_edge("process_excel", "response_generation")
374
+ workflow.add_edge("get_youtube_transcript", "response_generation")
375
+ workflow.add_edge("response_generation", END)
376
+
377
+ return workflow.compile()
378
+
379
+ def run_agent(user_input: str) -> str:
380
+ """
381
+ Exécute l'agent avec une entrée utilisateur.
382
+ """
383
+ agent = create_agent_graph()
384
+ initial_state = AgentState(
385
+ input=user_input,
386
+ tool="",
387
+ processed_input="",
388
+ tool_output="",
389
+ final_answer=""
390
+ )
391
+ try:
392
+ result = agent.invoke(initial_state)
393
+ return result['final_answer']
394
+ except Exception as e:
395
+ return f"Erreur lors de l'exécution : {str(e)}"
396
+
397
+ # === SCRIPT D'ÉVALUATION ===
398
+ def evaluate_agent_on_dataset(input_file_path, output_file_path):
399
+ """
400
+ Évalue l'agent sur un dataset de questions et sauvegarde les réponses.
401
+
402
+ Args:
403
+ input_file_path (str): Chemin vers le fichier JSON contenant les questions
404
+ output_file_path (str): Chemin vers le fichier de sortie pour les réponses
405
+ """
406
+
407
+ # Charger les questions depuis le fichier JSON
408
+ try:
409
+ with open(input_file_path, 'r', encoding='utf-8') as f:
410
+ questions_data = json.load(f)
411
+ # récupèrer les 5 premières questions
412
+ questions_data = questions_data[:-1] # Limiter à 5 questions pour l'évaluation
413
+ print(f" Fichier chargé avec succès: {len(questions_data)} questions trouvées")
414
+ except FileNotFoundError:
415
+ print(f" Erreur: Le fichier {input_file_path} n'a pas été trouvé")
416
+ return
417
+ except json.JSONDecodeError as e:
418
+ print(f" Erreur lors du parsing JSON: {e}")
419
+ return
420
+
421
+ # Préparer la structure des résultats
422
+ results = []
423
+ start_time = datetime.now()
424
+
425
+ print(f"\n Début de l'évaluation - {start_time.strftime('%Y-%m-%d %H:%M:%S')}")
426
+ print("=" * 60)
427
+
428
+ # Traiter chaque question
429
+ for i, item in enumerate(questions_data, 1):
430
+ task_id = item.get('task_id', 'unknown')
431
+ question = item.get('question', '')
432
+ level = item.get('Level', 'unknown')
433
+ file_name = item.get('file_name', '')
434
+
435
+ print(f"\n Question {i}/{len(questions_data)}")
436
+ print(f"Task ID: {task_id}")
437
+ print(f"Level: {level}")
438
+ print(f"Question: {question[:100]}{'...' if len(question) > 100 else ''}")
439
+
440
+ if file_name:
441
+ print(f" Note: Cette question fait référence au fichier: {file_name}")
442
+ print(" L'agent ne peut pas traiter les fichiers joints actuellement.")
443
+
444
+ # Exécuter l'agent
445
+ try:
446
+ print("🤖 Traitement en cours...")
447
+ answer = run_agent(question)
448
+ error_message = None
449
+
450
+ except Exception as e:
451
+ print(f" Erreur lors du traitement: {str(e)}")
452
+ answer = f"Erreur: {str(e)}"
453
+ error_message = str(e)
454
+
455
+ # Sauvegarder le résultat
456
+ result = {
457
+ "username": "Bachir00",
458
+ "code_agent": "https://huggingface.co/spaces/Bachir00/Final_Assignment_Template/tree/main",
459
+ "task_id": task_id,
460
+ "submitted_answer": answer,
461
+ }
462
+
463
+ if error_message:
464
+ result["error_message"] = error_message
465
+
466
+ results.append(result)
467
+
468
+ print(f"Réponse: {answer[:150]}{'...' if len(answer) > 150 else ''}")
469
+
470
+ # Pause entre les requêtes pour éviter la surcharge
471
+ time.sleep(1)
472
+
473
+ # Sauvegarder tous les résultats
474
+ try:
475
+ with open(output_file_path, 'w', encoding='utf-8') as f:
476
+ json.dump(results, f, ensure_ascii=False, indent=2)
477
+
478
+ end_time = datetime.now()
479
+ duration = end_time - start_time
480
+
481
+ print("\n" + "=" * 60)
482
+ print("RÉSUMÉ DE L'ÉVALUATION")
483
+ print("=" * 60)
484
+ print(f" Total des questions traitées: {len(results)}")
485
+ print(f"⏱ Temps total: {duration}")
486
+ print(f" Résultats sauvegardés dans: {output_file_path}")
487
+
488
+ # Statistiques par status
489
+ success_count = sum(1 for r in results if r['status'] == 'success')
490
+ error_count = len(results) - success_count
491
+
492
+ print(f" Succès: {success_count}")
493
+ print(f" Erreurs: {error_count}")
494
+
495
+ if error_count > 0:
496
+ print(f" Taux de réussite: {success_count/len(results)*100:.1f}%")
497
+
498
+ # Statistiques par niveau
499
+ levels = {}
500
+ for result in results:
501
+ level = result['level']
502
+ if level not in levels:
503
+ levels[level] = 0
504
+ levels[level] += 1
505
+
506
+ print(f"\ Répartition par niveau:")
507
+ for level, count in sorted(levels.items()):
508
+ print(f" Niveau {level}: {count} questions")
509
+
510
+ print("\n Évaluation terminée avec succès!")
511
+
512
+ except Exception as e:
513
+ print(f" Erreur lors de la sauvegarde: {str(e)}")
514
+
515
+ # === UTILISATION ===
516
+ if __name__ == "__main__":
517
+ # Chemins des fichiers
518
+ input_file = "response_1748862846167.json" # Remplacez par le chemin de votre fichier JSON
519
+ output_file = f"agent_results_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
520
+
521
+ # Lancer l'évaluation
522
+ evaluate_agent_on_dataset(input_file, output_file)
523
+