Viske commited on
Commit
36e9084
·
verified ·
1 Parent(s): 4da6af4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +328 -537
app.py CHANGED
@@ -1,196 +1,264 @@
1
  import os
2
  import json
3
- import re
4
  import time
5
-
6
- import gradio as gr
7
  import requests
 
8
  import spaces
9
 
10
  from groq import Groq
11
 
12
  from prompt import SYSTEM_PROMPT
13
- from tools import execute_tool, download_file
14
-
 
 
 
 
 
 
 
15
 
16
- # ============================================================
17
- # CONFIG
18
- # ============================================================
19
 
20
  SCORING_API = "https://agents-course-unit4-scoring.hf.space"
21
-
22
  GROQ_API_KEY = os.getenv("GROQ_API_KEY")
23
 
24
- TEXT_MODEL = os.getenv(
25
- "GROQ_TEXT_MODEL",
26
- "openai/gpt-oss-120b"
27
- )
28
 
29
  MAX_STEPS = 5
30
 
31
- client = Groq(
32
- api_key=GROQ_API_KEY
33
- )
34
-
35
 
36
- # ============================================================
37
- # ZERO GPU COMPATIBILITY
38
- # ============================================================
39
 
40
- # The Space is running on ZeroGPU.
41
- # Our actual agent does NOT need the GPU because Groq
42
- # performs the model inference remotely.
43
- #
44
- # This function exists so the Space is recognized as
45
- # ZeroGPU-compatible.
46
 
47
  @spaces.GPU(duration=1)
48
  def zerogpu_probe():
49
  return "ZeroGPU available"
50
 
51
 
52
- # ============================================================
53
- # JSON PARSING
54
- # ============================================================
55
-
56
- def extract_json(text):
57
-
58
- if not text:
59
- return None
60
-
61
- text = text.strip()
62
-
63
- try:
64
- return json.loads(text)
65
- except Exception:
66
- pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
 
68
- # Remove markdown code fences
69
- cleaned = re.sub(
70
- r"```(?:json)?",
71
- "",
72
- text,
73
- flags=re.IGNORECASE
74
- )
75
 
76
- cleaned = cleaned.replace(
77
- "```",
78
- ""
79
- ).strip()
80
 
 
81
  try:
82
- return json.loads(cleaned)
83
- except Exception:
84
- pass
85
 
86
- # Find first JSON object
87
- start = cleaned.find("{")
88
- end = cleaned.rfind("}")
89
 
90
- if start != -1 and end != -1 and end > start:
 
91
 
92
- candidate = cleaned[start:end + 1]
 
 
93
 
94
- try:
95
- return json.loads(candidate)
96
- except Exception:
97
- pass
98
 
99
- return None
 
100
 
 
 
 
101
 
102
- # ============================================================
103
- # ANSWER CLEANING
104
- # ============================================================
105
-
106
- def clean_answer(answer):
107
-
108
- if answer is None:
109
- return ""
110
-
111
- answer = str(answer).strip()
112
-
113
- answer = re.sub(
114
- r"^```(?:text|json)?\s*",
115
- "",
116
- answer,
117
- flags=re.IGNORECASE
118
- )
119
-
120
- answer = re.sub(
121
- r"\s*```$",
122
- "",
123
- answer
124
- )
125
-
126
- prefixes = [
127
- "FINAL ANSWER:",
128
- "Final Answer:",
129
- "THE ANSWER IS:",
130
- "The answer is:",
131
- "ANSWER:"
132
- ]
133
-
134
- for prefix in prefixes:
135
 
136
- if answer.startswith(prefix):
137
 
138
- answer = answer[
139
- len(prefix):
140
- ].strip()
141
 
142
- return answer.strip()
143
 
 
 
 
144
 
145
- # ============================================================
146
- # GROQ CALL
147
- # ============================================================
148
 
149
- def call_groq(messages):
 
 
 
 
 
150
 
151
- last_error = None
 
 
 
 
 
 
 
 
152
 
153
  for attempt in range(3):
154
 
155
  try:
156
-
157
- response = client.chat.completions.create(
158
- model=TEXT_MODEL,
159
- messages=messages,
160
- temperature=0.1,
161
- max_tokens=1200
162
- )
163
-
164
- return response.choices[0].message.content
165
 
