Upload 3 files

#541
by MananSiingh - opened
Files changed (3) hide show
  1. agent.py +510 -0
  2. app.py +188 -195
  3. requirements.txt +10 -2
agent.py ADDED
@@ -0,0 +1,510 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """GAIA Level-1 agent for the HF Agents Course final assignment.
2
+
3
+ Built on smolagents CodeAgent. Works with ANY of these free LLM backends —
4
+ set whichever API key you can get and the agent auto-detects it:
5
+
6
+ GROQ_API_KEY console.groq.com/keys (free, no card, fast)
7
+ CEREBRAS_API_KEY cloud.cerebras.ai (free tier)
8
+ OPENROUTER_API_KEY openrouter.ai/keys (has free models)
9
+ MISTRAL_API_KEY console.mistral.ai (free tier)
10
+ GOOGLE_API_KEY aistudio.google.com/apikey (free, best multimodal)
11
+ HF_TOKEN huggingface.co/settings/tokens (needs credits)
12
+
13
+ Generic escape hatch for any other OpenAI-compatible endpoint:
14
+ OPENAI_API_KEY + OPENAI_BASE_URL + AGENT_MODEL
15
+
16
+ Override the model with AGENT_MODEL if a default model id has been retired.
17
+ """
18
+
19
+ import base64
20
+ import mimetypes
21
+ import os
22
+ import re
23
+ import tempfile
24
+ import time
25
+
26
+ import requests
27
+ from smolagents import (
28
+ CodeAgent,
29
+ DuckDuckGoSearchTool,
30
+ VisitWebpageTool,
31
+ tool,
32
+ )
33
+
34
+ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
35
+ WIKI_UA = "HF-Agents-Course-GAIA-Agent/1.0 (educational use)"
36
+
37
+ # Provider registry: env var -> (base_url, default model, label)
38
+ # Model ids are defaults only; override with AGENT_MODEL if one is retired.
39
+ PROVIDERS = [
40
+ (
41
+ "GROQ_API_KEY",
42
+ "https://api.groq.com/openai/v1",
43
+ "llama-3.3-70b-versatile",
44
+ "Groq",
45
+ ),
46
+ (
47
+ "CEREBRAS_API_KEY",
48
+ "https://api.cerebras.ai/v1",
49
+ "llama-3.3-70b",
50
+ "Cerebras",
51
+ ),
52
+ (
53
+ "OPENROUTER_API_KEY",
54
+ "https://openrouter.ai/api/v1",
55
+ "meta-llama/llama-3.3-70b-instruct:free",
56
+ "OpenRouter",
57
+ ),
58
+ (
59
+ "MISTRAL_API_KEY",
60
+ "https://api.mistral.ai/v1",
61
+ "mistral-large-latest",
62
+ "Mistral",
63
+ ),
64
+ (
65
+ "GOOGLE_API_KEY",
66
+ "https://generativelanguage.googleapis.com/v1beta/openai/",
67
+ "gemini-2.5-flash",
68
+ "Gemini",
69
+ ),
70
+ (
71
+ "OPENAI_API_KEY",
72
+ os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1"),
73
+ "gpt-4o-mini",
74
+ "OpenAI-compatible",
75
+ ),
76
+ ]
77
+
78
+
79
+ def _active_provider():
80
+ """Return (api_key, base_url, model_id, label) for the first key found."""
81
+ for env_var, base_url, default_model, label in PROVIDERS:
82
+ key = os.getenv(env_var)
83
+ if key:
84
+ return key, base_url, os.getenv("AGENT_MODEL", default_model), label
85
+ return None, None, None, None
86
+
87
+
88
+ API_KEY, BASE_URL, MODEL_ID, PROVIDER = _active_provider()
89
+ GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY", "")
90
+
91
+
92
+ # --------------------------------------------------------------------------
93
+ # Gemini-only helper: native audio / video understanding
94
+ # --------------------------------------------------------------------------
95
+ def _gemini_generate(parts: list, retries: int = 3) -> str:
96
+ model = os.getenv("GEMINI_MODEL", "gemini-2.5-flash")
97
+ url = (
98
+ "https://generativelanguage.googleapis.com/v1beta/models/"
99
+ f"{model}:generateContent?key={GOOGLE_API_KEY}"
100
+ )
101
+ for attempt in range(retries):
102
+ resp = requests.post(url, json={"contents": [{"parts": parts}]}, timeout=180)
103
+ if resp.status_code == 429 and attempt < retries - 1:
104
+ time.sleep(20 * (attempt + 1))
105
+ continue
106
+ resp.raise_for_status()
107
+ return resp.json()["candidates"][0]["content"]["parts"][0]["text"]
108
+ return "ERROR: Gemini rate limited."
109
+
110
+
111
+ def _inline_part(file_path: str) -> dict:
112
+ mime = mimetypes.guess_type(file_path)[0] or "application/octet-stream"
113
+ with open(file_path, "rb") as f:
114
+ return {
115
+ "inline_data": {
116
+ "mime_type": mime,
117
+ "data": base64.b64encode(f.read()).decode(),
118
+ }
119
+ }
120
+
121
+
122
+ # --------------------------------------------------------------------------
123
+ # Tools
124
+ # --------------------------------------------------------------------------
125
+ @tool
126
+ def wikipedia_page(title: str) -> str:
127
+ """Fetch the full plain text of an English Wikipedia article. Use this
128
+ instead of visit_webpage for Wikipedia — it never gets blocked.
129
+
130
+ Args:
131
+ title: Article title, e.g. "Mercedes Sosa" or "1928 Summer Olympics".
132
+ """
133
+ try:
134
+ resp = requests.get(
135
+ "https://en.wikipedia.org/w/api.php",
136
+ params={
137
+ "action": "query",
138
+ "prop": "extracts",
139
+ "explaintext": 1,
140
+ "redirects": 1,
141
+ "format": "json",
142
+ "titles": title,
143
+ },
144
+ headers={"User-Agent": WIKI_UA},
145
+ timeout=45,
146
+ )
147
+ resp.raise_for_status()
148
+ pages = resp.json()["query"]["pages"]
149
+ page = list(pages.values())[0]
150
+ if "extract" not in page:
151
+ return f"No Wikipedia article found for '{title}'. Try wikipedia_search."
152
+ return page["extract"][:60000]
153
+ except Exception as e:
154
+ return f"ERROR fetching Wikipedia page: {e}"
155
+
156
+
157
+ @tool
158
+ def wikipedia_search(query: str) -> str:
159
+ """Search English Wikipedia and return matching article titles with snippets.
160
+ Use this to find the right title, then call wikipedia_page.
161
+
162
+ Args:
163
+ query: Search terms.
164
+ """
165
+ try:
166
+ resp = requests.get(
167
+ "https://en.wikipedia.org/w/api.php",
168
+ params={
169
+ "action": "query",
170
+ "list": "search",
171
+ "srsearch": query,
172
+ "srlimit": 10,
173
+ "format": "json",
174
+ },
175
+ headers={"User-Agent": WIKI_UA},
176
+ timeout=45,
177
+ )
178
+ resp.raise_for_status()
179
+ hits = resp.json()["query"]["search"]
180
+ return "\n".join(
181
+ f"- {h['title']}: {re.sub('<[^<]+?>', '', h['snippet'])}" for h in hits
182
+ ) or "No results."
183
+ except Exception as e:
184
+ return f"ERROR searching Wikipedia: {e}"
185
+
186
+
187
+ @tool
188
+ def fetch_url(url: str) -> str:
189
+ """Fetch a web page as text with a browser-like user agent. Use when
190
+ visit_webpage fails with a 403 Forbidden error.
191
+
192
+ Args:
193
+ url: The full URL to fetch.
194
+ """
195
+ try:
196
+ resp = requests.get(
197
+ url,
198
+ headers={
199
+ "User-Agent": (
200
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
201
+ "(KHTML, like Gecko) Chrome/122.0 Safari/537.36"
202
+ )
203
+ },
204
+ timeout=60,
205
+ )
206
+ resp.raise_for_status()
207
+ try:
208
+ from markdownify import markdownify
209
+
210
+ text = markdownify(resp.text)
211
+ except Exception:
212
+ text = resp.text
213
+ text = re.sub(r"\n{3,}", "\n\n", text)
214
+ return text[:50000]
215
+ except Exception as e:
216
+ return f"ERROR fetching url: {e}"
217
+
218
+
219
+ @tool
220
+ def transcribe_audio(file_path: str) -> str:
221
+ """Transcribe a local audio file (mp3/wav/m4a) to English text.
222
+
223
+ Args:
224
+ file_path: Absolute path to the local audio file to transcribe.
225
+ """
226
+ # Gemini: native audio understanding
227
+ if GOOGLE_API_KEY:
228
+ try:
229
+ return _gemini_generate(
230
+ [{"text": "Transcribe this audio verbatim."}, _inline_part(file_path)]
231
+ )
232
+ except Exception as e:
233
+ return f"ERROR transcribing audio: {e}"
234
+ # Groq hosts Whisper on an OpenAI-compatible endpoint
235
+ if os.getenv("GROQ_API_KEY"):
236
+ try:
237
+ with open(file_path, "rb") as f:
238
+ resp = requests.post(
239
+ "https://api.groq.com/openai/v1/audio/transcriptions",
240
+ headers={"Authorization": f"Bearer {os.getenv('GROQ_API_KEY')}"},
241
+ files={"file": (os.path.basename(file_path), f)},
242
+ data={"model": os.getenv("ASR_MODEL", "whisper-large-v3")},
243
+ timeout=180,
244
+ )
245
+ resp.raise_for_status()
246
+ return resp.json()["text"]
247
+ except Exception as e:
248
+ return f"ERROR transcribing audio: {e}"
249
+ # HF Inference fallback
250
+ try:
251
+ from huggingface_hub import InferenceClient
252
+
253
+ result = InferenceClient(
254
+ token=os.getenv("HF_TOKEN")
255
+ ).automatic_speech_recognition(
256
+ file_path, model=os.getenv("ASR_MODEL", "openai/whisper-large-v3")
257
+ )
258
+ return result.text if hasattr(result, "text") else str(result)
259
+ except Exception as e:
260
+ return f"ERROR transcribing audio (no ASR backend available): {e}"
261
+
262
+
263
+ @tool
264
+ def analyze_image(file_path: str, question: str) -> str:
265
+ """Answer a question about a local image file using a vision model.
266
+
267
+ Args:
268
+ file_path: Absolute path to the local image file (png/jpg).
269
+ question: The question to answer about the image. Be specific; for
270
+ chess positions, ask for a full square-by-square board reading
271
+ AND the winning move, verified carefully.
272
+ """
273
+ if GOOGLE_API_KEY:
274
+ try:
275
+ return _gemini_generate([{"text": question}, _inline_part(file_path)])
276
+ except Exception as e:
277
+ return f"ERROR analyzing image: {e}"
278
+ if not API_KEY:
279
+ return "ERROR: no vision backend configured."
280
+ try:
281
+ mime = mimetypes.guess_type(file_path)[0] or "image/png"
282
+ with open(file_path, "rb") as f:
283
+ b64 = base64.b64encode(f.read()).decode()
284
+ vision_model = os.getenv("VISION_MODEL", MODEL_ID)
285
+ resp = requests.post(
286
+ BASE_URL.rstrip("/") + "/chat/completions",
287
+ headers={"Authorization": f"Bearer {API_KEY}"},
288
+ json={
289
+ "model": vision_model,
290
+ "max_tokens": 1500,
291
+ "messages": [
292
+ {
293
+ "role": "user",
294
+ "content": [
295
+ {"type": "text", "text": question},
296
+ {
297
+ "type": "image_url",
298
+ "image_url": {"url": f"data:{mime};base64,{b64}"},
299
+ },
300
+ ],
301
+ }
302
+ ],
303
+ },
304
+ timeout=180,
305
+ )
306
+ resp.raise_for_status()
307
+ return resp.json()["choices"][0]["message"]["content"]
308
+ except Exception as e:
309
+ return (
310
+ f"ERROR analyzing image: {e}. The model may not support images; "
311
+ "set VISION_MODEL to a vision-capable model id."
312
+ )
313
+
314
+
315
+ @tool
316
+ def analyze_youtube_video(video_url: str, question: str) -> str:
317
+ """Watch a YouTube video and answer a question about its visual and audio
318
+ content (counting things on screen, quotes, scenes). Requires a Gemini key.
319
+
320
+ Args:
321
+ video_url: Full YouTube URL, e.g. https://www.youtube.com/watch?v=XXXX
322
+ question: The question to answer about the video.
323
+ """
324
+ if not GOOGLE_API_KEY:
325
+ return (
326
+ "ERROR: video analysis needs GOOGLE_API_KEY. Use "
327
+ "get_youtube_transcript or web_search for descriptions instead."
328
+ )
329
+ try:
330
+ return _gemini_generate(
331
+ [{"text": question}, {"file_data": {"file_uri": video_url}}]
332
+ )
333
+ except Exception as e:
334
+ return f"ERROR analyzing video: {e}"
335
+
336
+
337
+ @tool
338
+ def get_youtube_transcript(video_url: str) -> str:
339
+ """Fetch the transcript/captions of a YouTube video as plain text.
340
+
341
+ Args:
342
+ video_url: Full YouTube URL, e.g. https://www.youtube.com/watch?v=XXXX
343
+ """
344
+ try:
345
+ from youtube_transcript_api import YouTubeTranscriptApi
346
+
347
+ m = re.search(r"(?:v=|youtu\.be/)([\w-]{11})", video_url)
348
+ if not m:
349
+ return "ERROR: could not parse video id from URL."
350
+ vid = m.group(1)
351
+ try:
352
+ entries = [s.text for s in YouTubeTranscriptApi().fetch(vid)]
353
+ except AttributeError:
354
+ entries = [s["text"] for s in YouTubeTranscriptApi.get_transcript(vid)]
355
+ return " ".join(entries)[:20000]
356
+ except Exception as e:
357
+ return f"ERROR fetching transcript: {e}. Try web_search instead."
358
+
359
+
360
+ @tool
361
+ def read_file_as_text(file_path: str) -> str:
362
+ """Read a local text-like file (py, txt, csv, json, md) and return its content.
363
+
364
+ Args:
365
+ file_path: Absolute path to the local file.
366
+ """
367
+ try:
368
+ with open(file_path, "r", encoding="utf-8", errors="replace") as f:
369
+ return f.read()[:30000]
370
+ except Exception as e:
371
+ return f"ERROR reading file: {e}"
372
+
373
+
374
+ # --------------------------------------------------------------------------
375
+ # Answer-format guidance (GAIA is scored by EXACT MATCH)
376
+ # --------------------------------------------------------------------------
377
+ GAIA_INSTRUCTIONS = """You are a general AI assistant answering a benchmark
378
+ question scored by EXACT string match. Work step by step with your tools,
379
+ then call final_answer() with ONLY the answer itself.
380
+
381
+ Formatting rules for the final answer (critical):
382
+ - Do NOT write "FINAL ANSWER" or any prefix/suffix, explanation, or period
383
+ at the end. Output the bare answer only.
384
+ - Numbers: plain digits, no thousands separators, no units ($, %, kg) unless
385
+ the question explicitly asks for them, no trailing ".0".
386
+ - Strings: no articles ("the", "a"), no abbreviations unless asked.
387
+ - Comma-separated lists: apply the rules above to each element, use ", "
388
+ (comma + space) between elements, and respect any ordering the question
389
+ asks for (e.g. alphabetical, ascending).
390
+ - If asked for a first name / last name / city / country code only, return
391
+ exactly that and nothing more.
392
+
393
+ Tool strategy:
394
+ - Wikipedia questions: use wikipedia_search then wikipedia_page. Do NOT use
395
+ visit_webpage on wikipedia.org — it returns 403. The article text often
396
+ contains a discography or results table; read it carefully and count.
397
+ - If visit_webpage returns 403 Forbidden, retry that URL with fetch_url.
398
+ - Attached files: a local path is given; use read_file_as_text,
399
+ transcribe_audio, analyze_image, or pandas (pd.read_excel) for .xlsx.
400
+ - For .xlsx, inspect the columns first, then compute. Format money like
401
+ 89706.00 only when the question asks for two decimal places.
402
+ - Python-code questions: read the code and reason through it carefully.
403
+ - YouTube: try analyze_youtube_video, then get_youtube_transcript, then
404
+ web_search for third-party descriptions of the video.
405
+ - Some questions are pure reasoning (reversed text, a group-theory table).
406
+ Solve those directly in python without searching.
407
+
408
+ Reliability rules:
409
+ - Never give up and guess a number you did not verify. If one source is
410
+ blocked, try another tool or another source.
411
+ - Re-read the question's exact wording before answering (e.g. "included",
412
+ "as of July 2023", "IOC country code", "without abbreviations",
413
+ "first name only").
414
+ """
415
+
416
+
417
+ class GAIAAgent:
418
+ """Wraps a smolagents CodeAgent with GAIA-specific tooling and prompting."""
419
+
420
+ def __init__(self):
421
+ if not API_KEY:
422
+ raise RuntimeError(
423
+ "No LLM API key found. Set one of: GROQ_API_KEY, "
424
+ "CEREBRAS_API_KEY, OPENROUTER_API_KEY, MISTRAL_API_KEY, "
425
+ "GOOGLE_API_KEY, or OPENAI_API_KEY (+OPENAI_BASE_URL)."
426
+ )
427
+ from smolagents import OpenAIServerModel
428
+
429
+ model = OpenAIServerModel(
430
+ model_id=MODEL_ID, api_base=BASE_URL, api_key=API_KEY
431
+ )
432
+ self.agent = CodeAgent(
433
+ model=model,
434
+ tools=[
435
+ DuckDuckGoSearchTool(),
436
+ VisitWebpageTool(),
437
+ fetch_url,
438
+ wikipedia_search,
439
+ wikipedia_page,
440
+ transcribe_audio,
441
+ analyze_image,
442
+ analyze_youtube_video,
443
+ get_youtube_transcript,
444
+ read_file_as_text,
445
+ ],
446
+ additional_authorized_imports=[
447
+ "pandas",
448
+ "numpy",
449
+ "openpyxl",
450
+ "json",
451
+ "csv",
452
+ "re",
453
+ "math",
454
+ "statistics",
455
+ "itertools",
456
+ "collections",
457
+ "datetime",
458
+ ],
459
+ max_steps=15,
460
+ )
461
+ print(f"GAIAAgent initialized (backend={PROVIDER}, model={MODEL_ID}).")
462
+
463
+ @staticmethod
464
+ def download_task_file(task_id: str, file_name: str) -> str | None:
465
+ """Download the file attached to a task; returns a local path or None."""
466
+ if not file_name:
467
+ return None
468
+ url = f"{DEFAULT_API_URL}/files/{task_id}"
469
+ for attempt in range(4):
470
+ try:
471
+ resp = requests.get(url, timeout=30)
472
+ resp.raise_for_status()
473
+ fd, path = tempfile.mkstemp(suffix=os.path.splitext(file_name)[1] or "")
474
+ with os.fdopen(fd, "wb") as f:
475
+ f.write(resp.content)
476
+ return path
477
+ except Exception as e:
478
+ print(f"File download attempt {attempt + 1} failed for {task_id}: {e}")
479
+ time.sleep(3 * (attempt + 1))
480
+ return None
481
+
482
+ @staticmethod
483
+ def _clean(answer: str) -> str:
484
+ """Strip wrappers the model sometimes adds despite instructions."""
485
+ a = str(answer).strip()
486
+ a = re.sub(r"^(final answer\s*:?\s*)", "", a, flags=re.IGNORECASE)
487
+ a = a.strip().strip('"').strip("'").strip()
488
+ if a.endswith("."):
489
+ a = a[:-1]
490
+ return a
491
+
492
+ def __call__(self, question: str, task_id: str = "", file_name: str = "") -> str:
493
+ prompt = GAIA_INSTRUCTIONS + "\n\nQuestion: " + question
494
+ file_path = self.download_task_file(task_id, file_name)
495
+ if file_path:
496
+ prompt += (
497
+ f"\n\nAn attached file for this question was downloaded to the "
498
+ f"local path: {file_path} (original name: {file_name})."
499
+ )
500
+ elif file_name:
501
+ prompt += (
502
+ f"\n\nNOTE: this question references an attached file "
503
+ f"({file_name}) that could not be downloaded. Answer from "
504
+ f"other sources if possible."
505
+ )
506
+ try:
507
+ return self._clean(self.agent.run(prompt))
508
+ except Exception as e:
509
+ print(f"Agent error on task {task_id}: {e}")
510
+ return f"AGENT ERROR: {e}"
app.py CHANGED
@@ -1,196 +1,189 @@
1
- import os
2
- import gradio as gr
3
- import requests
4
- import inspect
5
- import pandas as pd
6
-
7
- # (Keep Constants as is)
8
- # --- Constants ---
9
- DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
10
-
11
- # --- Basic Agent Definition ---
12
- # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
13
- class BasicAgent:
14
- def __init__(self):
15
- print("BasicAgent initialized.")
16
- def __call__(self, question: str) -> str:
17
- print(f"Agent received question (first 50 chars): {question[:50]}...")
18
- fixed_answer = "This is a default answer."
19
- print(f"Agent returning fixed answer: {fixed_answer}")
20
- return fixed_answer
21
-
22
- def run_and_submit_all( profile: gr.OAuthProfile | None):
23
- """
24
- Fetches all questions, runs the BasicAgent on them, submits all answers,
25
- and displays the results.
26
- """
27
- # --- Determine HF Space Runtime URL and Repo URL ---
28
- space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
29
-
30
- if profile:
31
- username= f"{profile.username}"
32
- print(f"User logged in: {username}")
33
- else:
34
- print("User not logged in.")
35
- return "Please Login to Hugging Face with the button.", None
36
-
37
- api_url = DEFAULT_API_URL
38
- questions_url = f"{api_url}/questions"
39
- submit_url = f"{api_url}/submit"
40
-
41
- # 1. Instantiate Agent ( modify this part to create your agent)
42
- try:
43
- agent = BasicAgent()
44
- except Exception as e:
45
- print(f"Error instantiating agent: {e}")
46
- return f"Error initializing agent: {e}", None
47
- # In the case of an app running as a hugging Face space, this link points toward your codebase ( usefull for others so please keep it public)
48
- agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
49
- print(agent_code)
50
-
51
- # 2. Fetch Questions
52
- print(f"Fetching questions from: {questions_url}")
53
- try:
54
- response = requests.get(questions_url, timeout=15)
55
- response.raise_for_status()
56
- questions_data = response.json()
57
- if not questions_data:
58
- print("Fetched questions list is empty.")
59
- return "Fetched questions list is empty or invalid format.", None
60
- print(f"Fetched {len(questions_data)} questions.")
61
- except requests.exceptions.RequestException as e:
62
- print(f"Error fetching questions: {e}")
63
- return f"Error fetching questions: {e}", None
64
- except requests.exceptions.JSONDecodeError as e:
65
- print(f"Error decoding JSON response from questions endpoint: {e}")
66
- print(f"Response text: {response.text[:500]}")
67
- return f"Error decoding server response for questions: {e}", None
68
- except Exception as e:
69
- print(f"An unexpected error occurred fetching questions: {e}")
70
- return f"An unexpected error occurred fetching questions: {e}", None
71
-
72
- # 3. Run your Agent
73
- results_log = []
74
- answers_payload = []
75
- print(f"Running agent on {len(questions_data)} questions...")
76
- for item in questions_data:
77
- task_id = item.get("task_id")
78
- question_text = item.get("question")
79
- if not task_id or question_text is None:
80
- print(f"Skipping item with missing task_id or question: {item}")
81
- continue
82
- try:
83
- submitted_answer = agent(question_text)
84
- answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
85
- results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
86
- except Exception as e:
87
- print(f"Error running agent on task {task_id}: {e}")
88
- results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"})
89
-
90
- if not answers_payload:
91
- print("Agent did not produce any answers to submit.")
92
- return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
93
-
94
- # 4. Prepare Submission
95
- submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
96
- status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
97
- print(status_update)
98
-
99
- # 5. Submit
100
- print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
101
- try:
102
- response = requests.post(submit_url, json=submission_data, timeout=60)
103
- response.raise_for_status()
104
- result_data = response.json()
105
- final_status = (
106
- f"Submission Successful!\n"
107
- f"User: {result_data.get('username')}\n"
108
- f"Overall Score: {result_data.get('score', 'N/A')}% "
109
- f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
110
- f"Message: {result_data.get('message', 'No message received.')}"
111
- )
112
- print("Submission successful.")
113
- results_df = pd.DataFrame(results_log)
114
- return final_status, results_df
115
- except requests.exceptions.HTTPError as e:
116
- error_detail = f"Server responded with status {e.response.status_code}."
117
- try:
118
- error_json = e.response.json()
119
- error_detail += f" Detail: {error_json.get('detail', e.response.text)}"
120
- except requests.exceptions.JSONDecodeError:
121
- error_detail += f" Response: {e.response.text[:500]}"
122
- status_message = f"Submission Failed: {error_detail}"
123
- print(status_message)
124
- results_df = pd.DataFrame(results_log)
125
- return status_message, results_df
126
- except requests.exceptions.Timeout:
127
- status_message = "Submission Failed: The request timed out."
128
- print(status_message)
129
- results_df = pd.DataFrame(results_log)
130
- return status_message, results_df
131
- except requests.exceptions.RequestException as e:
132
- status_message = f"Submission Failed: Network error - {e}"
133
- print(status_message)
134
- results_df = pd.DataFrame(results_log)
135
- return status_message, results_df
136
- except Exception as e:
137
- status_message = f"An unexpected error occurred during submission: {e}"
138
- print(status_message)
139
- results_df = pd.DataFrame(results_log)
140
- return status_message, results_df
141
-
142
-
143
- # --- Build Gradio Interface using Blocks ---
144
- with gr.Blocks() as demo:
145
- gr.Markdown("# Basic Agent Evaluation Runner")
146
- gr.Markdown(
147
- """
148
- **Instructions:**
149
-
150
- 1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ...
151
- 2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
152
- 3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score.
153
-
154
- ---
155
- **Disclaimers:**
156
- Once clicking on the "submit button, it can take quite some time ( this is the time for the agent to go through all the questions).
157
- This space provides a basic setup and is intentionally sub-optimal to encourage you to develop your own, more robust solution. For instance for the delay process of the submit button, a solution could be to cache the answers and submit in a seperate action or even to answer the questions in async.
158
- """
159
- )
160
-
161
- gr.LoginButton()
162
-
163
- run_button = gr.Button("Run Evaluation & Submit All Answers")
164
-
165
- status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
166
- # Removed max_rows=10 from DataFrame constructor
167
- results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
168
-
169
- run_button.click(
170
- fn=run_and_submit_all,
171
- outputs=[status_output, results_table]
172
- )
173
-
174
- if __name__ == "__main__":
175
- print("\n" + "-"*30 + " App Starting " + "-"*30)
176
- # Check for SPACE_HOST and SPACE_ID at startup for information
177
- space_host_startup = os.getenv("SPACE_HOST")
178
- space_id_startup = os.getenv("SPACE_ID") # Get SPACE_ID at startup
179
-
180
- if space_host_startup:
181
- print(f" SPACE_HOST found: {space_host_startup}")
182
- print(f" Runtime URL should be: https://{space_host_startup}.hf.space")
183
- else:
184
- print("ℹ️ SPACE_HOST environment variable not found (running locally?).")
185
-
186
- if space_id_startup: # Print repo URLs if SPACE_ID is found
187
- print(f"✅ SPACE_ID found: {space_id_startup}")
188
- print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}")
189
- print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main")
190
- else:
191
- print("ℹ️ SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined.")
192
-
193
- print("-"*(60 + len(" App Starting ")) + "\n")
194
-
195
- print("Launching Gradio Interface for Basic Agent Evaluation...")
196
  demo.launch(debug=True, share=False)
 
