#!/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()