Final_Assignment_Template / gaia_workbench.py
smashburger-dev's picture
Add GAIA Unit 4 workbench
b6fa5fc verified
Raw History Blame Contribute Delete
6.4 kB
from __future__ import annotations
import argparse
import json
import os
from dataclasses import dataclass
from pathlib import Path
from typing import Any
import requests
API_URL = "https://agents-course-unit4-scoring.hf.space"
ROOT = Path(__file__).resolve().parent
DATA_DIR = ROOT / "data"
QUESTIONS_PATH = DATA_DIR / "questions.json"
FILES_DIR = DATA_DIR / "files"
ANSWERS_PATH = ROOT / "answers.local.json"
@dataclass(frozen=True)
class Question:
task_id: str
question: str
file_name: str = ""
def _request_json(method: str, url: str, **kwargs: Any) -> Any:
response = requests.request(method, url, timeout=60, **kwargs)
response.raise_for_status()
return response.json()
def fetch_questions() -> list[Question]:
DATA_DIR.mkdir(parents=True, exist_ok=True)
raw_questions = _request_json("GET", f"{API_URL}/questions")
QUESTIONS_PATH.write_text(json.dumps(raw_questions, indent=2, ensure_ascii=False), encoding="utf-8")
return [
Question(
task_id=str(item["task_id"]),
question=str(item["question"]),
file_name=str(item.get("file_name") or ""),
)
for item in raw_questions
]
def load_questions() -> list[Question]:
if not QUESTIONS_PATH.exists():
return fetch_questions()
raw_questions = json.loads(QUESTIONS_PATH.read_text(encoding="utf-8"))
return [
Question(
task_id=str(item["task_id"]),
question=str(item["question"]),
file_name=str(item.get("file_name") or ""),
)
for item in raw_questions
]
def load_answers() -> dict[str, str]:
if not ANSWERS_PATH.exists():
return {}
raw_answers = json.loads(ANSWERS_PATH.read_text(encoding="utf-8"))
return {str(task_id): str(answer).strip() for task_id, answer in raw_answers.items() if str(answer).strip()}
def write_answer(task_id: str, answer: str) -> None:
answers = load_answers()
answers[task_id] = answer.strip()
ANSWERS_PATH.write_text(json.dumps(answers, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
def download_task_file(task_id: str, file_name: str = "") -> Path:
FILES_DIR.mkdir(parents=True, exist_ok=True)
response = requests.get(f"{API_URL}/files/{task_id}", timeout=60)
response.raise_for_status()
target_name = file_name or task_id
target = FILES_DIR / target_name
target.write_bytes(response.content)
return target
def download_files() -> tuple[list[Path], list[str]]:
downloaded: list[Path] = []
failed: list[str] = []
for question in load_questions():
if question.file_name:
try:
downloaded.append(download_task_file(question.task_id, question.file_name))
except requests.HTTPError as exc:
failed.append(f"{question.task_id} ({question.file_name}): {exc}")
return downloaded, failed
def answer_payload() -> list[dict[str, str]]:
answers = load_answers()
questions = load_questions()
return [
{"task_id": question.task_id, "submitted_answer": answers[question.task_id]}
for question in questions
if question.task_id in answers
]
def submit_answers(username: str, agent_code: str) -> dict[str, Any]:
payload = {
"username": username.strip(),
"agent_code": agent_code.strip(),
"answers": answer_payload(),
}
if not payload["username"]:
raise ValueError("Missing username")
if not payload["agent_code"]:
raise ValueError("Missing agent_code")
if not payload["answers"]:
raise ValueError("No answers found in answers.local.json")
return _request_json("POST", f"{API_URL}/submit", json=payload)
def status_rows() -> list[dict[str, str]]:
answers = load_answers()
rows = []
for index, question in enumerate(load_questions(), start=1):
rows.append(
{
"#": str(index),
"task_id": question.task_id,
"file": question.file_name,
"answered": "yes" if question.task_id in answers else "no",
"answer": answers.get(question.task_id, ""),
"question": question.question,
}
)
return rows
def main() -> None:
parser = argparse.ArgumentParser(description="Hugging Face GAIA Unit 4 workbench")
subparsers = parser.add_subparsers(dest="command", required=True)
subparsers.add_parser("fetch", help="Fetch and cache questions")
subparsers.add_parser("download-files", help="Download all attached task files")
subparsers.add_parser("status", help="Print answer coverage")
set_answer_parser = subparsers.add_parser("set-answer", help="Set one exact-match answer")
set_answer_parser.add_argument("task_id")
set_answer_parser.add_argument("answer")
submit_parser = subparsers.add_parser("submit", help="Submit filled local answers")
submit_parser.add_argument("--username", default=os.getenv("HF_USERNAME", ""))
submit_parser.add_argument("--agent-code", default=os.getenv("AGENT_CODE", ""))
args = parser.parse_args()
if args.command == "fetch":
questions = fetch_questions()
print(f"Fetched {len(questions)} questions -> {QUESTIONS_PATH}")
elif args.command == "download-files":
downloaded, failed = download_files()
print(f"Downloaded {len(downloaded)} files")
for path in downloaded:
print(path)
if failed:
print(f"Failed {len(failed)} files")
for item in failed:
print(item)
elif args.command == "status":
rows = status_rows()
answered_count = sum(1 for row in rows if row["answered"] == "yes")
print(f"Answered {answered_count}/{len(rows)}")
for row in rows:
marker = "[x]" if row["answered"] == "yes" else "[ ]"
file_part = f" file={row['file']}" if row["file"] else ""
print(f"{marker} {row['#']}. {row['task_id']}{file_part} - {row['question']}")
elif args.command == "set-answer":
write_answer(args.task_id, args.answer)
print(f"Saved answer for {args.task_id}")
elif args.command == "submit":
result = submit_answers(args.username, args.agent_code)
print(json.dumps(result, indent=2, ensure_ascii=False))
if __name__ == "__main__":
main()