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

Clean project structure with English comments

Browse files
Files changed (11) hide show
  1. agent.py +10 -10
  2. app.py +45 -45
  3. config.py +6 -6
  4. tools.py +1 -1
  5. utils.py +19 -19
  6. v2/agent.py +0 -251
  7. v2/config.py +0 -47
  8. v2/main_simple.py +0 -227
  9. v2/requirements-v2.txt +0 -20
  10. v2/tools.py +0 -125
  11. v2/utils.py +0 -212
agent.py CHANGED
@@ -22,10 +22,10 @@ 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
@@ -34,14 +34,14 @@ class EnhancedAgent:
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()
@@ -200,15 +200,15 @@ Examples of BAD answers:
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']
 
22
 
23
 
24
  class EnhancedAgent:
25
+ """Enhanced agent with question-type specific strategies."""
26
 
27
  def __init__(self):
28
+ print(f" 🤖 Initializing agent...")
29
 
30
  if USE_LOCAL_MODEL:
31
  # Usar Ollama local
 
34
  api_base=OLLAMA_API_BASE,
35
  api_key=OLLAMA_API_KEY
36
  )
37
+ print(f" 📦 Model: {OLLAMA_MODEL_ID} (local)")
38
  else:
39
+ # Use HuggingFace API
40
  self.model = InferenceClientModel(
41
  model_id=HF_MODEL_ID,
42
  token=HF_TOKEN
43
  )
44
+ print(f" ☁️ Model: {HF_MODEL_ID} (HuggingFace)")
45
 
46
  search_tool = DuckDuckGoSearchTool()
47
  visit_tool = VisitWebpageTool()
 
200
 
201
  def solve(self, question, local_file=None, question_type=None):
202
  """
203
+ Solve a question using an optimized strategy.
204
 
205
  Args:
206
+ question: The question text
207
+ local_file: Path to attached file (optional)
208
+ question_type: Detected question type
209
 
210
  Returns:
211
+ tuple: (answer, execution logs)
212
  """
213
  if question_type is None:
214
  question_type = QUESTION_TYPES['GENERAL']
