smashburger-dev commited on
Commit
b6fa5fc
·
verified ·
1 Parent(s): 81917a3

Add GAIA Unit 4 workbench

Browse files
Files changed (7) hide show
  1. .gitignore +6 -0
  2. README.md +55 -9
  3. answers.template.json +1 -0
  4. app.py +113 -182
  5. deploy_to_hf.py +37 -0
  6. gaia_workbench.py +186 -0
  7. requirements.txt +5 -2
.gitignore ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ .env
5
+ answers.local.json
6
+ data/
README.md CHANGED
@@ -1,15 +1,61 @@
1
  ---
2
- title: Template Final Assignment
3
- emoji: 🕵🏻‍♂️
4
- colorFrom: indigo
5
- colorTo: indigo
6
  sdk: gradio
7
- sdk_version: 5.25.2
8
  app_file: app.py
9
  pinned: false
10
- hf_oauth: true
11
- # optional, default duration is 8 hours/480 minutes. Max duration is 30 days/43200 minutes.
12
- hf_oauth_expiration_minutes: 480
13
  ---
14
 
15
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: GAIA Unit 4 Workbench
 
 
 
3
  sdk: gradio
 
4
  app_file: app.py
5
  pinned: false
 
 
 
6
  ---
7
 
8
+ # Hugging Face GAIA Agent Workbench
9
+
10
+ Small Hugging Face Space/workbench for the Agents Course Unit 4 final assignment.
11
+
12
+ ## What It Does
13
+
14
+ - Fetches the 20 filtered GAIA Level 1 questions from the course API.
15
+ - Downloads task files into `data/files/`.
16
+ - Lets you maintain exact-match answers in `answers.local.json`.
17
+ - Submits answers to the course scoring API from the Gradio UI or CLI.
18
+
19
+ ## Required For Final Submission
20
+
21
+ - Hugging Face account.
22
+ - Public Hugging Face Space with this code.
23
+ - HF login in the Space UI.
24
+ - Exact final answers without `FINAL ANSWER` prefix.
25
+
26
+ ## Local Setup
27
+
28
+ ```bash
29
+ python -m venv .venv
30
+ source .venv/bin/activate
31
+ pip install -r requirements.txt
32
+ python gaia_workbench.py fetch
33
+ python app.py
34
+ ```
35
+
36
+ ## CLI
37
+
38
+ ```bash
39
+ python gaia_workbench.py fetch
40
+ python gaia_workbench.py download-files
41
+ python gaia_workbench.py status
42
+ python gaia_workbench.py submit --username YOUR_HF_USERNAME --agent-code https://huggingface.co/spaces/YOUR_USERNAME/YOUR_SPACE/tree/main
43
+ ```
44
+
45
+ ## Answer File
46
+
47
+ Create `answers.local.json` from the template:
48
+
49
+ ```bash
50
+ cp answers.template.json answers.local.json
51
+ ```
52
+
53
+ Then fill:
54
+
55
+ ```json
56
+ {
57
+ "task-id": "exact answer"
58
+ }
59
+ ```
60
+
61
+ `answers.local.json` is gitignored because it may contain your final submission answers.
answers.template.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {}
app.py CHANGED
@@ -1,196 +1,127 @@
 
 
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
+ from __future__ import annotations
2
+
3
  import os
4
+
5
  import gradio as gr
 
 
6
  import pandas as pd
7
 
