avi080704 commited on
Commit
c8dd000
·
verified ·
1 Parent(s): 99a347f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +457 -352
app.py CHANGED
@@ -1,421 +1,526 @@
1
  import os
2
  import re
3
- import ast
4
  import json
 
 
 
 
 
 
5
  import requests
6
  import pandas as pd
7
- import gradio as gr
8
-
9
- from groq import Groq
10
- from duckduckgo_search import DDGS
11
 
 
12
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
 
 
13
 
14
- # ---------------------------------------------------
15
- # SEARCH TOOL
16
- # ---------------------------------------------------
17
- class WebSearchTool:
18
 
19
- def __init__(self):
20
- self.ddgs = DDGS()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
- def search(self, query, max_results=5):
23
 
24
- try:
25
-
26
- results = self.ddgs.text(
27
- query,
28
- max_results=max_results
 
 
 
29
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
- snippets = []
32
-
33
- for r in results:
34
-
35
- title = r.get("title", "")
36
- body = r.get("body", "")
37
-
38
- snippets.append(f"{title}: {body}")
39
 
40
- return "\n".join(snippets[:max_results])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
- except Exception as e:
43
 
44
- print(f"Search error: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
- return ""
47
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
 
49
- # ---------------------------------------------------
50
- # AGENT
51
- # ---------------------------------------------------
52
- class BasicAgent:
53
 
 
 
 
 
54
  def __init__(self):
55
-
56
- self.client = Groq(
57
- api_key=os.getenv("GROQ_API_KEY")
58
- )
59
-
60
- self.search_tool = WebSearchTool()
61
-
62
- print("Lightweight GAIA agent initialized.")
63
-
64
- # ---------------------------------------------------
65
- # CLEAN ANSWER
66
- # ---------------------------------------------------
67
- def clean_answer(self, text):
68
-
69
- if text is None:
70
- return ""
71
-
72
- text = str(text)
73
-
74
- bad_phrases = [
75
- "FINAL ANSWER:",
76
- "Answer:",
77
- "answer:",
78
- "```",
79
- "`"
80
- ]
81
-
82
- for b in bad_phrases:
83
- text = text.replace(b, "")
84
-
85
- text = re.sub(r"\s+", " ", text)
86
-
87
- text = text.strip()
88
-
89
- # first line only
90
- text = text.split("\n")[0]
91
-
92
- return text[:300]
93
-
94
- # ---------------------------------------------------
95
- # REVERSE STRING TASK
96
- # ---------------------------------------------------
97
- def handle_reverse_text(self, question):
98
-
99
- reversed_text = question[::-1]
100
-
101
- return reversed_text[:300]
102
-
103
- # ---------------------------------------------------
104
- # DETECT SEARCH NEED
105
- # ---------------------------------------------------
106
- def needs_search(self, question):
107
-
108
- q = question.lower()
109
-
110
- keywords = [
111
- "who",
112
- "when",
113
- "where",
114
- "which",
115
- "youtube",
116
- "wikipedia",
117
- "movie",
118
- "actor",
119
- "award",
120
- "paper",
121
- "country",
122
- "city",
123
- "population",
124
- "published",
125
- "album",
126
- "song",
127
- "species",
128
- "nasa",
129
- "video"
130
- ]
131
-
132
- return any(k in q for k in keywords)
133
-
134
- # ---------------------------------------------------
135
- # BUILD CONTEXT
136
- # ---------------------------------------------------
137
- def build_context(self, question):
138
-
139
- q = question.lower()
140
-
141
- context = []
142
-
143
- # ---------------------------------------------------
144
- # SEARCH
145
- # ---------------------------------------------------
146
- if self.needs_search(question):
147
-
148
- print("\nRunning web search...")
149
-
150
- web = self.search_tool.search(question)
151
-
152
- context.append(
153
- f"WEB SEARCH RESULTS:\n{web}"
154
- )
155
-
156
- # ---------------------------------------------------
157
- # CHESS
158
- # ---------------------------------------------------
159
- if "chess" in q:
160
-
161
- context.append(
162
- "This is a chess puzzle. "
163
- "Return the best move only."
164
- )
165
-
166
- # ---------------------------------------------------
167
- # CODE
168
- # ---------------------------------------------------
169
- if "python code" in q:
170
-
171
- context.append(
172
- "Infer likely numeric output."
173
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
174
 
175
- # ---------------------------------------------------
176
- # YOUTUBE
177
- # ---------------------------------------------------
178
- if "youtube.com" in q or "youtu.be" in q:
179
-
180
- context.append(
181
- "Use search results and inference "
182
- "to answer the YouTube question."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  )
184
 
185
- return "\n\n".join(context)
186
-
187
- # ---------------------------------------------------
188
- # MAIN CALL
189
- # ---------------------------------------------------
190
- def __call__(self, question: str) -> str:
191
-
192
- try:
193
-
194
- # ---------------------------------------------------
195
- # REVERSE STRING
196
- # ---------------------------------------------------
197
- if question.strip().startswith("."):
198
-
199
- print("Reverse text detected.")
200
-
201
- return self.handle_reverse_text(question)
202
-
203
- # ---------------------------------------------------
204
- # CONTEXT
205
- # ---------------------------------------------------
206
- context = self.build_context(question)
207
-
208
- # ---------------------------------------------------
209
- # PROMPT
210
- # ---------------------------------------------------
211
- system_prompt = """
212
- You are a lightweight GAIA benchmark solving assistant.
213
-
214
- STRICT RULES:
215
- - Return ONLY the final answer.
216
- - No explanations.
217
- - No markdown.
218
- - No bullet points.
219
- - No reasoning traces.
220
- - Keep answers concise.
221
- - Use context carefully.
222
- """
223
-
224
- user_prompt = f"""
225
- QUESTION:
226
- {question}
227
-
228
- CONTEXT:
229
- {context}
230
- """
231
-
232
- # ---------------------------------------------------
233
- # GROQ
234
- # ---------------------------------------------------
235
- completion = self.client.chat.completions.create(
236
- model="llama-3.1-8b-instant",
237
- messages=[
238
  {
239
- "role": "system",
240
- "content": system_prompt
241
- },
242
- {
243
- "role": "user",
244
- "content": user_prompt
245
  }
246
- ],
247
- temperature=0,
248
- max_tokens=96
 
 
 
 
 
 
 
 
 
 
 
 
249
  )
250
-
251
- answer = completion.choices[0].message.content
252
-
253
- cleaned = self.clean_answer(answer)
254
-
255
- print("\n======================")
256
- print("QUESTION:")
257
- print(question)
258
- print("\nANSWER:")
259
- print(cleaned)
260
- print("======================\n")
261
-
262
- return cleaned
263
-
264
  except Exception as e:
 
265
 
266
- print(f"Agent error: {e}")
267
-
 
268
  return ""
 
 
 
 
 
 
 
269
 
270
 
271
- # ---------------------------------------------------
272
- # MAIN EVALUATION
273
- # ---------------------------------------------------
274
  def run_and_submit_all(profile: gr.OAuthProfile | None):
 
275
 
276
  if profile:
277
- username = profile.username
 
278
  else:
279
- return "Please login first.", None
280
 
281
- questions_url = f"{DEFAULT_API_URL}/questions"
282
- submit_url = f"{DEFAULT_API_URL}/submit"
 
283
 
284
- # ---------------------------------------------------
285
- # INIT AGENT
286
- # ---------------------------------------------------
287
  try:
288
-
289
- agent = BasicAgent()
290
-
291
  except Exception as e:
 
 
292
 
293
- return f"Agent init error: {e}", None
 
294
 
295
- # ---------------------------------------------------
296
- # FETCH QUESTIONS
297
- # ---------------------------------------------------
298
  try:
299
-
300
- response = requests.get(
301
- questions_url,
302
- timeout=30
303
- )
304
-
305
  response.raise_for_status()
306
-
307
  questions_data = response.json()
308
-
 
 
 
 
309
  except Exception as e:
 
310
 
311
- return f"Question fetch error: {e}", None
312
-
313
- answers_payload = []
314
  results_log = []
315
-
316
- # ---------------------------------------------------
317
- # RUN TASKS
318
- # ---------------------------------------------------
319
- for idx, item in enumerate(questions_data):
320
-
321
  task_id = item.get("task_id")
322
- question = item.get("question")
323
-
324
- print(f"\nTASK {idx+1}")
325
-
 
326
  try:
327
-
328
- answer = agent(question)
329
-
330
- answers_payload.append({
331
- "task_id": task_id,
332
- "submitted_answer": answer
333
- })
334
-
335
- results_log.append({
336
- "Task ID": task_id,
337
- "Question": question,
338
- "Answer": answer
339
- })
340
-
341
  except Exception as e:
342
-
343
- results_log.append({
344
- "Task ID": task_id,
345
- "Question": question,
346
- "Answer": f"ERROR: {e}"
347
- })
348
-
349
- # ---------------------------------------------------
350
- # SUBMIT
351
- # ---------------------------------------------------
352
- try:
353
-
354
- submission = {
355
- "username": username,
356
- "agent_code": "https://huggingface.co",
357
- "answers": answers_payload
358
- }
359
-
360
- response = requests.post(
361
- submit_url,
362
- json=submission,
363
- timeout=120
364
  )
365
 
366
- result = response.json()
 
367
 
368
- status = (
369
- f"Score: {result.get('score')}%\n"
370
- f"Correct: "
371
- f"{result.get('correct_count')}/"
372
- f"{result.get('total_attempted')}"
373
- )
374
-
375
- return status, pd.DataFrame(results_log)
376
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
377
  except Exception as e:
378
-
379
- return f"Submit error: {e}", pd.DataFrame(results_log)
380
 
381
 
382
- # ---------------------------------------------------
383
- # UI
384
- # ---------------------------------------------------
385
  with gr.Blocks() as demo:
386
-
387
- gr.Markdown("# Lightweight GAIA Agent")
388
-
389
- gr.Markdown("""
390
- Enabled tools:
391
- - Groq Llama 3.1 8B Instant
392
- - DuckDuckGo Search
393
- - Reverse text handling
394
- - Lightweight reasoning
395
- """)
 
396
 
397
  gr.LoginButton()
 
 
 
398
 
399
- btn = gr.Button("Run Evaluation")
400
 
401
- output = gr.Textbox(
402
- label="Submission Result",
403
- lines=6
404
- )
405
-
406
- table = gr.DataFrame()
407
 
408
- btn.click(
409
- fn=run_and_submit_all,
410
- outputs=[output, table]
411
- )
412
 
 
 
 
 
413
 
414
- # ---------------------------------------------------
415
- # LAUNCH
416
- # ---------------------------------------------------
417
- if __name__ == "__main__":
 
418
 
419
- print("Launching lightweight GAIA agent...")
 
420
 
421
- demo.launch()
 
 
1
  import os
2
  import re
 
3
  import json
4
+ import io
5
+ import traceback
6
+ import contextlib
7
+ import tempfile
8
+
9
+ import gradio as gr
10
  import requests
11
  import pandas as pd
 
 
 
 
12
 
13
+ # --- Constants ---
14
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
15
+ GROQ_MODEL = os.getenv("GROQ_MODEL", "llama-3.3-70b-versatile")
16
+ MAX_TOOL_ITERATIONS = 8
17
 
 
 
 
 
18
 
19
+ # ---------------------------------------------------------------------------
20
+ # Tool implementations
21
+ # ---------------------------------------------------------------------------
22
+ def tool_web_search(query: str, max_results: int = 5) -> str:
23
+ """DuckDuckGo text search. Returns a short list of titles + snippets + URLs."""
24
+ try:
25
+ from duckduckgo_search import DDGS
26
+ results = []
27
+ with DDGS() as ddgs:
28
+ for r in ddgs.text(query, max_results=max_results):
29
+ results.append(
30
+ f"- {r.get('title', '')}\n {r.get('href', '')}\n {r.get('body', '')}"
31
+ )
32
+ if not results:
33
+ return "No results."
34
+ return "\n".join(results)
35
+ except Exception as e:
36
+ return f"web_search error: {e}"
37
 
 
38
 
39
+ def tool_fetch_url(url: str, max_chars: int = 6000) -> str:
40
+ """Fetch a URL and return readable text (HTML stripped)."""
41
+ try:
42
+ from bs4 import BeautifulSoup
43
+ headers = {
44
+ "User-Agent": (
45
+ "Mozilla/5.0 (compatible; GAIA-Agent/1.0; "
46
+ "+https://huggingface.co/learn/agents-course)"
47
  )
48
+ }
49
+ resp = requests.get(url, headers=headers, timeout=20)
50
+ resp.raise_for_status()
51
+ ctype = resp.headers.get("Content-Type", "")
52
+ if "html" in ctype or url.endswith((".html", ".htm")) or "<html" in resp.text[:500].lower():
53
+ soup = BeautifulSoup(resp.text, "lxml")
54
+ for tag in soup(["script", "style", "noscript"]):
55
+ tag.decompose()
56
+ text = soup.get_text(separator="\n")
57
+ else:
58
+ text = resp.text
59
+ text = re.sub(r"\n\s*\n+", "\n\n", text).strip()
60
+ if len(text) > max_chars:
61
+ text = text[:max_chars] + "\n...[truncated]"
62
+ return text
63
+ except Exception as e:
64
+ return f"fetch_url error: {e}"
65
 
 
 
 
 
 
 
 
 
66
 
67
+ def tool_wikipedia(query: str, sentences: int = 6) -> str:
68
+ """Look up a topic on Wikipedia and return a summary."""
69
+ try:
70
+ import wikipedia
71
+ wikipedia.set_lang("en")
72
+ try:
73
+ return wikipedia.summary(query, sentences=sentences, auto_suggest=True, redirect=True)
74
+ except wikipedia.DisambiguationError as de:
75
+ options = ", ".join(de.options[:8])
76
+ return f"Disambiguation. Options: {options}"
77
+ except wikipedia.PageError:
78
+ hits = wikipedia.search(query, results=5)
79
+ if not hits:
80
+ return "No Wikipedia page found."
81
+ return wikipedia.summary(hits[0], sentences=sentences, auto_suggest=False, redirect=True)
82
+ except Exception as e:
83
+ return f"wikipedia error: {e}"
84
 
 
85
 
86
+ def tool_python(code: str) -> str:
87
+ """Run a small Python snippet and return stdout (or the value of `result`)."""
88
+ buf = io.StringIO()
89
+ local_ns: dict = {}
90
+ try:
91
+ with contextlib.redirect_stdout(buf):
92
+ exec(code, {"__builtins__": __builtins__}, local_ns)
93
+ out = buf.getvalue().strip()
94
+ if not out and "result" in local_ns:
95
+ out = str(local_ns["result"])
96
+ return out or "(no output)"
97
+ except Exception as e:
98
+ return f"python error: {e}\n{traceback.format_exc(limit=2)}"
99
 
 
100
 
101
+ def tool_get_task_file(task_id: str, api_url: str = DEFAULT_API_URL) -> str:
102
+ """Download the file attached to a task and return a text preview."""
103
+ try:
104
+ resp = requests.get(f"{api_url}/files/{task_id}", timeout=30)
105
+ resp.raise_for_status()
106
+ ctype = resp.headers.get("Content-Type", "")
107
+ cdisp = resp.headers.get("Content-Disposition", "")
108
+ fname_match = re.search(r'filename="?([^"]+)"?', cdisp)
109
+ fname = fname_match.group(1) if fname_match else f"{task_id}"
110
+ suffix = os.path.splitext(fname)[1].lower()
111
+
112
+ # Save to temp for tools that need a path
113
+ tmp = tempfile.NamedTemporaryFile(prefix=f"{task_id}_", suffix=suffix, delete=False)
114
+ tmp.write(resp.content)
115
+ tmp.close()
116
+
117
+ info = f"File: {fname}\nContent-Type: {ctype}\nSaved to: {tmp.name}\nSize: {len(resp.content)} bytes\n"
118
+
119
+ # Try to give a readable preview
120
+ if suffix in {".txt", ".md", ".csv", ".json", ".py", ".tsv", ".log", ".xml", ".html"}:
121
+ try:
122
+ text = resp.content.decode("utf-8", errors="replace")
123
+ except Exception:
124
+ text = resp.text
125
+ return info + "\n--- preview ---\n" + text[:6000]
126
+
127
+ if suffix in {".xlsx", ".xls"}:
128
+ try:
129
+ df = pd.read_excel(tmp.name)
130
+ return info + "\n--- preview (head 30) ---\n" + df.head(30).to_csv(index=False)
131
+ except Exception as e:
132
+ return info + f"\n(excel parse error: {e})"
133
+
134
+ if suffix == ".pdf":
135
+ try:
136
+ from pypdf import PdfReader
137
+ reader = PdfReader(tmp.name)
138
+ pages = [p.extract_text() or "" for p in reader.pages[:10]]
139
+ return info + "\n--- pdf text (first 10 pages) ---\n" + "\n".join(pages)[:6000]
140
+ except Exception as e:
141
+ return info + f"\n(pdf parse error: {e})"
142
+
143
+ if suffix in {".mp3", ".wav", ".m4a", ".ogg"}:
144
+ return info + "\n(audio file; no transcription tool available)"
145
+
146
+ if suffix in {".png", ".jpg", ".jpeg", ".gif", ".webp"}:
147
+ return info + "\n(image file; no vision tool available)"
148
+
149
+ return info + "\n(binary file; no preview)"
150
+ except Exception as e:
151
+ return f"get_task_file error: {e}"
152
+
153
+
154
+ # ---------------------------------------------------------------------------
155
+ # Tool schema for Groq function calling
156
+ # ---------------------------------------------------------------------------
157
+ TOOLS_SPEC = [
158
+ {
159
+ "type": "function",
160
+ "function": {
161
+ "name": "web_search",
162
+ "description": "Search the web with DuckDuckGo. Returns titles, URLs, and snippets.",
163
+ "parameters": {
164
+ "type": "object",
165
+ "properties": {
166
+ "query": {"type": "string"},
167
+ "max_results": {"type": "integer", "default": 5},
168
+ },
169
+ "required": ["query"],
170
+ },
171
+ },
172
+ },
173
+ {
174
+ "type": "function",
175
+ "function": {
176
+ "name": "fetch_url",
177
+ "description": "Fetch a URL and return cleaned page text. Use after web_search to read a result.",
178
+ "parameters": {
179
+ "type": "object",
180
+ "properties": {
181
+ "url": {"type": "string"},
182
+ "max_chars": {"type": "integer", "default": 6000},
183
+ },
184
+ "required": ["url"],
185
+ },
186
+ },
187
+ },
188
+ {
189
+ "type": "function",
190
+ "function": {
191
+ "name": "wikipedia",
192
+ "description": "Get a Wikipedia summary for a topic.",
193
+ "parameters": {
194
+ "type": "object",
195
+ "properties": {
196
+ "query": {"type": "string"},
197
+ "sentences": {"type": "integer", "default": 6},
198
+ },
199
+ "required": ["query"],
200
+ },
201
+ },
202
+ },
203
+ {
204
+ "type": "function",
205
+ "function": {
206
+ "name": "python",
207
+ "description": "Execute a short Python snippet for math, string parsing, or data work. Use print() or assign to `result`.",
208
+ "parameters": {
209
+ "type": "object",
210
+ "properties": {"code": {"type": "string"}},
211
+ "required": ["code"],
212
+ },
213
+ },
214
+ },
215
+ {
216
+ "type": "function",
217
+ "function": {
218
+ "name": "get_task_file",
219
+ "description": "Download the file attached to a GAIA task by task_id and return a text preview.",
220
+ "parameters": {
221
+ "type": "object",
222
+ "properties": {"task_id": {"type": "string"}},
223
+ "required": ["task_id"],
224
+ },
225
+ },
226
+ },
227
+ ]
228
+
229
+ TOOL_FUNCTIONS = {
230
+ "web_search": lambda args: tool_web_search(args["query"], int(args.get("max_results", 5))),
231
+ "fetch_url": lambda args: tool_fetch_url(args["url"], int(args.get("max_chars", 6000))),
232
+ "wikipedia": lambda args: tool_wikipedia(args["query"], int(args.get("sentences", 6))),
233
+ "python": lambda args: tool_python(args["code"]),
234
+ "get_task_file": lambda args: tool_get_task_file(args["task_id"]),
235
+ }
236
+
237
+
238
+ SYSTEM_PROMPT = """You are a careful research agent answering GAIA benchmark questions.
239
+
240
+ You have tools: web_search, fetch_url, wikipedia, python, get_task_file.
241
+
242
+ Workflow:
243
+ - If the question references an attached file, image, audio, code, or table, call get_task_file with the task_id first.
244
+ - Use web_search then fetch_url to verify facts from primary sources.
245
+ - Use wikipedia for well-known entities or historical facts.
246
+ - Use python for arithmetic, date math, string/list manipulation, or parsing CSV data.
247
+ - Cross-check before answering. Avoid guessing.
248
+
249
+ Answer formatting (critical, the grader does EXACT string match):
250
+ - Reply with ONLY the answer. No preamble, no explanation, no quotes, no trailing period unless part of the answer.
251
+ - Do NOT include the words "FINAL ANSWER" or any label.
252
+ - Numbers: digits only, no commas, no units, no $ sign, unless the question asks for the unit.
253
+ - Strings: no leading articles ("the", "a") unless required, no abbreviations, write digits as digits.
254
+ - Lists: comma-separated, single space after each comma, applying the rules above to each element.
255
+ - If the question asks for a name, give just the name. If it asks "how many", give just the number.
256
+ """
257
 
 
 
 
 
258
 
259
+ # ---------------------------------------------------------------------------
260
+ # Agent
261
+ # ---------------------------------------------------------------------------
262
+ class GroqAgent:
263
  def __init__(self):
264
+ try:
265
+ from groq import Groq
266
+ except ImportError as e:
267
+ raise RuntimeError("groq package not installed") from e
268
+
269
+ api_key = os.getenv("GROQ_API_KEY")
270
+ if not api_key:
271
+ raise RuntimeError(
272
+ "GROQ_API_KEY is not set. Add it as a Secret in your HF Space settings."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
  )
274
+ self.client = Groq(api_key=api_key)
275
+ self.model = GROQ_MODEL
276
+ print(f"GroqAgent initialized with model={self.model}")
277
+
278
+ def __call__(self, question: str, task_id: str | None = None) -> str:
279
+ user_content = question
280
+ if task_id:
281
+ user_content = f"task_id: {task_id}\n\nQuestion: {question}"
282
+
283
+ messages = [
284
+ {"role": "system", "content": SYSTEM_PROMPT},
285
+ {"role": "user", "content": user_content},
286
+ ]
287
 
288
+ for step in range(MAX_TOOL_ITERATIONS):
289
+ try:
290
+ resp = self.client.chat.completions.create(
291
+ model=self.model,
292
+ messages=messages,
293
+ tools=TOOLS_SPEC,
294
+ tool_choice="auto",
295
+ temperature=0.0,
296
+ max_tokens=1024,
297
+ )
298
+ except Exception as e:
299
+ print(f"Groq API error: {e}")
300
+ return f"AGENT ERROR: {e}"
301
+
302
+ msg = resp.choices[0].message
303
+ tool_calls = getattr(msg, "tool_calls", None)
304
+
305
+ if not tool_calls:
306
+ answer = (msg.content or "").strip()
307
+ return self._postprocess_answer(answer)
308
+
309
+ # Append assistant message with the tool calls
310
+ messages.append(
311
+ {
312
+ "role": "assistant",
313
+ "content": msg.content or "",
314
+ "tool_calls": [
315
+ {
316
+ "id": tc.id,
317
+ "type": "function",
318
+ "function": {
319
+ "name": tc.function.name,
320
+ "arguments": tc.function.arguments,
321
+ },
322
+ }
323
+ for tc in tool_calls
324
+ ],
325
+ }
326
  )
327
 
328
+ for tc in tool_calls:
329
+ name = tc.function.name
330
+ try:
331
+ args = json.loads(tc.function.arguments or "{}")
332
+ except json.JSONDecodeError:
333
+ args = {}
334
+ fn = TOOL_FUNCTIONS.get(name)
335
+ print(f"[tool] {name}({args})")
336
+ if fn is None:
337
+ result = f"unknown tool: {name}"
338
+ else:
339
+ try:
340
+ result = fn(args)
341
+ except Exception as e:
342
+ result = f"{name} error: {e}"
343
+
344
+ if not isinstance(result, str):
345
+ result = str(result)
346
+ if len(result) > 8000:
347
+ result = result[:8000] + "\n...[truncated]"
348
+
349
+ messages.append(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
350
  {
351
+ "role": "tool",
352
+ "tool_call_id": tc.id,
353
+ "name": name,
354
+ "content": result,
 
 
355
  }
356
+ )
357
+
358
+ # Out of iterations: ask for a final, no-tool answer
359
+ messages.append(
360
+ {
361
+ "role": "user",
362
+ "content": "Stop using tools. Reply with ONLY the final answer string per the formatting rules.",
363
+ }
364
+ )
365
+ try:
366
+ resp = self.client.chat.completions.create(
367
+ model=self.model,
368
+ messages=messages,
369
+ temperature=0.0,
370
+ max_tokens=256,
371
  )
372
+ return self._postprocess_answer((resp.choices[0].message.content or "").strip())
 
 
 
 
 
 
 
 
 
 
 
 
 
373
  except Exception as e:
374
+ return f"AGENT ERROR: {e}"
375
 
376
+ @staticmethod
377
+ def _postprocess_answer(text: str) -> str:
378
+ if not text:
379
  return ""
380
+ # Strip common prefixes the model may sneak in despite instructions.
381
+ text = text.strip()
382
+ text = re.sub(r"^(final answer|answer)\s*:\s*", "", text, flags=re.IGNORECASE)
383
+ # Remove surrounding quotes/backticks
384
+ if len(text) >= 2 and text[0] == text[-1] and text[0] in {'"', "'", "`"}:
385
+ text = text[1:-1].strip()
386
+ return text
387
 
388
 
389
+ # ---------------------------------------------------------------------------
390
+ # Gradio submission flow
391
+ # ---------------------------------------------------------------------------
392
  def run_and_submit_all(profile: gr.OAuthProfile | None):
393
+ space_id = os.getenv("SPACE_ID")
394
 
395
  if profile:
396
+ username = f"{profile.username}"
397
+ print(f"User logged in: {username}")
398
  else:
399
+ return "Please Login to Hugging Face with the button.", None
400
 
401
+ api_url = DEFAULT_API_URL
402
+ questions_url = f"{api_url}/questions"
403
+ submit_url = f"{api_url}/submit"
404
 
 
 
 
405
  try:
406
+ agent = GroqAgent()
 
 
407
  except Exception as e:
408
+ print(f"Error instantiating agent: {e}")
409
+ return f"Error initializing agent: {e}", None
410
 
411
+ agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
412
+ print(agent_code)
413
 
414
+ print(f"Fetching questions from: {questions_url}")
 
 
415
  try:
416
+ response = requests.get(questions_url, timeout=15)
 
 
 
 
 
417
  response.raise_for_status()
 
418
  questions_data = response.json()
419
+ if not questions_data:
420
+ return "Fetched questions list is empty or invalid format.", None
421
+ print(f"Fetched {len(questions_data)} questions.")
422
+ except requests.exceptions.RequestException as e:
423
+ return f"Error fetching questions: {e}", None
424
  except Exception as e:
425
+ return f"An unexpected error occurred fetching questions: {e}", None
426
 
 
 
 
427
  results_log = []
428
+ answers_payload = []
429
+ print(f"Running agent on {len(questions_data)} questions...")
430
+ for idx, item in enumerate(questions_data, 1):
 
 
 
431
  task_id = item.get("task_id")
432
+ question_text = item.get("question")
433
+ if not task_id or question_text is None:
434
+ print(f"Skipping item with missing task_id or question: {item}")
435
+ continue
436
+ print(f"\n=== [{idx}/{len(questions_data)}] task_id={task_id} ===")
437
  try:
438
+ submitted_answer = agent(question_text, task_id=task_id)
 
 
 
 
 
 
 
 
 
 
 
 
 
439
  except Exception as e:
440
+ print(f"Error running agent on task {task_id}: {e}")
441
+ submitted_answer = f"AGENT ERROR: {e}"
442
+ answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
443
+ results_log.append(
444
+ {"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
445
  )
446
 
447
+ if not answers_payload:
448
+ return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
449
 
450
+ submission_data = {
451
+ "username": username.strip(),
452
+ "agent_code": agent_code,
453
+ "answers": answers_payload,
454
+ }
455
+ print(f"Submitting {len(answers_payload)} answers for user '{username}'...")
 
 
456
 
457
+ try:
458
+ response = requests.post(submit_url, json=submission_data, timeout=120)
459
+ response.raise_for_status()
460
+ result_data = response.json()
461
+ final_status = (
462
+ f"Submission Successful!\n"
463
+ f"User: {result_data.get('username')}\n"
464
+ f"Overall Score: {result_data.get('score', 'N/A')}% "
465
+ f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
466
+ f"Message: {result_data.get('message', 'No message received.')}"
467
+ )
468
+ return final_status, pd.DataFrame(results_log)
469
+ except requests.exceptions.HTTPError as e:
470
+ error_detail = f"Server responded with status {e.response.status_code}."
471
+ try:
472
+ error_detail += f" Detail: {e.response.json().get('detail', e.response.text)}"
473
+ except requests.exceptions.JSONDecodeError:
474
+ error_detail += f" Response: {e.response.text[:500]}"
475
+ return f"Submission Failed: {error_detail}", pd.DataFrame(results_log)
476
+ except requests.exceptions.Timeout:
477
+ return "Submission Failed: The request timed out.", pd.DataFrame(results_log)
478
+ except requests.exceptions.RequestException as e:
479
+ return f"Submission Failed: Network error - {e}", pd.DataFrame(results_log)
480
  except Exception as e:
481
+ return f"An unexpected error occurred during submission: {e}", pd.DataFrame(results_log)
 
482
 
483
 
484
+ # --- Gradio UI ---
 
 
485
  with gr.Blocks() as demo:
486
+ gr.Markdown("# GAIA Agent (Groq) — Evaluation Runner")
487
+ gr.Markdown(
488
+ """
489
+ **Setup**
490
+ 1. Add a Space secret named `GROQ_API_KEY` with your Groq API key.
491
+ 2. Optional: set `GROQ_MODEL` (default `llama-3.3-70b-versatile`).
492
+ 3. Log in to Hugging Face below and click **Run Evaluation & Submit All Answers**.
493
+
494
+ Tools available to the agent: `web_search`, `fetch_url`, `wikipedia`, `python`, `get_task_file`.
495
+ """
496
+ )
497
 
498
  gr.LoginButton()
499
+ run_button = gr.Button("Run Evaluation & Submit All Answers")
500
+ status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
501
+ results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
502
 
503
+ run_button.click(fn=run_and_submit_all, outputs=[status_output, results_table])
504
 
 
 
 
 
 
 
505
 
506
+ if __name__ == "__main__":
507
+ print("\n" + "-" * 30 + " App Starting " + "-" * 30)
508
+ space_host_startup = os.getenv("SPACE_HOST")
509
+ space_id_startup = os.getenv("SPACE_ID")
510
 
511
+ if space_host_startup:
512
+ print(f"✅ SPACE_HOST found: {space_host_startup}")
513
+ else:
514
+ print("ℹ️ SPACE_HOST not found (running locally?).")
515
 
516
+ if space_id_startup:
517
+ print(f"✅ SPACE_ID found: {space_id_startup}")
518
+ print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main")
519
+ else:
520
+ print("ℹ️ SPACE_ID not found (running locally?).")
521
 
522
+ if not os.getenv("GROQ_API_KEY"):
523
+ print("⚠️ GROQ_API_KEY is not set. Set it before running evaluation.")
524
 
525
+ print("-" * (60 + len(" App Starting ")) + "\n")
526
+ demo.launch(debug=True, share=False)