166
  except Exception as e:
167
 
168
- last_error = e
169
-
170
  print(
171
- f"Groq error "
172
- f"(attempt {attempt + 1}/3): {e}"
173
  )
174
 
175
- # Backoff for rate limits
176
  if attempt < 2:
177
- time.sleep(
178
- 2 ** attempt
179
- )
180
 
181
- raise RuntimeError(
182
- f"Groq request failed: {last_error}"
183
- )
184
 
 
 
 
185
 
186
- # ============================================================
187
- # SOLVE ONE QUESTION
188
- # ============================================================
189
 
190
- def solve_question(
191
- task_id,
192
- question
193
- ):
194
 
195
  messages = [
196
  {
@@ -200,236 +268,131 @@ def solve_question(
200
  {
201
  "role": "user",
202
  "content": (
203
- f"GAIA TASK ID:\n{task_id}\n\n"
204
  f"QUESTION:\n{question}\n\n"
205
- "Solve this question. "
206
- "Use tools only when necessary. "
207
- "Return the exact requested answer."
208
  )
209
  }
210
  ]
211
 
212
- current_file = None
213
-
214
- for step in range(MAX_STEPS):
215
-
216
- print(
217
- f" Agent step "
218
- f"{step + 1}/{MAX_STEPS}"
219
- )
220
-
221
- try:
222
-
223
- response = call_groq(
224
- messages
225
- )
226
-
227
- except Exception as e:
228
-
229
- return (
230
- f"ERROR: model request failed: {e}"
231
- )
232
-
233
- print(
234
- f" Model: {response[:500]}"
235
- )
236
-
237
- parsed = extract_json(
238
- response
239
- )
240
-
241
- # ----------------------------------------------------
242
- # Model failed JSON formatting
243
- # ----------------------------------------------------
244
-
245
- if parsed is None:
246
 
247
- messages.append(
248
- {
249
- "role": "assistant",
250
- "content": response
251
- }
252
- )
253
-
254
- messages.append(
255
- {
256
- "role": "user",
257
- "content": (
258
- "Invalid format. "
259
- "Respond ONLY with valid JSON.\n\n"
260
- "For a tool:\n"
261
- '{"action":"tool","tool":"TOOL_NAME","input":"..."}\n\n'
262
- "For the final answer:\n"
263
- '{"action":"final","answer":"..."}'
264
- )
265
- }
266
- )
267
 
268
- continue
269
-
270
- action = parsed.get(
271
- "action"
272
- )
273
 
274
- # ----------------------------------------------------
275
- # FINAL
276
- # ----------------------------------------------------
277
 
278
- if action == "final":
279
 
280
- answer = parsed.get(
281
- "answer",
282
- ""
283
  )
284
 
285
- return clean_answer(
286
- answer
287
- )
288
 
289
- # ----------------------------------------------------
290
- # TOOL
291
- # ----------------------------------------------------
292
 
293
- if action == "tool":
294
 
295
- tool_name = parsed.get(
296
- "tool"
297
- )
 
298
 
299
- tool_input = parsed.get(
300
- "input",
301
- ""
302
- )
303
 
304
- print(
305
- f" Tool: {tool_name}"
306
- )
307
-
308
- try:
309
-
310
- # Downloading a GAIA attachment
311
- if tool_name == "download_file":
312
-
313
- current_file = download_file(
314
- task_id
315
- )
316
 
317
- result = (
318
- "File downloaded successfully.\n"
319
- f"FILE_PATH: {current_file}"
320
- )
321
 
322
- # Tools that require current file
323
- elif tool_name in [
324
- "inspect_file",
325
- "transcribe_audio"
326
- ]:
327
 
328
- if not current_file:
 
329
 
330
- result = (
331
- "No attachment has been downloaded. "
332
- "Call download_file first."
333
- )
334
 
335
- else:
 
 
 
 
 
 
 
336
 
337
- result = execute_tool(
338
- tool_name,
339
- current_file
340
- )
341
 
342
- elif tool_name == "analyze_image":
 
 
343
 
344
- if not current_file:
345
 
346
- result = (
347
- "No image has been downloaded. "
348
- "Call download_file first."
349
- )
350
 
351
- else:
 
352
 
353
- result = execute_tool(
354
- tool_name,
355
- {
356
- "path": current_file,
357
- "question": tool_input
358
- }
359
- )
360
 
361
- else:
362
 
363
- result = execute_tool(
364
- tool_name,
365
- tool_input
366
- )
367
 
368
- except Exception as e:
369
 
370
- result = (
371
- f"TOOL ERROR: {e}"
372
- )
373
 
374
- # Add the tool call and result
375
- # to the conversation.
376
- messages.append(
377
- {
378
- "role": "assistant",
379
- "content": json.dumps(
380
- parsed
381
- )
382
- }
383
- )
384
 
385
- # Keep tool output reasonably short.
386
- result = str(result)
387
 
388
- if len(result) > 18000:
 
389
 
390
- result = (
391
- result[:18000]
392
- + "\n[TOOL OUTPUT TRUNCATED]"
393
- )
394
 
395
- messages.append(
396
- {
397
- "role": "user",
398
- "content": (
399
- f"TOOL RESULT:\n{result}\n\n"
400
- "Continue solving the original "
401
- "question. If you now know the "
402
- "answer, return the final JSON."
403
- )
404
- }
405
- )
406
 
407
- continue
 
 
 
 
 
408
 
409
- # ----------------------------------------------------
410
- # INVALID ACTION
411
- # ----------------------------------------------------
412
 
413
- messages.append(
414
- {
415
- "role": "user",
416
- "content": (
417
- "Invalid action. Use exactly:\n"
418
- '{"action":"tool","tool":"TOOL_NAME","input":"..."}\n'
419
- "or\n"
420
- '{"action":"final","answer":"..."}'
421
- )
422
- }
423
- )
424
 
425
- return (
426
- "ERROR: maximum reasoning steps reached."
427
- )
428
 
429
 
430
- # ============================================================
431
- # GET GAIA QUESTIONS
432
- # ============================================================
433
 
434
  def get_questions():
435
 
@@ -443,152 +406,70 @@ def get_questions():
443
  data = response.json()
444
 
445
  if isinstance(data, dict):
446
-
447
  if "questions" in data:
448
  return data["questions"]
449
 
450
- if "data" in data:
451
- return data["data"]
452
-
453
  return data
454
 
455
 
456
- # ============================================================
457
- # SUBMIT
458
- # ============================================================
459
-
460
- def submit_answers(
461
- username,
462
- answers
463
- ):
464
 
465
- space_id = os.getenv(
466
- "SPACE_ID"
467
- )
468
-
469
- if not space_id:
470
-
471
- raise RuntimeError(
472
- "SPACE_ID is missing."
473
- )
474
 
475
  agent_code = (
476
  f"https://huggingface.co/spaces/"
477
- f"{space_id}/tree/main"
478
  )
479
 
480
  payload = {
481
- "username": username.strip(),
482
  "agent_code": agent_code,
483
  "answers": answers
484
  }
485
 
486
- print(
487
- "\nAgent code:"
488
- )
489
-
490
- print(
491
- agent_code
492
- )
493
-
494
  response = requests.post(
495
  f"{SCORING_API}/submit",
496
  json=payload,
497
- timeout=120
498
  )
499
 
500
- response.raise_for_status()
 
501
 
502
- return response.json()
503
 
504
 
505
- # ============================================================
506
- # RUN ALL 20
507
- # ============================================================
508
 
509
  def run_agent(username):
510
 
511
- if not GROQ_API_KEY:
512
-
513
- return (
514
- "ERROR: GROQ_API_KEY is not configured.",
515
- []
516
- )
517
-
518
- if not username:
519
-
520
- return (
521
- "ERROR: Enter your Hugging Face username.",
522
- []
523
- )
524
-
525
- username = username.strip()
526
-
527
- print("\n")
528
- print("=" * 60)
529
  print("GAIA UNIT 4 AGENT")
530
  print("=" * 60)
531
 
532
- print(
533
- f"Username: {username}"
534
- )
535
-
536
- print(
537
- f"Model: {TEXT_MODEL}"
538
- )
539
-
540
- # --------------------------------------------------------
541
- # Get questions
542
- # --------------------------------------------------------
543
-
544
- try:
545
 
546
- questions = get_questions()
547
 
548
- except Exception as e:
549
-
550
- return (
551
- f"ERROR getting questions: {e}",
552
- []
553
- )
554
-
555
- print(
556
- f"Questions: {len(questions)}"
557
- )
558
-
559
- answers_payload = []
560
-
561
- display_rows = []
562
-
563
- # --------------------------------------------------------
564
- # Sequential processing
565
- # --------------------------------------------------------
566
 
567
- for index, item in enumerate(
568
- questions,
569
- start=1
570
- ):
571
 
572
- task_id = item.get(
573
- "task_id"
574
- )
575
 
576
- question = item.get(
577
- "question",
578
- item.get(
579
- "prompt",
580
- ""
581
- )
582
- )
583
 
584
  print("\n")
585
  print("=" * 60)
586
- print(
587
- f"QUESTION {index}/{len(questions)}"
588
- )
589
- print(
590
- f"TASK ID: {task_id}"
591
- )
592
  print("=" * 60)
593
 
594
  try:
@@ -601,160 +482,70 @@ def run_agent(username):
601
  except Exception as e:
602
 
603
  answer = (
604
- f"ERROR: {e}"
605
  )
606
 
607
- answer = clean_answer(
608
- answer
609
- )
610
-
611
- print(
612
- f"FINAL ANSWER: {answer}"
613
- )
614
 
615
- answers_payload.append(
616
  {
617
  "task_id": task_id,
618
  "submitted_answer": answer
619
  }
620
  )
621
 
622
- display_rows.append(
623
- [
624
- index,
625
- task_id,
626
- answer
627
- ]
628
- )
629
-
630
  # Small pause between questions.
631
  time.sleep(0.5)
632
 
633
- # --------------------------------------------------------
634
- # Submit once
635
- # --------------------------------------------------------
636
-
637
  print("\n")
638
  print("=" * 60)
639
  print("SUBMITTING ALL ANSWERS")
640
  print("=" * 60)
641
 
642
- try:
643
-
644
- result = submit_answers(
645
- username,
646
- answers_payload
647
- )
648
-
649
- except Exception as e:
650
-
651
- return (
652
- f"SUBMISSION ERROR: {e}",
653
- display_rows
654
- )
655
-
656
- print(
657
- "SUBMISSION RESULT:"
658
  )
659
 
660
- print(
661
- result
662
- )
663
 
664
- score = result.get(
665
- "score",
666
- result.get(
667
- "total_score",
668
- "N/A"
669
- )
670
- )
671
 
672
- correct = result.get(
673
- "correct_count",
674
- result.get(
675
- "correct",
676
- "N/A"
677
- )
678
- )
679
 
680
- total = result.get(
681
- "total_attempted",
682
- len(answers_payload)
683
- )
684
-
685
- status = (
686
- "SUBMITTED\n"
687
- f"Score: {score}\n"
688
- f"Correct: {correct}/{total}"
689
- )
690
-
691
- return (
692
- status,
693
- display_rows
694
- )
695
-
696
-
697
- # ============================================================
698
- # GRADIO UI
699
- # ============================================================
700
-
701
- with gr.Blocks(
702
- title="GAIA Unit 4 Agent"
703
- ) as demo:
704
 
705
  gr.Markdown(
706
  """
707
  # GAIA Unit 4 Agent
708
 
709
- Groq-powered autonomous agent for the GAIA Unit 4 final assignment.
710
-
711
- Enter your Hugging Face username and run the evaluation.
712
  """
713
  )
714
 
715
  username = gr.Textbox(
716
  label="Hugging Face Username",
717
- placeholder="e.g. Viske"
718
  )
719
 
720
  run_button = gr.Button(
721
- "Run 20 Questions & Submit",
722
  variant="primary"
723
  )
724
 
725
- status = gr.Textbox(
726
- label="Status",
727
- lines=5
728
- )
729
-
730
- results = gr.Dataframe(
731
- headers=[
732
- "Question",
733
- "Task ID",
734
- "Submitted Answer"
735
- ],
736
- datatype=[
737
- "number",
738
- "str",
739
- "str"
740
- ],
741
- label="Answers"
742
  )
743
 
744
  run_button.click(
745
  fn=run_agent,
746
  inputs=username,
747
- outputs=[
748
- status,
749
- results
750
- ]
751
  )
752
 
753
 
754
- # ============================================================
755
- # LAUNCH
756
- # ============================================================
757
-
758
  demo.launch(
759
  debug=True,
760
  share=False,
 
1
  import os
2
  import json
 
3
  import time
 
 
4
  import requests
5
+ import gradio as gr
6
  import spaces
7
 
8
  from groq import Groq
9
 
10
  from prompt import SYSTEM_PROMPT
11
+ from tools import (
12
+ web_search,
13
+ read_webpage,
14
+ download_file,
15
+ inspect_file,
16
+ run_python,
17
+ transcribe_audio,
18
+ analyze_image,
19
+ )
20
 
 
 
 
21
 
22
  SCORING_API = "https://agents-course-unit4-scoring.hf.space"
 
23
  GROQ_API_KEY = os.getenv("GROQ_API_KEY")
24
 
25
+ TEXT_MODEL = os.getenv("GROQ_TEXT_MODEL", "openai/gpt-oss-120b")
 
 
 
26
 
27
  MAX_STEPS = 5
28
 
29
+ client = Groq(api_key=GROQ_API_KEY)
 
 
 
30
 
 
 
 
31
 
32
+ # ---------------------------------------------------------
33
+ # ZeroGPU compatibility
34
+ # ---------------------------------------------------------
 
 
 
35
 
36
  @spaces.GPU(duration=1)
37
  def zerogpu_probe():
38
  return "ZeroGPU available"
39
 
40
 
41
+ # ---------------------------------------------------------
42
+ # Real Groq function tools
43
+ # ---------------------------------------------------------
44
+
45
+ TOOLS = [
46
+ {
47
+ "type": "function",
48
+ "function": {
49
+ "name": "web_search",
50
+ "description": "Search the web for factual information. Use this when the answer requires external research.",
51
+ "parameters": {
52
+ "type": "object",
53
+ "properties": {
54
+ "query": {
55
+ "type": "string",
56
+ "description": "A concise web search query."
57
+ }
58
+ },
59
+ "required": ["query"]
60
+ }
61
+ }
62
+ },
63
+ {
64
+ "type": "function",
65
+ "function": {
66
+ "name": "read_webpage",
67
+ "description": "Read and extract useful text from a webpage URL.",
68
+ "parameters": {
69
+ "type": "object",
70
+ "properties": {
71
+ "url": {
72
+ "type": "string",
73
+ "description": "The webpage URL to read."
74
+ }
75
+ },
76
+ "required": ["url"]
77
+ }
78
+ }
79
+ },
80
+ {
81
+ "type": "function",
82
+ "function": {
83
+ "name": "download_file",
84
+ "description": "Download the attachment associated with a GAIA task. Must be used before inspecting, transcribing, or analyzing an attachment.",
85
+ "parameters": {
86
+ "type": "object",
87
+ "properties": {
88
+ "task_id": {
89
+ "type": "string",
90
+ "description": "The GAIA task ID."
91
+ }
92
+ },
93
+ "required": ["task_id"]
94
+ }
95
+ }
96
+ },
97
+ {
98
+ "type": "function",
99
+ "function": {
100
+ "name": "inspect_file",
101
+ "description": "Inspect a downloaded attachment such as TXT, CSV, XLSX, PDF, PY, or other text/data files. The task_id is used to locate and download the attachment automatically.",
102
+ "parameters": {
103
+ "type": "object",
104
+ "properties": {
105
+ "task_id": {
106
+ "type": "string",
107
+ "description": "The GAIA task ID whose attachment should be inspected."
108
+ }
109
+ },
110
+ "required": ["task_id"]
111
+ }
112
+ }
113
+ },
114
+ {
115
+ "type": "function",
116
+ "function": {
117
+ "name": "run_python",
118
+ "description": "Run Python code for calculations or analysis. Use this for numerical, tabular, or computational tasks.",
119
+ "parameters": {
120
+ "type": "object",
121
+ "properties": {
122
+ "code": {
123
+ "type": "string",
124
+ "description": "Python code to execute."
125
+ }
126
+ },
127
+ "required": ["code"]
128
+ }
129
+ }
130
+ },
131
+ {
132
+ "type": "function",
133
+ "function": {
134
+ "name": "transcribe_audio",
135
+ "description": "Transcribe an audio attachment from a GAIA task. The task attachment is automatically downloaded first.",
136
+ "parameters": {
137
+ "type": "object",
138
+ "properties": {
139
+ "task_id": {
140
+ "type": "string",
141
+ "description": "The GAIA task ID."
142
+ }
143
+ },
144
+ "required": ["task_id"]
145
+ }
146
+ }
147
+ },
148
+ {
149
+ "type": "function",
150
+ "function": {
151
+ "name": "analyze_image",
152
+ "description": "Analyze an image attachment from a GAIA task. The task attachment is automatically downloaded first.",
153
+ "parameters": {
154
+ "type": "object",
155
+ "properties": {
156
+ "task_id": {
157
+ "type": "string",
158
+ "description": "The GAIA task ID."
159
+ },
160
+ "question": {
161
+ "type": "string",
162
+ "description": "What should be determined from the image?"
163
+ }
164
+ },
165
+ "required": ["task_id", "question"]
166
+ }
167
+ }
168
+ }
169
+ ]
170
 
 
 
 
 
 
 
 
171
 
172
+ # ---------------------------------------------------------
173
+ # Tool execution
174
+ # ---------------------------------------------------------
 
175
 
176
+ def execute_tool(name, args, task_id):
177
  try:
 
 
 
178
 
179
+ if name == "web_search":
180
+ return web_search(args["query"])
 
181
 
182
+ if name == "read_webpage":
183
+ return read_webpage(args["url"])
184
 
185
+ if name == "download_file":
186
+ path = download_file(task_id)
187
+ return f"Attachment downloaded successfully.\nPath: {path}"
188
 
189
+ if name == "inspect_file":
190
+ path = download_file(task_id)
191
+ return inspect_file(path)
 
192
 
193
+ if name == "run_python":
194
+ return run_python(args["code"])
195
 
196
+ if name == "transcribe_audio":
197
+ path = download_file(task_id)
198
+ return transcribe_audio(path)
199
 
200
+ if name == "analyze_image":
201
+ path = download_file(task_id)
202
+ return analyze_image(
203
+ path,
204
+ args.get("question", "")
205
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
 
207
+ return f"Unknown tool: {name}"
208
 
209
+ except Exception as e:
210
+ return f"Tool error: {type(e).__name__}: {str(e)}"
 
211
 
 
212
 
213
+ # ---------------------------------------------------------
214
+ # Groq call
215
+ # ---------------------------------------------------------
216
 
217
+ def call_groq(messages, use_tools=True):
 
 
218
 
219
+ kwargs = {
220
+ "model": TEXT_MODEL,
221
+ "messages": messages,
222
+ "temperature": 0.1,
223
+ "max_completion_tokens": 1200,
224
+ }
225
 
226
+ if use_tools:
227
+ kwargs["tools"] = TOOLS
228
+ kwargs["tool_choice"] = "auto"
229
+ else:
230
+ # Important:
231
+ # After receiving tool results, explicitly prevent another
232
+ # tool call and force a final textual answer.
233
+ kwargs["tools"] = TOOLS
234
+ kwargs["tool_choice"] = "none"
235
 
236
  for attempt in range(3):
237
 
238
  try:
239
+ response = client.chat.completions.create(**kwargs)
240
+ return response.choices[0].message
 
 
 
 
 
 
 
241
 
242
  except Exception as e:
243
 
 
 
244
  print(
245
+ f"Groq error (attempt {attempt + 1}/3): "
246
+ f"{type(e).__name__}: {e}"
247
  )
248
 
 
249
  if attempt < 2:
250
+ time.sleep(2 ** attempt)
251
+
252
+ raise RuntimeError("Groq request failed after 3 attempts.")
253
 
 
 
 
254
 
255
+ # ---------------------------------------------------------
256
+ # Solve one GAIA question
257
+ # ---------------------------------------------------------
258
 
259
+ def solve_question(task_id, question):
 
 
260
 
261
+ print(f"\n Agent solving task {task_id}")
 
 
 
262
 
263
  messages = [
264
  {
 
268
  {
269
  "role": "user",
270
  "content": (
271
+ f"GAIA TASK ID: {task_id}\n\n"
272
  f"QUESTION:\n{question}\n\n"
273
+ "Solve this question. Use tools when necessary. "
274
+ "Return only the exact final answer when finished."
 
275
  )
276
  }
277
  ]
278
 
279
+ for step in range(1, MAX_STEPS + 1):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280
 
281
+ print(f" Agent step {step}/{MAX_STEPS}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
282
 
283
+ message = call_groq(messages, use_tools=True)
 
 
 
 
284
 
285
+ # -------------------------------------------------
286
+ # Model requested tools
287
+ # -------------------------------------------------
288
 
289
+ if message.tool_calls:
290
 
291
+ print(
292
+ " Tool calls:",
293
+ [x.function.name for x in message.tool_calls]
294
  )
295
 
296
+ # Add assistant message containing the tool calls
297
+ messages.append(message)
 
298
 
299
+ for tool_call in message.tool_calls:
 
 
300
 
301
+ name = tool_call.function.name
302
 
303
+ try:
304
+ args = json.loads(tool_call.function.arguments)
305
+ except Exception:
306
+ args = {}
307
 
308
+ print(f" Executing: {name}")
 
 
 
309
 
310
+ result = execute_tool(
311
+ name,
312
+ args,
313
+ task_id
314
+ )
 
 
 
 
 
 
 
315
 
316
+ # Keep context manageable.
317
+ if result is None:
318
+ result = ""
 
319
 
320
+ result = str(result)
 
 
 
 
321
 
322
+ if len(result) > 9000:
323
+ result = result[:9000] + "\n[OUTPUT TRUNCATED]"
324
 
325
+ print(
326
+ f" Tool result: {len(result)} characters"
327
+ )
 
328
 
329
+ messages.append(
330
+ {
331
+ "role": "tool",
332
+ "tool_call_id": tool_call.id,
333
+ "name": name,
334
+ "content": result
335
+ }
336
+ )
337
 
338
+ # Continue agent loop.
339
+ continue
 
 
340
 
341
+ # -------------------------------------------------
342
+ # No tool call = model has an answer
343
+ # -------------------------------------------------
344
 
345
+ content = message.content or ""
346
 
347
+ print(" Model:", content[:1000])
 
 
 
348
 
349
+ if content.strip():
350
+ return clean_answer(content)
351
 
352
+ return "ERROR: maximum reasoning steps reached."
 
 
 
 
 
 
353
 
 
354
 
355
+ # ---------------------------------------------------------
356
+ # Clean final answer
357
+ # ---------------------------------------------------------
 
358
 
359
+ def clean_answer(text):
360
 
361
+ text = text.strip()
 
 
362
 
363
+ # Sometimes the model still wraps the final answer in JSON.
364
+ try:
365
+ obj = json.loads(text)
 
 
 
 
 
 
 
366
 
367
+ if isinstance(obj, dict):
 
368
 
369
+ if "answer" in obj:
370
+ return str(obj["answer"]).strip()
371
 
372
+ if "final_answer" in obj:
373
+ return str(obj["final_answer"]).strip()
 
 
374
 
375
+ except Exception:
376
+ pass
 
 
 
 
 
 
 
 
 
377
 
378
+ # Remove accidental prefixes.
379
+ prefixes = [
380
+ "FINAL ANSWER:",
381
+ "Final answer:",
382
+ "final answer:",
383
+ ]
384
 
385
+ for prefix in prefixes:
 
 
386
 
387
+ if text.startswith(prefix):
388
+ text = text[len(prefix):].strip()
 
 
 
 
 
 
 
 
 
389
 
390
+ return text
 
 
391
 
392
 
393
+ # ---------------------------------------------------------
394
+ # Get GAIA questions
395
+ # ---------------------------------------------------------
396
 
397
  def get_questions():
398
 
 
406
  data = response.json()
407
 
408
  if isinstance(data, dict):
 
409
  if "questions" in data:
410
  return data["questions"]
411
 
 
 
 
412
  return data
413
 
414
 
415
+ # ---------------------------------------------------------
416
+ # Submit answers
417
+ # ---------------------------------------------------------
 
 
 
 
 
418
 
419
+ def submit_answers(username, answers):
 
 
 
 
 
 
 
 
420
 
421
  agent_code = (
422
  f"https://huggingface.co/spaces/"
423
+ f"{username}/Final_Assignment_Template/tree/main"
424
  )
425
 
426
  payload = {
427
+ "username": username,
428
  "agent_code": agent_code,
429
  "answers": answers
430
  }
431
 
 
 
 
 
 
 
 
 
432
  response = requests.post(
433
  f"{SCORING_API}/submit",
434
  json=payload,
435
+ timeout=60
436
  )
437
 
438
+ print("\nSUBMISSION RESPONSE:")
439
+ print(response.text)
440
 
441
+ return response.text
442
 
443
 
444
+ # ---------------------------------------------------------
445
+ # Main run
446
+ # ---------------------------------------------------------
447
 
448
  def run_agent(username):
449
 
450
+ print("\n" + "=" * 60)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
451
  print("GAIA UNIT 4 AGENT")
452
  print("=" * 60)
453
 
454
+ print(f"Username: {username}")
455
+ print(f"Model: {TEXT_MODEL}")
 
 
 
 
 
 
 
 
 
 
 
456
 
457
+ questions = get_questions()
458
 
459
+ print(f"Questions: {len(questions)}")
460
+ print("=" * 60)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
461
 
462
+ answers = []
 
 
 
463
 
464
+ for index, item in enumerate(questions, 1):
 
 
465
 
466
+ task_id = item.get("task_id")
467
+ question = item.get("question")
 
 
 
 
 
468
 
469
  print("\n")
470
  print("=" * 60)
471
+ print(f"QUESTION {index}/{len(questions)}")
472
+ print(f"TASK ID: {task_id}")
 
 
 
 
473
  print("=" * 60)
474
 
475
  try:
 
482
  except Exception as e:
483
 
484
  answer = (
485
+ f"ERROR: {type(e).__name__}: {str(e)}"
486
  )
487
 
488
+ print("FINAL ANSWER:", answer)
 
 
 
 
 
 
489
 
490
+ answers.append(
491
  {
492
  "task_id": task_id,
493
  "submitted_answer": answer
494
  }
495
  )
496
 
 
 
 
 
 
 
 
 
497
  # Small pause between questions.
498
  time.sleep(0.5)
499
 
 
 
 
 
500
  print("\n")
501
  print("=" * 60)
502
  print("SUBMITTING ALL ANSWERS")
503
  print("=" * 60)
504
 
505
+ result = submit_answers(
506
+ username,
507
+ answers
 
 
 
 
 
 
 
 
 
 
 
 
 
508
  )
509
 
510
+ return result
 
 
511
 
 
 
 
 
 
 
 
512
 
513
+ # ---------------------------------------------------------
514
+ # Gradio UI
515
+ # ---------------------------------------------------------
 
 
 
 
516
 
517
+ with gr.Blocks(title="GAIA Unit 4 Agent") as demo:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
518
 
519
  gr.Markdown(
520
  """
521
  # GAIA Unit 4 Agent
522
 
523
+ Enter your Hugging Face username and run the 20-question GAIA validation.
 
 
524
  """
525
  )
526
 
527
  username = gr.Textbox(
528
  label="Hugging Face Username",
529
+ value="Viske"
530
  )
531
 
532
  run_button = gr.Button(
533
+ "Run GAIA Agent",
534
  variant="primary"
535
  )
536
 
537
+ output = gr.Textbox(
538
+ label="Result",
539
+ lines=10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
540
  )
541
 
542
  run_button.click(
543
  fn=run_agent,
544
  inputs=username,
545
+ outputs=output
 
 
 
546
  )
547
 
548
 
 
 
 
 
549
  demo.launch(
550
  debug=True,
551
  share=False,