Diego-Fco commited on
Commit
602a16c
·
1 Parent(s): 66aa5ca

feat: Implement GAIA Agent v2 with enhanced question handling and strategies

Browse files

- Added configuration and constants for GAIA Agent v2 in config.py
- Developed custom tools for web content retrieval and YouTube video information in tools.py
- Created utility functions for file handling and question type detection in utils.py
- Built the main agent logic with specific strategies for different question types in agent.py
- Simplified main execution script for processing questions and submitting results in main_simple.py
- Updated requirements for necessary dependencies in requirements-v2.txt

Files changed (12) hide show
  1. agent.py +240 -0
  2. app.py +174 -120
  3. config.py +53 -0
  4. requirements.txt +15 -1
  5. tools.py +89 -0
  6. utils.py +180 -0
  7. v2/agent.py +251 -0
  8. v2/config.py +47 -0
  9. v2/main_simple.py +227 -0
  10. v2/requirements-v2.txt +20 -0
  11. v2/tools.py +125 -0
  12. v2/utils.py +212 -0
agent.py ADDED
@@ -0,0 +1,240 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ from contextlib import redirect_stdout
3
+
4
+ from smolagents import (
5
+ CodeAgent,
6
+ LiteLLMModel,
7
+ InferenceClientModel,
8
+ DuckDuckGoSearchTool,
9
+ VisitWebpageTool,
10
+ WikipediaSearchTool
11
+ )
12
+
13
+ from config import (
14
+ USE_LOCAL_MODEL,
15
+ OLLAMA_MODEL_ID, OLLAMA_API_BASE, OLLAMA_API_KEY,
16
+ HF_MODEL_ID, HF_TOKEN,
17
+ MAX_STEPS, VERBOSITY_LEVEL, AUTHORIZED_IMPORTS,
18
+ QUESTION_TYPES
19
+ )
20
+ from tools import smart_visit, get_youtube_info
21
+ from utils import clean_answer, clean_ansi_codes
22
+
23
+
24
+ class EnhancedAgent:
25
+ """Agente mejorado con estrategias específicas por tipo de pregunta."""
26
+
27
+ def __init__(self):
28
+ print(f" 🤖 Inicializando agente...")
29
+
30
+ if USE_LOCAL_MODEL:
31
+ # Usar Ollama local
32
+ self.model = LiteLLMModel(
33
+ model_id=OLLAMA_MODEL_ID,
34
+ api_base=OLLAMA_API_BASE,
35
+ api_key=OLLAMA_API_KEY
36
+ )
37
+ print(f" 📦 Modelo: {OLLAMA_MODEL_ID} (local)")
38
+ else:
39
+ # Usar HuggingFace API
40
+ self.model = InferenceClientModel(
41
+ model_id=HF_MODEL_ID,
42
+ token=HF_TOKEN
43
+ )
44
+ print(f" ☁️ Modelo: {HF_MODEL_ID} (HuggingFace)")
45
+
46
+ search_tool = DuckDuckGoSearchTool()
47
+ visit_tool = VisitWebpageTool()
48
+ wiki_tool = WikipediaSearchTool()
49
+
50
+ self.agent = CodeAgent(
51
+ tools=[search_tool, visit_tool, wiki_tool, smart_visit, get_youtube_info],
52
+ model=self.model,
53
+ max_steps=MAX_STEPS,
54
+ verbosity_level=VERBOSITY_LEVEL,
55
+ additional_authorized_imports=AUTHORIZED_IMPORTS
56
+ )
57
+
58
+ def build_prompt(self, question, local_file, question_type):
59
+ """Construye prompt optimizado según el tipo de pregunta."""
60
+
61
+ base_context = f"""TASK: You are solving a GAIA benchmark question. Be precise and methodical.
62
+
63
+ QUESTION: {question}
64
+ """
65
+
66
+ strategies = {
67
+ QUESTION_TYPES['YOUTUBE_VIDEO']: """
68
+ STRATEGY - YouTube Video:
69
+ 1. Extract the video ID from the URL in the question
70
+ 2. Use get_youtube_info tool to get context
71
+ 3. Search DuckDuckGo for: "[video_id] transcript" or "[video_id] [keywords_from_question]"
72
+ 4. Look for Reddit threads, forums, or blogs discussing this video
73
+ 5. Find the specific information requested
74
+
75
+ IMPORTANT: You CANNOT watch the video. Search for transcripts or discussions online.
76
+ """,
77
+
78
+ QUESTION_TYPES['IMAGE_FILE']: f"""
79
+ STRATEGY - Image File:
80
+ 1. File '{local_file}' is in current directory
81
+ 2. You CANNOT read image files directly with Python
82
+ 3. Search online for: "{local_file}" OR search for keywords from the question
83
+ 4. Look for discussions, analysis, or descriptions of this image online
84
+ 5. For chess positions: search "[piece positions] chess position solution"
85
+
86
+ IMPORTANT: Do NOT attempt cv2, PIL, or any image processing. Search online instead.
87
+ """,
88
+
89
+ QUESTION_TYPES['AUDIO_FILE']: f"""
90
+ STRATEGY - Audio File:
91
+ 1. File '{local_file}' is in current directory
92
+ 2. You CANNOT play or transcribe audio with Python
93
+ 3. Search online for: "{local_file}" OR the exact question text
94
+ 4. Look for transcripts, Reddit threads, or forums discussing this audio
95
+
96
+ IMPORTANT: Do NOT attempt librosa, soundfile, or audio processing. Search online.
97
+ """,
98
+
99
+ QUESTION_TYPES['DATA_FILE']: f"""
100
+ STRATEGY - Data File (Excel/CSV):
101
+ 1. File '{local_file}' is in current directory
102
+ 2. Use pandas to read: pd.read_excel('{local_file}') or pd.read_csv('{local_file}')
103
+ 3. Explore columns with df.columns and df.head()
104
+ 4. Filter and sum/count as needed
105
+ 5. Double-check calculations
106
+
107
+ CODE TEMPLATE:
108
+ ```python
109
+ import pandas as pd
110
+ df = pd.read_excel('{local_file}') # or read_csv
111
+ print(df.columns)
112
+ print(df.head())
113
+ # ... your analysis
114
+ ```
115
+ """,
116
+
117
+ QUESTION_TYPES['CODE_FILE']: f"""
118
+ STRATEGY - Code File:
119
+ 1. File '{local_file}' is in current directory
120
+ 2. Read it with open('{local_file}', 'r').read()
121
+ 3. Analyze the code logic carefully
122
+ 4. If needed, execute it: exec(open('{local_file}').read())
123
+ 5. Return the requested output
124
+
125
+ IMPORTANT: Read and understand before executing.
126
+ """,
127
+
128
+ QUESTION_TYPES['WIKIPEDIA']: """
129
+ STRATEGY - Wikipedia Search:
130
+ 1. Identify the exact topic/entity from the question
131
+ 2. Use web_search to find the correct Wikipedia article URL
132
+ 3. Use smart_visit to read the Wikipedia page content
133
+ 4. Extract the specific information requested (dates, numbers, names, etc.)
134
+ 5. For counting tasks: CREATE A PYTHON LIST with each item, then count with len()
135
+
136
+ TIPS:
137
+ - Search: "[topic] Wikipedia 2022" for latest version
138
+ - For discographies: look for "Discography" section or table
139
+ - For featured articles: search "Wikipedia Featured Article [topic] [date]"
140
+ - ALWAYS create a list and count programmatically, don't count manually
141
+ """,
142
+
143
+ QUESTION_TYPES['COUNTING']: """
144
+ STRATEGY - Counting Task:
145
+ 1. Research and LIST all items first (don't just count)
146
+ 2. Use smart_visit to get complete data from Wikipedia or official sources
147
+ 3. Store items in a Python list: items = []
148
+ 4. Count with len(items) and verify manually
149
+ 5. Double-check you haven't missed anything
150
+
151
+ IMPORTANT: First collect ALL items, THEN count. Show your work.
152
+ """,
153
+
154
+ QUESTION_TYPES['TEXT_MANIPULATION']: """
155
+ STRATEGY - Text Manipulation:
156
+ 1. Read the question VERY carefully
157
+ 2. If text is backwards, reverse it: text[::-1]
158
+ 3. If asking for opposite: use logic (left ↔ right, up ↔ down, etc.)
159
+ 4. Return ONLY the answer, no explanation
160
+
161
+ EXAMPLE: ".rewsna eht sa 'tfel' drow..."
162
+ → Reverse to read: "...word 'left' as the answer."
163
+ → Opposite of "left" is "right"
164
+ """,
165
+
166
+ QUESTION_TYPES['GENERAL']: """
167
+ STRATEGY - General Research:
168
+ 1. Break down the question into sub-tasks
169
+ 2. Use web_search for initial research
170
+ 3. Use smart_visit to read relevant pages in detail
171
+ 4. Cross-reference multiple sources if needed
172
+ 5. Extract the precise answer requested
173
+
174
+ TIPS:
175
+ - Be specific in searches: include years, full names, exact terms
176
+ - Read carefully - answers are often in tables, lists, or footnotes
177
+ """
178
+ }
179
+
180
+ strategy = strategies.get(question_type, strategies[QUESTION_TYPES['GENERAL']])
181
+
182
+ output_format = """
183
+ FINAL OUTPUT FORMAT:
184
+ Return ONLY the answer value. No markdown, no "The answer is", no explanations.
185
+
186
+ Examples of GOOD answers:
187
+ - "3"
188
+ - "right"
189
+ - "Ian Rose"
190
+ - "14.50"
191
+ - "d5, e2"
192
+
193
+ Examples of BAD answers:
194
+ - "The answer is 3"
195
+ - "**3**"
196
+ - "Based on my research, the answer is 3."
197
+ """
198
+
199
+ return base_context + strategy + output_format
200
+
201
+ def solve(self, question, local_file=None, question_type=None):
202
+ """
203
+ Resuelve una pregunta con estrategia optimizada.
204
+
205
+ Args:
206
+ question: Texto de la pregunta
207
+ local_file: Ruta al archivo adjunto (opcional)
208
+ question_type: Tipo de pregunta detectado
209
+
210
+ Returns:
211
+ tuple: (respuesta, logs de ejecución)
212
+ """
213
+ if question_type is None:
214
+ question_type = QUESTION_TYPES['GENERAL']
215
+
216
+ prompt = self.build_prompt(question, local_file, question_type)
217
+
218
+ log_capture = io.StringIO()
219
+ final_answer = "Error"
220
+
221
+ try:
222
+ with redirect_stdout(log_capture):
223
+ answer = self.agent.run(prompt)
224
+ final_answer = clean_answer(answer)
225
+
226
+ # Si está vacío después de limpiar, buscar en logs
227
+ if not final_answer or final_answer == "Error":
228
+ logs = log_capture.getvalue()
229
+ for line in reversed(logs.split('\n')):
230
+ if line.strip() and not any(x in line for x in ['===', '---', 'Step', 'Tool']):
231
+ potential_answer = line.strip()
232
+ if len(potential_answer) < 200:
233
+ final_answer = potential_answer
234
+ break
235
+
236
+ except Exception as e:
237
+ log_capture.write(f"\n❌ CRITICAL ERROR: {e}\n")
238
+ final_answer = "Error"
239
+
240
+ return final_answer, clean_ansi_codes(log_capture.getvalue())
app.py CHANGED
@@ -1,96 +1,29 @@
 
 
 
 
1
  import os
 
 
 