app.py CHANGED
@@ -1,6 +1,6 @@
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
@@ -21,7 +21,7 @@ from utils import detect_question_type, download_file_for_task
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:
@@ -34,17 +34,17 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
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 = []
@@ -52,7 +52,7 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
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):
@@ -66,27 +66,27 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
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(
@@ -106,10 +106,10 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
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
@@ -117,12 +117,12 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
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({
@@ -135,26 +135,26 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
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(),
@@ -191,16 +191,16 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
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()
@@ -211,9 +211,9 @@ with gr.Blocks() as demo:
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(
@@ -223,5 +223,5 @@ with gr.Blocks() as demo:
223
 
224
 
225
  if __name__ == "__main__":
226
- print("🚀 Iniciando GAIA Agent v2...")
227
  demo.launch()
 
1
  """
2
+ GAIA Agent - Main Gradio Interface
3
+ Enhanced agent with question-type specific strategies
4
  """
5
  import os
6
  import re
 
21
  # ============================================================================
22
 
23
  def run_and_submit_all(profile: gr.OAuthProfile | None):
24
+ """Run the agent on all questions and submit the results."""
25
  space_id = os.getenv("SPACE_ID")
26
 
27
  if profile:
 
34
  submit_url = f"{api_url}/submit"
35
  agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
36
 
37
+ # Load questions
38
  try:
39
+ print("📥 Loading questions from server...")
40
  response = requests.get(questions_url, timeout=15)
41
  questions_data = response.json()
42
+ print(f" ✓ {len(questions_data)} questions loaded")
43
  except Exception as e:
44
  return f"Error fetching questions: {e}", None
45
 
46
+ # Create agent (reusable)
47
+ print("\n🤖 Creating agent...")
48
  agent = EnhancedAgent()
49
 
50
  results_log = []
 
52
  diagnostics = []
53
 
54
  print(f"\n{'='*80}")
55
+ print(f"🚀 Starting processing of {len(questions_data)} questions")
56
  print(f"{'='*80}\n")
57
 
58
  for i, item in enumerate(questions_data):
 
66
  print(f"\n{'='*80}")
67
  print(f"[{i+1}/{len(questions_data)}] Task: {task_id}")
68
  print(f"{'='*80}")
69
+ print(f"❓ Question: {question_text[:150]}...")
70
 
71
+ # Detect question type
72
  question_type = detect_question_type(question_text, file_name)
73
+ print(f"🔍 Detected type: {question_type}")
74
 
75
  if file_name:
76
+ print(f"📎 Expected file: {file_name}")
77
 
78
+ # Download file if exists
79
  local_file = download_file_for_task(task_id)
80
 
81
+ # Show URLs found in the question
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 found: {url}")
86
 
87
+ # Execute agent
88
  start_time = time.time()
89
+ print(f"⚙️ Processing with strategy '{question_type}'...")
90
 
91
  try:
92
  submitted_answer, execution_logs = agent.solve(
 
106
 
107
  elapsed = time.time() - start_time
108
 
109
+ print(f"\n✅ Answer: {submitted_answer}")
110
+ print(f"⏱️ Time: {elapsed:.1f}s")
111
 
112
+ # Save results
113
  answers_payload.append({
114
  "task_id": task_id,
115
  "submitted_answer": submitted_answer
 
117
 
118
  results_log.append({
119
  "Task ID": task_id,
120
+ "Index": i,
121
+ "Type": question_type,
122
+ "Question": question_text[:100] + "..." if len(question_text) > 100 else question_text,
123
+ "File": file_name or "N/A",
124
+ "Answer": submitted_answer,
125
+ "Time (s)": round(elapsed, 1)
126
  })
127
 
128
  diagnostics.append({
 
135
  "elapsed_seconds": round(elapsed, 1)
136
  })
137
 
138
+ # Clean up temporary file
139
  if local_file and os.path.exists(local_file):
140
  try:
141
  os.remove(local_file)
142
  except:
143
  pass
144
 
145
+ # Save diagnostics
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📊 Diagnostics saved: {diag_path}")
152
  except Exception as e:
153
+ print(f"⚠️ Error saving diagnostics: {e}")
154
 
155
+ # Submit results
156
  print(f"\n{'='*80}")
157
+ print("📤 Submitting answers to server...")
158
 
159
  submission_data = {
160
  "username": username.strip(),
 
191
 
192
  with gr.Blocks() as demo:
193
  gr.Markdown("""
194
+ # 🤖 GAIA Agent - Optimized for Files, YouTube and Logic
195
 
196
+ This agent uses question-specific strategies:
197
+ - 📊 **Excel/CSV Files**: Reads and analyzes data with pandas
198
+ - 🎬 **YouTube**: Searches for transcripts and online discussions
199
+ - 🖼️ **Images**: Searches for information on the web
200
+ - 🎵 **Audio**: Searches for transcripts online
201
+ - 📝 **Wikipedia**: Navigates and extracts information
202
+ - 🔢 **Counting**: Lists items and counts programmatically
203
+ - 🔄 **Text Manipulation**: Handles reversed text, opposites, etc.
204
  """)
205
 
206
  gr.LoginButton()
 
211
  status_output = gr.Textbox(label="📋 Status", lines=6, interactive=False)
212
 
213
  results_table = gr.DataFrame(
214
+ label="📊 Detailed Results",
215
  wrap=True,
216
+ headers=["Task ID", "Index", "Type", "Question", "File", "Answer", "Time (s)"]
217
  )
218
 
219
  run_button.click(
 
223
 
224
 
225
  if __name__ == "__main__":
226
+ print("🚀 Starting GAIA Agent...")
227
  demo.launch()
config.py CHANGED
@@ -1,5 +1,5 @@
1
  """
2
- Configuración y constantes del GAIA Agent v2
3
  """
4
  import os
5
 
@@ -11,16 +11,16 @@ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
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
 
 
1
  """
2
+ Configuration and constants for the GAIA Agent
3
  """
4
  import os
5
 
 
11
  # ============================================================================
12
  # MODEL CONFIGURATION
13
  # ============================================================================
14
+ # Set based on environment
15
+ USE_LOCAL_MODEL = False # True = Local Ollama, False = HuggingFace API
16
 
17
+ # Ollama configuration (local)
18
  OLLAMA_MODEL_ID = "ollama/qwen2.5-coder:14b"
19
  OLLAMA_API_BASE = "http://localhost:11434"
20
  OLLAMA_API_KEY = "ollama"
21
 
22
+ # HuggingFace configuration (cloud)
23
+ # Using a powerful model for better GAIA benchmark performance
24
  HF_MODEL_ID = "Qwen/Qwen2.5-72B-Instruct"
25
  HF_TOKEN = os.getenv("HF_TOKEN")
26
 
tools.py CHANGED
@@ -1,5 +1,5 @@
1
  """
2
- Herramientas personalizadas para el GAIA Agent v2
3
  """
4
  import requests
5
  from smolagents import tool
 
1
  """
2
+ Custom tools for the GAIA Agent
3
  """
4
  import requests
5
  from smolagents import tool
utils.py CHANGED
@@ -1,5 +1,5 @@
1
  """
2
- Funciones de utilidad para el GAIA Agent v2
3
  """
4
  import os
5
  import re
@@ -12,13 +12,13 @@ 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 = [
@@ -39,14 +39,14 @@ def clean_answer(answer):
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
 
@@ -72,13 +72,13 @@ def detect_question_type(question, file_name):
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:
@@ -86,13 +86,13 @@ def download_file_for_task(task_id):
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:
@@ -107,24 +107,24 @@ def download_file_for_task(task_id):
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:
 
1
  """
2
+ Utility functions for the GAIA Agent
3
  """
4
  import os
5
  import re
 
12
 
13
 
14
  def clean_ansi_codes(text):
15
+ """Remove ANSI color codes from terminal output."""
16
  ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
17
  return ansi_escape.sub('', text)
18
 
19
 
20
  def clean_answer(answer):
21
+ """Clean the agent response by removing unnecessary formatting."""
22
  answer = str(answer).strip()
23
 
24
  patterns_to_remove = [
 
39
 
40
  def detect_question_type(question, file_name):
41
  """
42
+ Detect the question type to apply a specific strategy.
43
 
44
  Args:
45
+ question: The question text
46
+ file_name: Name of the attached file (if any)
47
 
48
  Returns:
49
+ str: Question type (see QUESTION_TYPES in config.py)
50
  """
51
  q_lower = question.lower()
52
 
 
72
 
73
  def download_file_for_task(task_id):
74
  """
75
+ Download the attached file for a task if it exists.
76
 
77
  Args:
78
+ task_id: The task ID
79
 
80
  Returns:
81
+ str: Path to downloaded file or None if no file exists
82
  """
83
  file_url = f"{DEFAULT_API_URL}/files/{task_id}"
84
  try:
 
86
  if response.status_code == 200:
87
  filename = f"file_{task_id}"
88
 
89
+ # Get real filename from 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
+ # Ensure correct extension
96
  if "." not in filename:
97
  content_type = response.headers.get("content-type", "")
98
  if "excel" in content_type or "spreadsheet" in content_type:
 
107
  with open(filename, 'wb') as f:
108
  shutil.copyfileobj(response.raw, f)
109
 
110
+ print(f" ✓ File downloaded: {filename} ({os.path.getsize(filename)} bytes)")
111
  return filename
112
  except Exception as e:
113
+ print(f" ✗ Error downloading file: {e}")
114
  return None
115
 
116
 
117
  def fetch_and_download_links(url, dest_dir, max_files=20):
118
  """
119
+ Download linked resources from a URL.
120
 
121
  Args:
122
+ url: URL of the page to scan
123
+ dest_dir: Destination directory for files
124
+ max_files: Maximum number of files to download
125
 
126
  Returns:
127
+ list: List of downloaded file paths
128
  """
129
  downloaded = []
130
  try:
v2/agent.py DELETED
@@ -1,251 +0,0 @@
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 DELETED
@@ -1,47 +0,0 @@
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 DELETED
@@ -1,227 +0,0 @@
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 DELETED
@@ -1,20 +0,0 @@
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 DELETED
@@ -1,125 +0,0 @@
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 DELETED
@@ -1,212 +0,0 @@
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()