8
+ from gaia_workbench import (
9
+ API_URL,
10
+ answer_payload,
11
+ download_files,
12
+ fetch_questions,
13
+ load_answers,
14
+ load_questions,
15
+ status_rows,
16
+ submit_answers,
17
+ write_answer,
18
+ )
19
+
20
+
21
+ def refresh_questions() -> tuple[str, pd.DataFrame]:
22
+ questions = fetch_questions()
23
+ return f"Fetched {len(questions)} questions from {API_URL}", pd.DataFrame(status_rows())
24
+
25
+
26
+ def refresh_status() -> pd.DataFrame:
27
+ return pd.DataFrame(status_rows())
28
+
29
+
30
+ def save_answer(task_id: str, answer: str) -> tuple[str, pd.DataFrame]:
31
+ if not task_id.strip():
32
+ return "Missing task_id", refresh_status()
33
+ if not answer.strip():
34
+ return "Missing answer", refresh_status()
35
+ write_answer(task_id.strip(), answer.strip())
36
+ return f"Saved answer for {task_id.strip()}", refresh_status()
37
+
38
+
39
+ def download_attached_files() -> str:
40
+ downloaded, failed = download_files()
41
+ parts = []
42
+ if downloaded:
43
+ parts.append("Downloaded:\n" + "\n".join(str(path) for path in downloaded))
44
+ if failed:
45
+ parts.append("Failed:\n" + "\n".join(failed))
46
+ return "\n\n".join(parts) if parts else "No attached files found."
47
+
48
+
49
+ def _default_agent_code() -> str:
50
+ space_id = os.getenv("SPACE_ID", "").strip()
51
+ return f"https://huggingface.co/spaces/{space_id}/tree/main" if space_id else ""
52
+
53
+
54
+ def _submit_with(username: str, agent_code: str) -> tuple[str, pd.DataFrame]:
55
+ agent_code = agent_code.strip() or _default_agent_code()
56
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  try:
58
+ result = submit_answers(username=username, agent_code=agent_code)
59
+ except Exception as exc:
60
+ return f"Submission failed: {exc}", refresh_status()
61
+
62
+ return (
63
+ "Submission successful\n"
64
+ f"User: {result.get('username')}\n"
65
+ f"Score: {result.get('score')}%\n"
66
+ f"Correct: {result.get('correct_count')}/{result.get('total_attempted')}\n"
67
+ f"Message: {result.get('message')}",
68
+ refresh_status(),
69
+ )
70
+
71
+
72
+ def submit_logged_in(profile: gr.OAuthProfile | None) -> tuple[str, pd.DataFrame]:
73
+ if not profile:
74
+ return "Please login with Hugging Face first, or use manual submit.", refresh_status()
75
+ return _submit_with(profile.username, _default_agent_code())
76
+
77
+
78
+ def submit_manual(manual_username: str, manual_agent_code: str) -> tuple[str, pd.DataFrame]:
79
+ return _submit_with(manual_username.strip(), manual_agent_code.strip())
80
+
81
+
82
+ with gr.Blocks(title="GAIA Unit 4 Workbench") as demo:
83
+ gr.Markdown("# GAIA Unit 4 Workbench")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  gr.Markdown(
85
+ "Fetch the course questions, fill exact-match answers, then submit. "
86
+ "On Hugging Face Spaces, login with the button before submission."
 
 
 
 
 
 
 
 
 
 
87
  )
88
 
89
+ is_space_runtime = bool(os.getenv("SPACE_ID", "").strip())
90
+ if is_space_runtime:
91
+ gr.LoginButton()
92
 
93
+ with gr.Row():
94
+ fetch_button = gr.Button("Fetch Questions")
95
+ download_button = gr.Button("Download Attached Files")
96
+ refresh_button = gr.Button("Refresh Status")
97
 
98
+ status = gr.Textbox(label="Status", lines=5)
99
+ table = gr.DataFrame(label="Questions / Answers", wrap=True, value=pd.DataFrame(status_rows()))
 
100
 
101
+ with gr.Row():
102
+ task_id_input = gr.Textbox(label="Task ID")
103
+ answer_input = gr.Textbox(label="Exact Answer")
104
+ save_button = gr.Button("Save Answer")
105
+
106
+ gr.Markdown("## Submit")
107
+ manual_username = gr.Textbox(label="HF Username fallback", placeholder="Used only when not logged in via HF OAuth")
108
+ manual_agent_code = gr.Textbox(
109
+ label="Agent Code URL fallback",
110
+ placeholder="https://huggingface.co/spaces/YOUR_USERNAME/YOUR_SPACE/tree/main",
111
  )
112
+ with gr.Row():
113
+ submit_login_button = gr.Button("Submit With HF Login", visible=is_space_runtime)
114
+ submit_manual_button = gr.Button("Submit Manual")
115
+
116
+ fetch_button.click(fn=refresh_questions, outputs=[status, table])
117
+ download_button.click(fn=download_attached_files, outputs=status)
118
+ refresh_button.click(fn=refresh_status, outputs=table)
119
+ save_button.click(fn=save_answer, inputs=[task_id_input, answer_input], outputs=[status, table])
120
+ submit_login_button.click(fn=submit_logged_in, outputs=[status, table])
121
+ submit_manual_button.click(fn=submit_manual, inputs=[manual_username, manual_agent_code], outputs=[status, table])
122
+
123
 
124
  if __name__ == "__main__":
