Csuarezg commited on
Commit
b56c671
ยท
verified ยท
1 Parent(s): d15e45c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +957 -188
app.py CHANGED
@@ -8,9 +8,10 @@ import tempfile
8
  import logging
9
  from typing import List, Dict, Optional, TypedDict, Annotated
10
  import numpy as np
 
11
 
12
  # Core ML/AI imports
13
- from langchain_core.messages import HumanMessage, SystemMessage, AnyMessage
14
  from langchain_openai import ChatOpenAI
15
  from langchain_core.tools import tool
16
  from langchain_community.tools.tavily_search import TavilySearchResults
@@ -22,10 +23,10 @@ from langgraph.checkpoint.memory import MemorySaver
22
 
23
  # File processing
24
  import wikipedia
25
- from youtube_transcript_api import YouTubeTranscriptApi
26
  import speech_recognition as sr
27
 
28
- # Computer vision (will be downloaded at runtime)
29
  try:
30
  from ultralytics import YOLO
31
  import cv2
@@ -49,65 +50,106 @@ os.environ['YOLO_VERBOSE'] = 'false'
49
  logging.getLogger("ultralytics").setLevel(logging.ERROR)
50
 
51
  # --- Constants ---
52
- DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
 
 
53
 
54
- # System prompt for the agent
55
  SYSTEM_PROMPT = """You are a precision research assistant for the GAIA benchmark. Your mission is EXTREME ACCURACY.
56
-
57
  CRITICAL ANSWER FORMAT RULES:
58
- - ALWAYS end with: FINAL ANSWER: [answer]
59
- - READ THE QUESTION CAREFULLY - answer EXACTLY what is asked for, nothing more, nothing less
60
-
61
  SPECIFIC FORMATTING BY QUESTION TYPE:
62
- - Numbers: ONLY the number, no units, no text
63
- Example: "FINAL ANSWER: 2" NOT "FINAL ANSWER: 2 albums"
64
- - First name only: ONLY the first name
65
- Example: If person is "John Smith" โ†’ "FINAL ANSWER: John"
66
- - Country codes, IOC codes, abbreviations, symbols: ONLY the code/abbreviation, no country name or brackets
67
- Example: If asked for IOC country code โ†’ "FINAL ANSWER: PHI" NOT "FINAL ANSWER: PHILIPPINES [PHI]"
68
- - Lists/Sets: Exactly as requested format
69
- Example: "FINAL ANSWER: a, b, d, e" (comma-separated, alphabetical order)
70
-
 
71
  CRITICAL TOOL SELECTION:
72
- - Wikipedia questions โ†’ wikipedia_tool ONLY
73
- - File questions โ†’ file_analyzer_tool FIRST to inspect contents, then reason based on structure
74
- - Current events โ†’ web_search_tool ONLY
75
- - Mathematical analysis/calculations โ†’ wolfram_alpha_tool or python_repl_tool ONLY
76
- - Tables, matrices, systematic checking โ†’ python_repl_tool ONLY
77
-
78
  FOR MATHEMATICAL PROBLEMS:
79
- ALWAYS use python_repl_tool when:
80
- - Analyzing mathematical tables or matrices
81
- - Checking properties like commutativity, associativity
82
- - Systematic verification of mathematical statements
83
- - Complex calculations that need precision
84
- - ANY problem involving tables, sets, or systematic checking
85
-
 
 
 
 
 
 
 
 
 
 
86
  FILE HANDLING:
87
- - You HAVE the ability to read and analyze uploaded files
88
- - ALWAYS use file_analyzer_tool when questions mention files
89
- - The tool automatically finds and analyzes Excel, CSV, images, and audio files
90
- - For Excel/CSV: Returns columns, data types, sample rows, and numeric totals
91
- - NEVER say "I can't access files" - you CAN access them via file_analyzer_tool
92
- - Example: "The attached Excel file..." โ†’ Use file_analyzer_tool immediately
93
-
 
 
 
 
 
94
  REASONING PROCESS:
95
- 1. Carefully read what the question is asking for
96
- 2. Identify if it needs systematic/mathematical analysis
97
- 3. Use appropriate tool (python_repl_tool for math problems)
98
- 4. Extract ONLY the specific part requested
99
- 5. Format according to the rules above
 
 
 
 
 
 
100
  """
101
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  class GAIAAgent:
103
  def __init__(self):
104
  print("๐Ÿš€ Initializing GAIA Agent...")
105
 
106
  # API Keys from HF Secrets
107
  self.openai_api_key = os.getenv("OPENAI_API_KEY")
108
- self.tavily_api_key = os.getenv("TAVILY_API_KEY")
109
  self.wolfram_api_key = os.getenv("WOLFRAM_API_KEY")
110
  self.hf_token = os.getenv("HUGGING_FACE_API_TOKEN")
 
111
 
112
  if not self.openai_api_key:
113
  raise ValueError("OPENAI_API_KEY not found in environment variables")
@@ -135,7 +177,7 @@ class GAIAAgent:
135
  print("โœ… GAIA Agent initialized successfully!")
136
 
137
  def _setup_tools(self):
138
- """Setup all the tools for the agent"""
139
 
140
  # Store reference to self for use in nested functions
141
  agent_instance = self
@@ -143,56 +185,313 @@ class GAIAAgent:
143
  # Wikipedia tool
144
  @tool
145
  def wikipedia_tool(query: str) -> str:
146
- """Search Wikipedia for encyclopedic information"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  try:
148
  wikipedia.set_lang("en")
149
- summary = wikipedia.summary(query, sentences=3)
150
- page = wikipedia.page(query)
151
- return f"WIKIPEDIA: {page.title}\n\n{summary}\n\nURL: {page.url}"
152
- except wikipedia.DisambiguationError as e:
153
- summary = wikipedia.summary(e.options[0], sentences=3)
154
- page = wikipedia.page(e.options[0])
155
- return f"WIKIPEDIA: {page.title}\n\n{summary}\n\nURL: {page.url}"
 
 
 
 
 
 
 
 
156
  except Exception as e:
157
  return f"Wikipedia error: {str(e)}"
158
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  # Web search tool
160
- @tool
161
- def web_search_tool(query: str) -> str:
162
- """Web search for current information"""
 
 
 
 
 
163
  if not agent_instance.tavily_api_key:
164
- return "Tavily API key not available"
 
165
  try:
166
- tavily_search = TavilySearchResults(api_key=agent_instance.tavily_api_key, max_results=5)
167
- results = tavily_search.invoke(query)
168
- formatted_results = []
169
- for i, res in enumerate(results, 1):
170
- formatted_results.append(f"RESULT {i}:\nTitle: {res.get('title', 'N/A')}\nContent: {res.get('content', 'N/A')}")
171
- return "\n\n".join(formatted_results)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  except Exception as e:
173
- return f"Search error: {str(e)}"
 
 
 
 
 
 
174
 
175
  # Wolfram Alpha tool
176
  @tool
177
  def wolfram_alpha_tool(query: str) -> str:
178
- """Use Wolfram Alpha for computational questions"""
 
179
  if not agent_instance.wolfram_api_key:
180
- return "Wolfram API key not available"
181
-
182
  params = {
183
  'appid': agent_instance.wolfram_api_key,
184
  'input': query,
185
  'format': 'plaintext',
186
- 'output': 'JSON'
 
187
  }
188
  try:
 
189
  resp = requests.get("http://api.wolframalpha.com/v2/query", params=params, timeout=30)
190
  resp.raise_for_status()
191
  data = resp.json().get('queryresult', {})
192
-
193
  if not data.get('success'):
194
- return f"Wolfram Alpha couldn't process: {query}"
195
-
196
  results = []
197
  for pod in data.get('pods', []):
198
  pod_title = pod.get('title', 'Unknown')
@@ -200,67 +499,63 @@ class GAIAAgent:
200
  plaintext = subpod.get('plaintext')
201
  if plaintext and plaintext.strip():
202
  results.append(f"{pod_title}: {plaintext}")
203
-
204
- return " | ".join(results[:5]) if results else "No readable results"
205
-
206
- except Exception as e:
 
 
 
207
  return f"Wolfram Alpha error: {e}"
 
 
208
 
209
- # File analyzer tool
210
  @tool
211
- def file_analyzer_tool(file_description: str = "uploaded file") -> str:
212
- """Analyze uploaded files (Excel, CSV, images, audio)"""
 
 
 
 
 
 
 
 
 
213
  try:
214
- search_paths = ["./", "./uploads", "./files", "./data"]
215
- data_exts = ['.xlsx', '.xls', '.csv']
216
- found_files = []
217
 
218
- for path in search_paths:
219
- if os.path.exists(path):
220
- for file in os.listdir(path):
221
- if any(file.lower().endswith(ext) for ext in data_exts):
222
- found_files.append(os.path.join(path, file))
223
 
224
- if not found_files:
225
- return "No supported data files found"
226
 
227
- results = []
228
- for file_path in found_files:
229
- try:
230
- ext = os.path.splitext(file_path)[1].lower()
231
- if ext in ['.xlsx', '.xls']:
232
- df = pd.read_excel(file_path)
233
- elif ext == '.csv':
234
- df = pd.read_csv(file_path)
235
- else:
236
- continue
237
-
238
- result = f"๐Ÿ“„ FILE: {file_path}\n"
239
- result += f"๐Ÿ”ข SHAPE: {df.shape}\n"
240
- result += f"๐Ÿง  COLUMNS: {list(df.columns)}\n"
241
- result += f"๐Ÿ“Š FIRST 5 ROWS:\n{df.head().to_string(index=False)}\n"
242
-
243
- numeric_cols = df.select_dtypes(include=['number']).columns
244
- if len(numeric_cols) > 0:
245
- totals = df[numeric_cols].sum().round(2)
246
- result += f"๐Ÿ’ฐ NUMERIC TOTALS:\n{totals.to_string()}\n"
247
-
248
- results.append(result)
249
- except Exception as e:
250
- results.append(f"Error processing {file_path}: {e}")
251
 
252
- return "\n\n".join(results)
 
 
 
 
 
253
  except Exception as e:
254
- return f"File analysis error: {e}"
255
 
256
  # Python REPL tool
257
  python_repl_tool = PythonREPLTool()
258
 
259
  tools = [
260
  wikipedia_tool,
261
- web_search_tool,
262
- wolfram_alpha_tool,
263
  file_analyzer_tool,
 
 
 
 
264
  python_repl_tool
265
  ]
266
 
@@ -279,7 +574,11 @@ class GAIAAgent:
279
  if not messages or not isinstance(messages[0], SystemMessage):
280
  messages = [SystemMessage(content=SYSTEM_PROMPT)] + messages
281
 
 
282
  response = model_with_tools.invoke(messages)
 
 
 
283
  return {"messages": [response]}
284
 
285
  tool_node = ToolNode(self.tools)
@@ -289,39 +588,390 @@ class GAIAAgent:
289
  builder.add_node("tools", tool_node)
290
 
291
  builder.add_edge(START, "agent")
292
- builder.add_conditional_edges("agent", tools_condition, {"tools": "tools", END: END})
 
 
 
 
 
 
 
293
  builder.add_edge("tools", "agent")
294
 
295
  memory = MemorySaver()
296
  return builder.compile(checkpointer=memory)
297
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
298
  def _extract_final_answer(self, response_text: str) -> str:
299
  """Extract the final answer from agent response"""
300
  match = re.search(r"FINAL ANSWER:\s*(.*)", response_text, re.DOTALL | re.IGNORECASE)
 
301
  if match:
302
  raw_answer = match.group(1).strip()
303
- if "\n" in raw_answer:
304
- raw_answer = raw_answer.split("\n", 1)[0].strip()
 
305
  if raw_answer.endswith('.') and not raw_answer[:-1].replace('.', '').isdigit():
306
  raw_answer = raw_answer[:-1]
 
 
 
 
 
 
307
  return raw_answer.strip()
308
-
309
  lines = [line.strip() for line in response_text.strip().split('\n') if line.strip()]
310
  return lines[-1] if lines else response_text.strip()
311
 
312
- def __call__(self, question: str) -> str:
313
- """Main method called by Gradio interface"""
314
- print(f"๐Ÿค– Processing question: {question[:100]}...")
315
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
316
  try:
317
- config = {"configurable": {"thread_id": "gaia_session"}}
318
-
319
- # Run the agent
320
  final_state = None
321
  max_iterations = 0
322
 
 
323
  events = self.agent_runner.stream(
324
- {"messages": [HumanMessage(content=question)]},
325
  config=config,
326
  stream_mode="values"
327
  )
@@ -329,28 +979,45 @@ class GAIAAgent:
329
  for event in events:
330
  final_state = event
331
  max_iterations += 1
332
- if max_iterations > 8: # Prevent infinite loops
 
333
  break
334
-
335
  if not final_state or not final_state['messages']:
336
- return "Agent execution failed - no response generated"
337
-
 
338
  last_message = final_state['messages'][-1]
339
- full_response = last_message.content
340
 
341
- print(f"๐Ÿ“ Agent response: {full_response[:200]}...")
342
-
343
- # Extract final answer
 
 
 
 
 
 
 
 
 
344
  final_answer = self._extract_final_answer(full_response)
345
- print(f"๐ŸŽฏ Final answer: {final_answer}")
346
-
347
- return final_answer
 
 
 
 
 
 
 
348
 
349
  except Exception as e:
350
- print(f"โŒ Error processing question: {e}")
351
  import traceback
352
  traceback.print_exc()
353
- return f"Error: {str(e)}"
354
 
355
  def run_and_submit_all(profile: gr.OAuthProfile | None):
356
  """
@@ -366,10 +1033,6 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
366
  print("User not logged in.")
367
  return "Please Login to Hugging Face with the button.", None
368
 
369
- api_url = DEFAULT_API_URL
370
- questions_url = f"{api_url}/questions"
371
- submit_url = f"{api_url}/submit"
372
-
373
  # 1. Instantiate GAIA Agent
374
  try:
375
  agent = GAIAAgent()
@@ -377,84 +1040,172 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
377
  print(f"Error instantiating GAIA agent: {e}")
378
  return f"Error initializing GAIA agent: {e}", None
379
 
380
- agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
381
- print(f"Agent code URL: {agent_code}")
382
 
383
  # 2. Fetch Questions
 
 
 
 
 
 
384
  print(f"Fetching questions from: {questions_url}")
 
385
  try:
386
- response = requests.get(questions_url, timeout=15)
387
  response.raise_for_status()
388
  questions_data = response.json()
389
  if not questions_data:
390
  return "Fetched questions list is empty.", None
391
- print(f"Fetched {len(questions_data)} questions.")
392
  except Exception as e:
393
- print(f"Error fetching questions: {e}")
394
  return f"Error fetching questions: {e}", None
395
 
396
- # 3. Run GAIA Agent on questions
 
 
 
 
397
  results_log = []
398
  answers_payload = []
399
- print(f"Running GAIA agent on {len(questions_data)} questions...")
 
 
 
 
 
400
 
401
- for i, item in enumerate(questions_data):
402
  task_id = item.get("task_id")
403
- question_text = item.get("question") or item.get("Question")
404
 
405
- if not task_id or question_text is None:
406
- print(f"Skipping item {i} with missing data")
407
  continue
408
-
409
- print(f"Processing question {i+1}/{len(questions_data)}: {task_id}")
 
410
 
411
  try:
412
- submitted_answer = agent(question_text)
413
- answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
414
- results_log.append({
415
- "Task ID": task_id,
416
- "Question": question_text[:100] + "..." if len(question_text) > 100 else question_text,
417
- "Submitted Answer": submitted_answer
418
- })
419
- print(f"โœ… Question {i+1} completed: {submitted_answer}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
420
  except Exception as e:
421
- print(f"โŒ Error on question {i+1}: {e}")
422
- error_msg = f"AGENT ERROR: {str(e)}"
423
- answers_payload.append({"task_id": task_id, "submitted_answer": error_msg})
 
424
  results_log.append({
425
  "Task ID": task_id,
426
  "Question": question_text[:100] + "..." if len(question_text) > 100 else question_text,
427
- "Submitted Answer": error_msg
 
428
  })
 
429
 
430
  if not answers_payload:
431
  return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
432
 
433
- # 4. Prepare and Submit
434
  submission_data = {
435
  "username": username.strip(),
436
  "agent_code": agent_code,
437
  "answers": answers_payload
438
  }
439
 
440
- print(f"Submitting {len(answers_payload)} answers...")
 
 
441
  try:
442
- response = requests.post(submit_url, json=submission_data, timeout=120)
 
 
 
 
 
443
  response.raise_for_status()
444
  result_data = response.json()
445
 
 
 
 
 
 
 
446
  final_status = (
447
- f"๐ŸŽ‰ Submission Successful!\n"
448
- f"User: {result_data.get('username')}\n"
449
- f"Overall Score: {result_data.get('score', 'N/A')}% "
450
- f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
451
- f"Message: {result_data.get('message', 'No message received.')}"
 
 
 
 
 
 
 
 
 
452
  )
 
453
  print("โœ… Submission successful!")
 
 
454
  return final_status, pd.DataFrame(results_log)
455
 
456
  except Exception as e:
457
- error_msg = f"โŒ Submission Failed: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
458
  print(error_msg)
459
  return error_msg, pd.DataFrame(results_log)
460
 
@@ -463,16 +1214,24 @@ with gr.Blocks(title="GAIA Agent Evaluation") as demo:
463
  gr.Markdown("# ๐Ÿค– GAIA Agent Evaluation Runner")
464
  gr.Markdown(
465
  """
466
- **Advanced GAIA Benchmark Agent**
467
 
468
  This agent uses:
469
- - ๐Ÿง  GPT-4 Turbo with specialized tools
470
- - ๐Ÿ“š Wikipedia search for encyclopedic information
471
- - ๐ŸŒ Web search for current events
472
  - ๐Ÿงฎ Wolfram Alpha for computational tasks
473
- - ๐Ÿ“Š File analysis for Excel/CSV data
 
 
474
  - ๐Ÿ Python REPL for mathematical analysis
475
- - ๐ŸŽฏ Specialized prompt engineering for GAIA benchmark
 
 
 
 
 
 
476
 
477
  **Instructions:**
478
  1. Log in to your Hugging Face account
@@ -488,15 +1247,15 @@ with gr.Blocks(title="GAIA Agent Evaluation") as demo:
488
  run_button = gr.Button("๐Ÿš€ Run Evaluation & Submit All Answers", variant="primary")
489
 
490
  status_output = gr.Textbox(
491
- label="๐Ÿ“Š Run Status / Submission Result",
492
- lines=8,
493
  interactive=False
494
  )
495
 
496
  results_table = gr.DataFrame(
497
- label="๐Ÿ“ Questions and Agent Answers",
498
  wrap=True,
499
- max_height=400
500
  )
501
 
502
  run_button.click(
@@ -521,6 +1280,16 @@ if __name__ == "__main__":
521
  print(f"โœ… SPACE_ID: {space_id}")
522
  print(f" Repo URL: https://huggingface.co/spaces/{space_id}")
523
 
 
 
 
 
 
 
 
 
 
 
524
  print("="*50 + "\n")
525
  print("๐ŸŒŸ Launching GAIA Agent Interface...")
526
  demo.launch(debug=True, share=False)
 
8
  import logging
9
  from typing import List, Dict, Optional, TypedDict, Annotated
10
  import numpy as np
11
+ import base64
12
 
13
  # Core ML/AI imports
14
+ from langchain_core.messages import HumanMessage, SystemMessage, AnyMessage, ToolMessage
15
  from langchain_openai import ChatOpenAI
16
  from langchain_core.tools import tool
17
  from langchain_community.tools.tavily_search import TavilySearchResults
 
23
 
24
  # File processing
25
  import wikipedia
26
+ from youtube_transcript_api import YouTubeTranscriptApi, TranscriptsDisabled, NoTranscriptFound
27
  import speech_recognition as sr
28
 
29
+ # Computer vision
30
  try:
31
  from ultralytics import YOLO
32
  import cv2
 
50
  logging.getLogger("ultralytics").setLevel(logging.ERROR)
51
 
52
  # --- Constants ---
53
+ HF_API_BASE_URL = "https://agents-course-unit4-scoring.hf.space"
54
+ USERNAME = "YOUR_USERNAME" # Will be replaced with OAuth profile username
55
+ AGENT_CODE = "langgraph_gaia_agent"
56
 
57
+ # System prompt - EXACTLY as in gaia_agent.py
58
  SYSTEM_PROMPT = """You are a precision research assistant for the GAIA benchmark. Your mission is EXTREME ACCURACY.
 
59
  CRITICAL ANSWER FORMAT RULES:
60
+ # - ALWAYS end with: FINAL ANSWER: [answer]
61
+ # - READ THE QUESTION CAREFULLY - answer EXACTLY what is asked for, nothing more, nothing less
 
62
  SPECIFIC FORMATTING BY QUESTION TYPE:
63
+ # - Numbers: ONLY the number, no units, no text
64
+ # Example: "FINAL ANSWER: 2" NOT "FINAL ANSWER: 2 albums"
65
+ # - First name only: ONLY the first name
66
+ # Example: If person is "John Smith" โ†’ "FINAL ANSWER: John"
67
+ # - Country codes, IOC codes, abbreviations, symbols: ONLY the code/abbreviation, no country name or brackets
68
+ # Example: If asked for IOC country code โ†’ "FINAL ANSWER: PHI" NOT "FINAL ANSWER: PHILIPPINES [PHI]"
69
+ # - When asked for a specific type of identifier (code, abbreviation, symbol):
70
+ # Give ONLY that identifier, strip all explanatory text, brackets, or full names
71
+ # - Lists/Sets: Exactly as requested format
72
+ # Example: "FINAL ANSWER: a, b, d, e" (comma-separated, alphabetical order)
73
  CRITICAL TOOL SELECTION:
74
+ # - Wikipedia questions โ†’ wikipedia_tool ONLY
75
+ # - File questions โ†’ file_analyzer_tool FIRST to inspect contents, then reason based on structure
76
+ # - Current events โ†’ web_search_tool ONLY
77
+ # - Mathematical analysis/calculations โ†’ wolfram_alpha_tool or python_repl_tool ONLY
78
+ # - Tables, matrices, systematic checking โ†’ python_repl_tool ONLY
 
79
  FOR MATHEMATICAL PROBLEMS:
80
+ # ALWAYS use python_repl_tool when:
81
+ # - Analyzing mathematical tables or matrices
82
+ # - Checking properties like commutativity, associativity
83
+ # - Systematic verification of mathematical statements
84
+ # - Complex calculations that need precision
85
+ # - ANY problem involving tables, sets, or systematic checking
86
+ MATHEMATICAL ANALYSIS PROCESS:
87
+ # 1. Use python_repl_tool to parse data systematically
88
+ # 2. Write code to check ALL cases (don't rely on manual inspection)
89
+ # 3. Collect results programmatically
90
+ # 4. Verify your logic with multiple approaches
91
+ # 5. Format answer exactly as requested
92
+ # Example for commutativity checking:
93
+ # - Parse the operation table into a data structure
94
+ # - Check ALL pairs (x,y) to see if x*y = y*x
95
+ # - Collect ALL elements involved in ANY counter-example
96
+ # - Return in requested format (e.g., comma-separated, alphabetical)
97
  FILE HANDLING:
98
+ # - You HAVE the ability to read and analyze uploaded files
99
+ # - ALWAYS use file_analyzer_tool when questions mention files
100
+ # - The tool automatically finds and analyzes Excel, CSV, images, and audio files
101
+ # - For Excel/CSV: Returns columns, data types, sample rows, and numeric totals
102
+ # - NEVER say "I can't access files" - you CAN access them via file_analyzer_tool
103
+ # - Example: "The attached Excel file..." โ†’ Use file_analyzer_tool immediately
104
+ SPECIAL CASES TO HANDLE:
105
+ # - If the question appears reversed or encoded, decode it first.
106
+ # - If the question includes an instruction (e.g., "write the opposite of..."), follow the instruction precisely.
107
+ # - DO NOT repeat or paraphrase the question in your answer.
108
+ # - NEVER answer with the full sentence unless explicitly asked to.
109
+ # - If the decoded question asks for a word, give ONLY the word, in the required format.
110
  REASONING PROCESS:
111
+ # 1. Carefully read what the question is asking for
112
+ # 2. Identify if it needs systematic/mathematical analysis
113
+ # 3. Use appropriate tool (python_repl_tool for math problems)
114
+ # 4. Extract ONLY the specific part requested
115
+ # 5. Format according to the rules above
116
+ # 6. For file questions:
117
+ # a. First use file_analyzer_tool to inspect column names, types, and sample data
118
+ # b. Identify relevant columns based on the question
119
+ # c. Reason using the data (e.g., by counting, filtering, or identifying patterns)
120
+ # d. Only use python_repl_tool if additional computation is necessary
121
+ # 7. If the Wikipedia tool is used but fails to provide an answer (no relevant entry or content), automatically attempt a web search using the same query or a refined version of it
122
  """
123
 
124
+ # YOLO detectable classes
125
+ DETECTABLE_CLASSES = {
126
+ 'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus',
127
+ 'train', 'truck', 'boat', 'traffic light', 'fire hydrant',
128
+ 'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog',
129
+ 'horse', 'sheep', 'cow', 'elephant', 'bear', 'zebra', 'giraffe',
130
+ 'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee',
131
+ 'skis', 'snowboard', 'sports ball', 'kite', 'baseball bat',
132
+ 'baseball glove', 'skateboard', 'surfboard', 'tennis racket',
133
+ 'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl',
134
+ 'banana', 'apple', 'sandwich', 'orange', 'broccoli', 'carrot',
135
+ 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch',
136
+ 'potted plant', 'bed', 'dining table', 'toilet', 'tv',
137
+ 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone',
138
+ 'microwave', 'oven', 'toaster', 'sink', 'refrigerator',
139
+ 'book', 'clock', 'vase', 'scissors', 'teddy bear',
140
+ 'hair drier', 'toothbrush'
141
+ }
142
+
143
  class GAIAAgent:
144
  def __init__(self):
145
  print("๐Ÿš€ Initializing GAIA Agent...")
146
 
147
  # API Keys from HF Secrets
148
  self.openai_api_key = os.getenv("OPENAI_API_KEY")
149
+ self.tavily_api_key = os.getenv("TAVILY_API_KEY")
150
  self.wolfram_api_key = os.getenv("WOLFRAM_API_KEY")
151
  self.hf_token = os.getenv("HUGGING_FACE_API_TOKEN")
152
+ self.openweather_api_key = os.getenv("OPENWEATHER_API_KEY")
153
 
154
  if not self.openai_api_key:
155
  raise ValueError("OPENAI_API_KEY not found in environment variables")
 
177
  print("โœ… GAIA Agent initialized successfully!")
178
 
179
  def _setup_tools(self):
180
+ """Setup all the tools for the agent - EXACTLY as in gaia_agent.py"""
181
 
182
  # Store reference to self for use in nested functions
183
  agent_instance = self
 
185
  # Wikipedia tool
186
  @tool
187
  def wikipedia_tool(query: str) -> str:
188
+ """
189
+ Tool: Search Wikipedia for encyclopedic, historical, and biographical information.
190
+
191
+ โญ PREFERRED TOOL when the question mentions:
192
+ - "Wikipedia" explicitly
193
+ - Historical information, biographies, encyclopedic topics
194
+ - Facts about people, places, concepts, events from the past
195
+ - Scientific concepts, country information, cultural topics
196
+ - Any information that would typically be found in an encyclopedia
197
+
198
+ ๐ŸŽฏ ALWAYS USE THIS TOOL when:
199
+ - Question explicitly mentions "Wikipedia" or "encyclopedia"
200
+ - Looking for biographical information about notable people
201
+ - Need historical data, timelines, or established facts
202
+ - Question is about scientific concepts, countries, or cultural topics
203
+
204
+ Args:
205
+ query: Topic to search for (be specific, e.g., "Mercedes Sosa discography")
206
+ sentences: Number of sentences to return (default: 5, max: 10)
207
+
208
+ Examples of when to use:
209
+ - "Mercedes Sosa studio albums" โœ…
210
+ - "Albert Einstein biography" โœ…
211
+ - "World War II timeline" โœ…
212
+ - "Photosynthesis process" โœ…
213
+
214
+ This tool accesses Wikipedia's comprehensive, well-sourced encyclopedia content.
215
+ """
216
+ print(f"๐Ÿ“š USING WIKIPEDIA TOOL")
217
  try:
218
  wikipedia.set_lang("en")
219
+
220
+ try:
221
+ summary = wikipedia.summary(query, sentences=3)
222
+ page = wikipedia.page(query)
223
+ return f"WIKIPEDIA: {page.title}\n\n{summary}\n\nURL: {page.url}"
224
+ except wikipedia.DisambiguationError as e:
225
+ # Take first option
226
+ summary = wikipedia.summary(e.options[0], sentences=30)
227
+ page = wikipedia.page(e.options[0])
228
+ return f"WIKIPEDIA: {page.title}\n\n{summary}\n\nURL: {page.url}"
229
+ except wikipedia.PageError:
230
+ search_results = wikipedia.search(query, results=30)
231
+ if search_results:
232
+ return f"No exact match. Similar topics: {', '.join(search_results)}"
233
+ return f"No Wikipedia results for '{query}'"
234
  except Exception as e:
235
  return f"Wikipedia error: {str(e)}"
236
 
237
+ # File analyzer tool
238
+ @tool
239
+ def file_analyzer_tool(file_description: str = "uploaded file") -> str:
240
+ """
241
+ Analyzes uploaded files including Excel, CSV, images, and audio (e.g., .mp3).
242
+ For data files: returns column summary and numeric stats.
243
+ For images: returns visual attributes and OCR text.
244
+ For audio files: transcribes speech and extracts structured data (e.g., ingredients).
245
+ """
246
+ try:
247
+ print(f"๐Ÿ” Searching for files related to: {file_description}")
248
+ search_paths = ["./", "./uploads", "./files", "./data", "./images", "./audio"]
249
+ data_exts = ['.xlsx', '.xls', '.csv']
250
+ image_exts = ['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.tiff', '.webp']
251
+ audio_exts = ['.mp3', '.wav']
252
+ all_exts = data_exts + image_exts + audio_exts
253
+
254
+ found_files = []
255
+ for path in search_paths:
256
+ if os.path.exists(path):
257
+ for file in os.listdir(path):
258
+ if any(file.lower().endswith(ext) for ext in all_exts):
259
+ found_files.append(os.path.join(path, file))
260
+
261
+ if not found_files:
262
+ return f"No supported files found. Looking for: {', '.join(all_exts)}"
263
+
264
+ results = []
265
+ for file_path in found_files:
266
+ ext = os.path.splitext(file_path)[1].lower()
267
+ try:
268
+ if ext in data_exts:
269
+ results.append(agent_instance._analyze_data_file(file_path, ext))
270
+ elif ext in image_exts:
271
+ results.append(agent_instance._analyze_image_file(file_path))
272
+ elif ext in audio_exts:
273
+ results.append(agent_instance._analyze_audio_file(file_path))
274
+ except Exception as e:
275
+ results.append(f"โš ๏ธ Error processing {file_path}: {e}")
276
+
277
+ return "\n\n".join(results)
278
+ except Exception as error:
279
+ return f"โŒ Unexpected error: {error}"
280
+
281
+ # Computer vision analyzer
282
+ @tool
283
+ def computer_vision_analyzer(video_url: str, frames_per_second: int = 0.5) -> str:
284
+ """
285
+ tool: Analyzes a YouTube video and returns object detection counts per each frame.
286
+
287
+ Args:
288
+ video_url: YouTube video URL to analyze
289
+ frames_per_second: How many frames to extract per second (default: 1)
290
+
291
+ Returns:
292
+ JSON-like string with detection results per frame that can be used for various analyses.
293
+ """
294
+ if not VISION_AVAILABLE or not agent_instance.yolo_model:
295
+ return "Computer vision libraries not available"
296
+
297
+ try:
298
+ with tempfile.TemporaryDirectory() as temp_dir:
299
+ print("๐Ÿ“ฅ Downloading video...")
300
+ video_path = agent_instance._download_youtube_video(video_url, temp_dir)
301
+
302
+ print("๐Ÿ“ธ Extracting frames...")
303
+ frames = agent_instance._extract_frames(video_path, frame_rate=frames_per_second)
304
+
305
+ if not frames:
306
+ return "โŒ No frames could be extracted from the video."
307
+
308
+ print("๐Ÿ” Detecting objects in each frame...")
309
+ frame_results = agent_instance._detect_objects_per_frame(frames)
310
+
311
+ # Format results as a readable string that the LLM can parse and analyze
312
+ output_lines = []
313
+ output_lines.append(f"FRAME_ANALYSIS_RESULTS:")
314
+ output_lines.append(f"Total frames analyzed: {len(frame_results)}")
315
+ output_lines.append(f"Extraction rate: {frames_per_second} frame(s) per second")
316
+ output_lines.append("")
317
+
318
+ for frame_data in frame_results:
319
+ frame_num = frame_data['frame_number']
320
+ timestamp = frame_data['timestamp_seconds']
321
+ detections = frame_data['detections']
322
+
323
+ if detections:
324
+ output_lines.append(f"Frame {frame_num} (t={timestamp}s):")
325
+ for obj_type, count in sorted(detections.items()):
326
+ output_lines.append(f" {obj_type}: {count}")
327
+ else:
328
+ output_lines.append(f"Frame {frame_num} (t={timestamp}s): No objects detected")
329
+
330
+ return "\n".join(output_lines)
331
+
332
+ except Exception as e:
333
+ return f"โŒ Error processing video: {e}"
334
+
335
  # Web search tool
336
+ @tool
337
+ def web_search_tool(query: str, search_mode: str = "comprehensive") -> str:
338
+ """
339
+ Tool: Web search for CURRENT, REAL-TIME information and recent events.
340
+ """
341
+
342
+ print(f"๐ŸŒ USING WEB SEARCH TOOL with query: '{query}', mode: '{search_mode}'")
343
+
344
  if not agent_instance.tavily_api_key:
345
+ return "Error: TAVILY_API_KEY environment variable not set."
346
+
347
  try:
348
+ tavily_search = TavilySearchResults(max_results=5 if search_mode == "comprehensive" else 8)
349
+
350
+ if search_mode == "simple":
351
+ # Direct search approach - single query with more results
352
+ print(f"๐Ÿ” Executing simple search: '{query}'")
353
+ results = tavily_search.invoke(query)
354
+
355
+ if not results:
356
+ return "No search results found."
357
+
358
+ # Format results with clear structure
359
+ formatted_results = []
360
+ for i, res in enumerate(results, 1):
361
+ url = res.get('url', 'N/A')
362
+ content = res.get('content', 'N/A')
363
+ title = res.get('title', 'N/A')
364
+
365
+ formatted_results.append(
366
+ f"RESULT {i}:\nTitle: {title}\nURL: {url}\nContent: {content}"
367
+ )
368
+ return "\n\n".join(formatted_results)
369
+
370
+ else: # comprehensive mode
371
+ # Generate intelligent search variations based on query type
372
+ base_query = query.strip()
373
+ variations = [base_query] # Always include the original query
374
+
375
+ # Smart variation generation based on question patterns
376
+ query_lower = base_query.lower()
377
+
378
+ if any(phrase in query_lower for phrase in ["how many", "how much", "number of"]):
379
+ # Quantity-focused searches
380
+ clean_query = query_lower.replace('how many', '').replace('how much', '').strip()
381
+ variations.extend([
382
+ f"count of {clean_query}",
383
+ f"total {clean_query}",
384
+ f"list of {clean_query}"
385
+ ])
386
+
387
+ elif query_lower.startswith(("who is", "who was", "who are")):
388
+ # Person/entity identification searches
389
+ variations.extend([
390
+ f"{base_query} biography",
391
+ f"{base_query} wiki",
392
+ f"{base_query} profile"
393
+ ])
394
+
395
+ elif query_lower.startswith(("where is", "where are", "where was")):
396
+ # Location-based searches
397
+ variations.extend([
398
+ f"{base_query} location",
399
+ f"{base_query} address",
400
+ f"{base_query} map"
401
+ ])
402
+
403
+ elif query_lower.startswith(("what is", "what are", "what was")):
404
+ # Definition/explanation searches
405
+ variations.extend([
406
+ f"{base_query} definition",
407
+ f"{base_query} explanation",
408
+ f"{base_query} facts"
409
+ ])
410
+
411
+ elif query_lower.startswith(("when is", "when was", "when did")):
412
+ # Time/date searches
413
+ variations.extend([
414
+ f"{base_query} date",
415
+ f"{base_query} timeline",
416
+ f"{base_query} history"
417
+ ])
418
+
419
+ else:
420
+ # General searches - add broader context
421
+ variations.extend([
422
+ f"{base_query} information",
423
+ f"{base_query} facts details",
424
+ f"{base_query} overview"
425
+ ])
426
+
427
+ # Remove duplicates while preserving order, limit to 4 total variations
428
+ variations = list(dict.fromkeys(variations))[:4]
429
+
430
+ # Execute searches for each variation
431
+ all_results = []
432
+
433
+ for i, variation in enumerate(variations):
434
+ try:
435
+ print(f"๐Ÿ” Search variation {i+1}: '{variation}'")
436
+ results = tavily_search.invoke(variation)
437
+
438
+ if results:
439
+ # Format results for this variation
440
+ formatted_res = []
441
+ for j, res in enumerate(results):
442
+ url = res.get('url', 'N/A')
443
+ content = res.get('content', 'N/A')
444
+ title = res.get('title', 'N/A')
445
+
446
+ formatted_res.append(
447
+ f"Title: {title}\nURL: {url}\nContent: {content}"
448
+ )
449
+
450
+ search_header = f"=== SEARCH {i+1}: \"{variation}\" ==="
451
+ search_results = "\n---\n".join(formatted_res)
452
+ all_results.append(f"{search_header}\n{search_results}")
453
+ else:
454
+ all_results.append(f"=== SEARCH {i+1}: \"{variation}\" ===\nNo results found.")
455
+
456
+ except Exception as e:
457
+ all_results.append(f"=== SEARCH {i+1}: \"{variation}\" ===\nError: {e}")
458
+
459
+ final_result = "\n\n".join(all_results)
460
+ return final_result
461
+
462
  except Exception as e:
463
+ return f"Search error: {e}"
464
+
465
+ # Reverse text tool
466
+ @tool
467
+ def reverse_text_tool(text: str) -> str:
468
+ """Tool: Reverses text for handling backwards questions."""
469
+ return text[::-1]
470
 
471
  # Wolfram Alpha tool
472
  @tool
473
  def wolfram_alpha_tool(query: str) -> str:
474
+ """Tool: Use Wolfram Alpha for fact-based, computational questions like math, science, data lookups, or unit conversions,
475
+ but not for opinions, real-time updates, or creative tasks"""
476
  if not agent_instance.wolfram_api_key:
477
+ return "Error: WOLFRAM_API_KEY environment variable not set."
478
+
479
  params = {
480
  'appid': agent_instance.wolfram_api_key,
481
  'input': query,
482
  'format': 'plaintext',
483
+ 'output': 'JSON',
484
+ 'units': 'metric',
485
  }
486
  try:
487
+ print(f"๐Ÿง  Wolfram Alpha query: '{query}'")
488
  resp = requests.get("http://api.wolframalpha.com/v2/query", params=params, timeout=30)
489
  resp.raise_for_status()
490
  data = resp.json().get('queryresult', {})
491
+
492
  if not data.get('success'):
493
+ return f"Wolfram Alpha couldn't process: {query}. Try rephrasing the query."
494
+
495
  results = []
496
  for pod in data.get('pods', []):
497
  pod_title = pod.get('title', 'Unknown')
 
499
  plaintext = subpod.get('plaintext')
500
  if plaintext and plaintext.strip():
501
  results.append(f"{pod_title}: {plaintext}")
502
+
503
+ if not results:
504
+ return "Wolfram Alpha returned no readable results."
505
+
506
+ return " | ".join(results[:5]) # Limit results
507
+
508
+ except requests.exceptions.RequestException as e:
509
  return f"Wolfram Alpha error: {e}"
510
+ except json.JSONDecodeError:
511
+ return "Wolfram Alpha returned invalid data."
512
 
513
+ # YouTube transcript tool
514
  @tool
515
+ def youtube_transcript_tool(url: str, question: str) -> str:
516
+ """
517
+ tool: Use this to transcript and answer questions about specific phrases in YouTube videos.
518
+
519
+ Args:
520
+ url: YouTube video URL
521
+ question: The question or phrase to search for in the transcript
522
+
523
+ Returns:
524
+ A string with the response found after the question in the transcript.
525
+ """
526
  try:
527
+ if not url or not question:
528
+ return "Both 'url' and 'question' are required."
 
529
 
530
+ video_id = agent_instance._extract_video_id(url)
531
+ transcript = agent_instance._get_transcript(video_id)
 
 
 
532
 
533
+ if not transcript:
534
+ return "No transcript available for this video."
535
 
536
+ response = agent_instance._find_response(transcript, question)
537
+ return response
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
538
 
539
+ except TranscriptsDisabled:
540
+ return "Transcripts are disabled for this video."
541
+ except NoTranscriptFound:
542
+ return "No transcript found for this video."
543
+ except ValueError as e:
544
+ return str(e)
545
  except Exception as e:
546
+ return f"Error during transcript analysis: {str(e)}"
547
 
548
  # Python REPL tool
549
  python_repl_tool = PythonREPLTool()
550
 
551
  tools = [
552
  wikipedia_tool,
553
+ youtube_transcript_tool,
 
554
  file_analyzer_tool,
555
+ computer_vision_analyzer,
556
+ web_search_tool,
557
+ wolfram_alpha_tool,
558
+ reverse_text_tool,
559
  python_repl_tool
560
  ]
561
 
 
574
  if not messages or not isinstance(messages[0], SystemMessage):
575
  messages = [SystemMessage(content=SYSTEM_PROMPT)] + messages
576
 
577
+ print("\n๐Ÿค– Agent analyzing question...")
578
  response = model_with_tools.invoke(messages)
579
+ print(f"๐Ÿค– Response type: {type(response)}")
580
+ print(f"๐Ÿค– Content preview: {response.content[:200]}...")
581
+ print(f"๐Ÿค– Tool calls: {len(response.tool_calls) if response.tool_calls else 0}")
582
  return {"messages": [response]}
583
 
584
  tool_node = ToolNode(self.tools)
 
588
  builder.add_node("tools", tool_node)
589
 
590
  builder.add_edge(START, "agent")
591
+ builder.add_conditional_edges(
592
+ "agent",
593
+ tools_condition,
594
+ {
595
+ "tools": "tools",
596
+ END: END
597
+ }
598
+ )
599
  builder.add_edge("tools", "agent")
600
 
601
  memory = MemorySaver()
602
  return builder.compile(checkpointer=memory)
603
 
604
+ # Helper methods for file analysis
605
+ def _analyze_data_file(self, file_path: str, ext: str) -> str:
606
+ """Analyze Excel or CSV files"""
607
+ try:
608
+ if ext in ['.xlsx', '.xls']:
609
+ df = pd.read_excel(file_path)
610
+ elif ext == '.csv':
611
+ df = pd.read_csv(file_path)
612
+ else:
613
+ return f"Unsupported data file type: {ext}"
614
+
615
+ result = f"๐Ÿ“„ DATA FILE: {file_path}\n"
616
+ result += f"๐Ÿ”ข SHAPE: {df.shape}\n"
617
+ result += f"๐Ÿง  COLUMNS: {list(df.columns)}\n"
618
+ result += f"๐Ÿ” COLUMN TYPES:\n{df.dtypes.to_string()}\n"
619
+ result += f"\n๐Ÿ“Š FIRST 5 ROWS:\n{df.head().to_string(index=False)}\n"
620
+
621
+ numeric_cols = df.select_dtypes(include=['number']).columns
622
+ if len(numeric_cols) > 0:
623
+ totals = df[numeric_cols].sum().round(2)
624
+ result += f"\n๐Ÿ’ฐ NUMERIC TOTALS:\n{totals.to_string()}\n"
625
+
626
+ return result
627
+
628
+ except Exception as e:
629
+ return f"Error analyzing data file {file_path}: {e}"
630
+
631
+ def _analyze_image_file(self, file_path: str) -> str:
632
+ """Analyze image files using OpenCV and other tools"""
633
+ result = f"๐Ÿ–ผ๏ธ IMAGE FILE: {file_path}\n"
634
+
635
+ try:
636
+ if cv2 is not None:
637
+ # Read image with OpenCV
638
+ img = cv2.imread(file_path)
639
+ if img is None:
640
+ return result + "Error: Could not read image file"
641
+
642
+ height, width = img.shape[:2]
643
+ channels = img.shape[2] if len(img.shape) > 2 else 1
644
+
645
+ result += f"๐Ÿ“ DIMENSIONS: {width}x{height} pixels\n"
646
+ result += f"๐ŸŽจ CHANNELS: {channels} ({'Color' if channels > 1 else 'Grayscale'})\n"
647
+
648
+ # Convert to grayscale for analysis
649
+ gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) if channels > 1 else img
650
+
651
+ # Edge detection to understand structure
652
+ edges = cv2.Canny(gray, 50, 150)
653
+ edge_pixels = np.count_nonzero(edges)
654
+ edge_percentage = (edge_pixels / (width * height)) * 100
655
+ result += f"๐Ÿ“ EDGE DENSITY: {edge_percentage:.1f}% (complexity indicator)\n"
656
+
657
+ # Detect basic shapes/contours
658
+ contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
659
+ result += f"๐Ÿ”ท DETECTED CONTOURS: {len(contours)}\n"
660
+
661
+ # Analyze color distribution
662
+ if channels > 1:
663
+ # Calculate dominant colors
664
+ img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
665
+ pixels = img_rgb.reshape(-1, 3)
666
+ unique_colors = len(np.unique(pixels, axis=0))
667
+ result += f"๐ŸŽจ UNIQUE COLORS: {unique_colors}\n"
668
+
669
+ # Calculate average color
670
+ avg_color = pixels.mean(axis=0).astype(int)
671
+ result += f"๐ŸŽจ AVERAGE COLOR (RGB): {tuple(avg_color)}\n"
672
+
673
+ # Detect if it's likely a chess board (8x8 grid pattern)
674
+ result += self._analyze_chess_pattern(gray)
675
+
676
+ # OCR text detection if available
677
+ if OCR_AVAILABLE:
678
+ try:
679
+ pil_image = Image.open(file_path)
680
+ text = pytesseract.image_to_string(pil_image).strip()
681
+ if text:
682
+ result += f"\n๐Ÿ“ DETECTED TEXT:\n{text[:500]}{'...' if len(text) > 500 else ''}\n"
683
+ except Exception as ocr_error:
684
+ result += f"\nโš ๏ธ OCR failed: {ocr_error}\n"
685
+
686
+ else:
687
+ # Basic analysis without OpenCV
688
+ result += "โš ๏ธ OpenCV not available. Limited analysis:\n"
689
+ try:
690
+ from PIL import Image
691
+ img = Image.open(file_path)
692
+ result += f"๐Ÿ“ DIMENSIONS: {img.size[0]}x{img.size[1]} pixels\n"
693
+ result += f"๐Ÿ“„ FORMAT: {img.format}\n"
694
+ result += f"๐ŸŽจ MODE: {img.mode}\n"
695
+ except:
696
+ result += "Unable to analyze image without proper libraries installed.\n"
697
+
698
+ return result
699
+
700
+ except Exception as e:
701
+ return result + f"Error analyzing image: {e}"
702
+
703
+ def _analyze_chess_pattern(self, gray_img):
704
+ """Detect if image contains a chess board pattern"""
705
+ result = ""
706
+
707
+ try:
708
+ # Try to detect chessboard corners (typical 8x8 pattern)
709
+ ret, corners = cv2.findChessboardCorners(gray_img, (7, 7), None)
710
+
711
+ if ret:
712
+ result += "\nโ™Ÿ๏ธ CHESS BOARD DETECTED: Yes (found corner pattern)\n"
713
+ result += "โ™Ÿ๏ธ This appears to be a chess position image.\n"
714
+ else:
715
+ # Alternative: check for grid-like structure
716
+ # Detect lines using Hough transform
717
+ edges = cv2.Canny(gray_img, 50, 150)
718
+ lines = cv2.HoughLinesP(edges, 1, np.pi/180, 100, minLineLength=100, maxLineGap=10)
719
+
720
+ if lines is not None and len(lines) > 20:
721
+ # Check for perpendicular lines (potential grid)
722
+ horizontal_lines = 0
723
+ vertical_lines = 0
724
+
725
+ for line in lines:
726
+ x1, y1, x2, y2 = line[0]
727
+ angle = np.abs(np.arctan2(y2 - y1, x2 - x1) * 180 / np.pi)
728
+ if angle < 10 or angle > 170:
729
+ horizontal_lines += 1
730
+ elif 80 < angle < 100:
731
+ vertical_lines += 1
732
+
733
+ if horizontal_lines > 5 and vertical_lines > 5:
734
+ result += "\nGRID PATTERN DETECTED: Possible chess board\n"
735
+ result += f"โ™Ÿ๏ธ Horizontal lines: {horizontal_lines}, Vertical lines: {vertical_lines}\n"
736
+ except:
737
+ pass
738
+
739
+ return result
740
+
741
+ def _analyze_audio_file(self, file_path: str) -> str:
742
+ """Transcribes audio and extracts ingredients if it's a recipe voice note"""
743
+ result = f"๐Ÿ”Š AUDIO FILE: {file_path}\n"
744
+ recognizer = sr.Recognizer()
745
+ try:
746
+ with sr.AudioFile(file_path) as source:
747
+ audio_data = recognizer.record(source)
748
+ text = recognizer.recognize_google(audio_data)
749
+ result += f"๐Ÿ“ TRANSCRIPTION:\n{text}\n"
750
+
751
+ # Ingredient extraction logic
752
+ if "ingredient" in text.lower() or "filling" in text.lower():
753
+ ingredients = self._extract_ingredients(text)
754
+ result += f"\n๐Ÿ“ EXTRACTED INGREDIENTS (filling only, alphabetized):\n{', '.join(ingredients)}\n"
755
+ except Exception as e:
756
+ result += f"โš ๏ธ Audio processing failed: {e}"
757
+ return result
758
+
759
+ def _extract_ingredients(self, text: str) -> list:
760
+ """
761
+ Extracts a list of ingredients from a recipe transcription.
762
+ It strips quantities and returns only ingredient names.
763
+ """
764
+ lines = text.split('\n')
765
+ keywords = ["filling", "add", "mix", "combine", "put", "use", "for the filling"]
766
+ ingredient_list = []
767
+
768
+ for line in lines:
769
+ if any(k in line.lower() for k in keywords):
770
+ matches = re.findall(r"(?:a\s|an\s|some\s|[0-9]+[\/0-9\s]*)?([a-zA-Z\s\-]+?)(?=[\.,]|$)", line)
771
+ ingredient_list.extend([m.strip().lower() for m in matches if m.strip()])
772
+
773
+ # Post-process and alphabetize
774
+ unique_ingredients = sorted(set(ingredient_list))
775
+ return unique_ingredients
776
+
777
+ # Video processing helpers
778
+ def _download_youtube_video(self, video_url: str, output_dir: str) -> str:
779
+ output_template = os.path.join(output_dir, "downloaded_video.%(ext)s")
780
+
781
+ ydl_opts = {
782
+ 'outtmpl': output_template,
783
+ 'format': 'mp4',
784
+ 'quiet': True,
785
+ 'no_warnings': True,
786
+ }
787
+
788
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
789
+ info = ydl.extract_info(video_url, download=True)
790
+ downloaded_file = ydl.prepare_filename(info)
791
+ downloaded_file = downloaded_file.replace(".webm", ".mp4")
792
+ return downloaded_file
793
+
794
+ def _extract_frames(self, video_path: str, frame_rate: int = 1) -> list:
795
+ cap = cv2.VideoCapture(video_path)
796
+ frames = []
797
+ fps = cap.get(cv2.CAP_PROP_FPS)
798
+ interval = int(fps * frame_rate)
799
+ count = 0
800
+
801
+ while cap.isOpened():
802
+ ret, frame = cap.read()
803
+ if not ret:
804
+ break
805
+ if count % interval == 0:
806
+ frames.append(frame)
807
+ count += 1
808
+
809
+ cap.release()
810
+ return frames
811
+
812
+ def _detect_objects_per_frame(self, frames: list) -> list:
813
+ """
814
+ Detects and counts objects in each frame individually.
815
+ Returns a list with detection results for each frame.
816
+ """
817
+ results = []
818
+
819
+ for frame_idx, frame in enumerate(frames):
820
+ # Get detections for this frame
821
+ detections = self.yolo_model(frame, verbose=False)
822
+
823
+ # Count objects in this frame
824
+ frame_counts = {}
825
+ for detection in detections[0].boxes.cls:
826
+ label = self.yolo_model.names[int(detection)]
827
+ if label in DETECTABLE_CLASSES:
828
+ frame_counts[label] = frame_counts.get(label, 0) + 1
829
+
830
+ # Store frame result
831
+ frame_result = {
832
+ 'frame_number': frame_idx,
833
+ 'timestamp_seconds': frame_idx, # assuming 1 frame per second
834
+ 'detections': frame_counts
835
+ }
836
+ results.append(frame_result)
837
+
838
+ return results
839
+
840
+ # YouTube transcript helpers
841
+ def _extract_video_id(self, url: str) -> str:
842
+ """Extracts YouTube video ID from a URL."""
843
+ patterns = [
844
+ r'(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/v\/|youtube\.com\/embed\/)([a-zA-Z0-9_-]{11})',
845
+ r'youtube\.com\/watch\?.*&v=([a-zA-Z0-9_-]{11})'
846
+ ]
847
+
848
+ for pattern in patterns:
849
+ match = re.search(pattern, url)
850
+ if match:
851
+ return match.group(1)
852
+
853
+ raise ValueError("Invalid YouTube URL format. Could not extract video ID.")
854
+
855
+ def _get_transcript(self, video_id: str) -> List[dict]:
856
+ """Fetch transcript using the YouTube Transcript API."""
857
+ try:
858
+ # Try to get transcript in English first, then any available language
859
+ transcript = YouTubeTranscriptApi.get_transcript(video_id, languages=['en'])
860
+ except :
861
+ # If English not available, get any available transcript
862
+ transcript_list = YouTubeTranscriptApi.list_transcripts(video_id)
863
+ transcript = transcript_list.find_transcript(['en']).fetch()
864
+
865
+ return transcript
866
+
867
+ def _find_response(self, transcript: List[dict], question: str) -> Optional[str]:
868
+ """Find the transcript entry after a given question."""
869
+ question_lower = question.strip().lower()
870
+
871
+ # Remove common punctuation for better matching
872
+ question_normalized = re.sub(r'[^\w\s]', '', question_lower)
873
+
874
+ for i, entry in enumerate(transcript):
875
+ text = entry["text"].strip().lower()
876
+ text_normalized = re.sub(r'[^\w\s]', '', text)
877
+
878
+ # Check for partial matches (at least 70% of the words match)
879
+ question_words = set(question_normalized.split())
880
+ text_words = set(text_normalized.split())
881
+
882
+ if question_words and len(question_words.intersection(text_words)) / len(question_words) >= 0.7:
883
+ # Collect response lines (up to 5 lines or 30 seconds of content)
884
+ response_lines = []
885
+ total_duration = 0
886
+
887
+ for j in range(i + 1, min(i + 6, len(transcript))):
888
+ response_lines.append(transcript[j]["text"])
889
+ if "duration" in transcript[j]:
890
+ total_duration += transcript[j]["duration"]
891
+ if total_duration >= 30: # Stop after 30 seconds
892
+ break
893
+
894
+ if response_lines:
895
+ return " ".join(response_lines)
896
+
897
+ return "Could not find a response to the question in the transcript."
898
+
899
  def _extract_final_answer(self, response_text: str) -> str:
900
  """Extract the final answer from agent response"""
901
  match = re.search(r"FINAL ANSWER:\s*(.*)", response_text, re.DOTALL | re.IGNORECASE)
902
+
903
  if match:
904
  raw_answer = match.group(1).strip()
905
+ if "\n" in raw_answer and not (',' in raw_answer and '\n' not in raw_answer.split(',', 1)[0]):
906
+ raw_answer = raw_answer.split("\n", 1)[0].strip()
907
+
908
  if raw_answer.endswith('.') and not raw_answer[:-1].replace('.', '').isdigit():
909
  raw_answer = raw_answer[:-1]
910
+
911
+ common_phrases = ["which is", "because", " as ", " since "]
912
+ for phrase in common_phrases:
913
+ if phrase in raw_answer.lower():
914
+ raw_answer = raw_answer.split(phrase)[0].strip()
915
+
916
  return raw_answer.strip()
917
+
918
  lines = [line.strip() for line in response_text.strip().split('\n') if line.strip()]
919
  return lines[-1] if lines else response_text.strip()
920
 
921
+ def _preprocess_question(self, question: str) -> str:
922
+ """Pre-process questions to handle special cases."""
923
+ q = question.strip()
924
 
925
+ # Check for reversed text
926
+ if (q.endswith('.') or q.endswith('?')) and len(q) > 10 and q[0].islower() and ' ' in q:
927
+ words = q.split()
928
+ if sum(1 for w in words[1:] if len(w) > 1 and w[0].isupper()) > len(words) / 3:
929
+ reversed_q = q[::-1]
930
+ print(f"๐Ÿ‘€ Question appears reversed. Reversed: '{reversed_q}'")
931
+ return f"[This question *might* be reversed. Original: '{q}'. Reversed: '{reversed_q}'] {reversed_q}"
932
+
933
+ # Check for attachments/files mentioned
934
+ file_indicators = [
935
+ "attached", "attachment", "file", "excel", "mp3", "audio", "image",
936
+ "recording", "python code", ".py", ".xlsx", ".mp3", ".wav", ".jpg",
937
+ ".png", ".pdf", "listen to", "analyze the", "review the", "examine the"
938
+ ]
939
+
940
+ if any(indicator in q.lower() for indicator in file_indicators):
941
+ print("๐Ÿ“Ž File/attachment detected in question.")
942
+ return f"{q}\n[NOTE: This question mentions files/attachments. Use file_analyzer_tool to read and analyze any uploaded files.]"
943
+
944
+ # Check for video URLs
945
+ video_patterns = [
946
+ r'youtube\.com/watch\?v=',
947
+ r'youtu\.be/',
948
+ r'\.mp4', r'\.avi', r'\.mov', r'\.mkv'
949
+ ]
950
+
951
+ for pattern in video_patterns:
952
+ if re.search(pattern, q, re.IGNORECASE):
953
+ print("๐Ÿ“น Video URL detected in question.")
954
+ return f"{q}\n[NOTE: Video detected. Use youtube_transcript_tool for dialogue or search tools for video content analysis.]"
955
+
956
+ return q
957
+
958
+ def process_question(self, task_id: str, question_text: str) -> Dict:
959
+ """Process a single question"""
960
+ print(f"\n{'='*80}")
961
+ print(f"โšก Processing Task ID: {task_id}")
962
+ print(f"โ“ Question: {question_text}")
963
+ print(f"{'='*80}")
964
+
965
+ processed_question = self._preprocess_question(question_text)
966
+ config = {"configurable": {"thread_id": f"gaia_task_{task_id}"}}
967
+
968
  try:
 
 
 
969
  final_state = None
970
  max_iterations = 0
971
 
972
+ # Stream events with iteration limit
973
  events = self.agent_runner.stream(
974
+ {"messages": [HumanMessage(content=processed_question)]},
975
  config=config,
976
  stream_mode="values"
977
  )
 
979
  for event in events:
980
  final_state = event
981
  max_iterations += 1
982
+ if max_iterations > 10: # Prevent infinite loops
983
+ print("โš ๏ธ Max iterations reached, stopping...")
984
  break
985
+
986
  if not final_state or not final_state['messages']:
987
+ print("โŒ Agent did not return a final state.")
988
+ return {"success": False, "error": "Agent execution failed."}
989
+
990
  last_message = final_state['messages'][-1]
 
991
 
992
+ # If last message has tool calls, try one more time
993
+ if last_message.tool_calls and max_iterations < 10:
994
+ print("๐Ÿ”„ Getting final answer from agent...")
995
+ try:
996
+ final_state = self.agent_runner.invoke({"messages": []}, config=config)
997
+ last_message = final_state['messages'][-1]
998
+ except:
999
+ pass # Continue with current state
1000
+
1001
+ full_response = last_message.content
1002
+ print(f"\n๐Ÿ“ Full Agent Response:\n{full_response}")
1003
+
1004
  final_answer = self._extract_final_answer(full_response)
1005
+ print(f"\n๐ŸŽฏ Extracted Final Answer: '{final_answer}'")
1006
+
1007
+ if not final_answer or final_answer == full_response:
1008
+ print("โš ๏ธ Could not extract a 'FINAL ANSWER:' block.")
1009
+
1010
+ return {
1011
+ "success": True,
1012
+ "answer": final_answer,
1013
+ "full_response": full_response
1014
+ }
1015
 
1016
  except Exception as e:
1017
+ print(f"โŒ CRITICAL ERROR processing question {task_id}: {e}")
1018
  import traceback
1019
  traceback.print_exc()
1020
+ return {"success": False, "error": str(e)}
1021
 
1022
  def run_and_submit_all(profile: gr.OAuthProfile | None):
1023
  """
 
1033
  print("User not logged in.")
1034
  return "Please Login to Hugging Face with the button.", None
1035
 
 
 
 
 
1036
  # 1. Instantiate GAIA Agent
1037
  try:
1038
  agent = GAIAAgent()
 
1040
  print(f"Error instantiating GAIA agent: {e}")
1041
  return f"Error initializing GAIA agent: {e}", None
1042
 
1043
+ agent_code = AGENT_CODE if space_id else f"https://huggingface.co/spaces/{space_id}/tree/main"
1044
+ print(f"Agent code: {agent_code}")
1045
 
1046
  # 2. Fetch Questions
1047
+ hf_token = os.getenv("HUGGING_FACE_API_TOKEN")
1048
+ headers = {}
1049
+ if hf_token:
1050
+ headers["Authorization"] = f"Bearer {hf_token}"
1051
+
1052
+ questions_url = f"{HF_API_BASE_URL}/questions"
1053
  print(f"Fetching questions from: {questions_url}")
1054
+
1055
  try:
1056
+ response = requests.get(questions_url, headers=headers, timeout=60)
1057
  response.raise_for_status()
1058
  questions_data = response.json()
1059
  if not questions_data:
1060
  return "Fetched questions list is empty.", None
1061
+ print(f"โœ… Retrieved {len(questions_data)} questions.")
1062
  except Exception as e:
1063
+ print(f"โŒ Error fetching questions: {e}")
1064
  return f"Error fetching questions: {e}", None
1065
 
1066
+ # 3. Filter for Level 1 questions
1067
+ level_1_questions = [q for q in questions_data if q.get('level', 1) == 1]
1068
+ print(f"๐Ÿ“‹ Processing {len(level_1_questions)} Level 1 questions.")
1069
+
1070
+ # 4. Run GAIA Agent on questions
1071
  results_log = []
1072
  answers_payload = []
1073
+ stats = {
1074
+ "total": len(level_1_questions),
1075
+ "attempted": 0,
1076
+ "processed": 0,
1077
+ "failed": 0
1078
+ }
1079
 
1080
+ for i, item in enumerate(level_1_questions):
1081
  task_id = item.get("task_id")
1082
+ question_text = item.get('Question', item.get('question'))
1083
 
1084
+ if not task_id or not question_text:
1085
+ print(f"โš ๏ธ Question {i+1} missing data, skipping...")
1086
  continue
1087
+
1088
+ stats["attempted"] += 1
1089
+ print(f"\n๐Ÿ”„ Processing question {i+1}/{len(level_1_questions)}: {task_id}")
1090
 
1091
  try:
1092
+ result = agent.process_question(task_id, question_text)
1093
+
1094
+ if result.get("success"):
1095
+ submitted_answer = result.get("answer", "")
1096
+
1097
+ # Attempt to convert to number if it looks like one
1098
+ try:
1099
+ if re.fullmatch(r"-?\d+", submitted_answer):
1100
+ submitted_value = int(submitted_answer)
1101
+ elif re.fullmatch(r"-?\d+\.\d+", submitted_answer):
1102
+ submitted_value = float(submitted_answer)
1103
+ else:
1104
+ submitted_value = submitted_answer
1105
+ except ValueError:
1106
+ submitted_value = submitted_answer
1107
+
1108
+ answers_payload.append({
1109
+ "task_id": task_id,
1110
+ "submitted_answer": submitted_value
1111
+ })
1112
+
1113
+ results_log.append({
1114
+ "Task ID": task_id,
1115
+ "Question": question_text[:100] + "..." if len(question_text) > 100 else question_text,
1116
+ "Submitted Answer": submitted_answer,
1117
+ "Status": "โœ… Success"
1118
+ })
1119
+ stats["processed"] += 1
1120
+ print(f"โœ… Question {i+1} completed: {submitted_answer}")
1121
+ else:
1122
+ error_msg = result.get("error", "Unknown error")
1123
+ results_log.append({
1124
+ "Task ID": task_id,
1125
+ "Question": question_text[:100] + "..." if len(question_text) > 100 else question_text,
1126
+ "Submitted Answer": f"ERROR: {error_msg}",
1127
+ "Status": "โŒ Failed"
1128
+ })
1129
+ stats["failed"] += 1
1130
+ print(f"โŒ Question {i+1} failed: {error_msg}")
1131
+
1132
  except Exception as e:
1133
+ print(f"โŒ Critical error on question {i+1}: {e}")
1134
+ import traceback
1135
+ traceback.print_exc()
1136
+
1137
  results_log.append({
1138
  "Task ID": task_id,
1139
  "Question": question_text[:100] + "..." if len(question_text) > 100 else question_text,
1140
+ "Submitted Answer": f"CRITICAL ERROR: {str(e)}",
1141
+ "Status": "๐Ÿ’ฅ Critical Error"
1142
  })
1143
+ stats["failed"] += 1
1144
 
1145
  if not answers_payload:
1146
  return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
1147
 
1148
+ # 5. Submit answers
1149
  submission_data = {
1150
  "username": username.strip(),
1151
  "agent_code": agent_code,
1152
  "answers": answers_payload
1153
  }
1154
 
1155
+ print(f"\n๐Ÿ“ค Submitting {len(answers_payload)} answers...")
1156
+ print(f"Submission payload: {json.dumps(submission_data, indent=2)}")
1157
+
1158
  try:
1159
+ response = requests.post(
1160
+ f"{HF_API_BASE_URL}/submit",
1161
+ headers=headers,
1162
+ json=submission_data,
1163
+ timeout=120
1164
+ )
1165
  response.raise_for_status()
1166
  result_data = response.json()
1167
 
1168
+ print(f"๐Ÿ“ฆ API Response: {json.dumps(result_data, indent=2)}")
1169
+
1170
+ score = result_data.get('score', 0)
1171
+ correct_count = result_data.get('correct_count', 0)
1172
+ total_attempted = result_data.get('total_attempted', len(answers_payload))
1173
+
1174
  final_status = (
1175
+ f"{'='*50}\n"
1176
+ f"๐Ÿ“Š SUBMISSION RESULTS\n"
1177
+ f"{'='*50}\n"
1178
+ f"โœ… Submission Successful!\n"
1179
+ f"๐Ÿ‘ค User: {result_data.get('username', username)}\n"
1180
+ f"๐ŸŽฏ Overall Score: {score}%\n"
1181
+ f"๐Ÿ“Š Correct Answers: {correct_count}/{total_attempted}\n"
1182
+ f"๐Ÿ’ฌ Message: {result_data.get('message', 'No message received.')}\n"
1183
+ f"\n๐Ÿ“ˆ PROCESSING STATS:\n"
1184
+ f" Total Level 1 Questions: {stats['total']}\n"
1185
+ f" Questions Attempted: {stats['attempted']}\n"
1186
+ f" Successfully Processed: {stats['processed']}\n"
1187
+ f" Failed to Process: {stats['failed']}\n"
1188
+ f"{'='*50}"
1189
  )
1190
+
1191
  print("โœ… Submission successful!")
1192
+ print(final_status)
1193
+
1194
  return final_status, pd.DataFrame(results_log)
1195
 
1196
  except Exception as e:
1197
+ error_msg = (
1198
+ f"โŒ SUBMISSION FAILED\n"
1199
+ f"Error: {str(e)}\n"
1200
+ f"\nProcessing Stats:\n"
1201
+ f" Questions Attempted: {stats['attempted']}\n"
1202
+ f" Successfully Processed: {stats['processed']}\n"
1203
+ f" Failed to Process: {stats['failed']}"
1204
+ )
1205
+
1206
+ if hasattr(e, 'response') and e.response:
1207
+ error_msg += f"\n\nAPI Response: {e.response.text}"
1208
+
1209
  print(error_msg)
1210
  return error_msg, pd.DataFrame(results_log)
1211
 
 
1214
  gr.Markdown("# ๐Ÿค– GAIA Agent Evaluation Runner")
1215
  gr.Markdown(
1216
  """
1217
+ **Advanced GAIA Benchmark Agent (Exact Match with gaia_agent.py)**
1218
 
1219
  This agent uses:
1220
+ - ๐Ÿง  GPT-4 Turbo with specialized GAIA prompt engineering
1221
+ - ๐Ÿ“š Wikipedia search for encyclopedic information
1222
+ - ๐ŸŒ Tavily web search for current events
1223
  - ๐Ÿงฎ Wolfram Alpha for computational tasks
1224
+ - ๐Ÿ“Š File analysis for Excel/CSV/Image/Audio data
1225
+ - ๐ŸŽฅ YouTube transcript analysis
1226
+ - ๐Ÿ‘๏ธ Computer vision with YOLO for video analysis
1227
  - ๐Ÿ Python REPL for mathematical analysis
1228
+ - ๐Ÿ”„ Text reversal tool for encoded questions
1229
+
1230
+ **Features:**
1231
+ - Processes only Level 1 questions
1232
+ - Exact answer extraction with FINAL ANSWER format
1233
+ - Comprehensive error handling and retry logic
1234
+ - Detailed processing statistics
1235
 
1236
  **Instructions:**
1237
  1. Log in to your Hugging Face account
 
1247
  run_button = gr.Button("๐Ÿš€ Run Evaluation & Submit All Answers", variant="primary")
1248
 
1249
  status_output = gr.Textbox(
1250
+ label="๐Ÿ“Š Run Status / Submission Result",
1251
+ lines=15,
1252
  interactive=False
1253
  )
1254
 
1255
  results_table = gr.DataFrame(
1256
+ label="๐Ÿ“ Questions and Agent Answers",
1257
  wrap=True,
1258
+ max_height=600
1259
  )
1260
 
1261
  run_button.click(
 
1280
  print(f"โœ… SPACE_ID: {space_id}")
1281
  print(f" Repo URL: https://huggingface.co/spaces/{space_id}")
1282
 
1283
+ # Check for required API keys
1284
+ required_keys = ["OPENAI_API_KEY", "TAVILY_API_KEY", "WOLFRAM_API_KEY"]
1285
+ missing_keys = [key for key in required_keys if not os.getenv(key)]
1286
+
1287
+ if missing_keys:
1288
+ print(f"\nโš ๏ธ WARNING: Missing API keys: {', '.join(missing_keys)}")
1289
+ print(" Please set these in your HuggingFace Space secrets!")
1290
+ else:
1291
+ print("\nโœ… All required API keys found!")
1292
+
1293
  print("="*50 + "\n")
1294
  print("๐ŸŒŸ Launching GAIA Agent Interface...")
1295
  demo.launch(debug=True, share=False)