osma77 commited on
Commit
9efb8d3
·
verified ·
1 Parent(s): d84e45c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +480 -472
app.py CHANGED
@@ -13,529 +13,537 @@ from smolagents import (
13
  # --- Constants ---
14
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
15
 
16
- GAIA_SYSTEM_PROMPT = """Tu es un assistant expert spécialisé dans la résolution de questions GAIA.
17
-
18
- RÈGLES ABSOLUES:
19
- 1. Lis la question 3 fois avant de commencer
20
- 2. Décompose TOUJOURS la question en sous-problèmes identifiables
21
- 3. Vérifie CHAQUE information avec au moins 2 sources différentes
22
- 4. Pour les calculs: utilise OBLIGATOIREMENT Python pour tous les calculs numériques
23
- 5. Pour les dates: vérifie l'année actuelle (nous sommes en 2025)
24
- 6. JAMAIS de réponse approximative - sois précis au maximum
25
-
26
- PROCESSUS OBLIGATOIRE:
27
- 1. ANALYSE: Que demande exactement la question? Quel type de réponse?
28
- 2. RECHERCHE: Quelles informations spécifiques me manquent?
29
- 3. VÉRIFICATION: Les sources sont-elles cohérentes entre elles?
30
- 4. CALCUL: Si nécessaire, utilise Python pour calculs précis
31
- 5. SYNTHÈSE: Donne une réponse finale précise et concise
32
-
33
- FORMAT DE RÉPONSE FINAL:
34
- - Si c'est un nombre: donne UNIQUEMENT le nombre (ex: "42")
35
- - Si c'est un nom: donne UNIQUEMENT le nom (ex: "Paris")
36
- - Si c'est une date: format précis demandé
37
- - Pas d'explication supplémentaire dans la réponse finale
38
-
39
- OUTILS DISPONIBLES:
40
- - DuckDuckGoSearchTool: pour informations récentes et générales
41
- - WikipediaSearchTool: pour faits établis et données encyclopédiques
42
- - PythonInterpreterTool: pour calculs, manipulations de données, dates"""
 
43
 
44
 
45
  # Configure logging
46
  logging.basicConfig(level=logging.INFO)
47
  logger = logging.getLogger(__name__)
48
 
49
- # class BasicAgent:
50
- # def __init__(self, model_id: Optional[str] = None, api_key: Optional[str] = None):
51
- # """
52
- # Initialize BasicAgent with model and tools.
53
 
54
- # Args:
55
- # model_id: Optional model ID to use
56
- # api_key: Optional API key (will use environment variable if not provided)
57
- # """
58
 
59
- # # Initialize model
60
- # self.model = AzureOpenAIServerModel(
61
- # model_id = "o3-mini",
62
- # azure_endpoint=os.environ.get("AZURE_OPENAI_ENDPOINT"),
63
- # api_key=os.environ.get("AZURE_OPENAI_API_KEY"),
64
- # api_version="2024-12-01-preview" #2024-02-15-preview
65
- # )
66
 
67
- # # Initialize tools
68
- # self.tools = [
69
- # WikipediaSearchTool(),
70
- # # DuckDuckGoSearchTool(),
71
- # PythonInterpreterTool(),
72
- # VisitWebpageTool()
73
- # ]
74
-
75
- # # Create CodeAgent
76
- # self.agent = CodeAgent(
77
- # tools=self.tools,
78
- # model=self.model,
79
- # max_steps=2,
80
- # add_base_tools=True,
81
- # additional_authorized_imports=["pandas","requests"],
82
- # name="gaia_nureyni",
83
- # description="Agent designed to solve GAIA benchmark level 1 questions using tools like DuckDuckGo and LLM for reasoning."
84
- # )
85
 
86
- # # logger.info(f"BasicAgent initialized with model: {self.default_model_id}")
87
 
88
- # def __call__(self, question: str) -> str:
89
- # """
90
- # Make the agent callable directly.
91
 
92
- # Args:
93
- # question: The question to ask the agent
94
 
95
- # Returns:
96
- # Agent's response
97
- # """
98
- # logger.info(f"Agent received question (first 50 chars): {question[:50]}...")
99
 
100
- # try:
101
- # question = f"""{GAIA_SYSTEM_PROMPT}\n
102
- # QUESTION GAIA:
103
- # {question}
104
 
105
- # APPLIQUE LES RÈGLES CI-DESSUS ET RÉSOUS:
106
- # """
107
- # answer = self.agent.run(question)
108
- # logger.info("Agent successfully generated response")
109
- # return answer
110
- # except Exception as e:
111
- # logger.error(f"Agent failed to generate response: {e}")
112
- # raise
113
- # from langgraph.gr import StateGraph, END
114
- from langgraph.graph import StateGraph, END
115
- from langchain_core.messages import HumanMessage, AIMessage
116
- from langchain_openai import AzureChatOpenAI
117
- from langchain_community.tools import WikipediaQueryRun, DuckDuckGoSearchRun
118
- from langchain_community.utilities import WikipediaAPIWrapper, DuckDuckGoSearchAPIWrapper
119
- from langchain_experimental.tools import PythonREPLTool
120
- from langchain_core.tools import tool
121
- import os
122
- import math
123
- import numpy as np
124
- import re
125
- from typing import Optional, Dict, Any, List
126
- from langchain_core.agents import AgentAction, AgentFinish
127
 
128
- class BasicAgent:
129
- def __init__(self, model_id: Optional[str] = None, api_key: Optional[str] = None):
130
- """
131
- Initialize BasicAgent optimized for GAIA benchmark success.
132
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
 
134
- # Initialize model
135
- self.model = AzureChatOpenAI(
136
- deployment_name="o3-mini",
137
- azure_endpoint=os.environ.get("AZURE_OPENAI_ENDPOINT"),
138
- api_key=os.environ.get("AZURE_OPENAI_API_KEY"),
139
- api_version="2024-12-01-preview"
140
- )
141
 
142
- # Initialize tools
143
- self.tools = self._initialize_tools()
144
-
145
- # Create LangGraph workflow
146
- self.workflow = self._create_workflow()
147
- self.app = self.workflow.compile()
148
-
149
- def _initialize_tools(self):
150
- """Initialize tools with GAIA-specific optimizations."""
151
-
152
- @tool
153
- def web_search(query: str) -> str:
154
- """
155
- Search for current information on the web. Use specific, targeted queries.
156
- Best for: recent events, current data, specific facts, news.
157
- """
158
- try:
159
- ddg = DuckDuckGoSearchAPIWrapper(max_results=5)
160
- results = ddg.run(query)
161
- return results[:1500]
162
- except Exception as e:
163
- return f"Search failed: {str(e)}"
164
-
165
- @tool
166
- def wikipedia_search(query: str) -> str:
167
- """
168
- Search Wikipedia for established facts, definitions, historical data.
169
- Best for: biographical info, historical events, scientific concepts, definitions.
170
- """
171
- try:
172
- wiki = WikipediaAPIWrapper(top_k_results=2, doc_content_chars_max=1000)
173
- result = wiki.run(query)
174
- return result
175
- except Exception as e:
176
- return f"Wikipedia search failed: {str(e)}"
177
-
178
- @tool
179
- def python_calculator(code: str) -> str:
180
- """
181
- Execute Python code for calculations, data processing, file operations.
182
- Best for: complex math, data analysis, file processing, calculations.
183
- Always include print() statements to see results.
184
- """
185
- try:
186
- # Enhanced Python environment
187
- exec_globals = {
188
- '__builtins__': __builtins__,
189
- 'math': math,
190
- 'np': np,
191
- 'numpy': np,
192
- 'pd': None, # Will try to import if needed
193
- 'os': os,
194
- 're': re
195
- }
196
 
197
- # Try to import common libraries
198
- try:
199
- import pandas as pd
200
- exec_globals['pd'] = pd
201
- exec_globals['pandas'] = pd
202
- except:
203
- pass
204
 
205
- # Capture output
206
- import io
207
- import sys
208
- old_stdout = sys.stdout
209
- sys.stdout = captured_output = io.StringIO()
210
 
211
- # Execute code
212
- exec(code, exec_globals)
213
 
214
- # Get output
215
- sys.stdout = old_stdout
216
- output = captured_output.getvalue()
217
 
218
- return output if output.strip() else "Code executed successfully (no output)"
219
 
220
- except Exception as e:
221
- return f"Python execution error: {str(e)}"
222
-
223
- @tool
224
- def simple_math(expression: str) -> str:
225
- """
226
- Evaluate simple mathematical expressions quickly.
227
- Best for: basic arithmetic, simple calculations.
228
- Examples: "2+3*4", "sqrt(16)", "sin(pi/4)"
229
- """
230
- try:
231
- # Safe evaluation environment
232
- allowed_names = {
233
- k: v for k, v in math.__dict__.items() if not k.startswith("__")
234
- }
235
- allowed_names.update({
236
- "abs": abs, "round": round, "min": min, "max": max,
237
- "sum": sum, "pow": pow, "divmod": divmod
238
- })
239
 
240
- result = eval(expression, {"__builtins__": {}}, allowed_names)
241
- return str(result)
242
- except Exception as e:
243
- return f"Math error: {str(e)}"
244
-
245
- @tool
246
- def file_analyzer(task: str) -> str:
247
- """
248
- Analyze files in the current directory.
249
- Best for: examining uploaded files, extracting data from files.
250
- """
251
- try:
252
- # List available files
253
- files = [f for f in os.listdir('.') if os.path.isfile(f)]
254
 
255
- result = f"Available files: {files}\n"
256
- result += f"Task: {task}\n"
257
- result += "Use python_calculator for detailed file processing."
258
 
259
- return result
260
- except Exception as e:
261
- return f"File analysis error: {str(e)}"
262
-
263
- return [
264
- # web_search,
265
- wikipedia_search, python_calculator
266
- # , simple_math
267
- # , file_analyzer
268
- ]
269
 
270
- def _create_workflow(self):
271
- """Create optimized LangGraph workflow."""
272
- workflow = StateGraph(dict)
273
-
274
- workflow.add_node("planner", self._planner_node)
275
- workflow.add_node("executor", self._executor_node)
276
- workflow.add_node("validator", self._validator_node)
277
-
278
- workflow.set_entry_point("planner")
279
-
280
- workflow.add_conditional_edges(
281
- "planner",
282
- self._plan_decision,
283
- {
284
- "execute": "executor",
285
- "final": "validator"
286
- }
287
- )
288
 
289
- workflow.add_conditional_edges(
290
- "executor",
291
- self._execution_decision,
292
- {
293
- "continue": "planner",
294
- "validate": "validator"
295
- }
296
- )
297
 
298
- workflow.add_edge("validator", END)
299
 
300
- return workflow
301
 
302
- def _planner_node(self, state: Dict[str, Any]) -> Dict[str, Any]:
303
- """Enhanced planning node focused on GAIA success patterns."""
304
- messages = state.get("messages", [])
305
- step_count = state.get("step_count", 0)
306
- max_steps = state.get("max_steps", 4)
307
- plan_history = state.get("plan_history", [])
308
-
309
- if step_count >= max_steps:
310
- return {
311
- **state,
312
- "final_answer": "Maximum steps reached. Providing best available answer.",
313
- "action_type": "final"
314
- }
315
-
316
- planning_prompt = f"""
317
- You are a general AI assistant. I will ask you a question.
318
- Report your thoughts, and finish your answer with the following template:
319
- FINAL ANSWER: [YOUR FINAL ANSWER]. YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma separated list of numbers and/or strings.
320
- If you are asked for a number, don't use comma to write your number neither use units such as $ or percent sign unless specified otherwise.
321
- If you are asked for a string, don't use articles,
322
- neither abbreviations (e.g. for cities),
323
- and write the digits in plain text unless specified otherwise.
324
- If you are asked for a comma separated list,
325
- apply the above rules depending of whether the element to be put in the list is a number or a string.
326
-
327
- QUESTION: {messages[0]['content'] if messages else 'No question provided'}
328
 
329
- EXECUTION HISTORY: {plan_history}\n
330
-
331
- RÈGLES ABSOLUES:
332
- 1. Lis la question 3 fois avant de commencer
333
- 2. Décompose TOUJOURS la question en sous-problèmes identifiables
334
- 3. Vérifie CHAQUE information avec au moins 2 sources différentes
335
- 4. Pour les calculs: utilise OBLIGATOIREMENT Python pour tous les calculs numériques
336
- 5. Pour les dates: vérifie l'année actuelle (nous sommes en 2025)
337
- 6. JAMAIS de réponse approximative - sois précis au maximum
338
 
339
- PROCESSUS OBLIGATOIRE:
340
- 1. ANALYSE: Que demande exactement la question? Quel type de réponse?
341
- 2. RECHERCHE: Quelles informations spécifiques me manquent?
342
- 3. VÉRIFICATION: Les sources sont-elles cohérentes entre elles?
343
- 4. CALCUL: Si nécessaire, utilise Python pour calculs précis
344
- 5. SYNTHÈSE: Donne une réponse finale précise et concise
345
 
346
- FORMAT DE RÉPONSE FINAL:
347
- - Si c'est un nombre: donne UNIQUEMENT le nombre (ex: "42")
348
- - Si c'est un nom: donne UNIQUEMENT le nom (ex: "Paris")
349
- - Si c'est une date: format précis demandé
350
- - Pas d'explication supplémentaire dans la réponse finale
351
- """
352
 
353
- response = self.model.invoke([{"role": "system", "content": planning_prompt}])
354
- content = response.content.strip()
355
-
356
- if content.startswith("FINAL:"):
357
- answer = content.replace("FINAL:", "").strip()
358
- return {
359
- **state,
360
- "final_answer": answer,
361
- "action_type": "final",
362
- "step_count": step_count
363
- }
364
- elif content.startswith("EXECUTE:"):
365
- # Parse execution command
366
- try:
367
- parts = content.replace("EXECUTE:", "").split("|")
368
- tool_name = parts[0].split()[0].strip()
369
- input_part = [p for p in parts if p.strip().startswith("INPUT:")][0]
370
- tool_input = input_part.replace("INPUT:", "").strip()
371
- goal_part = [p for p in parts if p.strip().startswith("GOAL:")][0] if len(parts) > 2 else ""
372
- goal = goal_part.replace("GOAL:", "").strip() if goal_part else ""
373
 
374
- return {
375
- **state,
376
- "current_tool": tool_name,
377
- "current_input": tool_input,
378
- "current_goal": goal,
379
- "action_type": "execute",
380
- "step_count": step_count + 1
381
- }
382
- except Exception as e:
383
- return {
384
- **state,
385
- "final_answer": f"Planning error: {str(e)}",
386
- "action_type": "final"
387
- }
388
- else:
389
- return {
390
- **state,
391
- "final_answer": content,
392
- "action_type": "final"
393
- }
394
 
395
- def _executor_node(self, state: Dict[str, Any]) -> Dict[str, Any]:
396
- """Execute the planned action."""
397
- tool_name = state.get("current_tool", "")
398
- tool_input = state.get("current_input", "")
399
- goal = state.get("current_goal", "")
400
- plan_history = state.get("plan_history", [])
401
-
402
- # Find and execute tool
403
- tool_map = {tool.name: tool for tool in self.tools}
404
-
405
- # Add flexible matching
406
- tool_matches = {
407
- # "web_search": ["web", "search", "google", "internet"],
408
- "wikipedia_search": ["wiki", "wikipedia"],
409
- "python_calculator": ["python", "code", "calc", "calculate"],
410
- # "simple_math": ["math", "arithmetic"],
411
- # "file_analyzer": ["file", "analyze"]
412
- }
413
-
414
- matched_tool = None
415
- for tool_real_name, aliases in tool_matches.items():
416
- if tool_name.lower() in aliases or tool_name.lower() == tool_real_name.lower():
417
- matched_tool = tool_map.get(tool_real_name)
418
- break
419
-
420
- if not matched_tool:
421
- matched_tool = tool_map.get(tool_name)
422
-
423
- if matched_tool:
424
- try:
425
- result = matched_tool.run(tool_input)
426
- execution_record = f"STEP: Used {tool_name} with '{tool_input}' -> {result[:200]}..."
427
- plan_history.append(execution_record)
428
 
429
- return {
430
- **state,
431
- "last_result": result,
432
- "plan_history": plan_history,
433
- "action_type": "continue"
434
- }
435
- except Exception as e:
436
- error_msg = f"Tool {tool_name} failed: {str(e)}"
437
- plan_history.append(f"ERROR: {error_msg}")
438
- return {
439
- **state,
440
- "last_result": error_msg,
441
- "plan_history": plan_history,
442
- "action_type": "validate"
443
- }
444
- else:
445
- available = list(tool_map.keys())
446
- error_msg = f"Tool '{tool_name}' not found. Available: {available}"
447
- plan_history.append(f"ERROR: {error_msg}")
448
- return {
449
- **state,
450
- "last_result": error_msg,
451
- "plan_history": plan_history,
452
- "action_type": "validate"
453
- }
454
 
455
- def _validator_node(self, state: Dict[str, Any]) -> Dict[str, Any]:
456
- """Validate and finalize the answer."""
457
- final_answer = state.get("final_answer", "")
458
- plan_history = state.get("plan_history", [])
459
- last_result = state.get("last_result", "")
460
-
461
- if not final_answer and last_result:
462
- # Extract answer from last result
463
- validation_prompt = f"""Extract the EXACT answer from this result for the GAIA question.
464
-
465
- QUESTION: {state.get('messages', [{}])[0].get('content', '')}
466
- TOOL RESULT: {last_result}
467
-
468
- Provide ONLY the precise answer - no explanations, no context, just the exact answer required.
469
- Examples:
470
- - If asked for a number: "42"
471
- - If asked for a name: "John Smith"
472
- - If asked for a date: "1969"
473
- - If asked for a yes/no: "Yes"
474
-
475
- EXACT ANSWER:"""
476
 
477
- response = self.model.invoke([{"role": "user", "content": validation_prompt}])
478
- final_answer = response.content.strip()
479
 
480
- # Clean up the answer
481
- final_answer = self._clean_answer(final_answer)
482
 
483
- return {
484
- **state,
485
- "final_answer": final_answer,
486
- "completed": True
487
- }
488
 
489
- def _clean_answer(self, answer: str) -> str:
490
- """Clean and format the final answer for GAIA."""
491
- if not answer:
492
- return "No answer found"
493
 
494
- # Remove common prefixes
495
- prefixes = [
496
- "the answer is", "answer:", "final answer:", "result:",
497
- "exact answer:", "solution:", "response:", "output:"
498
- ]
499
-
500
- cleaned = answer.strip()
501
- for prefix in prefixes:
502
- if cleaned.lower().startswith(prefix):
503
- cleaned = cleaned[len(prefix):].strip()
504
-
505
- # Remove quotes if they wrap the entire answer
506
- if cleaned.startswith('"') and cleaned.endswith('"'):
507
- cleaned = cleaned[1:-1]
508
- if cleaned.startswith("'") and cleaned.endswith("'"):
509
- cleaned = cleaned[1:-1]
510
 
511
- return cleaned
512
 
513
- def _plan_decision(self, state: Dict[str, Any]) -> str:
514
- """Decide whether to execute or finalize."""
515
- return state.get("action_type", "execute")
516
 
517
- def _execution_decision(self, state: Dict[str, Any]) -> str:
518
- """Decide next step after execution."""
519
- return state.get("action_type", "continue")
520
 
521
- def run(self, question: str, max_steps: int = 4) -> str:
522
- """
523
- Run the agent with GAIA-optimized settings.
524
- """
525
- initial_state = {
526
- "messages": [{"role": "user", "content": question}],
527
- "step_count": 0,
528
- "max_steps": max_steps,
529
- "plan_history": [],
530
- "completed": False
531
- }
532
 
533
- try:
534
- result = self.app.invoke(initial_state)
535
- return result.get("final_answer", "No answer generated")
536
 
537
- except Exception as e:
538
- return f"Error: {str(e)}"
539
 
540
 
541
 
 
13
  # --- Constants ---
14
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
15
 
16
+ GAIA_SYSTEM_PROMPT = """
17
+ Tu es un assistant expert spécialisé dans la résolution de questions GAIA.
18
+
19
+ RÈGLES ABSOLUES:
20
+ 1. Lis la question 3 fois avant de commencer
21
+ 2. Décompose TOUJOURS la question en sous-problèmes identifiables
22
+ 3. Vérifie CHAQUE information avec au moins 2 sources différentes
23
+ 4. Pour les calculs: utilise OBLIGATOIREMENT Python pour tous les calculs numériques
24
+ 5. Pour les dates: vérifie l'année actuelle (nous sommes en 2025)
25
+ 6. JAMAIS de réponse approximative - sois précis au maximum
26
+
27
+ PROCESSUS OBLIGATOIRE:
28
+ 1. ANALYSE: Que demande exactement la question? Quel type de réponse?
29
+ 2. RECHERCHE: Quelles informations spécifiques me manquent?
30
+ 3. VÉRIFICATION: Les sources sont-elles cohérentes entre elles?
31
+ 4. CALCUL: Si nécessaire, utilise Python pour calculs précis
32
+ 5. SYNTHÈSE: Donne une réponse finale précise et concise
33
+
34
+ FORMAT DE RÉPONSE FINAL:
35
+ - Si c'est un nombre: donne UNIQUEMENT le nombre (ex: "42")
36
+ - Si c'est un nom: donne UNIQUEMENT le nom (ex: "Paris")
37
+ - Si c'est une date: format précis demandé
38
+ - Pas d'explication supplémentaire dans la réponse finale
39
+
40
+ OUTILS DISPONIBLES:
41
+ - DuckDuckGoSearchTool: pour informations récentes et générales
42
+ - WikipediaSearchTool: pour faits établis et données encyclopédiques
43
+ - PythonInterpreterTool: pour calculs, manipulations de données, dates"""
44
 
45
 
46
  # Configure logging
47
  logging.basicConfig(level=logging.INFO)
48
  logger = logging.getLogger(__name__)
49
 
50
+ class BasicAgent:
51
+ def __init__(self, model_id: Optional[str] = None, api_key: Optional[str] = None):
52
+ """
53
+ Initialize BasicAgent with model and tools.
54
 
55
+ Args:
56
+ model_id: Optional model ID to use
57
+ api_key: Optional API key (will use environment variable if not provided)
58
+ """
59
 
60
+ # Initialize model
61
+ self.model = AzureOpenAIServerModel(
62
+ model_id = "o3-mini",
63
+ azure_endpoint=os.environ.get("AZURE_OPENAI_ENDPOINT"),
64
+ api_key=os.environ.get("AZURE_OPENAI_API_KEY"),
65
+ api_version="2024-12-01-preview" #2024-02-15-preview
66
+ )
67
 
68
+ # Initialize tools
69
+ self.tools = [
70
+ WikipediaSearchTool(),
71
+ # DuckDuckGoSearchTool(),
72
+ PythonInterpreterTool(),
73
+ VisitWebpageTool()
74
+ ]
75
+
76
+ # Create CodeAgent
77
+ self.agent = CodeAgent(
78
+ tools=self.tools,
79
+ model=self.model,
80
+ max_steps=2,
81
+ add_base_tools=True,
82
+ additional_authorized_imports=["pandas","requests"],
83
+ name="gaia_nureyni",
84
+ description="Agent designed to solve GAIA benchmark level 1 questions using tools like DuckDuckGo and LLM for reasoning."
85
+ )
86
 
87
+ # logger.info(f"BasicAgent initialized with model: {self.default_model_id}")
88
 
89
+ def __call__(self, question: str) -> str:
90
+ """
91
+ Make the agent callable directly.
92
 
93
+ Args:
94
+ question: The question to ask the agent
95
 
96
+ Returns:
97
+ Agent's response
98
+ """
99
+ logger.info(f"Agent received question (first 50 chars): {question[:50]}...")
100
 
101
+ try:
102
+ question = f"""{GAIA_SYSTEM_PROMPT}\n
103
+ QUESTION GAIA:
104
+ {question}
105
 
106
+ APPLIQUE LES RÈGLES CI-DESSUS ET RÉSOUS:
107
+ """
108
+ answer = self.agent.run(question)
109
+ logger.info("Agent successfully generated response")
110
+ return answer
111
+ except Exception as e:
112
+ logger.error(f"Agent failed to generate response: {e}")
113
+ raise
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
 
115
+
116
+
117
+
118
+
119
+
120
+
121
+ # # from langgraph.gr import StateGraph, END
122
+ # from langgraph.graph import StateGraph, END
123
+ # from langchain_core.messages import HumanMessage, AIMessage
124
+ # from langchain_openai import AzureChatOpenAI
125
+ # from langchain_community.tools import WikipediaQueryRun, DuckDuckGoSearchRun
126
+ # from langchain_community.utilities import WikipediaAPIWrapper, DuckDuckGoSearchAPIWrapper
127
+ # from langchain_experimental.tools import PythonREPLTool
128
+ # from langchain_core.tools import tool
129
+ # import os
130
+ # import math
131
+ # import numpy as np
132
+ # import re
133
+ # from typing import Optional, Dict, Any, List
134
+ # from langchain_core.agents import AgentAction, AgentFinish
135
+
136
+ # class BasicAgent:
137
+ # def __init__(self, model_id: Optional[str] = None, api_key: Optional[str] = None):
138
+ # """
139
+ # Initialize BasicAgent optimized for GAIA benchmark success.
140
+ # """
141
 
142
+ # # Initialize model
143
+ # self.model = AzureChatOpenAI(
144
+ # deployment_name="o3-mini",
145
+ # azure_endpoint=os.environ.get("AZURE_OPENAI_ENDPOINT"),
146
+ # api_key=os.environ.get("AZURE_OPENAI_API_KEY"),
147
+ # api_version="2024-12-01-preview"
148
+ # )
149
 
150
+ # # Initialize tools
151
+ # self.tools = self._initialize_tools()
152
+
153
+ # # Create LangGraph workflow
154
+ # self.workflow = self._create_workflow()
155
+ # self.app = self.workflow.compile()
156
+
157
+ # def _initialize_tools(self):
158
+ # """Initialize tools with GAIA-specific optimizations."""
159
+
160
+ # @tool
161
+ # def web_search(query: str) -> str:
162
+ # """
163
+ # Search for current information on the web. Use specific, targeted queries.
164
+ # Best for: recent events, current data, specific facts, news.
165
+ # """
166
+ # try:
167
+ # ddg = DuckDuckGoSearchAPIWrapper(max_results=5)
168
+ # results = ddg.run(query)
169
+ # return results[:1500]
170
+ # except Exception as e:
171
+ # return f"Search failed: {str(e)}"
172
+
173
+ # @tool
174
+ # def wikipedia_search(query: str) -> str:
175
+ # """
176
+ # Search Wikipedia for established facts, definitions, historical data.
177
+ # Best for: biographical info, historical events, scientific concepts, definitions.
178
+ # """
179
+ # try:
180
+ # wiki = WikipediaAPIWrapper(top_k_results=2, doc_content_chars_max=1000)
181
+ # result = wiki.run(query)
182
+ # return result
183
+ # except Exception as e:
184
+ # return f"Wikipedia search failed: {str(e)}"
185
+
186
+ # @tool
187
+ # def python_calculator(code: str) -> str:
188
+ # """
189
+ # Execute Python code for calculations, data processing, file operations.
190
+ # Best for: complex math, data analysis, file processing, calculations.
191
+ # Always include print() statements to see results.
192
+ # """
193
+ # try:
194
+ # # Enhanced Python environment
195
+ # exec_globals = {
196
+ # '__builtins__': __builtins__,
197
+ # 'math': math,
198
+ # 'np': np,
199
+ # 'numpy': np,
200
+ # 'pd': None, # Will try to import if needed
201
+ # 'os': os,
202
+ # 're': re
203
+ # }
204
 
205
+ # # Try to import common libraries
206
+ # try:
207
+ # import pandas as pd
208
+ # exec_globals['pd'] = pd
209
+ # exec_globals['pandas'] = pd
210
+ # except:
211
+ # pass
212
 
213
+ # # Capture output
214
+ # import io
215
+ # import sys
216
+ # old_stdout = sys.stdout
217
+ # sys.stdout = captured_output = io.StringIO()
218
 
219
+ # # Execute code
220
+ # exec(code, exec_globals)
221
 
222
+ # # Get output
223
+ # sys.stdout = old_stdout
224
+ # output = captured_output.getvalue()
225
 
226
+ # return output if output.strip() else "Code executed successfully (no output)"
227
 
228
+ # except Exception as e:
229
+ # return f"Python execution error: {str(e)}"
230
+
231
+ # @tool
232
+ # def simple_math(expression: str) -> str:
233
+ # """
234
+ # Evaluate simple mathematical expressions quickly.
235
+ # Best for: basic arithmetic, simple calculations.
236
+ # Examples: "2+3*4", "sqrt(16)", "sin(pi/4)"
237
+ # """
238
+ # try:
239
+ # # Safe evaluation environment
240
+ # allowed_names = {
241
+ # k: v for k, v in math.__dict__.items() if not k.startswith("__")
242
+ # }
243
+ # allowed_names.update({
244
+ # "abs": abs, "round": round, "min": min, "max": max,
245
+ # "sum": sum, "pow": pow, "divmod": divmod
246
+ # })
247
 
248
+ # result = eval(expression, {"__builtins__": {}}, allowed_names)
249
+ # return str(result)
250
+ # except Exception as e:
251
+ # return f"Math error: {str(e)}"
252
+
253
+ # @tool
254
+ # def file_analyzer(task: str) -> str:
255
+ # """
256
+ # Analyze files in the current directory.
257
+ # Best for: examining uploaded files, extracting data from files.
258
+ # """
259
+ # try:
260
+ # # List available files
261
+ # files = [f for f in os.listdir('.') if os.path.isfile(f)]
262
 
263
+ # result = f"Available files: {files}\n"
264
+ # result += f"Task: {task}\n"
265
+ # result += "Use python_calculator for detailed file processing."
266
 
267
+ # return result
268
+ # except Exception as e:
269
+ # return f"File analysis error: {str(e)}"
270
+
271
+ # return [
272
+ # # web_search,
273
+ # wikipedia_search, python_calculator
274
+ # # , simple_math
275
+ # # , file_analyzer
276
+ # ]
277
 
278
+ # def _create_workflow(self):
279
+ # """Create optimized LangGraph workflow."""
280
+ # workflow = StateGraph(dict)
281
+
282
+ # workflow.add_node("planner", self._planner_node)
283
+ # workflow.add_node("executor", self._executor_node)
284
+ # workflow.add_node("validator", self._validator_node)
285
+
286
+ # workflow.set_entry_point("planner")
287
+
288
+ # workflow.add_conditional_edges(
289
+ # "planner",
290
+ # self._plan_decision,
291
+ # {
292
+ # "execute": "executor",
293
+ # "final": "validator"
294
+ # }
295
+ # )
296
 
297
+ # workflow.add_conditional_edges(
298
+ # "executor",
299
+ # self._execution_decision,
300
+ # {
301
+ # "continue": "planner",
302
+ # "validate": "validator"
303
+ # }
304
+ # )
305
 
306
+ # workflow.add_edge("validator", END)
307
 
308
+ # return workflow
309
 
310
+ # def _planner_node(self, state: Dict[str, Any]) -> Dict[str, Any]:
311
+ # """Enhanced planning node focused on GAIA success patterns."""
312
+ # messages = state.get("messages", [])
313
+ # step_count = state.get("step_count", 0)
314
+ # max_steps = state.get("max_steps", 4)
315
+ # plan_history = state.get("plan_history", [])
316
+
317
+ # if step_count >= max_steps:
318
+ # return {
319
+ # **state,
320
+ # "final_answer": "Maximum steps reached. Providing best available answer.",
321
+ # "action_type": "final"
322
+ # }
323
+
324
+ # planning_prompt = f"""
325
+ # You are a general AI assistant. I will ask you a question.
326
+ # Report your thoughts, and finish your answer with the following template:
327
+ # FINAL ANSWER: [YOUR FINAL ANSWER]. YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma separated list of numbers and/or strings.
328
+ # If you are asked for a number, don't use comma to write your number neither use units such as $ or percent sign unless specified otherwise.
329
+ # If you are asked for a string, don't use articles,
330
+ # neither abbreviations (e.g. for cities),
331
+ # and write the digits in plain text unless specified otherwise.
332
+ # If you are asked for a comma separated list,
333
+ # apply the above rules depending of whether the element to be put in the list is a number or a string.
334
+
335
+ # QUESTION: {messages[0]['content'] if messages else 'No question provided'}
336
 
337
+ # EXECUTION HISTORY: {plan_history}\n
338
+
339
+ # RÈGLES ABSOLUES:
340
+ # 1. Lis la question 3 fois avant de commencer
341
+ # 2. Décompose TOUJOURS la question en sous-problèmes identifiables
342
+ # 3. Vérifie CHAQUE information avec au moins 2 sources différentes
343
+ # 4. Pour les calculs: utilise OBLIGATOIREMENT Python pour tous les calculs numériques
344
+ # 5. Pour les dates: vérifie l'année actuelle (nous sommes en 2025)
345
+ # 6. JAMAIS de réponse approximative - sois précis au maximum
346
 
347
+ # PROCESSUS OBLIGATOIRE:
348
+ # 1. ANALYSE: Que demande exactement la question? Quel type de réponse?
349
+ # 2. RECHERCHE: Quelles informations spécifiques me manquent?
350
+ # 3. VÉRIFICATION: Les sources sont-elles cohérentes entre elles?
351
+ # 4. CALCUL: Si nécessaire, utilise Python pour calculs précis
352
+ # 5. SYNTHÈSE: Donne une réponse finale précise et concise
353
 
354
+ # FORMAT DE RÉPONSE FINAL:
355
+ # - Si c'est un nombre: donne UNIQUEMENT le nombre (ex: "42")
356
+ # - Si c'est un nom: donne UNIQUEMENT le nom (ex: "Paris")
357
+ # - Si c'est une date: format précis demandé
358
+ # - Pas d'explication supplémentaire dans la réponse finale
359
+ # """
360
 
361
+ # response = self.model.invoke([{"role": "system", "content": planning_prompt}])
362
+ # content = response.content.strip()
363
+
364
+ # if content.startswith("FINAL:"):
365
+ # answer = content.replace("FINAL:", "").strip()
366
+ # return {
367
+ # **state,
368
+ # "final_answer": answer,
369
+ # "action_type": "final",
370
+ # "step_count": step_count
371
+ # }
372
+ # elif content.startswith("EXECUTE:"):
373
+ # # Parse execution command
374
+ # try:
375
+ # parts = content.replace("EXECUTE:", "").split("|")
376
+ # tool_name = parts[0].split()[0].strip()
377
+ # input_part = [p for p in parts if p.strip().startswith("INPUT:")][0]
378
+ # tool_input = input_part.replace("INPUT:", "").strip()
379
+ # goal_part = [p for p in parts if p.strip().startswith("GOAL:")][0] if len(parts) > 2 else ""
380
+ # goal = goal_part.replace("GOAL:", "").strip() if goal_part else ""
381
 
382
+ # return {
383
+ # **state,
384
+ # "current_tool": tool_name,
385
+ # "current_input": tool_input,
386
+ # "current_goal": goal,
387
+ # "action_type": "execute",
388
+ # "step_count": step_count + 1
389
+ # }
390
+ # except Exception as e:
391
+ # return {
392
+ # **state,
393
+ # "final_answer": f"Planning error: {str(e)}",
394
+ # "action_type": "final"
395
+ # }
396
+ # else:
397
+ # return {
398
+ # **state,
399
+ # "final_answer": content,
400
+ # "action_type": "final"
401
+ # }
402
 
403
+ # def _executor_node(self, state: Dict[str, Any]) -> Dict[str, Any]:
404
+ # """Execute the planned action."""
405
+ # tool_name = state.get("current_tool", "")
406
+ # tool_input = state.get("current_input", "")
407
+ # goal = state.get("current_goal", "")
408
+ # plan_history = state.get("plan_history", [])
409
+
410
+ # # Find and execute tool
411
+ # tool_map = {tool.name: tool for tool in self.tools}
412
+
413
+ # # Add flexible matching
414
+ # tool_matches = {
415
+ # # "web_search": ["web", "search", "google", "internet"],
416
+ # "wikipedia_search": ["wiki", "wikipedia"],
417
+ # "python_calculator": ["python", "code", "calc", "calculate"],
418
+ # # "simple_math": ["math", "arithmetic"],
419
+ # # "file_analyzer": ["file", "analyze"]
420
+ # }
421
+
422
+ # matched_tool = None
423
+ # for tool_real_name, aliases in tool_matches.items():
424
+ # if tool_name.lower() in aliases or tool_name.lower() == tool_real_name.lower():
425
+ # matched_tool = tool_map.get(tool_real_name)
426
+ # break
427
+
428
+ # if not matched_tool:
429
+ # matched_tool = tool_map.get(tool_name)
430
+
431
+ # if matched_tool:
432
+ # try:
433
+ # result = matched_tool.run(tool_input)
434
+ # execution_record = f"STEP: Used {tool_name} with '{tool_input}' -> {result[:200]}..."
435
+ # plan_history.append(execution_record)
436
 
437
+ # return {
438
+ # **state,
439
+ # "last_result": result,
440
+ # "plan_history": plan_history,
441
+ # "action_type": "continue"
442
+ # }
443
+ # except Exception as e:
444
+ # error_msg = f"Tool {tool_name} failed: {str(e)}"
445
+ # plan_history.append(f"ERROR: {error_msg}")
446
+ # return {
447
+ # **state,
448
+ # "last_result": error_msg,
449
+ # "plan_history": plan_history,
450
+ # "action_type": "validate"
451
+ # }
452
+ # else:
453
+ # available = list(tool_map.keys())
454
+ # error_msg = f"Tool '{tool_name}' not found. Available: {available}"
455
+ # plan_history.append(f"ERROR: {error_msg}")
456
+ # return {
457
+ # **state,
458
+ # "last_result": error_msg,
459
+ # "plan_history": plan_history,
460
+ # "action_type": "validate"
461
+ # }
462
 
463
+ # def _validator_node(self, state: Dict[str, Any]) -> Dict[str, Any]:
464
+ # """Validate and finalize the answer."""
465
+ # final_answer = state.get("final_answer", "")
466
+ # plan_history = state.get("plan_history", [])
467
+ # last_result = state.get("last_result", "")
468
+
469
+ # if not final_answer and last_result:
470
+ # # Extract answer from last result
471
+ # validation_prompt = f"""Extract the EXACT answer from this result for the GAIA question.
472
+
473
+ # QUESTION: {state.get('messages', [{}])[0].get('content', '')}
474
+ # TOOL RESULT: {last_result}
475
+
476
+ # Provide ONLY the precise answer - no explanations, no context, just the exact answer required.
477
+ # Examples:
478
+ # - If asked for a number: "42"
479
+ # - If asked for a name: "John Smith"
480
+ # - If asked for a date: "1969"
481
+ # - If asked for a yes/no: "Yes"
482
+
483
+ # EXACT ANSWER:"""
484
 
485
+ # response = self.model.invoke([{"role": "user", "content": validation_prompt}])
486
+ # final_answer = response.content.strip()
487
 
488
+ # # Clean up the answer
489
+ # final_answer = self._clean_answer(final_answer)
490
 
491
+ # return {
492
+ # **state,
493
+ # "final_answer": final_answer,
494
+ # "completed": True
495
+ # }
496
 
497
+ # def _clean_answer(self, answer: str) -> str:
498
+ # """Clean and format the final answer for GAIA."""
499
+ # if not answer:
500
+ # return "No answer found"
501
 
502
+ # # Remove common prefixes
503
+ # prefixes = [
504
+ # "the answer is", "answer:", "final answer:", "result:",
505
+ # "exact answer:", "solution:", "response:", "output:"
506
+ # ]
507
+
508
+ # cleaned = answer.strip()
509
+ # for prefix in prefixes:
510
+ # if cleaned.lower().startswith(prefix):
511
+ # cleaned = cleaned[len(prefix):].strip()
512
+
513
+ # # Remove quotes if they wrap the entire answer
514
+ # if cleaned.startswith('"') and cleaned.endswith('"'):
515
+ # cleaned = cleaned[1:-1]
516
+ # if cleaned.startswith("'") and cleaned.endswith("'"):
517
+ # cleaned = cleaned[1:-1]
518
 
519
+ # return cleaned
520
 
521
+ # def _plan_decision(self, state: Dict[str, Any]) -> str:
522
+ # """Decide whether to execute or finalize."""
523
+ # return state.get("action_type", "execute")
524
 
525
+ # def _execution_decision(self, state: Dict[str, Any]) -> str:
526
+ # """Decide next step after execution."""
527
+ # return state.get("action_type", "continue")
528
 
529
+ # def run(self, question: str, max_steps: int = 4) -> str:
530
+ # """
531
+ # Run the agent with GAIA-optimized settings.
532
+ # """
533
+ # initial_state = {
534
+ # "messages": [{"role": "user", "content": question}],
535
+ # "step_count": 0,
536
+ # "max_steps": max_steps,
537
+ # "plan_history": [],
538
+ # "completed": False
539
+ # }
540
 
541
+ # try:
542
+ # result = self.app.invoke(initial_state)
543
+ # return result.get("final_answer", "No answer generated")
544
 
545
+ # except Exception as e:
546
+ # return f"Error: {str(e)}"
547
 
548
 
549