125
+ print(f"Loaded {len(load_questions())} questions and {len(load_answers())} local answers.")
126
+ print(f"Prepared {len(answer_payload())} answers for submission.")
127
+ demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
deploy_to_hf.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import getpass
4
+ from pathlib import Path
5
+
6
+ from huggingface_hub import HfApi
7
+
8
+
9
+ ROOT = Path(__file__).resolve().parent
10
+ REPO_ID = "smashburger-dev/Final_Assignment_Template"
11
+
12
+
13
+ def main() -> None:
14
+ token = getpass.getpass("HF token: ").strip()
15
+ if not token:
16
+ raise SystemExit("Missing token")
17
+
18
+ api = HfApi(token=token)
19
+ api.upload_folder(
20
+ repo_id=REPO_ID,
21
+ repo_type="space",
22
+ folder_path=str(ROOT),
23
+ ignore_patterns=[
24
+ ".venv/**",
25
+ "__pycache__/**",
26
+ "data/**",
27
+ "answers.local.json",
28
+ "*.pyc",
29
+ ".env",
30
+ ],
31
+ commit_message="Add GAIA Unit 4 workbench",
32
+ )
33
+ print(f"Uploaded to https://huggingface.co/spaces/{REPO_ID}/tree/main")
34
+
35
+
36
+ if __name__ == "__main__":
37
+ main()
gaia_workbench.py ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ import os
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ import requests
11
+
12
+
13
+ API_URL = "https://agents-course-unit4-scoring.hf.space"
14
+ ROOT = Path(__file__).resolve().parent
15
+ DATA_DIR = ROOT / "data"
16
+ QUESTIONS_PATH = DATA_DIR / "questions.json"
17
+ FILES_DIR = DATA_DIR / "files"
18
+ ANSWERS_PATH = ROOT / "answers.local.json"
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class Question:
23
+ task_id: str
24
+ question: str
25
+ file_name: str = ""
26
+
27
+
28
+ def _request_json(method: str, url: str, **kwargs: Any) -> Any:
29
+ response = requests.request(method, url, timeout=60, **kwargs)
30
+ response.raise_for_status()
31
+ return response.json()
32
+
33
+
34
+ def fetch_questions() -> list[Question]:
35
+ DATA_DIR.mkdir(parents=True, exist_ok=True)
36
+ raw_questions = _request_json("GET", f"{API_URL}/questions")
37
+ QUESTIONS_PATH.write_text(json.dumps(raw_questions, indent=2, ensure_ascii=False), encoding="utf-8")
38
+ return [
39
+ Question(
40
+ task_id=str(item["task_id"]),
41
+ question=str(item["question"]),
42
+ file_name=str(item.get("file_name") or ""),
43
+ )
44
+ for item in raw_questions
45
+ ]
46
+
47
+
48
+ def load_questions() -> list[Question]:
49
+ if not QUESTIONS_PATH.exists():
50
+ return fetch_questions()
51
+ raw_questions = json.loads(QUESTIONS_PATH.read_text(encoding="utf-8"))
52
+ return [
53
+ Question(
54
+ task_id=str(item["task_id"]),
55
+ question=str(item["question"]),
56
+ file_name=str(item.get("file_name") or ""),
57
+ )
58
+ for item in raw_questions
59
+ ]
60
+
61
+
62
+ def load_answers() -> dict[str, str]:
63
+ if not ANSWERS_PATH.exists():
64
+ return {}
65
+ raw_answers = json.loads(ANSWERS_PATH.read_text(encoding="utf-8"))
66
+ return {str(task_id): str(answer).strip() for task_id, answer in raw_answers.items() if str(answer).strip()}
67
+
68
+
69
+ def write_answer(task_id: str, answer: str) -> None:
70
+ answers = load_answers()
71
+ answers[task_id] = answer.strip()
72
+ ANSWERS_PATH.write_text(json.dumps(answers, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
73
+
74
+
75
+ def download_task_file(task_id: str, file_name: str = "") -> Path:
76
+ FILES_DIR.mkdir(parents=True, exist_ok=True)
77
+ response = requests.get(f"{API_URL}/files/{task_id}", timeout=60)
78
+ response.raise_for_status()
79
+ target_name = file_name or task_id
80
+ target = FILES_DIR / target_name
81
+ target.write_bytes(response.content)
82
+ return target
83
+
84
+
85
+ def download_files() -> tuple[list[Path], list[str]]:
86
+ downloaded: list[Path] = []
87
+ failed: list[str] = []
88
+ for question in load_questions():
89
+ if question.file_name:
90
+ try:
91
+ downloaded.append(download_task_file(question.task_id, question.file_name))
92
+ except requests.HTTPError as exc:
93
+ failed.append(f"{question.task_id} ({question.file_name}): {exc}")
94
+ return downloaded, failed
95
+
96
+
97
+ def answer_payload() -> list[dict[str, str]]:
98
+ answers = load_answers()
99
+ questions = load_questions()
100
+ return [
101
+ {"task_id": question.task_id, "submitted_answer": answers[question.task_id]}
102
+ for question in questions
103
+ if question.task_id in answers
104
+ ]
105
+
106
+
107
+ def submit_answers(username: str, agent_code: str) -> dict[str, Any]:
108
+ payload = {
109
+ "username": username.strip(),
110
+ "agent_code": agent_code.strip(),
111
+ "answers": answer_payload(),
112
+ }
113
+ if not payload["username"]:
114
+ raise ValueError("Missing username")
115
+ if not payload["agent_code"]:
116
+ raise ValueError("Missing agent_code")
117
+ if not payload["answers"]:
118
+ raise ValueError("No answers found in answers.local.json")
119
+ return _request_json("POST", f"{API_URL}/submit", json=payload)
120
+
121
+
122
+ def status_rows() -> list[dict[str, str]]:
123
+ answers = load_answers()
124
+ rows = []
125
+ for index, question in enumerate(load_questions(), start=1):
126
+ rows.append(
127
+ {
128
+ "#": str(index),
129
+ "task_id": question.task_id,
130
+ "file": question.file_name,
131
+ "answered": "yes" if question.task_id in answers else "no",
132
+ "answer": answers.get(question.task_id, ""),
133
+ "question": question.question,
134
+ }
135
+ )
136
+ return rows
137
+
138
+
139
+ def main() -> None:
140
+ parser = argparse.ArgumentParser(description="Hugging Face GAIA Unit 4 workbench")
141
+ subparsers = parser.add_subparsers(dest="command", required=True)
142
+
143
+ subparsers.add_parser("fetch", help="Fetch and cache questions")
144
+ subparsers.add_parser("download-files", help="Download all attached task files")
145
+ subparsers.add_parser("status", help="Print answer coverage")
146
+
147
+ set_answer_parser = subparsers.add_parser("set-answer", help="Set one exact-match answer")
148
+ set_answer_parser.add_argument("task_id")
149
+ set_answer_parser.add_argument("answer")
150
+
151
+ submit_parser = subparsers.add_parser("submit", help="Submit filled local answers")
152
+ submit_parser.add_argument("--username", default=os.getenv("HF_USERNAME", ""))
153
+ submit_parser.add_argument("--agent-code", default=os.getenv("AGENT_CODE", ""))
154
+
155
+ args = parser.parse_args()
156
+
157
+ if args.command == "fetch":
158
+ questions = fetch_questions()
159
+ print(f"Fetched {len(questions)} questions -> {QUESTIONS_PATH}")
160
+ elif args.command == "download-files":
161
+ downloaded, failed = download_files()
162
+ print(f"Downloaded {len(downloaded)} files")
163
+ for path in downloaded:
164
+ print(path)
165
+ if failed:
166
+ print(f"Failed {len(failed)} files")
167
+ for item in failed:
168
+ print(item)
169
+ elif args.command == "status":
170
+ rows = status_rows()
171
+ answered_count = sum(1 for row in rows if row["answered"] == "yes")
172
+ print(f"Answered {answered_count}/{len(rows)}")
173
+ for row in rows:
174
+ marker = "[x]" if row["answered"] == "yes" else "[ ]"
175
+ file_part = f" file={row['file']}" if row["file"] else ""
176
+ print(f"{marker} {row['#']}. {row['task_id']}{file_part} - {row['question']}")
177
+ elif args.command == "set-answer":
178
+ write_answer(args.task_id, args.answer)
179
+ print(f"Saved answer for {args.task_id}")
180
+ elif args.command == "submit":
181
+ result = submit_answers(args.username, args.agent_code)
182
+ print(json.dumps(result, indent=2, ensure_ascii=False))
183
+
184
+
185
+ if __name__ == "__main__":
186
+ main()
requirements.txt CHANGED
@@ -1,2 +1,5 @@
1
- gradio
2
- requests
 
 
 
 
1
+ gradio[oauth]>=4.44.0
2
+ huggingface_hub<1.0
3
+ pandas>=2.2.0
4
+ openpyxl>=3.1.0
5
+ requests>=2.32.0