1
+ import os
2
+ import gradio as gr
3
+ import requests
4
+ import inspect
5
+ import pandas as pd
6
+
7
+ # (Keep Constants as is)
8
+ # --- Constants ---
9
+ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
10
+
11
+ # --- Agent Definition ---
12
+ from agent import GAIAAgent
13
+
14
+ def run_and_submit_all( profile: gr.OAuthProfile | None):
15
+ """
16
+ Fetches all questions, runs the BasicAgent on them, submits all answers,
17
+ and displays the results.
18
+ """
19
+ # --- Determine HF Space Runtime URL and Repo URL ---
20
+ space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
21
+
22
+ if profile:
23
+ username= f"{profile.username}"
24
+ print(f"User logged in: {username}")
25
+ else:
26
+ print("User not logged in.")
27
+ return "Please Login to Hugging Face with the button.", None
28
+
29
+ api_url = DEFAULT_API_URL
30
+ questions_url = f"{api_url}/questions"
31
+ submit_url = f"{api_url}/submit"
32
+
33
+ # 1. Instantiate Agent ( modify this part to create your agent)
34
+ try:
35
+ agent = GAIAAgent()
36
+ except Exception as e:
37
+ print(f"Error instantiating agent: {e}")
38
+ return f"Error initializing agent: {e}", None
39
+ # In the case of an app running as a hugging Face space, this link points toward your codebase ( usefull for others so please keep it public)
40
+ agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
41
+ print(agent_code)
42
+
43
+ # 2. Fetch Questions
44
+ print(f"Fetching questions from: {questions_url}")
45
+ try:
46
+ response = requests.get(questions_url, timeout=15)
47
+ response.raise_for_status()
48
+ questions_data = response.json()
49
+ if not questions_data:
50
+ print("Fetched questions list is empty.")
51
+ return "Fetched questions list is empty or invalid format.", None
52
+ print(f"Fetched {len(questions_data)} questions.")
53
+ except requests.exceptions.RequestException as e:
54
+ print(f"Error fetching questions: {e}")
55
+ return f"Error fetching questions: {e}", None
56
+ except requests.exceptions.JSONDecodeError as e:
57
+ print(f"Error decoding JSON response from questions endpoint: {e}")
58
+ print(f"Response text: {response.text[:500]}")
59
+ return f"Error decoding server response for questions: {e}", None
60
+ except Exception as e:
61
+ print(f"An unexpected error occurred fetching questions: {e}")
62
+ return f"An unexpected error occurred fetching questions: {e}", None
63
+
64
+ # 3. Run your Agent
65
+ results_log = []
66
+ answers_payload = []
67
+ print(f"Running agent on {len(questions_data)} questions...")
68
+ for item in questions_data:
69
+ task_id = item.get("task_id")
70
+ question_text = item.get("question")
71
+ file_name = item.get("file_name", "")
72
+ if not task_id or question_text is None:
73
+ print(f"Skipping item with missing task_id or question: {item}")
74
+ continue
75
+ try:
76
+ submitted_answer = agent(question_text, task_id=task_id, file_name=file_name)
77
+ answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
78
+ results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
79
+ except Exception as e:
80
+ print(f"Error running agent on task {task_id}: {e}")
81
+ results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"})
82
+
83
+ if not answers_payload:
84
+ print("Agent did not produce any answers to submit.")
85
+ return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
86
+
87
+ # 4. Prepare Submission
88
+ submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
89
+ status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
90
+ print(status_update)
91
+
92
+ # 5. Submit
93
+ print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
94
+ try:
95
+ response = requests.post(submit_url, json=submission_data, timeout=60)
96
+ response.raise_for_status()
97
+ result_data = response.json()
98
+ final_status = (
99
+ f"Submission Successful!\n"
100
+ f"User: {result_data.get('username')}\n"
101
+ f"Overall Score: {result_data.get('score', 'N/A')}% "
102
+ f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
103
+ f"Message: {result_data.get('message', 'No message received.')}"
104
+ )
105
+ print("Submission successful.")
106
+ results_df = pd.DataFrame(results_log)
107
+ return final_status, results_df
108
+ except requests.exceptions.HTTPError as e:
109
+ error_detail = f"Server responded with status {e.response.status_code}."
110
+ try:
111
+ error_json = e.response.json()
112
+ error_detail += f" Detail: {error_json.get('detail', e.response.text)}"
113
+ except requests.exceptions.JSONDecodeError:
114
+ error_detail += f" Response: {e.response.text[:500]}"
115
+ status_message = f"Submission Failed: {error_detail}"
116
+ print(status_message)
117
+ results_df = pd.DataFrame(results_log)
118
+ return status_message, results_df
119
+ except requests.exceptions.Timeout:
120
+ status_message = "Submission Failed: The request timed out."
121
+ print(status_message)
122
+ results_df = pd.DataFrame(results_log)
123
+ return status_message, results_df
124
+ except requests.exceptions.RequestException as e:
125
+ status_message = f"Submission Failed: Network error - {e}"
126
+ print(status_message)
127
+ results_df = pd.DataFrame(results_log)
128
+ return status_message, results_df
129
+ except Exception as e:
130
+ status_message = f"An unexpected error occurred during submission: {e}"
131
+ print(status_message)
132
+ results_df = pd.DataFrame(results_log)
133
+ return status_message, results_df
134
+
135
+
136
+ # --- Build Gradio Interface using Blocks ---
137
+ with gr.Blocks() as demo:
138
+ gr.Markdown("# Basic Agent Evaluation Runner")
139
+ gr.Markdown(
140
+ """
141
+ **Instructions:**
142
+
143
+ 1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ...
144
+ 2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
145
+ 3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score.
146
+
147
+ ---
148
+ **Disclaimers:**
149
+ Once clicking on the "submit button, it can take quite some time ( this is the time for the agent to go through all the questions).
150
+ This space provides a basic setup and is intentionally sub-optimal to encourage you to develop your own, more robust solution. For instance for the delay process of the submit button, a solution could be to cache the answers and submit in a seperate action or even to answer the questions in async.
151
+ """
152
+ )
153
+
154
+ gr.LoginButton()
155
+
156
+ run_button = gr.Button("Run Evaluation & Submit All Answers")
157
+
158
+ status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
159
+ # Removed max_rows=10 from DataFrame constructor
160
+ results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
161
+
162
+ run_button.click(
163
+ fn=run_and_submit_all,
164
+ outputs=[status_output, results_table]
165
+ )
166
+
167
+ if __name__ == "__main__":
168
+ print("\n" + "-"*30 + " App Starting " + "-"*30)
169
+ # Check for SPACE_HOST and SPACE_ID at startup for information
170
+ space_host_startup = os.getenv("SPACE_HOST")
171
+ space_id_startup = os.getenv("SPACE_ID") # Get SPACE_ID at startup
172
+
173
+ if space_host_startup:
174
+ print(f"✅ SPACE_HOST found: {space_host_startup}")
175
+ print(f" Runtime URL should be: https://{space_host_startup}.hf.space")
176
+ else:
177
+ print("ℹ️ SPACE_HOST environment variable not found (running locally?).")
178
+
179
+ if space_id_startup: # Print repo URLs if SPACE_ID is found
180
+ print(f"✅ SPACE_ID found: {space_id_startup}")
181
+ print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}")
182
+ print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main")
183
+ else:
184
+ print("ℹ️ SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined.")
185
+
186
+ print("-"*(60 + len(" App Starting ")) + "\n")
187
+
188
+ print("Launching Gradio Interface for Basic Agent Evaluation...")
 
 
 
 
 
 
 
189
  demo.launch(debug=True, share=False)
requirements.txt CHANGED
@@ -1,2 +1,10 @@
1
- gradio
2
- requests
 
 
 
 
 
 
 
 
 
1
+ gradio[oauth]
2
+ requests
3
+ smolagents[toolkit]
4
+ openai
5
+ huggingface_hub
6
+ pandas
7
+ numpy
8
+ openpyxl
9
+ youtube-transcript-api
10
+ markdownify