ken2ki's picture
Ship the full GAIA agent for course submission.
3af908f
Raw
History Blame Contribute Delete
7.36 kB
#!/usr/bin/env python3
"""Integration eval: fixtures, course API, or single task."""
from __future__ import annotations
import argparse
import json
import os
import sys
import tempfile
from pathlib import Path
from dotenv import load_dotenv
load_dotenv()
from agent import GaiaAgent
from agent.evolve import record_run, update_strategy_deltas
from eval.course_client import fetch_course_questions, submit_answers
from eval.report import build_report, print_report_summary
from file_resolver import resolve_task_attachment
from scoring import course_answer_matches, passes_course_threshold
FIXTURES_DIR = Path(__file__).resolve().parent / "fixtures"
RESULTS_DIR = Path(__file__).resolve().parent / "results"
DEFAULT_API_URL = os.getenv(
"COURSE_API_URL", "https://agents-course-unit4-scoring.hf.space"
)
def load_fixtures() -> list[dict]:
items: list[dict] = []
for path in sorted(FIXTURES_DIR.glob("*.json")):
data = json.loads(path.read_text(encoding="utf-8"))
if isinstance(data, list):
items.extend(data)
return items
def run_fixture(agent: GaiaAgent, fixture: dict) -> dict:
answer = agent(
fixture["question"],
file_path=fixture.get("file_path"),
task_id=fixture.get("task_id"),
)
expected = fixture["expected"]
correct = course_answer_matches(answer, expected)
return {
"task_id": fixture.get("task_id", "fixture"),
"question": fixture["question"],
"submitted": answer,
"expected": expected,
"correct": correct,
"question_tags": fixture.get("question_tags", []),
"strategy": fixture.get("strategy", "direct"),
}
def run_course(agent: GaiaAgent, api_url: str) -> list[dict]:
questions = fetch_course_questions(api_url)
results: list[dict] = []
answers_payload: list[dict] = []
with tempfile.TemporaryDirectory(prefix="gaia_eval_") as temp_dir:
download_dir = Path(temp_dir)
for item in questions:
task_id = item["task_id"]
file_path = None
file_error = None
file_name = item.get("file_name") or ""
if file_name:
file_path, file_error = resolve_task_attachment(
api_url, task_id, file_name, download_dir
)
try:
submitted = agent(
item["question"],
file_path=file_path,
file_error=file_error,
task_id=task_id,
)
except Exception as error:
submitted = f"ERROR: {error}"
answers_payload.append(
{"task_id": task_id, "submitted_answer": submitted}
)
results.append(
{
"task_id": task_id,
"question": item["question"],
"submitted": submitted,
"correct": False,
}
)
username = os.getenv("HF_USERNAME", "").strip()
if username:
submit_result = submit_answers(
api_url,
username,
os.getenv("AGENT_CODE", "local-eval"),
answers_payload,
)
detail_map = {
row.get("task_id"): row
for row in submit_result.get("details") or submit_result.get("results") or []
}
correct_count = submit_result.get("correct_count", 0)
for row in results:
detail = detail_map.get(row["task_id"], {})
row["correct"] = detail.get("correct", False)
row["expected"] = detail.get("expected_answer", "?")
print(f"Course submit score: {submit_result.get('score', '?')}% ({correct_count} correct)")
else:
print("HF_USERNAME not set — answers collected but not graded via course API.")
return results
def main() -> None:
parser = argparse.ArgumentParser(description="Run GAIA eval harness.")
parser.add_argument(
"--mode",
choices=("fixtures", "course", "single"),
default="fixtures",
)
parser.add_argument("--task-id", help="Fixture or course task id for single mode.")
parser.add_argument(
"--min-score",
type=float,
default=float(os.getenv("EVAL_FIXTURE_MIN_SCORE", "80")),
help="Minimum percent for fixtures mode pass gate.",
)
args = parser.parse_args()
agent = GaiaAgent()
results: list[dict] = []
if args.mode == "fixtures":
fixtures = load_fixtures()
if args.task_id:
fixtures = [f for f in fixtures if f.get("task_id") == args.task_id]
for fixture in fixtures:
print(f"Running fixture: {fixture.get('task_id')}")
row = run_fixture(agent, fixture)
results.append(row)
record_run(
row["task_id"],
row["question"],
row.get("strategy", "direct"),
[],
row["correct"],
)
elif args.mode == "course":
results = run_course(agent, DEFAULT_API_URL)
elif args.mode == "single":
if not args.task_id:
print("--task-id required for single mode", file=sys.stderr)
sys.exit(1)
fixtures = [f for f in load_fixtures() if f.get("task_id") == args.task_id]
if fixtures:
results = [run_fixture(agent, fixtures[0])]
else:
questions = fetch_course_questions(DEFAULT_API_URL)
match = next((q for q in questions if q["task_id"] == args.task_id), None)
if not match:
print(f"Task {args.task_id} not found", file=sys.stderr)
sys.exit(1)
with tempfile.TemporaryDirectory(prefix="gaia_single_") as temp_dir:
file_path = None
file_error = None
if match.get("file_name"):
file_path, file_error = resolve_task_attachment(
DEFAULT_API_URL,
match["task_id"],
match["file_name"],
Path(temp_dir),
)
answer = agent(
match["question"],
file_path=file_path,
file_error=file_error,
task_id=match["task_id"],
)
results = [
{
"task_id": match["task_id"],
"question": match["question"],
"submitted": answer,
"correct": False,
}
]
report_path = build_report(args.mode, results, RESULTS_DIR)
update_strategy_deltas(report_path)
correct = sum(1 for row in results if row.get("correct"))
total = len(results)
report = {
"correct": correct,
"total": total,
"score_percent": round((correct / total) * 100, 2) if total else 0.0,
"passed_threshold": passes_course_threshold(correct, total),
}
print_report_summary(report)
if args.mode == "fixtures" and total:
score = (correct / total) * 100
if score < args.min_score:
print(f"Fixture gate failed: {score:.1f}% < {args.min_score}%")
sys.exit(1)
if __name__ == "__main__":
main()