#!/usr/bin/env python """Submission runner for Track A. This wrapper keeps the packaged output in the Phase 3 guideline shape: result/ traces.json results.csv runtime.json It delegates prediction to the copied main.py, then normalizes filenames and columns. It does not train or download anything. """ from __future__ import annotations import argparse import csv import json import os import shutil import subprocess import sys from pathlib import Path ROOT = Path(__file__).resolve().parent def _resolve(path_text: str) -> Path: path = Path(path_text) return path if path.is_absolute() else ROOT / path def _api_key_names(spec: str) -> list[str]: return [name.strip() for name in spec.split(",") if name.strip()] def _ensure_local_vllm_key(env: dict[str, str], spec: str) -> None: names = _api_key_names(spec) if names and not any(env.get(name) for name in names): env[names[0]] = "EMPTY" def _write_results_csv(raw_result_csv: Path, output_csv: Path) -> None: with raw_result_csv.open("r", encoding="utf-8", newline="") as f: reader = csv.DictReader(f) rows = list(reader) output_csv.parent.mkdir(parents=True, exist_ok=True) with output_csv.open("w", encoding="utf-8", newline="") as f: writer = csv.DictWriter(f, fieldnames=["scenario_id", "prediction"]) writer.writeheader() for row in rows: scenario_id = row.get("scenario_id") or row.get("ID") or row.get("id") or "" prediction = row.get("prediction") if prediction is None: prediction = row.get("Track A", "") writer.writerow( { "scenario_id": str(scenario_id).strip(), "prediction": str(prediction or "").strip(), } ) def _normalize_traces(traces_path: Path) -> list[dict]: if not traces_path.exists(): raise FileNotFoundError(f"Missing traces file: {traces_path}") with traces_path.open("r", encoding="utf-8") as f: traces = json.load(f) if not isinstance(traces, list): raise ValueError("traces.json must contain a JSON list") normalized = [] for i, row in enumerate(traces): if not isinstance(row, dict): row = {"completion": str(row)} row.setdefault("scenario_id", row.get("ID", f"scenario_{i + 1}")) row.setdefault("completion_id", 0) row.setdefault("completion", "") normalized.append(row) with traces_path.open("w", encoding="utf-8") as f: json.dump(normalized, f, indent=2, ensure_ascii=False) return normalized def _write_runtime_json(traces: list[dict], runtime_path: Path) -> None: runtimes = [] for row in traces: runtime_seconds = row.get("runtime_seconds", row.get("execution_time_seconds", 0.0)) try: runtime_seconds = float(runtime_seconds) except (TypeError, ValueError): runtime_seconds = 0.0 runtimes.append( { "scenario_id": str(row.get("scenario_id", "")), "runtime_seconds": runtime_seconds, } ) with runtime_path.open("w", encoding="utf-8") as f: json.dump(runtimes, f, indent=2) def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser() parser.add_argument( "--input", default=os.environ.get("TEST_PATH", "data/Phase_2/test.json"), help="Path to the Track A test JSON file.", ) parser.add_argument( "--output", default="result", help="Directory where traces.json, results.csv, and runtime.json are written.", ) parser.add_argument( "--model_bundle", default="models/model_v4_bundle.pkl", help="Path to the auxiliary Track A model bundle.", ) parser.add_argument( "--server_url", default=os.environ.get("TRACK_A_SERVER_URL", "https://localhost:8081/no"), ) parser.add_argument( "--model_url", default=os.environ.get("OPENAI_BASE_URL", "http://localhost:8001/v1"), ) parser.add_argument( "--model_name", default=os.environ.get("OPENAI_MODEL", "Qwen3.5-35B-A3B"), ) parser.add_argument( "--api_key_env", default="OPENAI_API_KEY,AGENT_API_KEY,OPENROUTER_API_KEY", ) parser.add_argument("--concurrency", type=int, default=1) parser.add_argument("--max_steps", type=int, default=4) parser.add_argument("--max_tool_calls", type=int, default=8) parser.add_argument("--question_timeout", type=float, default=180.0) parser.add_argument("--checkpoint_every", type=int, default=1) parser.add_argument("--max_samples", type=int, default=None) parser.add_argument("--no_agent", action="store_true") args, extra = parser.parse_known_args() args.extra_main_args = extra return args def main() -> None: args = parse_args() input_path = _resolve(args.input) model_bundle = _resolve(args.model_bundle) output_dir = _resolve(args.output) if not input_path.exists(): raise FileNotFoundError(f"Input test file not found: {input_path}") if not model_bundle.exists(): raise FileNotFoundError(f"Model bundle not found: {model_bundle}") output_dir.mkdir(parents=True, exist_ok=True) work_dir = output_dir / "_raw_track_a" if work_dir.exists(): shutil.rmtree(work_dir) work_dir.mkdir(parents=True) raw_result_csv = work_dir / "result.csv" debug_json = work_dir / "debug.json" traces_json = output_dir / "traces.json" command = [ sys.executable, str(ROOT / "main.py"), "--test_path", str(input_path), "--model_bundle", str(model_bundle), "--track_b_test", "", "--out", str(raw_result_csv), "--debug_out", str(debug_json), "--traces_out", str(traces_json), "--server_url", args.server_url, "--model_url", args.model_url, "--model_name", args.model_name, "--api_key_env", args.api_key_env, "--concurrency", str(args.concurrency), "--max_steps", str(args.max_steps), "--max_tool_calls", str(args.max_tool_calls), "--question_timeout", str(args.question_timeout), "--checkpoint_every", str(args.checkpoint_every), "--no_progress", ] if args.max_samples is not None: command.extend(["--max_samples", str(args.max_samples)]) if args.no_agent: command.append("--no_agent") command.extend(args.extra_main_args) env = os.environ.copy() _ensure_local_vllm_key(env, args.api_key_env) subprocess.run(command, cwd=str(ROOT), env=env, check=True) _write_results_csv(raw_result_csv, output_dir / "results.csv") traces = _normalize_traces(traces_json) _write_runtime_json(traces, output_dir / "runtime.json") shutil.rmtree(work_dir) if __name__ == "__main__": main()