2
  import gradio as gr
3
  import requests
4
  import pandas as pd
5
- import shutil
6
- from smolagents import CodeAgent, InferenceClientModel, DuckDuckGoSearchTool, VisitWebpageTool
7
 
8
- # --- Constants ---
9
- DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
 
 
10
 
11
- def download_file(task_id, filename):
12
- """
13
- Downloads the file associated with a task_id if it exists.
14
- """
15
- file_url = f"{DEFAULT_API_URL}/files/{task_id}"
16
- try:
17
- response = requests.get(file_url, stream=True)
18
- if response.status_code == 200:
19
- # Try to get filename from headers, otherwise use task_id
20
- if not filename:
21
- if "content-disposition" in response.headers:
22
- filename = response.headers["content-disposition"].split("filename=")[1].strip('"')
23
- else:
24
- filename = f"{task_id}_file"
25
-
26
- with open(filename, 'wb') as f:
27
- shutil.copyfileobj(response.raw, f)
28
- print(f"Downloaded file: {filename}")
29
- return filename
30
- except Exception as e:
31
- print(f"Failed to download file for task {task_id}: {e}")
32
- return None
33
-
34
- class BasicAgent:
35
- def __init__(self):
36
-
37
- model_id = "Qwen/Qwen2.5-Coder-32B-Instruct"
38
-
39
- search_tool = DuckDuckGoSearchTool()
40
- visit_tool = VisitWebpageTool()
41
-
42
- self.model = InferenceClientModel(
43
- model_id=model_id,
44
- token=os.getenv("HF_TOKEN")
45
- )
46
-
47
- self.agent = CodeAgent(
48
- tools=[search_tool, visit_tool],
49
- model=self.model,
50
- max_steps=12,
51
- verbosity_level=1,
52
- additional_authorized_imports=[
53
- 'csv', 'pandas', 'bs4', 'requests', 're', 'collections',
54
- 'itertools', 'io', 'json', 'math', 'statistics', 'queue',
55
- 'xml', 'datetime', 'time'
56
- ]
57
- )
58
-
59
- def __call__(self, question: str, file_path: str = None) -> str:
60
- # Prompt dinámico que avisa si hay un archivo local disponible
61
- file_instruction = ""
62
- if file_path:
63
- file_instruction = f"A file named '{file_path}' has been downloaded to your current directory. Use python to read it if relevant."
64
 
65
- prompt = f"""
66
- TASK: Answer the following question accurately.
67
- QUESTION: {question}
68
-
69
- {file_instruction}
70
-
71
- CRITICAL RULES:
72
- 1. If a file is mentioned and downloaded, use pandas or standard python libraries to read it directly.
73
- 2. Use 'visit_webpage' if you find promising URLs.
74
- 3. FINAL OUTPUT: Return ONLY the raw answer string.
75
- - NO markdown (no ```), NO explanations.
76
- - Just the value (e.g., "14", "Paris", "519").
77
- """
78
-
79
- try:
80
- answer = self.agent.run(prompt)
81
- clean = str(answer).strip()
82
- # Limpieza agresiva de basura en la respuesta
83
- if "Final Answer:" in clean:
84
- clean = clean.split("Final Answer:")[-1].strip()
85
- if "```" in clean:
86
- clean = clean.replace("```", "")
87
- return clean
88
- except Exception as e:
89
- print(f"Error executing agent: {e}")
90
- return "Error"
91
 
92
  def run_and_submit_all(profile: gr.OAuthProfile | None):
 
93
  space_id = os.getenv("SPACE_ID")
 
94
  if profile:
95
  username = f"{profile.username}"
96
  else:
@@ -99,75 +32,196 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
99
  api_url = DEFAULT_API_URL
100
  questions_url = f"{api_url}/questions"
101
  submit_url = f"{api_url}/submit"
102
- agent_code = f"[https://huggingface.co/spaces/](https://huggingface.co/spaces/){space_id}/tree/main"
103
 
 
104
  try:
 
105
  response = requests.get(questions_url, timeout=15)
106
  questions_data = response.json()
 
107
  except Exception as e:
108
  return f"Error fetching questions: {e}", None
109
 
 
 
 
 
110
  results_log = []
111
  answers_payload = []
 
112
 
113
- print(f"Starting run on {len(questions_data)} questions...")
 
 
114
 
115
- for item in questions_data:
116
  task_id = item.get("task_id")
117
- question_text = item.get("question")
118
- if not task_id: continue
119
 
