lmrkmrcs commited on
Commit
dd539c4
ยท
verified ยท
1 Parent(s): 30e7877

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +217 -217
app.py CHANGED
@@ -3,7 +3,7 @@ import re
3
  import requests
4
  import gradio as gr
5
  import pandas as pd
6
- from smolagents import CodeAgent, DuckDuckGoSearchTool, InferenceClientModel, tool
7
 
8
  # --- Constants ---
9
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
@@ -25,16 +25,15 @@ def calculator(expression: str) -> str:
25
  """
26
  import math
27
  try:
28
- # Clean the expression
29
  expression = expression.strip()
30
 
31
- # Create safe math context
32
  safe_dict = {
33
  "abs": abs, "round": round, "min": min, "max": max,
34
  "sum": sum, "pow": pow, "len": len,
35
  "sqrt": math.sqrt, "sin": math.sin, "cos": math.cos,
36
  "tan": math.tan, "log": math.log, "log10": math.log10,
37
  "pi": math.pi, "e": math.e, "floor": math.floor, "ceil": math.ceil,
 
38
  }
39
 
40
  result = eval(expression, {"__builtins__": {}}, safe_dict)
@@ -87,7 +86,6 @@ def visit_webpage(url: str) -> str:
87
  The text content of the webpage (truncated if too long)
88
  """
89
  try:
90
- import requests
91
  from bs4 import BeautifulSoup
92
 
93
  headers = {
@@ -99,15 +97,13 @@ def visit_webpage(url: str) -> str:
99
 
100
  soup = BeautifulSoup(response.text, 'html.parser')
101
 
102
- # Remove script and style elements
103
  for element in soup(['script', 'style', 'nav', 'footer', 'header']):
104
  element.decompose()
105
 
106
  text = soup.get_text(separator='\n', strip=True)
107
 
108
- # Truncate if too long
109
- if len(text) > 8000:
110
- text = text[:8000] + "\n...[truncated]"
111
 
112
  return text if text else "Could not extract text from webpage."
113
  except Exception as e:
@@ -126,9 +122,6 @@ def wikipedia_search(query: str) -> str:
126
  Wikipedia article summary and key information
127
  """
128
  try:
129
- import requests
130
-
131
- # Search Wikipedia API
132
  search_url = "https://en.wikipedia.org/w/api.php"
133
  search_params = {
134
  "action": "query",
@@ -144,14 +137,13 @@ def wikipedia_search(query: str) -> str:
144
  if not data.get("query", {}).get("search"):
145
  return f"No Wikipedia articles found for: {query}"
146
 
147
- # Get the first result's page content
148
  title = data["query"]["search"][0]["title"]
149
 
150
  content_params = {
151
  "action": "query",
152
  "titles": title,
153
  "prop": "extracts",
154
- "exintro": True,
155
  "explaintext": True,
156
  "format": "json"
157
  }
@@ -162,8 +154,8 @@ def wikipedia_search(query: str) -> str:
162
  pages = data.get("query", {}).get("pages", {})
163
  for page_id, page_data in pages.items():
164
  extract = page_data.get("extract", "No content available")
165
- if len(extract) > 3000:
166
- extract = extract[:3000] + "...[truncated]"
167
  return f"Wikipedia: {title}\n\n{extract}"
168
 
169
  return "Could not retrieve Wikipedia content."
@@ -171,54 +163,11 @@ def wikipedia_search(query: str) -> str:
171
  return f"Wikipedia error: {str(e)}"
172
 
173
 
174
- @tool
175
- def read_file_from_url(url: str) -> str:
176
- """
177
- Downloads and reads content from a file URL (txt, csv, json, etc).
178
-
179
- Args:
180
- url: The URL of the file to download and read
181
-
182
- Returns:
183
- The content of the file
184
- """
185
- try:
186
- import requests
187
-
188
- headers = {"User-Agent": "Mozilla/5.0"}
189
- response = requests.get(url, headers=headers, timeout=30)
190
- response.raise_for_status()
191
-
192
- content_type = response.headers.get('content-type', '').lower()
193
-
194
- # Handle different file types
195
- if 'json' in content_type or url.endswith('.json'):
196
- import json
197
- data = response.json()
198
- return json.dumps(data, indent=2)[:5000]
199
-
200
- elif 'csv' in content_type or url.endswith('.csv'):
201
- lines = response.text.split('\n')[:50] # First 50 lines
202
- return '\n'.join(lines)
203
-
204
- elif url.endswith('.xlsx') or url.endswith('.xls'):
205
- return "Excel file detected. Cannot read directly - need pandas with openpyxl."
206
-
207
- else:
208
- # Plain text
209
- text = response.text
210
- if len(text) > 5000:
211
- text = text[:5000] + "\n...[truncated]"
212
- return text
213
-
214
- except Exception as e:
215
- return f"Error reading file: {str(e)}"
216
-
217
-
218
  @tool
219
  def get_gaia_file(task_id: str) -> str:
220
  """
221
  Downloads a file associated with a GAIA task from the API.
 
222
 
223
  Args:
224
  task_id: The task ID to get the file for
@@ -227,8 +176,6 @@ def get_gaia_file(task_id: str) -> str:
227
  Information about the file or its content if text-based
228
  """
229
  try:
230
- import requests
231
-
232
  api_url = f"https://agents-course-unit4-scoring.hf.space/files/{task_id}"
233
  response = requests.get(api_url, timeout=30)
234
 
@@ -240,41 +187,82 @@ def get_gaia_file(task_id: str) -> str:
240
  content_type = response.headers.get('content-type', '').lower()
241
  content_disp = response.headers.get('content-disposition', '')
242
 
243
- # Try to get filename
244
  filename = "unknown"
245
  if 'filename=' in content_disp:
246
  filename = content_disp.split('filename=')[-1].strip('"\'')
247
 
248
- # Handle based on content type
249
- if 'text' in content_type or 'json' in content_type:
250
  content = response.text
251
- if len(content) > 5000:
252
- content = content[:5000] + "\n...[truncated]"
253
- return f"File: {filename}\nContent:\n{content}"
254
-
255
- elif 'image' in content_type:
256
- return f"File: {filename}\nType: Image file ({content_type})\nNote: This is an image file. I cannot view images directly."
257
-
258
- elif 'audio' in content_type:
259
- return f"File: {filename}\nType: Audio file ({content_type})\nNote: This is an audio file. I cannot process audio directly."
260
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
261
  elif 'pdf' in content_type or filename.endswith('.pdf'):
262
  return f"File: {filename}\nType: PDF document\nNote: This is a PDF file. I cannot read PDFs directly."
263
 
264
- elif 'spreadsheet' in content_type or filename.endswith(('.xlsx', '.xls')):
265
- return f"File: {filename}\nType: Excel spreadsheet\nNote: This is an Excel file. I cannot read Excel directly."
266
-
267
  else:
268
- return f"File: {filename}\nType: {content_type}\nSize: {len(response.content)} bytes\nNote: Binary file, cannot display content."
269
 
270
  except Exception as e:
271
  return f"Error getting file: {str(e)}"
272
 
273
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
  @tool
275
  def reverse_text(text: str) -> str:
276
  """
277
- Reverses the given text string.
278
 
279
  Args:
280
  text: The text to reverse
@@ -301,12 +289,12 @@ def count_items(text: str, item_type: str = "words") -> str:
301
 
302
  if item_type == "words":
303
  count = len(text.split())
304
- elif item_type == "characters" or item_type == "chars":
305
  count = len(text)
306
  elif item_type == "lines":
307
  count = len(text.split('\n'))
308
  elif item_type == "sentences":
309
- count = len(re.split(r'[.!?]+', text))
310
  else:
311
  return f"Unknown item type: {item_type}. Use: words, characters, lines, or sentences."
312
 
@@ -333,7 +321,7 @@ def extract_numbers(text: str) -> str:
333
  @tool
334
  def sort_list(items: str, order: str = "ascending") -> str:
335
  """
336
- Sorts a comma-separated list of items.
337
 
338
  Args:
339
  items: Comma-separated items to sort (e.g., "banana, apple, cherry")
@@ -344,13 +332,11 @@ def sort_list(items: str, order: str = "ascending") -> str:
344
  """
345
  item_list = [item.strip() for item in items.split(',')]
346
 
347
- # Try to sort as numbers if all items are numeric
348
  try:
349
  numeric_list = [float(item) for item in item_list]
350
  sorted_list = sorted(numeric_list, reverse=(order.lower() == "descending"))
351
- return ', '.join(str(int(x) if x.is_integer() else x) for x in sorted_list)
352
  except ValueError:
353
- # Sort as strings
354
  sorted_list = sorted(item_list, reverse=(order.lower() == "descending"))
355
  return ', '.join(sorted_list)
356
 
@@ -358,7 +344,7 @@ def sort_list(items: str, order: str = "ascending") -> str:
358
  @tool
359
  def convert_units(value: float, from_unit: str, to_unit: str) -> str:
360
  """
361
- Converts between common units.
362
 
363
  Args:
364
  value: The numeric value to convert
@@ -366,115 +352,138 @@ def convert_units(value: float, from_unit: str, to_unit: str) -> str:
366
  to_unit: The target unit
367
 
368
  Returns:
369
- The converted value
370
  """
371
  conversions = {
372
- # Length
373
  ("km", "miles"): lambda x: x * 0.621371,
374
  ("miles", "km"): lambda x: x * 1.60934,
375
  ("m", "feet"): lambda x: x * 3.28084,
376
  ("feet", "m"): lambda x: x * 0.3048,
377
  ("cm", "inches"): lambda x: x * 0.393701,
378
  ("inches", "cm"): lambda x: x * 2.54,
379
- # Temperature
380
  ("celsius", "fahrenheit"): lambda x: (x * 9/5) + 32,
381
  ("fahrenheit", "celsius"): lambda x: (x - 32) * 5/9,
382
  ("celsius", "kelvin"): lambda x: x + 273.15,
383
  ("kelvin", "celsius"): lambda x: x - 273.15,
384
- # Weight
385
  ("kg", "lbs"): lambda x: x * 2.20462,
386
  ("lbs", "kg"): lambda x: x * 0.453592,
387
  ("g", "oz"): lambda x: x * 0.035274,
388
  ("oz", "g"): lambda x: x * 28.3495,
389
  }
390
 
391
- key = (from_unit.lower(), to_unit.lower())
392
  if key in conversions:
393
  result = conversions[key](value)
394
- return f"{value} {from_unit} = {result:.4f} {to_unit}"
395
  else:
396
  return f"Conversion from {from_unit} to {to_unit} not supported."
397
 
398
 
 
 
 
 
 
 
 
 
 
 
 
 
 
399
  # ============================================
400
- # BASIC AGENT CLASS - USING SMOLAGENTS
401
  # ============================================
402
 
403
  class BasicAgent:
404
  def __init__(self):
405
- print("Initializing BasicAgent with smolagents...")
406
 
407
- # Use a larger model for better reasoning
408
- self.model = InferenceClientModel(
409
- model_id="Qwen/Qwen2.5-72B-Instruct", # Bigger model
410
- token=os.environ.get("HF_TOKEN"),
411
  )
412
 
413
- # Create the agent with comprehensive tools
414
  self.agent = CodeAgent(
415
  model=self.model,
416
  tools=[
417
- web_search, # Web search
418
- visit_webpage, # Visit and read webpages
419
- wikipedia_search, # Wikipedia lookup
420
- calculator, # Math calculations
421
- read_file_from_url, # Read files from URLs
422
- get_gaia_file, # Get GAIA task files
423
- reverse_text, # Reverse strings
424
- count_items, # Count words/chars/lines
425
- extract_numbers, # Extract numbers from text
426
- sort_list, # Sort lists
427
- convert_units, # Unit conversions
 
428
  ],
429
- max_steps=12, # More steps for complex reasoning
430
  verbosity_level=1,
431
  )
432
 
433
- print("BasicAgent initialized successfully!")
434
 
435
  def __call__(self, question: str, task_id: str = None) -> str:
436
- print(f"Agent received question: {question[:100]}...")
437
 
438
  try:
439
- # Build enhanced prompt
440
- file_note = ""
441
  if task_id:
442
- file_note = f"\n\nNote: This task may have an associated file. Use get_gaia_file('{task_id}') to check."
443
-
444
- enhanced_prompt = f"""You are an AI assistant solving questions for the GAIA benchmark.
445
-
446
- IMPORTANT INSTRUCTIONS:
447
- 1. Think step by step before answering
448
- 2. Use tools when needed to find information
449
- 3. If the question mentions a file, use get_gaia_file() with the task_id
450
- 4. For web lookups, use web_search() first, then visit_webpage() if needed
451
- 5. For factual info, try wikipedia_search()
452
- 6. Double-check calculations with the calculator tool
453
- 7. Give ONLY the final answer - no explanations
454
- 8. Keep answers concise and exact (the grading uses exact match)
455
- {file_note}
 
 
 
 
456
 
457
  Question: {question}
458
 
459
- Think through this step-by-step, then provide your final answer."""
460
 
461
  # Run the agent
462
  answer = self.agent.run(enhanced_prompt)
463
 
464
- # Clean up the answer
465
  answer = str(answer).strip()
466
 
467
- # Remove common prefixes that might hurt exact matching
468
- prefixes_to_remove = [
469
- "The answer is: ", "The answer is ",
470
- "Answer: ", "Final answer: ",
471
  "The final answer is: ", "The final answer is ",
 
472
  ]
473
- for prefix in prefixes_to_remove:
474
- if answer.lower().startswith(prefix.lower()):
 
 
475
  answer = answer[len(prefix):].strip()
476
 
477
- print(f"Agent answer: {answer[:200]}...")
 
 
 
 
 
478
  return answer
479
 
480
  except Exception as e:
@@ -512,7 +521,7 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
512
  return f"Error initializing agent: {e}", None
513
 
514
  agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
515
- print(agent_code)
516
 
517
  # 2. Fetch Questions
518
  print(f"Fetching questions from: {questions_url}")
@@ -521,92 +530,88 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
521
  response.raise_for_status()
522
  questions_data = response.json()
523
  if not questions_data:
524
- print("Fetched questions list is empty.")
525
- return "Fetched questions list is empty or invalid format.", None
526
  print(f"Fetched {len(questions_data)} questions.")
527
- except requests.exceptions.RequestException as e:
528
  print(f"Error fetching questions: {e}")
529
  return f"Error fetching questions: {e}", None
530
- except Exception as e:
531
- print(f"An unexpected error occurred fetching questions: {e}")
532
- return f"An unexpected error occurred fetching questions: {e}", None
533
 
534
- # 3. Run your Agent
535
  results_log = []
536
  answers_payload = []
 
537
  print(f"Running agent on {len(questions_data)} questions...")
 
538
 
539
  for i, item in enumerate(questions_data):
540
  task_id = item.get("task_id")
541
  question_text = item.get("question")
542
 
543
  if not task_id or question_text is None:
544
- print(f"Skipping item with missing task_id or question: {item}")
545
  continue
546
 
547
- print(f"\n--- Question {i+1}/{len(questions_data)} ---")
548
- print(f"Task ID: {task_id}")
549
- print(f"Question: {question_text[:100]}...")
550
 
551
  try:
552
- # Pass task_id so agent can fetch associated files
553
  submitted_answer = agent(question_text, task_id=task_id)
554
  answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
555
  results_log.append({
556
- "Task ID": task_id,
557
  "Question": question_text[:100] + "..." if len(question_text) > 100 else question_text,
558
- "Submitted Answer": submitted_answer
559
  })
560
- print(f"Answer: {submitted_answer}")
561
  except Exception as e:
562
- print(f"Error running agent on task {task_id}: {e}")
563
  results_log.append({
564
- "Task ID": task_id,
565
  "Question": question_text[:100] + "...",
566
- "Submitted Answer": f"AGENT ERROR: {e}"
567
  })
568
 
569
  if not answers_payload:
570
- print("Agent did not produce any answers to submit.")
571
- return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
572
 
573
- # 4. Prepare Submission
574
- submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
575
- status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
576
- print(status_update)
 
 
 
 
 
 
577
 
578
- # 5. Submit
579
- print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
580
  try:
581
- response = requests.post(submit_url, json=submission_data, timeout=120) # Longer timeout
582
  response.raise_for_status()
583
  result_data = response.json()
 
 
 
 
 
584
  final_status = (
585
- f"Submission Successful!\n"
586
- f"User: {result_data.get('username')}\n"
587
- f"Overall Score: {result_data.get('score', 'N/A')}% "
588
- f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
589
- f"Message: {result_data.get('message', 'No message received.')}"
590
  )
591
- print("Submission successful.")
592
- results_df = pd.DataFrame(results_log)
593
- return final_status, results_df
594
- except requests.exceptions.HTTPError as e:
595
- error_detail = f"Server responded with status {e.response.status_code}."
596
- try:
597
- error_json = e.response.json()
598
- error_detail += f" Detail: {error_json.get('detail', e.response.text)}"
599
- except:
600
- error_detail += f" Response: {e.response.text[:500]}"
601
- status_message = f"Submission Failed: {error_detail}"
602
- print(status_message)
603
- results_df = pd.DataFrame(results_log)
604
- return status_message, results_df
605
  except Exception as e:
606
- status_message = f"An unexpected error occurred during submission: {e}"
607
  print(status_message)
608
- results_df = pd.DataFrame(results_log)
609
- return status_message, results_df
610
 
611
 
612
  # ============================================
@@ -619,37 +624,31 @@ with gr.Blocks() as demo:
619
  """
620
  **Unit 4 Final Project - HuggingFace AI Agents Course**
621
 
622
- This agent uses **smolagents** with **Qwen2.5-72B-Instruct** and the following tools:
623
-
624
- | Tool | Description |
625
- |------|-------------|
626
- | ๐Ÿ” **Web Search** | Search the internet via DuckDuckGo |
627
- | ๐ŸŒ **Visit Webpage** | Extract text content from URLs |
628
- | ๐Ÿ“š **Wikipedia** | Search and read Wikipedia articles |
629
- | ๐Ÿงฎ **Calculator** | Perform math calculations |
630
- | ๐Ÿ“ **File Reader** | Read files from URLs |
631
- | ๐Ÿ“Ž **GAIA File** | Get task-associated files |
632
- | ๐Ÿ”„ **Reverse Text** | Reverse strings |
633
- | ๐Ÿ“Š **Count Items** | Count words/chars/lines |
634
- | ๐Ÿ”ข **Extract Numbers** | Find numbers in text |
635
- | ๐Ÿ“‹ **Sort List** | Sort comma-separated items |
636
- | โš–๏ธ **Convert Units** | Convert between units |
637
 
638
  ---
639
  **Instructions:**
640
- 1. Log in with your Hugging Face account
641
- 2. Click 'Run Evaluation & Submit All Answers'
642
- 3. Wait for the agent to process all questions (may take 10-15 minutes)
643
- 4. View your score!
644
  """
645
  )