120
- print(f"Processing Task: {task_id}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
- # 1. Intentar descargar archivo si la pregunta lo sugiere
123
- local_filename = None
124
- # Comprobamos si la pregunta menciona archivos comunes
125
- if "attached" in question_text.lower() or "file" in question_text.lower() or ".xlsx" in question_text.lower() or ".mp3" in question_text.lower():
126
- # Intentamos adivinar la extensión o nombre
127
- filename_hint = "data.xlsx" if "Excel" in question_text else "data.txt"
128
- if ".mp3" in question_text: filename_hint = "audio.mp3"
129
- if ".csv" in question_text: filename_hint = "data.csv"
130
-
131
- # Descargamos el archivo real de la API
132
- local_filename = download_file(task_id, filename_hint)
133
-
134
  try:
135
- # 2. Reiniciar agente (Memoria Limpia)
136
- current_agent = BasicAgent()
 
 
 
137
 
138
- # 3. Ejecutar con contexto del archivo
139
- submitted_answer = current_agent(question_text, file_path=local_filename)
140
-
141
- # 4. Limpieza de seguridad para envío
142
  if len(submitted_answer) > 200:
143
  submitted_answer = submitted_answer[:200]
144
 
145
- answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
146
- results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
147
-
148
  except Exception as e:
149
- results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": "Error"})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
 
151
- # Submit
152
- submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
 
 
 
 
 
 
 
153
 
154
  try:
155
  response = requests.post(submit_url, json=submission_data, timeout=60)
156
  try:
157
  result = response.json()
158
- return f"Submission Successful! Score: {result.get('score', 'N/A')}%", pd.DataFrame(results_log)
159
- except:
 
 
 
 
 
 
 
 
 
 
 
 
160
  return f"Submission Failed (Server Error): {response.text}", pd.DataFrame(results_log)
161
  except Exception as e:
162
  return f"Submission Failed: {e}", pd.DataFrame(results_log)
163
 
 
 
 
 
 
164
  with gr.Blocks() as demo:
165
- gr.Markdown("# GAIA Agent - Optimized for Files & Logic")
 
 
 
 
 
 
 
 
 
 
 
 
166
  gr.LoginButton()
167
- run_button = gr.Button("Run Evaluation & Submit All Answers")
168
- status_output = gr.Textbox(label="Status", interactive=False)
169
- results_table = gr.DataFrame(label="Results", wrap=True)
170
- run_button.click(fn=run_and_submit_all, outputs=[status_output, results_table])
 
 
 
 
 
 
 
 
 
 
 
 
 
171
 
172
  if __name__ == "__main__":
 
173
  demo.launch()
 
1
+ """
2
+ GAIA Agent v2 - Interfaz Principal Gradio
3
+ Agente mejorado con estrategias específicas por tipo de pregunta
4
+ """
5
  import os
6
+ import re
7
+ import json
8
+ import time
9
  import gradio as gr
10
  import requests
11
  import pandas as pd
 
 
12
 
13
+ # Importar módulos locales
14
+ from config import DEFAULT_API_URL
15
+ from agent import EnhancedAgent
16
+ from utils import detect_question_type, download_file_for_task
17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
+ # ============================================================================
20
+ # FUNCIONES PRINCIPALES DE GRADIO
21
+ # ============================================================================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
  def run_and_submit_all(profile: gr.OAuthProfile | None):
24
+ """Ejecuta el agente en todas las preguntas y envía los resultados."""
25
  space_id = os.getenv("SPACE_ID")
26
+
27
  if profile:
28
  username = f"{profile.username}"
29
  else:
 
32
  api_url = DEFAULT_API_URL
33
  questions_url = f"{api_url}/questions"
34
  submit_url = f"{api_url}/submit"
35
+ agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
36
 
37
+ # Cargar preguntas
38
  try:
39
+ print("📥 Cargando preguntas del servidor...")
40
  response = requests.get(questions_url, timeout=15)
41
  questions_data = response.json()
42
+ print(f" ✓ {len(questions_data)} preguntas cargadas")
43
  except Exception as e:
44
  return f"Error fetching questions: {e}", None
45
 
46
+ # Crear agente (reutilizable)
47
+ print("\n🤖 Creando agente...")
48
+ agent = EnhancedAgent()
49
+
50
  results_log = []
51
  answers_payload = []
52
+ diagnostics = []
53
 
54
+ print(f"\n{'='*80}")
55
+ print(f"🚀 Iniciando procesamiento de {len(questions_data)} preguntas")
56
+ print(f"{'='*80}\n")
57
 
58
+ for i, item in enumerate(questions_data):
59
  task_id = item.get("task_id")
60
+ question_text = item.get("question", "")
61
+ file_name = item.get("file_name", "")
62
 
63
+ if not task_id:
64
+ continue
65
+
66
+ print(f"\n{'='*80}")
67
+ print(f"[{i+1}/{len(questions_data)}] Task: {task_id}")
68
+ print(f"{'='*80}")
69
+ print(f"❓ Pregunta: {question_text[:150]}...")
70
+
71
+ # Detectar tipo de pregunta
72
+ question_type = detect_question_type(question_text, file_name)
73
+ print(f"🔍 Tipo detectado: {question_type}")
74
+
75
+ if file_name:
76
+ print(f"📎 Archivo esperado: {file_name}")
77
+
78
+ # Descargar archivo si existe
79
+ local_file = download_file_for_task(task_id)
80
+
81
+ # Mostrar URLs encontradas en la pregunta
82
+ url_pattern = r"https?://[\w\-\./?&=%#]+"
83
+ found_urls = re.findall(url_pattern, question_text)
84
+ for url in found_urls:
85
+ print(f" 🔗 URL encontrada: {url}")
86
+
87
+ # Ejecutar agente
88
+ start_time = time.time()
89
+ print(f"⚙️ Procesando con estrategia '{question_type}'...")
90
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  try:
92
+ submitted_answer, execution_logs = agent.solve(
93
+ question_text,
94
+ local_file,
95
+ question_type
96
+ )
97
 
98
+ # Limpieza de seguridad
 
 
 
99
  if len(submitted_answer) > 200:
100
  submitted_answer = submitted_answer[:200]
101
 
 
 
 
102
  except Exception as e:
103
+ print(f" Error: {e}")
104
+ submitted_answer = "Error"
105
+ execution_logs = str(e)
106
+
107
+ elapsed = time.time() - start_time
108
+
109
+ print(f"\n✅ Respuesta: {submitted_answer}")
110
+ print(f"⏱️ Tiempo: {elapsed:.1f}s")
111
+
112
+ # Guardar resultados
113
+ answers_payload.append({
114
+ "task_id": task_id,
115
+ "submitted_answer": submitted_answer
116
+ })
117
+
118
+ results_log.append({
119
+ "Task ID": task_id,
120
+ "Índice": i,
121
+ "Tipo": question_type,
122
+ "Pregunta": question_text[:100] + "..." if len(question_text) > 100 else question_text,
123
+ "Archivo": file_name or "N/A",
124
+ "Respuesta": submitted_answer,
125
+ "Tiempo (s)": round(elapsed, 1)
126
+ })
127
+
128
+ diagnostics.append({
129
+ "index": i,
130
+ "task_id": task_id,
131
+ "question_type": question_type,
132
+ "question": question_text,
133
+ "file_name": file_name,
134
+ "answer": submitted_answer,
135
+ "elapsed_seconds": round(elapsed, 1)
136
+ })
137
+
138
+ # Limpiar archivo temporal
139
+ if local_file and os.path.exists(local_file):
140
+ try:
141
+ os.remove(local_file)
142
+ except:
143
+ pass
144
+
145
+ # Guardar diagnóstico
146
+ try:
147
+ ts = time.strftime("%Y%m%d_%H%M%S")
148
+ diag_path = f"diagnostics_{ts}.json"
149
+ with open(diag_path, "w", encoding="utf-8") as f:
150
+ json.dump(diagnostics, f, ensure_ascii=False, indent=2)
151
+ print(f"\n📊 Diagnóstico guardado: {diag_path}")
152
+ except Exception as e:
153
+ print(f"⚠️ Error guardando diagnóstico: {e}")
154
 
155
+ # Enviar resultados
156
+ print(f"\n{'='*80}")
157
+ print("📤 Enviando respuestas al servidor...")
158
+
159
+ submission_data = {
160
+ "username": username.strip(),
161
+ "agent_code": agent_code,
162
+ "answers": answers_payload
163
+ }
164
 
165
  try:
166
  response = requests.post(submit_url, json=submission_data, timeout=60)
167
  try:
168
  result = response.json()
169
+ score = result.get('score', 'N/A')
170
+ correct = result.get('correct_count', '?')
171
+ total = result.get('total_count', len(questions_data))
172
+
173
+ status_msg = f"""✅ Submission Successful!
174
+
175
+ 📊 Score: {score}%
176
+ ✓ Correct: {correct}/{total}
177
+ 👤 Username: {username}
178
+ """
179
+ print(status_msg)
180
+ return status_msg, pd.DataFrame(results_log)
181
+
182
+ except Exception:
183
  return f"Submission Failed (Server Error): {response.text}", pd.DataFrame(results_log)
184
  except Exception as e:
185
  return f"Submission Failed: {e}", pd.DataFrame(results_log)
186
 
187
+
188
+ # ============================================================================
189
+ # INTERFAZ GRADIO
190
+ # ============================================================================
191
+
192
  with gr.Blocks() as demo:
193
+ gr.Markdown("""
194
+ # 🤖 GAIA Agent v2 - Optimizado para Archivos, YouTube y Lógica
195
+
196
+ Este agente usa estrategias específicas por tipo de pregunta:
197
+ - 📊 **Archivos Excel/CSV**: Lee y analiza datos con pandas
198
+ - 🎬 **YouTube**: Busca transcripciones y discusiones online
199
+ - 🖼️ **Imágenes**: Busca información en la web
200
+ - 🎵 **Audio**: Busca transcripciones online
201
+ - 📝 **Wikipedia**: Navega y extrae información
202
+ - 🔢 **Conteo**: Lista items y cuenta programáticamente
203
+ - 🔄 **Manipulación de texto**: Maneja texto invertido, opuestos, etc.
204
+ """)
205
+
206
  gr.LoginButton()
207
+
208
+ with gr.Row():
209
+ run_button = gr.Button("🚀 Run Evaluation & Submit All Answers", variant="primary")
210
+
211
+ status_output = gr.Textbox(label="📋 Status", lines=6, interactive=False)
212
+
213
+ results_table = gr.DataFrame(
214
+ label="📊 Resultados Detallados",
215
+ wrap=True,
216
+ headers=["Task ID", "Índice", "Tipo", "Pregunta", "Archivo", "Respuesta", "Tiempo (s)"]
217
+ )
218
+
219
+ run_button.click(
220
+ fn=run_and_submit_all,
221
+ outputs=[status_output, results_table]
222
+ )
223
+
224
 
225
  if __name__ == "__main__":
226
+ print("🚀 Iniciando GAIA Agent v2...")
227
  demo.launch()
config.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Configuración y constantes del GAIA Agent v2
3
+ """
4
+ import os
5
+
6
+ # ============================================================================
7
+ # API CONFIGURATION
8
+ # ============================================================================
9
+ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
10
+
11
+ # ============================================================================
12
+ # MODEL CONFIGURATION
13
+ # ============================================================================
14
+ # Cambiar según el entorno
15
+ USE_LOCAL_MODEL = False # True = Ollama local, False = HuggingFace API
16
+
17
+ # Configuración para Ollama (local)
18
+ OLLAMA_MODEL_ID = "ollama/qwen2.5-coder:14b"
19
+ OLLAMA_API_BASE = "http://localhost:11434"
20
+ OLLAMA_API_KEY = "ollama"
21
+
22
+ # Configuración para HuggingFace (cloud)
23
+ # Modelo más potente para mejor rendimiento en GAIA benchmark
24
+ HF_MODEL_ID = "Qwen/Qwen2.5-72B-Instruct"
25
+ HF_TOKEN = os.getenv("HF_TOKEN")
26
+
27
+ # ============================================================================
28
+ # AGENT CONFIGURATION
29
+ # ============================================================================
30
+ MAX_STEPS = 12
31
+ VERBOSITY_LEVEL = 2
32
+
33
+ AUTHORIZED_IMPORTS = [
34
+ 'csv', 'pandas', 'bs4', 'requests', 're', 'collections',
35
+ 'itertools', 'io', 'json', 'math', 'statistics', 'queue',
36
+ 'xml', 'datetime', 'time', 'openpyxl', 'numpy', 'markdownify',
37
+ 'urllib'
38
+ ]
39
+
40
+ # ============================================================================
41
+ # QUESTION TYPES
42
+ # ============================================================================
43
+ QUESTION_TYPES = {
44
+ 'YOUTUBE_VIDEO': 'youtube_video',
45
+ 'IMAGE_FILE': 'image_file',
46
+ 'AUDIO_FILE': 'audio_file',
47
+ 'DATA_FILE': 'data_file',
48
+ 'CODE_FILE': 'code_file',
49
+ 'WIKIPEDIA': 'wikipedia_search',
50
+ 'COUNTING': 'counting_task',
51
+ 'TEXT_MANIPULATION': 'text_manipulation',
52
+ 'GENERAL': 'general_research'
53
+ }
requirements.txt CHANGED
@@ -1,5 +1,19 @@
 
1
  gradio
2
  requests
3
  pandas
4
  smolagents[toolkit]
5
- duckduckgo-search
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Core dependencies
2
  gradio
3
  requests
4
  pandas
5
  smolagents[toolkit]
6
+ litellm>=1.0.0
7
+
8
+ # Data processing
9
+ openpyxl>=3.1.0
10
+ numpy
11
+
12
+ # Web scraping and parsing
13
+ beautifulsoup4>=4.12.0
14
+ lxml>=4.9.0
15
+ markdownify>=0.11.0
16
+
17
+ # Search tools
18
+ duckduckgo-search>=3.9.0
19
+ wikipedia-api
tools.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Herramientas personalizadas para el GAIA Agent v2
3
+ """
4
+ import requests
5
+ from smolagents import tool
6
+ from markdownify import markdownify as md
7
+
8
+
9
+ @tool
10
+ def smart_visit(url: str) -> str:
11
+ """
12
+ Visits a webpage and returns its content converted to Markdown.
13
+ Essential for Wikipedia, documentation, or any web content.
14
+
15
+ Args:
16
+ url: The URL of the page to visit.
17
+
18
+ Returns:
19
+ str: Webpage content in Markdown format (max 25000 chars)
20
+ """
21
+ try:
22
+ headers = {
23
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
24
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
25
+ 'Accept-Language': 'en-US,en;q=0.5',
26
+ 'Referer': 'https://www.google.com/'
27
+ }
28
+ response = requests.get(url, headers=headers, timeout=25)
29
+ response.raise_for_status()
30
+
31
+ content = md(response.text)
32
+ return content[:25000]
33
+ except Exception as e:
34
+ return f"Error visiting {url}: {str(e)}"
35
+
36
+
37
+ @tool
38
+ def get_youtube_info(video_url: str) -> str:
39
+ """
40
+ Gets information about a YouTube video including title, description,
41
+ and attempts to find transcripts or related information.
42
+
43
+ Args:
44
+ video_url: YouTube video URL (e.g., https://www.youtube.com/watch?v=VIDEO_ID)
45
+
46
+ Returns:
47
+ str: Video information and transcript search strategy
48
+ """
49
+ try:
50
+ if "youtube.com" in video_url:
51
+ video_id = video_url.split("v=")[1].split("&")[0] if "v=" in video_url else ""
52
+ elif "youtu.be" in video_url:
53
+ video_id = video_url.split("/")[-1].split("?")[0]
54
+ else:
55
+ return "Invalid YouTube URL"
56
+
57
+ if not video_id:
58
+ return "Could not extract video ID"
59
+
60
+ return f"""Video ID: {video_id}
61
+
62
+ STRATEGY TO ANSWER:
63
+ 1. Search for '{video_id}' + keywords from the question on DuckDuckGo
64
+ 2. Look for transcripts, comments, or discussion forums about this video
65
+ 3. The video URL is: {video_url}
66
+
67
+ Note: Direct video playback is not available. Search online for transcripts or summaries."""
68
+
69
+ except Exception as e:
70
+ return f"Error processing YouTube video: {str(e)}"
71
+
72
+
73
+ @tool
74
+ def wikipedia_search(query: str) -> str:
75
+ """
76
+ Searches Wikipedia for a query and returns the page content in Markdown format.
77
+
78
+ Args:
79
+ query: The search term or topic to look up on Wikipedia
80
+
81
+ Returns:
82
+ str: The Wikipedia page content in Markdown format, or an error message
83
+ """
84
+ try:
85
+ import urllib.parse
86
+ search_url = f"https://en.wikipedia.org/w/index.php?search={urllib.parse.quote_plus(query)}&title=Special%3ASearch&go=Go"
87
+ return smart_visit.forward(search_url)
88
+ except Exception as e:
89
+ return f"Error searching Wikipedia: {e}"
utils.py ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Funciones de utilidad para el GAIA Agent v2
3
+ """
4
+ import os
5
+ import re
6
+ import shutil
7
+ import urllib.parse
8
+ import requests
9
+ from bs4 import BeautifulSoup
10
+
11
+ from config import DEFAULT_API_URL, QUESTION_TYPES
12
+
13
+
14
+ def clean_ansi_codes(text):
15
+ """Limpia los códigos ANSI de color de la terminal."""
16
+ ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
17
+ return ansi_escape.sub('', text)
18
+
19
+
20
+ def clean_answer(answer):
21
+ """Limpia la respuesta del agente eliminando formato innecesario."""
22
+ answer = str(answer).strip()
23
+
24
+ patterns_to_remove = [
25
+ (r'^Final Answer:\s*', ''),
26
+ (r'^Answer:\s*', ''),
27
+ (r'^The answer is\s*', ''),
28
+ (r'^Based on[^,]*,\s*', ''),
29
+ (r'```', ''),
30
+ (r'\*\*', ''),
31
+ (r'^##\s*', '')
32
+ ]
33
+
34
+ for pattern, replacement in patterns_to_remove:
35
+ answer = re.sub(pattern, replacement, answer, flags=re.IGNORECASE)
36
+
37
+ return answer.strip()
38
+
39
+
40
+ def detect_question_type(question, file_name):
41
+ """
42
+ Detecta el tipo de pregunta para aplicar estrategia específica.
43
+
44
+ Args:
45
+ question: Texto de la pregunta
46
+ file_name: Nombre del archivo adjunto (si existe)
47
+
48
+ Returns:
49
+ str: Tipo de pregunta (ver QUESTION_TYPES en config.py)
50
+ """
51
+ q_lower = question.lower()
52
+
53
+ if "youtube.com" in question or "youtu.be" in question:
54
+ return QUESTION_TYPES['YOUTUBE_VIDEO']
55
+ elif file_name and file_name.endswith(".png"):
56
+ return QUESTION_TYPES['IMAGE_FILE']
57
+ elif file_name and file_name.endswith(".mp3"):
58
+ return QUESTION_TYPES['AUDIO_FILE']
59
+ elif file_name and (file_name.endswith(".xlsx") or file_name.endswith(".csv")):
60
+ return QUESTION_TYPES['DATA_FILE']
61
+ elif file_name and file_name.endswith(".py"):
62
+ return QUESTION_TYPES['CODE_FILE']
63
+ elif "wikipedia" in q_lower:
64
+ return QUESTION_TYPES['WIKIPEDIA']
65
+ elif any(word in q_lower for word in ["how many", "count", "number of"]):
66
+ return QUESTION_TYPES['COUNTING']
67
+ elif "reverse" in q_lower or "backwards" in q_lower or ".rewsna" in question:
68
+ return QUESTION_TYPES['TEXT_MANIPULATION']
69
+ else:
70
+ return QUESTION_TYPES['GENERAL']
71
+
72
+
73
+ def download_file_for_task(task_id):
74
+ """
75
+ Descarga el archivo adjunto de una tarea si existe.
76
+
77
+ Args:
78
+ task_id: ID de la tarea
79
+
80
+ Returns:
81
+ str: Ruta del archivo descargado o None si no hay archivo
82
+ """
83
+ file_url = f"{DEFAULT_API_URL}/files/{task_id}"
84
+ try:
85
+ response = requests.get(file_url, stream=True, timeout=30)
86
+ if response.status_code == 200:
87
+ filename = f"file_{task_id}"
88
+
89
+ # Obtener nombre real del header
90
+ if "content-disposition" in response.headers:
91
+ cd = response.headers["content-disposition"]
92
+ if "filename=" in cd:
93
+ filename = cd.split("filename=")[1].strip('"')
94
+
95
+ # Asegurar extensión correcta
96
+ if "." not in filename:
97
+ content_type = response.headers.get("content-type", "")
98
+ if "excel" in content_type or "spreadsheet" in content_type:
99
+ filename += ".xlsx"
100
+ elif "audio" in content_type or "mpeg" in content_type:
101
+ filename += ".mp3"
102
+ elif "image" in content_type or "png" in content_type:
103
+ filename += ".png"
104
+ elif "python" in content_type:
105
+ filename += ".py"
106
+
107
+ with open(filename, 'wb') as f:
108
+ shutil.copyfileobj(response.raw, f)
109
+
110
+ print(f" ✓ Archivo descargado: {filename} ({os.path.getsize(filename)} bytes)")
111
+ return filename
112
+ except Exception as e:
113
+ print(f" ✗ Error descargando archivo: {e}")
114
+ return None
115
+
116
+
117
+ def fetch_and_download_links(url, dest_dir, max_files=20):
118
+ """
119
+ Descarga recursos vinculados desde una URL.
120
+
121
+ Args:
122
+ url: URL de la página a escanear
123
+ dest_dir: Directorio destino para los archivos
124
+ max_files: Máximo número de archivos a descargar
125
+
126
+ Returns:
127
+ list: Lista de rutas de archivos descargados
128
+ """
129
+ downloaded = []
130
+ try:
131
+ os.makedirs(dest_dir, exist_ok=True)
132
+ resp = requests.get(url, timeout=20)
133
+ resp.raise_for_status()
134
+ soup = BeautifulSoup(resp.text, "lxml")
135
+
136
+ candidates = []
137
+ for tag in soup.find_all(['a', 'link']):
138
+ href = tag.get('href')
139
+ if href:
140
+ candidates.append(href)
141
+ for tag in soup.find_all(['img', 'script', 'source']):
142
+ src = tag.get('src')
143
+ if src:
144
+ candidates.append(src)
145
+
146
+ seen = set()
147
+ allowed_exts = {'.png', '.jpg', '.jpeg', '.gif', '.svg', '.pdf', '.zip',
148
+ '.mp3', '.mp4', '.py', '.txt', '.csv', '.xlsx', '.xls'}
149
+
150
+ for c in candidates:
151
+ if len(downloaded) >= max_files:
152
+ break
153
+ full = urllib.parse.urljoin(url, c)
154
+ if full in seen:
155
+ continue
156
+ seen.add(full)
157
+
158
+ path = urllib.parse.urlparse(full).path
159
+ ext = os.path.splitext(path)[1].lower()
160
+
161
+ if ext in allowed_exts:
162
+ try:
163
+ r = requests.get(full, stream=True, timeout=20)
164
+ r.raise_for_status()
165
+ cd = r.headers.get('content-disposition')
166
+ if cd and 'filename=' in cd:
167
+ fname = cd.split('filename=')[1].strip('"')
168
+ else:
169
+ fname = os.path.basename(path) or f"resource_{len(downloaded)}{ext}"
170
+ out_path = os.path.join(dest_dir, fname)
171
+ with open(out_path, 'wb') as of:
172
+ shutil.copyfileobj(r.raw, of)
173
+ downloaded.append(out_path)
174
+ except Exception:
175
+ continue
176
+
177
+ except Exception:
178
+ pass
179
+
180
+ return downloaded
v2/agent.py ADDED
@@ -0,0 +1,251 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ """
3
+ Agente mejorado con estrategias específicas por tipo de pregunta
4
+ """
5
+
6
+ import io
7
+ from contextlib import redirect_stdout
8
+ from smolagents import CodeAgent, LiteLLMModel, DuckDuckGoSearchTool, VisitWebpageTool, WikipediaSearchTool
9
+ from config import (
10
+ MODEL_ID, MODEL_API_BASE, MODEL_API_KEY,
11
+ MAX_STEPS, VERBOSITY_LEVEL, AUTHORIZED_IMPORTS,
12
+ QUESTION_TYPES
13
+ )
14
+ from tools import smart_visit, get_youtube_info
15
+ from utils import clean_answer, clean_ansi_codes
16
+
17
+ class EnhancedLocalAgent:
18
+ """Agente mejorado con estrategias específicas por tipo de pregunta."""
19
+
20
+ def __init__(self):
21
+ print(f" 🤖 Inicializando agente con {MODEL_ID.split('/')[-1]}...")
22
+
23
+ self.model = LiteLLMModel(
24
+ model_id=MODEL_ID,
25
+ api_base=MODEL_API_BASE,
26
+ api_key=MODEL_API_KEY
27
+ )
28
+
29
+ search_tool = DuckDuckGoSearchTool()
30
+ visit_tool = VisitWebpageTool()
31
+ wiki_tool = WikipediaSearchTool()
32
+
33
+ self.agent = CodeAgent(
34
+ tools=[search_tool, visit_tool, wiki_tool, smart_visit, get_youtube_info],
35
+ model=self.model,
36
+ max_steps=MAX_STEPS,
37
+ verbosity_level=VERBOSITY_LEVEL,
38
+ additional_authorized_imports=AUTHORIZED_IMPORTS
39
+ )
40
+
41
+ def build_prompt(self, question, local_file, question_type):
42
+ """Construye prompt optimizado según el tipo de pregunta."""
43
+
44
+ base_context = f"""TASK: You are solving a GAIA benchmark question. Be precise and methodical.
45
+
46
+ QUESTION: {question}
47
+ """
48
+
49
+ # Estrategias específicas por tipo
50
+ strategies = {
51
+ QUESTION_TYPES['YOUTUBE_VIDEO']: """
52
+ STRATEGY - YouTube Video:
53
+ 1. Extract the video ID from the URL in the question
54
+ 2. Use get_youtube_info tool to get context
55
+ 3. Search DuckDuckGo for: "[video_id] transcript" or "[video_id] [keywords_from_question]"
56
+ 4. Look for Reddit threads, forums, or blogs discussing this video
57
+ 5. Find the specific information requested
58
+
59
+ IMPORTANT: You CANNOT watch the video. Search for transcripts or discussions online.
60
+ """,
61
+
62
+ QUESTION_TYPES['IMAGE_FILE']: f"""
63
+ STRATEGY - Image File:
64
+ 1. File '{local_file}' is in current directory
65
+ 2. You CANNOT read image files directly with Python
66
+ 3. Search online for: "{local_file}" OR search for keywords from the question
67
+ 4. Look for discussions, analysis, or descriptions of this image online
68
+ 5. For chess positions: search "[piece positions] chess position solution"
69
+
70
+ IMPORTANT: Do NOT attempt cv2, PIL, or any image processing. Search online instead.
71
+ """,
72
+
73
+ QUESTION_TYPES['AUDIO_FILE']: f"""
74
+ STRATEGY - Audio File:
75
+ 1. File '{local_file}' is in current directory
76
+ 2. You CANNOT play or transcribe audio with Python
77
+ 3. Search online for: "{local_file}" OR the exact question text
78
+ 4. Look for transcripts, Reddit threads, or forums discussing this audio
79
+
80
+ IMPORTANT: Do NOT attempt librosa, soundfile, or audio processing. Search online.
81
+ """,
82
+
83
+ QUESTION_TYPES['DATA_FILE']: f"""
84
+ STRATEGY - Data File (Excel/CSV):
85
+ 1. File '{local_file}' is in current directory
86
+ 2. Use pandas to read: pd.read_excel('{local_file}') or pd.read_csv('{local_file}')
87
+ 3. Explore columns with df.columns and df.head()
88
+ 4. Filter and sum/count as needed
89
+ 5. Double-check calculations
90
+
91
+ CODE TEMPLATE:
92
+ ```python
93
+ import pandas as pd
94
+ df = pd.read_excel('{local_file}') # or read_csv
95
+ print(df.columns)
96
+ print(df.head())
97
+ # ... your analysis
98
+ ```
99
+ """,
100
+
101
+ QUESTION_TYPES['CODE_FILE']: f"""
102
+ STRATEGY - Code File:
103
+ 1. File '{local_file}' is in current directory
104
+ 2. Read it with open('{local_file}', 'r').read()
105
+ 3. Analyze the code logic carefully
106
+ 4. If needed, execute it: exec(open('{local_file}').read())
107
+ 5. Return the requested output
108
+
109
+ IMPORTANT: Read and understand before executing.
110
+ """,
111
+
112
+ QUESTION_TYPES['WIKIPEDIA']: """
113
+ STRATEGY - Wikipedia Search:
114
+ 1. Identify the exact topic/entity from the question
115
+ 2. Use web_search to find the correct Wikipedia article URL
116
+ 3. Use smart_visit to read the Wikipedia page content
117
+ 4. Extract the specific information requested (dates, numbers, names, etc.)
118
+ 5. For counting tasks: CREATE A PYTHON LIST with each item, then count with len()
119
+
120
+ TIPS:
121
+ - Search: "[topic] Wikipedia 2022" for latest version
122
+ - For discographies: look for "Discography" section or table
123
+ - For featured articles: search "Wikipedia Featured Article [topic] [date]"
124
+ - ALWAYS create a list and count programmatically, don't count manually
125
+
126
+ EXAMPLE for counting:
127
+ ```python
128
+ albums_2000_2009 = [
129
+ "Album 1 (2000)",
130
+ "Album 2 (2001)",
131
+ # ... list ALL albums
132
+ ]
133
+ count = len(albums_2000_2009)
134
+ print(count)
135
+ ```
136
+ """,
137
+
138
+ QUESTION_TYPES['COUNTING']: """
139
+ STRATEGY - Counting Task:
140
+ 1. Research and LIST all items first (don't just count)
141
+ 2. Use smart_visit to get complete data from Wikipedia or official sources
142
+ 3. Store items in a Python list: items = []
143
+ 4. Count with len(items) and verify manually
144
+ 5. Double-check you haven't missed anything
145
+
146
+ IMPORTANT: First collect ALL items, THEN count. Show your work.
147
+ """,
148
+
149
+ QUESTION_TYPES['TEXT_MANIPULATION']: """
150
+ STRATEGY - Text Manipulation:
151
+ 1. Read the question VERY carefully
152
+ 2. If text is backwards, reverse it: text[::-1]
153
+ 3. If asking for opposite: use logic (left ↔ right, up ↔ down, etc.)
154
+ 4. Return ONLY the answer, no explanation
155
+
156
+ EXAMPLE: ".rewsna eht sa 'tfel' drow..."
157
+ → Reverse to read: "...word 'left' as the answer."
158
+ → Opposite of "left" is "right"
159
+ """,
160
+
161
+ QUESTION_TYPES['GENERAL']: """
162
+ STRATEGY - General Research:
163
+ 1. Break down the question into sub-tasks
164
+ 2. Use web_search for initial research
165
+ 3. Use smart_visit to read relevant pages in detail
166
+ 4. Cross-reference multiple sources if needed
167
+ 5. Extract the precise answer requested
168
+
169
+ TIPS:
170
+ - Be specific in searches: include years, full names, exact terms
171
+ - Read carefully - answers are often in tables, lists, or footnotes
172
+ """
173
+ }
174
+
175
+ strategy = strategies.get(question_type, strategies[QUESTION_TYPES['GENERAL']])
176
+
177
+ output_format = """
178
+ FINAL OUTPUT FORMAT:
179
+ Return ONLY the answer value. No markdown, no "The answer is", no explanations.
180
+
181
+ Examples of GOOD answers:
182
+ - "3"
183
+ - "right"
184
+ - "Ian Rose"
185
+ - "14.50"
186
+ - "d5, e2"
187
+
188
+ Examples of BAD answers:
189
+ - "The answer is 3"
190
+ - "**3**"
191
+ - "Based on my research, the answer is 3."
192
+ """
193
+
194
+ return base_context + strategy + output_format
195
+
196
+ def solve(self, question, local_file=None, question_type=QUESTION_TYPES['GENERAL']):
197
+ """
198
+ Resuelve una pregunta con estrategia optimizada.
199
+
200
+ Args:
201
+ question: Texto de la pregunta
202
+ local_file: Ruta al archivo adjunto (opcional)
203
+ question_type: Tipo de pregunta detectado
204
+
205
+ Returns:
206
+ tuple: (respuesta, logs de ejecución)
207
+ """
208
+ prompt = self.build_prompt(question, local_file, question_type)
209
+
210
+ log_capture = io.StringIO()
211
+ final_answer = "Error"
212
+
213
+ try:
214
+ with redirect_stdout(log_capture):
215
+ answer = self.agent.run(prompt)
216
+ final_answer = clean_answer(answer)
217
+
218
+ # Si está vacío después de limpiar, buscar en logs
219
+ if not final_answer or final_answer == "Error":
220
+ logs = log_capture.getvalue()
221
+ for line in reversed(logs.split('\n')):
222
+ if line.strip() and not any(x in line for x in ['===', '---', 'Step', 'Tool']):
223
+ potential_answer = line.strip()
224
+ if len(potential_answer) < 200:
225
+ final_answer = potential_answer
226
+ break
227
+
228
+ except Exception as e:
229
+ log_capture.write(f"\n❌ CRITICAL ERROR: {e}\n")
230
+ final_answer = "Error"
231
+
232
+ return final_answer, clean_ansi_codes(log_capture.getvalue())
233
+
234
+
235
+ def call_agent(question: str, file_path: str = None):
236
+ """
237
+ Función de compatibilidad para llamar al agente de forma simple.
238
+
239
+ Args:
240
+ question: Pregunta a resolver
241
+ file_path: Ruta al archivo adjunto (opcional)
242
+
243
+ Returns:
244
+ str: Respuesta del agente
245
+ """
246
+ from utils import detect_question_type
247
+
248
+ agent = EnhancedLocalAgent()
249
+ question_type = detect_question_type(question, file_path or "")
250
+ answer, _ = agent.solve(question, file_path, question_type)
251
+ return answer
v2/config.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Configuración y constantes del proyecto GAIA Agent
3
+ """
4
+
5
+ # --- API CONFIGURATION ---
6
+ AGENT_CODE_URL = "https://huggingface.co/spaces/Diego-Fco/Final_Assignment_Template/tree/main"
7
+ USERNAME = "Diego-Fco"
8
+ API_URL = "https://agents-course-unit4-scoring.hf.space"
9
+
10
+ # --- QUESTION FILTERING ---
11
+ # Lista de índices (0-based) de preguntas específicas a testear
12
+ # Ejemplos: [3] = Ajedrez, [4] = Dinosaurio Wikipedia
13
+ INDICES_A_TESTEAR = []
14
+
15
+ # Si INDICES_A_TESTEAR está vacío, se usará este límite
16
+ # None = procesar todas las preguntas disponibles
17
+ LIMITE_PREGUNTAS = 6
18
+
19
+ # --- MODEL CONFIGURATION ---
20
+ MODEL_ID = "ollama/qwen2.5-coder:14b"
21
+ MODEL_API_BASE = "http://localhost:11434"
22
+ MODEL_API_KEY = "ollama"
23
+
24
+ # --- AGENT CONFIGURATION ---
25
+ MAX_STEPS = 12
26
+ VERBOSITY_LEVEL = 2 # Nivel de logs (1=básico, 2=detallado)
27
+
28
+ # Imports adicionales permitidos para el agente
29
+ AUTHORIZED_IMPORTS = [
30
+ 'csv', 'pandas', 'bs4', 'requests', 're', 'collections',
31
+ 'itertools', 'io', 'json', 'math', 'statistics', 'queue',
32
+ 'xml', 'datetime', 'time', 'openpyxl', 'numpy', 'markdownify',
33
+ 'urllib'
34
+ ]
35
+
36
+ # --- QUESTION TYPES ---
37
+ QUESTION_TYPES = {
38
+ 'YOUTUBE_VIDEO': 'youtube_video',
39
+ 'IMAGE_FILE': 'image_file',
40
+ 'AUDIO_FILE': 'audio_file',
41
+ 'DATA_FILE': 'data_file',
42
+ 'CODE_FILE': 'code_file',
43
+ 'WIKIPEDIA': 'wikipedia_search',
44
+ 'COUNTING': 'counting_task',
45
+ 'TEXT_MANIPULATION': 'text_manipulation',
46
+ 'GENERAL': 'general_research'
47
+ }
v2/main_simple.py ADDED
@@ -0,0 +1,227 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ GAIA Agent v2 - Script Principal Simplificado
3
+ Resuelve preguntas del benchmark GAIA usando estrategias optimizadas
4
+ """
5
+ import os
6
+ import re
7
+ import shutil
8
+ import requests
9
+ import json
10
+ import time
11
+
12
+ # Importar módulos locales
13
+ from config import (
14
+ AGENT_CODE_URL, USERNAME, API_URL,
15
+ INDICES_A_TESTEAR, LIMITE_PREGUNTAS
16
+ )
17
+ from agent import EnhancedLocalAgent
18
+ from utils import download_file_for_task, detect_question_type, fetch_and_download_links
19
+
20
+
21
+ def load_questions():
22
+ """Carga las preguntas desde el servidor y las guarda localmente."""
23
+ print("📥 Cargando preguntas...")
24
+ try:
25
+ all_questions = requests.get(f"{API_URL}/questions").json()
26
+
27
+ # Guardar copia local
28
+ if not os.path.exists("tasks"):
29
+ os.makedirs("tasks")
30
+ with open(os.path.join("tasks", "all_questions.json"), "w", encoding="utf-8") as qf:
31
+ json.dump(all_questions, qf, ensure_ascii=False, indent=2)
32
+
33
+ print(f" ✓ {len(all_questions)} preguntas cargadas\n")
34
+ return all_questions
35
+ except Exception as e:
36
+ print(f"❌ Error: {e}")
37
+ return None
38
+
39
+
40
+ def select_questions(all_questions):
41
+ """Selecciona qué preguntas procesar según configuración."""
42
+ questions_to_process = []
43
+
44
+ if INDICES_A_TESTEAR and len(INDICES_A_TESTEAR) > 0:
45
+ print(f"🎯 MODO QUIRÚRGICO: Procesando índices {INDICES_A_TESTEAR}\n")
46
+ for idx in INDICES_A_TESTEAR:
47
+ if 0 <= idx < len(all_questions):
48
+ q = all_questions[idx]
49
+ q['_original_index'] = idx
50
+ questions_to_process.append(q)
51
+ else:
52
+ limit = LIMITE_PREGUNTAS if LIMITE_PREGUNTAS else len(all_questions)
53
+ print(f"🔥 MODO SECUENCIAL: Procesando las primeras {limit} preguntas\n")
54
+ for i, q in enumerate(all_questions[:limit]):
55
+ q['_original_index'] = i
56
+ questions_to_process.append(q)
57
+
58
+ return questions_to_process
59
+
60
+
61
+ def process_questions(agent, questions_to_process):
62
+ """Procesa todas las preguntas con el agente."""
63
+ results = []
64
+ diagnostics = []
65
+
66
+ for i, item in enumerate(questions_to_process):
67
+ task_id = item["task_id"]
68
+ question = item["question"]
69
+ file_name = item.get("file_name", "")
70
+ idx_original = item.get('_original_index', '?')
71
+
72
+ print(f"\n{'='*80}")
73
+ print(f"[{i+1}/{len(questions_to_process)}] Índice: {idx_original} | Task: {task_id}")
74
+ print(f"{'='*80}")
75
+ print(f"❓ Pregunta: {question[:100]}...")
76
+ if file_name:
77
+ print(f"📎 Archivo: {file_name}")
78
+
79
+ # Detectar tipo y descargar archivo
80
+ question_type = detect_question_type(question, file_name)
81
+ print(f"🔍 Tipo detectado: {question_type}")
82
+
83
+ # Descargar archivo principal si existe en la API
84
+ local_file = download_file_for_task(task_id)
85
+
86
+ # Crear carpeta para esta pregunta
87
+ task_dir = os.path.join("tasks", f"question_{idx_original}_{task_id}")
88
+ os.makedirs(task_dir, exist_ok=True)
89
+
90
+ # Mover archivo descargado a la carpeta de la pregunta
91
+ if local_file and os.path.exists(local_file):
92
+ new_file_path = os.path.join(task_dir, os.path.basename(local_file))
93
+ shutil.move(local_file, new_file_path)
94
+ local_file = new_file_path
95
+ print(f" ✓ Archivo movido a: {local_file}")
96
+
97
+ # Descargar recursos vinculados desde URLs en la pregunta
98
+ resource_dir = os.path.join(task_dir, "resources")
99
+ # Extraer urls simples del texto de la pregunta
100
+ url_pattern = r"https?://[\w\-\./?&=%#]+"
101
+ found_urls = re.findall(url_pattern, question)
102
+ for u in found_urls:
103
+ print(f" 🔗 Encontrada URL en pregunta: {u} — descargando recursos...")
104
+ downloaded = fetch_and_download_links(u, resource_dir)
105
+ if downloaded:
106
+ print(f" ✓ {len(downloaded)} recursos descargados en {resource_dir}")
107
+
108
+ # Resolver
109
+ print(f"⚙️ Procesando con estrategia '{question_type}'...")
110
+ start_time = time.time()
111
+
112
+ answer, execution_logs = agent.solve(question, local_file, question_type)
113
+
114
+ elapsed = time.time() - start_time
115
+ print(f"\n✅ Respuesta: {answer}")
116
+ print(f"⏱️ Tiempo: {elapsed:.1f}s")
117
+
118
+ # Guardar logs
119
+ task_dir = save_logs(task_id, idx_original, question, question_type,
120
+ answer, local_file, elapsed, execution_logs)
121
+
122
+ results.append({"task_id": task_id, "submitted_answer": answer})
123
+ diagnostics.append({
124
+ "idx_original": idx_original,
125
+ "task_id": task_id,
126
+ "question_type": question_type,
127
+ "question": question[:200],
128
+ "answer": answer,
129
+ "elapsed_seconds": round(elapsed, 1),
130
+ "folder": task_dir
131
+ })
132
+
133
+ return results, diagnostics
134
+
135
+
136
+ def save_logs(task_id, idx_original, question, question_type,
137
+ answer, local_file, elapsed, execution_logs):
138
+ """Guarda los logs de ejecución de una pregunta en su propia carpeta."""
139
+ # Crear carpeta específica para esta pregunta
140
+ task_dir = f"tasks/question_{idx_original}_{task_id}"
141
+ os.makedirs(task_dir, exist_ok=True)
142
+
143
+ # Guardar archivo de respuesta
144
+ task_filename = os.path.join(task_dir, "answer.md")
145
+ with open(task_filename, "w", encoding="utf-8") as f:
146
+ f.write(f"# Pregunta {idx_original}\n\n")
147
+ f.write(f"**Task ID:** {task_id}\n\n")
148
+ f.write(f"**Tipo:** {question_type}\n\n")
149
+ f.write(f"**Pregunta:** {question}\n\n")
150
+ f.write(f"**Archivo adjunto:** {local_file or 'N/A'}\n\n")
151
+ f.write(f"**Tiempo de ejecución:** {elapsed:.1f}s\n\n")
152
+ f.write(f"## ✅ Respuesta Final\n\n```\n{answer}\n```\n\n")
153
+ f.write("## 📋 Logs de Ejecución\n\n```text\n")
154
+ f.write(execution_logs)
155
+ f.write("\n```\n")
156
+
157
+ return task_dir
158
+
159
+
160
+ def submit_results(results):
161
+ """Envía los resultados al servidor."""
162
+ print(f"\n{'='*80}")
163
+ print("📤 Enviando respuestas al servidor...")
164
+
165
+ payload = {
166
+ "username": USERNAME,
167
+ "agent_code": AGENT_CODE_URL,
168
+ "answers": results
169
+ }
170
+
171
+ try:
172
+ response = requests.post(f"{API_URL}/submit", json=payload, timeout=60)
173
+ result = response.json()
174
+ print(f"✅ Respuesta del servidor:")
175
+ print(f" {json.dumps(result, indent=2)}")
176
+ return result
177
+ except Exception as e:
178
+ print(f"❌ Error al enviar: {e}")
179
+ return None
180
+
181
+
182
+ def save_diagnostics(diagnostics):
183
+ """Guarda el archivo de diagnóstico."""
184
+ ts = time.strftime("%Y%m%d_%H%M%S")
185
+ diag_path = os.path.join("tasks", f"diagnostics_v2_{ts}.json")
186
+ with open(diag_path, "w", encoding="utf-8") as df:
187
+ json.dump(diagnostics, df, ensure_ascii=False, indent=2)
188
+ print(f"\n✅ Diagnóstico guardado: {diag_path}")
189
+
190
+
191
+ def main():
192
+ """Función principal del script."""
193
+ print("🚀 Iniciando Agente Local MEJORADO v2...")
194
+ print(" Modelo: qwen2.5-coder:14b")
195
+ print(" Objetivo: Resolver 6+ preguntas correctamente\n")
196
+
197
+ # 1. Cargar preguntas
198
+ all_questions = load_questions()
199
+ if not all_questions:
200
+ return
201
+
202
+ # 2. Seleccionar preguntas a procesar
203
+ questions_to_process = select_questions(all_questions)
204
+ if not questions_to_process:
205
+ print("⚠️ No hay preguntas para procesar.")
206
+ return
207
+
208
+ # 3. Crear agente (una sola instancia reutilizable)
209
+ print("🤖 Creando agente reutilizable...\n")
210
+ agent = EnhancedLocalAgent()
211
+
212
+ # 4. Procesar todas las preguntas
213
+ results, diagnostics = process_questions(agent, questions_to_process)
214
+
215
+ # 5. Enviar resultados
216
+ submit_results(results)
217
+
218
+ # 6. Guardar diagnóstico
219
+ save_diagnostics(diagnostics)
220
+
221
+ print(f"\n{'='*80}")
222
+ print("🎯 Ejecución completada")
223
+ print(f"{'='*80}\n")
224
+
225
+
226
+ if __name__ == "__main__":
227
+ main()
v2/requirements-v2.txt ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Core dependencies
2
+ requests>=2.31.0
3
+ smolagents>=1.0.0
4
+
5
+ # Model support
6
+ litellm>=1.0.0
7
+
8
+ # Data processing
9
+ pandas>=2.0.0
10
+ openpyxl>=3.1.0 # For Excel files
11
+
12
+ # Web scraping and parsing
13
+ beautifulsoup4>=4.12.0
14
+ lxml>=4.9.0
15
+ markdownify>=0.11.0
16
+
17
+ # Optional: For additional features
18
+ duckduckgo-search>=3.9.0 # If using DuckDuckGo search
19
+
20
+ wikipedia-api
v2/tools.py ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Herramientas personalizadas para el GAIA Agent
3
+ """
4
+ import requests
5
+ from smolagents import tool
6
+ from markdownify import markdownify as md
7
+
8
+
9
+ @tool
10
+ def smart_visit(url: str) -> str:
11
+ """
12
+ Visits a webpage and returns its content converted to Markdown.
13
+ Essential for Wikipedia, documentation, or any web content.
14
+
15
+ Args:
16
+ url: The URL of the page to visit.
17
+
18
+ Returns:
19
+ str: Webpage content in Markdown format (max 25000 chars)
20
+ """
21
+ try:
22
+ headers = {
23
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
24
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
25
+ 'Accept-Language': 'en-US,en;q=0.5',
26
+ 'Referer': 'https://www.google.com/'
27
+ }
28
+ response = requests.get(url, headers=headers, timeout=25)
29
+ response.raise_for_status()
30
+
31
+ content = md(response.text)
32
+ return content[:25000]
33
+ except Exception as e:
34
+ return f"Error visiting {url}: {str(e)}"
35
+
36
+
37
+ @tool
38
+ def get_youtube_info(video_url: str) -> str:
39
+ """
40
+ Gets information about a YouTube video including title, description,
41
+ and attempts to find transcripts or related information.
42
+
43
+ Args:
44
+ video_url: YouTube video URL (e.g., https://www.youtube.com/watch?v=VIDEO_ID)
45
+
46
+ Returns:
47
+ str: Video information and transcript search strategy
48
+ """
49
+ try:
50
+ # Extraer video ID
51
+ if "youtube.com" in video_url:
52
+ video_id = video_url.split("v=")[1].split("&")[0] if "v=" in video_url else ""
53
+ elif "youtu.be" in video_url:
54
+ video_id = video_url.split("/")[-1].split("?")[0]
55
+ else:
56
+ return "Invalid YouTube URL"
57
+
58
+ if not video_id:
59
+ return "Could not extract video ID"
60
+
61
+ return f"""Video ID: {video_id}
62
+
63
+ STRATEGY TO ANSWER:
64
+ 1. Search for '{video_id}' + keywords from the question on DuckDuckGo
65
+ 2. Look for transcripts, comments, or discussion forums about this video
66
+ 3. The video URL is: {video_url}
67
+
68
+ Note: Direct video playback is not available. Search online for transcripts or summaries."""
69
+
70
+ except Exception as e:
71
+ return f"Error processing YouTube video: {str(e)}"
72
+
73
+
74
+ @tool
75
+ def visit_webpage(url: str) -> str:
76
+ """
77
+ Visits a webpage and returns its content in Markdown format.
78
+
79
+ Args:
80
+ url: The URL of the webpage to visit
81
+
82
+ Returns:
83
+ str: The webpage content converted to Markdown
84
+ """
85
+ return smart_visit(url)
86
+
87
+
88
+ @tool
89
+ def wikipedia_search(query: str) -> str:
90
+ """
91
+ Searches Wikipedia for a query and returns the page content in Markdown format.
92
+
93
+ Args:
94
+ query: The search term or topic to look up on Wikipedia
95
+
96
+ Returns:
97
+ str: The Wikipedia page content in Markdown format, or an error message
98
+ """
99
+ try:
100
+ import urllib.parse
101
+ search_url = f"https://en.wikipedia.org/w/index.php?search={urllib.parse.quote_plus(query)}&title=Special%3ASearch&go=Go"
102
+ return smart_visit(search_url)
103
+ except Exception as e:
104
+ return f"Error searching Wikipedia: {e}"
105
+
106
+
107
+ @tool
108
+ def answer_video_questions(video_url: str, question: str) -> str:
109
+ """
110
+ Provides guidance on how to answer questions about a video by extracting metadata
111
+ and suggesting search queries to find transcripts or discussions.
112
+
113
+ Args:
114
+ video_url: The URL of the video (YouTube or similar platform)
115
+ question: The specific question to answer about the video
116
+
117
+ Returns:
118
+ str: Video metadata and suggested search queries to find answers
119
+ """
120
+ try:
121
+ info = get_youtube_info(video_url)
122
+ # Provide a compact actionable payload for the agent
123
+ return f"VIDEO_INFO:\n{info}\n\nSUGGESTED_QUERIES:\n- \"{video_url} transcript\"\n- \"{video_url} subtitles\"\n- \"{video_url} comments discussion\"\n\nUse VisitWebpageTool/WikipediaSearchTool to follow links."
124
+ except Exception as e:
125
+ return f"Error answering video question: {e}"
v2/utils.py ADDED
@@ -0,0 +1,212 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Funciones de utilidad para el GAIA Agent
3
+ """
4
+ import os
5
+ import re
6
+ import requests
7
+ import shutil
8
+ import urllib.parse
9
+ import mimetypes
10
+ from bs4 import BeautifulSoup
11
+ from config import API_URL, QUESTION_TYPES
12
+
13
+
14
+ def clean_ansi_codes(text):
15
+ """Limpia los códigos ANSI de color de la terminal."""
16
+ ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
17
+ return ansi_escape.sub('', text)
18
+
19
+
20
+ def download_file_for_task(task_id):
21
+ """
22
+ Descarga el archivo adjunto de una tarea si existe.
23
+
24
+ Args:
25
+ task_id: ID de la tarea
26
+
27
+ Returns:
28
+ str: Ruta del archivo descargado o None si no hay archivo
29
+ """
30
+ file_url = f"{API_URL}/files/{task_id}"
31
+ try:
32
+ response = requests.get(file_url, stream=True, timeout=30)
33
+ if response.status_code == 200:
34
+ filename = f"file_{task_id}"
35
+
36
+ # Obtener nombre real del header
37
+ if "content-disposition" in response.headers:
38
+ cd = response.headers["content-disposition"]
39
+ if "filename=" in cd:
40
+ filename = cd.split("filename=")[1].strip('"')
41
+
42
+ # Asegurar extensión correcta
43
+ if "." not in filename:
44
+ content_type = response.headers.get("content-type", "")
45
+ if "excel" in content_type or "spreadsheet" in content_type:
46
+ filename += ".xlsx"
47
+ elif "audio" in content_type or "mpeg" in content_type:
48
+ filename += ".mp3"
49
+ elif "image" in content_type or "png" in content_type:
50
+ filename += ".png"
51
+ elif "python" in content_type:
52
+ filename += ".py"
53
+
54
+ with open(filename, 'wb') as f:
55
+ shutil.copyfileobj(response.raw, f)
56
+
57
+ print(f" ✓ Archivo descargado: {filename} ({os.path.getsize(filename)} bytes)")
58
+ return filename
59
+ except Exception as e:
60
+ print(f" ✗ Error descargando archivo: {e}")
61
+ return None
62
+
63
+
64
+ def fetch_and_download_links(url, dest_dir, max_files=20):
65
+ """
66
+ Fetch a webpage, extract links to common resource file types and download them.
67
+
68
+ Args:
69
+ url (str): Webpage URL to scan for resources.
70
+ dest_dir (str): Directory where downloaded resources will be saved.
71
+ max_files (int): Maximum number of files to download.
72
+
73
+ Returns:
74
+ list: Paths of downloaded files.
75
+ """
76
+ downloaded = []
77
+ try:
78
+ os.makedirs(dest_dir, exist_ok=True)
79
+ resp = requests.get(url, timeout=20)
80
+ resp.raise_for_status()
81
+ soup = BeautifulSoup(resp.text, "lxml")
82
+
83
+ # find candidate links from href and src
84
+ candidates = []
85
+ for tag in soup.find_all(['a', 'link']):
86
+ href = tag.get('href')
87
+ if href:
88
+ candidates.append(href)
89
+ for tag in soup.find_all(['img', 'script', 'source']):
90
+ src = tag.get('src')
91
+ if src:
92
+ candidates.append(src)
93
+
94
+ # normalize and filter
95
+ seen = set()
96
+ allowed_exts = {'.png', '.jpg', '.jpeg', '.gif', '.svg', '.pdf', '.zip', '.mp3', '.mp4', '.py', '.txt', '.csv', '.xlsx', '.xls'}
97
+ for c in candidates:
98
+ if len(downloaded) >= max_files:
99
+ break
100
+ full = urllib.parse.urljoin(url, c)
101
+ if full in seen:
102
+ continue
103
+ seen.add(full)
104
+
105
+ path = urllib.parse.urlparse(full).path
106
+ ext = os.path.splitext(path)[1].lower()
107
+ # Accept if extension recognized or content-type later
108
+ if ext in allowed_exts:
109
+ try:
110
+ r = requests.get(full, stream=True, timeout=20)
111
+ r.raise_for_status()
112
+ cd = r.headers.get('content-disposition')
113
+ if cd and 'filename=' in cd:
114
+ fname = cd.split('filename=')[1].strip('"')
115
+ else:
116
+ fname = os.path.basename(path) or f"resource_{len(downloaded)}{ext}"
117
+ out_path = os.path.join(dest_dir, fname)
118
+ with open(out_path, 'wb') as of:
119
+ shutil.copyfileobj(r.raw, of)
120
+ downloaded.append(out_path)
121
+ except Exception:
122
+ continue
123
+ else:
124
+ # try a HEAD request to see if content-type indicates a file
125
+ try:
126
+ h = requests.head(full, timeout=10)
127
+ ctype = h.headers.get('content-type', '')
128
+ if any(t in ctype for t in ['image/', 'audio/', 'video/', 'application/pdf', 'text/', 'application/octet-stream', 'application/zip', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml']):
129
+ # download
130
+ try:
131
+ r = requests.get(full, stream=True, timeout=20)
132
+ r.raise_for_status()
133
+ fname = os.path.basename(urllib.parse.urlparse(full).path) or f"resource_{len(downloaded)}"
134
+ if not os.path.splitext(fname)[1]:
135
+ ext = mimetypes.guess_extension(ctype.split(';')[0].strip()) or ''
136
+ fname += ext
137
+ out_path = os.path.join(dest_dir, fname)
138
+ with open(out_path, 'wb') as of:
139
+ shutil.copyfileobj(r.raw, of)
140
+ downloaded.append(out_path)
141
+ except Exception:
142
+ continue
143
+ except Exception:
144
+ continue
145
+
146
+ except Exception:
147
+ return downloaded
148
+
149
+ return downloaded
150
+
151
+
152
+ def detect_question_type(question, file_name):
153
+ """
154
+ Detecta el tipo de pregunta para aplicar estrategia específica.
155
+
156
+ Args:
157
+ question: Texto de la pregunta
158
+ file_name: Nombre del archivo adjunto (si existe)
159
+
160
+ Returns:
161
+ str: Tipo de pregunta (ver QUESTION_TYPES en config.py)
162
+ """
163
+ q_lower = question.lower()
164
+
165
+ if "youtube.com" in question or "youtu.be" in question:
166
+ return QUESTION_TYPES['YOUTUBE_VIDEO']
167
+ elif file_name and file_name.endswith(".png"):
168
+ return QUESTION_TYPES['IMAGE_FILE']
169
+ elif file_name and file_name.endswith(".mp3"):
170
+ return QUESTION_TYPES['AUDIO_FILE']
171
+ elif file_name and file_name.endswith((".xlsx", ".csv")):
172
+ return QUESTION_TYPES['DATA_FILE']
173
+ elif file_name and file_name.endswith(".py"):
174
+ return QUESTION_TYPES['CODE_FILE']
175
+ elif "wikipedia" in q_lower:
176
+ return QUESTION_TYPES['WIKIPEDIA']
177
+ elif any(word in q_lower for word in ["how many", "count", "number of"]):
178
+ return QUESTION_TYPES['COUNTING']
179
+ elif "reverse" in q_lower or "backwards" in q_lower or ".rewsna" in question:
180
+ return QUESTION_TYPES['TEXT_MANIPULATION']
181
+ else:
182
+ return QUESTION_TYPES['GENERAL']
183
+
184
+
185
+ def clean_answer(answer):
186
+ """
187
+ Limpia la respuesta del agente eliminando formato innecesario.
188
+ Preserva mayúsculas originales.
189
+
190
+ Args:
191
+ answer: Respuesta del agente
192
+
193
+ Returns:
194
+ str: Respuesta limpia
195
+ """
196
+ answer = str(answer).strip()
197
+
198
+ # Limpiar patrones comunes (case-insensitive para búsqueda, pero preservar original)
199
+ patterns_to_remove = [
200
+ (r'^Final Answer:\s*', ''),
201
+ (r'^Answer:\s*', ''),
202
+ (r'^The answer is\s*', ''),
203
+ (r'^Based on[^,]*,\s*', ''),
204
+ (r'```', ''),
205
+ (r'\*\*', ''),
206
+ (r'^##\s*', '')
207
+ ]
208
+
209
+ for pattern, replacement in patterns_to_remove:
210
+ answer = re.sub(pattern, replacement, answer, flags=re.IGNORECASE)
211
+
212
+ return answer.strip()