646
 
647
  gr.LoginButton()
 
 
648
 
649
- run_button = gr.Button("๐Ÿš€ Run Evaluation & Submit All Answers", variant="primary")
650
-
651
- status_output = gr.Textbox(label="Run Status / Submission Result", lines=6, interactive=False)
652
- results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
653
 
654
  run_button.click(
655
  fn=run_and_submit_all,
@@ -658,17 +657,18 @@ with gr.Blocks() as demo:
658
 
659
  if __name__ == "__main__":
660
  print("\n" + "="*60)
661
- print("GAIA Agent Evaluation Runner - Starting")
662
  print("="*60)
663
 
664
- space_host = os.getenv("SPACE_HOST")
 
 
 
 
 
665
  space_id = os.getenv("SPACE_ID")
666
-
667
- if space_host:
668
- print(f"โœ… SPACE_HOST: {space_host}")
669
  if space_id:
670
- print(f"โœ… SPACE_ID: {space_id}")
671
- print(f" Repo: https://huggingface.co/spaces/{space_id}")
672
 
673
  print("="*60 + "\n")
674
 
 
3
  import requests
4
  import gradio as gr
5
  import pandas as pd
6
+ from smolagents import CodeAgent, tool, LiteLLMModel
7
 
8
  # --- Constants ---
9
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
 
25
  """
26
  import math
27
  try:
 
28
  expression = expression.strip()
29
 
 
30
  safe_dict = {
31
  "abs": abs, "round": round, "min": min, "max": max,
32
  "sum": sum, "pow": pow, "len": len,
33
  "sqrt": math.sqrt, "sin": math.sin, "cos": math.cos,
34
  "tan": math.tan, "log": math.log, "log10": math.log10,
35
  "pi": math.pi, "e": math.e, "floor": math.floor, "ceil": math.ceil,
36
+ "factorial": math.factorial,
37
  }
38
 
39
  result = eval(expression, {"__builtins__": {}}, safe_dict)
 
86
  The text content of the webpage (truncated if too long)
87
  """
88
  try:
 
89
  from bs4 import BeautifulSoup
90
 
91
  headers = {
 
97
 
98
  soup = BeautifulSoup(response.text, 'html.parser')
99
 
 
100
  for element in soup(['script', 'style', 'nav', 'footer', 'header']):
101
  element.decompose()
102
 
103
  text = soup.get_text(separator='\n', strip=True)
104
 
105
+ if len(text) > 10000:
106
+ text = text[:10000] + "\n...[truncated]"
 
107
 
108
  return text if text else "Could not extract text from webpage."
109
  except Exception as e:
 
122
  Wikipedia article summary and key information
123
  """
124
  try:
 
 
 
125
  search_url = "https://en.wikipedia.org/w/api.php"
126
  search_params = {
127
  "action": "query",
 
137
  if not data.get("query", {}).get("search"):
138
  return f"No Wikipedia articles found for: {query}"
139
 
 
140
  title = data["query"]["search"][0]["title"]
141
 
142
  content_params = {
143
  "action": "query",
144
  "titles": title,
145
  "prop": "extracts",
146
+ "exintro": False,
147
  "explaintext": True,
148
  "format": "json"
149
  }
 
154
  pages = data.get("query", {}).get("pages", {})
155
  for page_id, page_data in pages.items():
156
  extract = page_data.get("extract", "No content available")
157
+ if len(extract) > 5000:
158
+ extract = extract[:5000] + "...[truncated]"
159
  return f"Wikipedia: {title}\n\n{extract}"
160
 
161
  return "Could not retrieve Wikipedia content."
 
163
  return f"Wikipedia error: {str(e)}"
164
 
165
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
  @tool
167
  def get_gaia_file(task_id: str) -> str:
168
  """
169
  Downloads a file associated with a GAIA task from the API.
170
+ Use this tool when the question mentions a file or attachment.
171
 
172
  Args:
173
  task_id: The task ID to get the file for
 
176
  Information about the file or its content if text-based
177
  """
178
  try:
 
 
179
  api_url = f"https://agents-course-unit4-scoring.hf.space/files/{task_id}"
180
  response = requests.get(api_url, timeout=30)
181
 
 
187
  content_type = response.headers.get('content-type', '').lower()
188
  content_disp = response.headers.get('content-disposition', '')
189
 
 
190
  filename = "unknown"
191
  if 'filename=' in content_disp:
192
  filename = content_disp.split('filename=')[-1].strip('"\'')
193
 
194
+ # Handle text files
195
+ if 'text' in content_type or filename.endswith(('.txt', '.csv', '.json', '.md')):
196
  content = response.text
197
+ if len(content) > 8000:
198
+ content = content[:8000] + "\n...[truncated]"
199
+ return f"File: {filename}\n\nContent:\n{content}"
 
 
 
 
 
 
200
 
201
+ # Handle Python files
202
+ elif filename.endswith('.py'):
203
+ content = response.text
204
+ if len(content) > 8000:
205
+ content = content[:8000] + "\n...[truncated]"
206
+ return f"Python File: {filename}\n\nCode:\n{content}"
207
+
208
+ # Handle Excel files
209
+ elif filename.endswith(('.xlsx', '.xls')):
210
+ try:
211
+ import pandas as pd
212
+ from io import BytesIO
213
+ df = pd.read_excel(BytesIO(response.content))
214
+ return f"Excel File: {filename}\n\nData:\n{df.to_string()}"
215
+ except:
216
+ return f"Excel File: {filename}\nNote: Could not parse Excel file."
217
+
218
+ # Handle images
219
+ elif 'image' in content_type or filename.endswith(('.png', '.jpg', '.jpeg', '.gif')):
220
+ return f"File: {filename}\nType: Image ({content_type})\nNote: This is an image file. I cannot view images directly, but I can tell you it exists."
221
+
222
+ # Handle audio
223
+ elif 'audio' in content_type or filename.endswith(('.mp3', '.wav', '.m4a')):
224
+ return f"File: {filename}\nType: Audio ({content_type})\nNote: This is an audio file. I cannot process audio directly."
225
+
226
+ # Handle PDF
227
  elif 'pdf' in content_type or filename.endswith('.pdf'):
228
  return f"File: {filename}\nType: PDF document\nNote: This is a PDF file. I cannot read PDFs directly."
229
 
 
 
 
230
  else:
231
+ return f"File: {filename}\nType: {content_type}\nSize: {len(response.content)} bytes"
232
 
233
  except Exception as e:
234
  return f"Error getting file: {str(e)}"
235
 
236
 
237
+ @tool
238
+ def read_file_content(url: str) -> str:
239
+ """
240
+ Downloads and reads content from a file URL.
241
+
242
+ Args:
243
+ url: The URL of the file to download and read
244
+
245
+ Returns:
246
+ The content of the file
247
+ """
248
+ try:
249
+ headers = {"User-Agent": "Mozilla/5.0"}
250
+ response = requests.get(url, headers=headers, timeout=30)
251
+ response.raise_for_status()
252
+
253
+ content = response.text
254
+ if len(content) > 8000:
255
+ content = content[:8000] + "\n...[truncated]"
256
+ return content
257
+
258
+ except Exception as e:
259
+ return f"Error reading file: {str(e)}"
260
+
261
+
262
  @tool
263
  def reverse_text(text: str) -> str:
264
  """
265
+ Reverses the given text string character by character.
266
 
267
  Args:
268
  text: The text to reverse
 
289
 
290
  if item_type == "words":
291
  count = len(text.split())
292
+ elif item_type in ["characters", "chars", "char"]:
293
  count = len(text)
294
  elif item_type == "lines":
295
  count = len(text.split('\n'))
296
  elif item_type == "sentences":
297
+ count = len(re.split(r'[.!?]+', text.strip()))
298
  else:
299
  return f"Unknown item type: {item_type}. Use: words, characters, lines, or sentences."
300
 
 
321
  @tool
322
  def sort_list(items: str, order: str = "ascending") -> str:
323
  """
324
+ Sorts a comma-separated list of items alphabetically or numerically.
325
 
326
  Args:
327
  items: Comma-separated items to sort (e.g., "banana, apple, cherry")
 
332
  """
333
  item_list = [item.strip() for item in items.split(',')]
334
 
 
335
  try:
336
  numeric_list = [float(item) for item in item_list]
337
  sorted_list = sorted(numeric_list, reverse=(order.lower() == "descending"))
338
+ return ', '.join(str(int(x) if x == int(x) else x) for x in sorted_list)
339
  except ValueError:
 
340
  sorted_list = sorted(item_list, reverse=(order.lower() == "descending"))
341
  return ', '.join(sorted_list)
342
 
 
344
  @tool
345
  def convert_units(value: float, from_unit: str, to_unit: str) -> str:
346
  """
347
+ Converts between common units of measurement.
348
 
349
  Args:
350
  value: The numeric value to convert
 
352
  to_unit: The target unit
353
 
354
  Returns:
355
+ The converted value with units
356
  """
357
  conversions = {
 
358
  ("km", "miles"): lambda x: x * 0.621371,
359
  ("miles", "km"): lambda x: x * 1.60934,
360
  ("m", "feet"): lambda x: x * 3.28084,
361
  ("feet", "m"): lambda x: x * 0.3048,
362
  ("cm", "inches"): lambda x: x * 0.393701,
363
  ("inches", "cm"): lambda x: x * 2.54,
 
364
  ("celsius", "fahrenheit"): lambda x: (x * 9/5) + 32,
365
  ("fahrenheit", "celsius"): lambda x: (x - 32) * 5/9,
366
  ("celsius", "kelvin"): lambda x: x + 273.15,
367
  ("kelvin", "celsius"): lambda x: x - 273.15,
 
368
  ("kg", "lbs"): lambda x: x * 2.20462,
369
  ("lbs", "kg"): lambda x: x * 0.453592,
370
  ("g", "oz"): lambda x: x * 0.035274,
371
  ("oz", "g"): lambda x: x * 28.3495,
372
  }
373
 
374
+ key = (from_unit.lower().strip(), to_unit.lower().strip())
375
  if key in conversions:
376
  result = conversions[key](value)
377
+ return f"{value} {from_unit} = {result:.6f} {to_unit}"
378
  else:
379
  return f"Conversion from {from_unit} to {to_unit} not supported."
380
 
381
 
382
+ @tool
383
+ def get_current_time() -> str:
384
+ """
385
+ Gets the current date and time in UTC.
386
+
387
+ Returns:
388
+ The current date and time
389
+ """
390
+ from datetime import datetime
391
+ now = datetime.utcnow()
392
+ return f"Current UTC date/time: {now.strftime('%Y-%m-%d %H:%M:%S')}"
393
+
394
+
395
  # ============================================
396
+ # BASIC AGENT CLASS - USING GROQ
397
  # ============================================
398
 
399
  class BasicAgent:
400
  def __init__(self):
401
+ print("Initializing BasicAgent with Groq + Llama 3.3 70B...")
402
 
403
+ # Use Groq with Llama 3.3 70B - fast and smart!
404
+ self.model = LiteLLMModel(
405
+ model_id="groq/llama-3.3-70b-versatile",
406
+ api_key=os.environ.get("GROQ_API_KEY"),
407
  )
408
 
409
+ # Create the agent with all tools
410
  self.agent = CodeAgent(
411
  model=self.model,
412
  tools=[
413
+ web_search,
414
+ visit_webpage,
415
+ wikipedia_search,
416
+ calculator,
417
+ get_gaia_file,
418
+ read_file_content,
419
+ reverse_text,
420
+ count_items,
421
+ extract_numbers,
422
+ sort_list,
423
+ convert_units,
424
+ get_current_time,
425
  ],
426
+ max_steps=15,
427
  verbosity_level=1,
428
  )
429
 
430
+ print("BasicAgent initialized successfully with Groq!")
431
 
432
  def __call__(self, question: str, task_id: str = None) -> str:
433
+ print(f"Agent processing: {question[:100]}...")
434
 
435
  try:
436
+ # Build the prompt with clear instructions
437
+ file_instruction = ""
438
  if task_id:
439
+ file_instruction = f"""
440
+ IMPORTANT: This question may have an associated file.
441
+ To check for and read the file, use: get_gaia_file("{task_id}")
442
+ Always check for a file first if the question mentions any attachment, file, document, image, or data."""
443
+
444
+ enhanced_prompt = f"""You are solving a GAIA benchmark question. Follow these rules:
445
+
446
+ 1. THINK step by step before answering
447
+ 2. USE TOOLS when you need information:
448
+ - web_search() for current info or facts
449
+ - wikipedia_search() for encyclopedic knowledge
450
+ - visit_webpage() to read full webpage content
451
+ - calculator() for any math
452
+ - get_gaia_file("{task_id}") if there's an attached file
453
+ 3. VERIFY your answer before submitting
454
+ 4. Give ONLY the final answer - no explanation
455
+ 5. Be PRECISE - answers are graded by exact match
456
+ {file_instruction}
457
 
458
  Question: {question}
459
 
460
+ Solve this step-by-step, then give your final answer."""
461
 
462
  # Run the agent
463
  answer = self.agent.run(enhanced_prompt)
464
 
465
+ # Clean up answer
466
  answer = str(answer).strip()
467
 
468
+ # Remove common prefixes
469
+ prefixes = [
470
+ "The answer is: ", "The answer is ",
471
+ "Answer: ", "Final answer: ", "Final Answer: ",
472
  "The final answer is: ", "The final answer is ",
473
+ "FINAL ANSWER: ", "FINAL ANSWER ",
474
  ]
475
+ for prefix in prefixes:
476
+ if answer.startswith(prefix):
477
+ answer = answer[len(prefix):].strip()
478
+ elif answer.lower().startswith(prefix.lower()):
479
  answer = answer[len(prefix):].strip()
480
 
481
+ # Remove quotes if wrapped
482
+ if (answer.startswith('"') and answer.endswith('"')) or \
483
+ (answer.startswith("'") and answer.endswith("'")):
484
+ answer = answer[1:-1]
485
+
486
+ print(f"Final answer: {answer[:200]}")
487
  return answer
488
 
489
  except Exception as e:
 
521
  return f"Error initializing agent: {e}", None
522
 
523
  agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
524
+ print(f"Agent code URL: {agent_code}")
525
 
526
  # 2. Fetch Questions
527
  print(f"Fetching questions from: {questions_url}")
 
530
  response.raise_for_status()
531
  questions_data = response.json()
532
  if not questions_data:
533
+ return "Fetched questions list is empty.", None
 
534
  print(f"Fetched {len(questions_data)} questions.")
535
+ except Exception as e:
536
  print(f"Error fetching questions: {e}")
537
  return f"Error fetching questions: {e}", None
 
 
 
538
 
539
+ # 3. Run Agent on all questions
540
  results_log = []
541
  answers_payload = []
542
+ print(f"\n{'='*60}")
543
  print(f"Running agent on {len(questions_data)} questions...")
544
+ print(f"{'='*60}\n")
545
 
546
  for i, item in enumerate(questions_data):
547
  task_id = item.get("task_id")
548
  question_text = item.get("question")
549
 
550
  if not task_id or question_text is None:
 
551
  continue
552
 
553
+ print(f"\n[{i+1}/{len(questions_data)}] Task: {task_id}")
554
+ print(f"Question: {question_text[:150]}{'...' if len(question_text) > 150 else ''}")
 
555
 
556
  try:
 
557
  submitted_answer = agent(question_text, task_id=task_id)
558
  answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
559
  results_log.append({
560
+ "Task ID": task_id,
561
  "Question": question_text[:100] + "..." if len(question_text) > 100 else question_text,
562
+ "Answer": submitted_answer[:200] if len(submitted_answer) > 200 else submitted_answer
563
  })
564
+ print(f"โœ“ Answer: {submitted_answer[:100]}")
565
  except Exception as e:
566
+ print(f"โœ— Error: {e}")
567
  results_log.append({
568
+ "Task ID": task_id,
569
  "Question": question_text[:100] + "...",
570
+ "Answer": f"ERROR: {e}"
571
  })
572
 
573
  if not answers_payload:
574
+ return "Agent did not produce any answers.", pd.DataFrame(results_log)
 
575
 
576
+ # 4. Submit answers
577
+ submission_data = {
578
+ "username": username.strip(),
579
+ "agent_code": agent_code,
580
+ "answers": answers_payload
581
+ }
582
+
583
+ print(f"\n{'='*60}")
584
+ print(f"Submitting {len(answers_payload)} answers...")
585
+ print(f"{'='*60}\n")
586
 
 
 
587
  try:
588
+ response = requests.post(submit_url, json=submission_data, timeout=120)
589
  response.raise_for_status()
590
  result_data = response.json()
591
+
592
+ score = result_data.get('score', 'N/A')
593
+ correct = result_data.get('correct_count', '?')
594
+ total = result_data.get('total_attempted', '?')
595
+
596
  final_status = (
597
+ f"โœ… Submission Successful!\n\n"
598
+ f"๐Ÿ‘ค User: {result_data.get('username')}\n"
599
+ f"๐ŸŽฏ Score: {score}% ({correct}/{total} correct)\n\n"
600
+ f"๐Ÿ“ {result_data.get('message', '')}"
 
601
  )
602
+
603
+ if float(score) >= 30:
604
+ final_status += "\n\n๐ŸŽ‰ CONGRATULATIONS! You passed the 30% threshold!"
605
+ else:
606
+ final_status += f"\n\n๐Ÿ“ˆ Need {30 - float(score)}% more to reach 30% passing score."
607
+
608
+ print(final_status)
609
+ return final_status, pd.DataFrame(results_log)
610
+
 
 
 
 
 
611
  except Exception as e:
612
+ status_message = f"Submission Failed: {e}"
613
  print(status_message)
614
+ return status_message, pd.DataFrame(results_log)
 
615
 
616
 
617
  # ============================================
 
624
  """
625
  **Unit 4 Final Project - HuggingFace AI Agents Course**
626
 
627
+ This agent uses **Groq + Llama 3.3 70B** with the following tools:
628
+
629
+ | Category | Tools |
630
+ |----------|-------|
631
+ | ๐Ÿ” **Search** | Web Search, Wikipedia, Visit Webpage |
632
+ | ๐Ÿงฎ **Math** | Calculator, Unit Converter |
633
+ | ๐Ÿ“ **Files** | GAIA File Reader, URL File Reader |
634
+ | ๐Ÿ“ **Text** | Reverse, Count Items, Extract Numbers, Sort List |
635
+ | ๐Ÿ• **Utility** | Current Time |
 
 
 
 
 
 
636
 
637
  ---
638
  **Instructions:**
639
+ 1. Make sure `GROQ_API_KEY` is set in Space secrets
640
+ 2. Log in with your Hugging Face account
641
+ 3. Click the button and wait (~10-15 mins)
642
+ 4. You need **30%** to pass!
643
  """
644
  )
645
 
646
  gr.LoginButton()
647
+
648
+ run_button = gr.Button("๐Ÿš€ Run Evaluation & Submit All Answers", variant="primary", size="lg")
649
 
650
+ status_output = gr.Textbox(label="Status", lines=8, interactive=False)
651
+ results_table = gr.DataFrame(label="Results", wrap=True)
 
 
652
 
653
  run_button.click(
654
  fn=run_and_submit_all,
 
657
 
658
  if __name__ == "__main__":
659
  print("\n" + "="*60)
660
+ print("๐ŸŽฏ GAIA Agent - Powered by Groq + Llama 3.3 70B")
661
  print("="*60)
662
 
663
+ # Check for API key
664
+ if os.environ.get("GROQ_API_KEY"):
665
+ print("โœ… GROQ_API_KEY found")
666
+ else:
667
+ print("โš ๏ธ GROQ_API_KEY not found - add it to Space secrets!")
668
+
669
  space_id = os.getenv("SPACE_ID")
 
 
 
670
  if space_id:
671
+ print(f"โœ… Space: https://huggingface.co/spaces/{space_id}")
 
672
 
673
  print("="*60 + "\n")
674