#!/usr/bin/env python3 import argparse import hashlib import json import shlex import subprocess import time from pathlib import Path from typing import Any from run_output_eval import DEFAULT_CASES, display_path, grade_output, load_cases, validate_case ROOT = Path(__file__).resolve().parent.parent DEFAULT_OUTPUT_JSON = ROOT / "reports" / "output_execution_runs.json" DEFAULT_OUTPUT_MD = ROOT / "reports" / "output_execution_runs.md" VARIANTS = ("baseline", "with_skill") def output_for_variant(case: dict[str, Any], variant: str) -> str: return str(case.get("baseline_output" if variant == "baseline" else "with_skill_output", "")) def output_key(variant: str) -> str: return "baseline_output" if variant == "baseline" else "with_skill_output" def parse_runner_command(value: str | None) -> list[str]: if not value: return [] stripped = value.strip() if stripped.startswith("["): payload = json.loads(stripped) if not isinstance(payload, list) or not all(isinstance(item, str) and item for item in payload): raise ValueError("--runner-command JSON must be a non-empty string list") return payload return shlex.split(stripped) def display_command(command: list[str]) -> list[str]: displayed: list[str] = [] for item in command: path = Path(item) if path.is_absolute() and path.exists(): displayed.append(display_path(path)) else: displayed.append(item) return displayed def estimate_tokens(text: str) -> int: if not text: return 0 return max(1, round(len(text) / 4)) def parse_runner_stdout(stdout: str) -> tuple[str, dict[str, Any]]: stripped = stdout.strip() if not stripped: return "", {} try: payload = json.loads(stripped) except json.JSONDecodeError: return stdout, {} if not isinstance(payload, dict): return stdout, {} return str(payload.get("output", "")), payload def usage_payload(payload: dict[str, Any], prompt: str, output: str) -> dict[str, Any]: usage = payload.get("usage", {}) if isinstance(payload.get("usage"), dict) else {} input_tokens = usage.get("input_tokens", usage.get("prompt_tokens")) output_tokens = usage.get("output_tokens", usage.get("completion_tokens")) total_tokens = usage.get("total_tokens") estimated = bool(usage.get("estimated", False)) if input_tokens is None: input_tokens = estimate_tokens(prompt) estimated = True if output_tokens is None: output_tokens = estimate_tokens(output) estimated = True if total_tokens is None: total_tokens = int(input_tokens or 0) + int(output_tokens or 0) estimated = True return { "input_tokens": int(input_tokens or 0), "output_tokens": int(output_tokens or 0), "total_tokens": int(total_tokens or 0), "estimated": estimated, } def recorded_fixture_run(case: dict[str, Any], variant: str, assertions: list[dict[str, Any]]) -> dict[str, Any]: output = output_for_variant(case, variant) grade = grade_output(output, assertions) return { "case_id": str(case.get("id", "")), "variant": variant, "status": "pass", "execution_mode": "recorded_fixture", "model_executed": False, "command_executed": False, "duration_ms": None, "provider": "", "model": "", "usage": usage_payload({}, str(case.get("prompt", "")), output), "score": grade["score"], "passed_count": grade["passed_count"], "failed_count": grade["failed_count"], "failed_assertions": [item["id"] for item in grade["failed"]], "output_sha256": hashlib.sha256(output.encode("utf-8")).hexdigest(), "failure": "", } def command_run( case: dict[str, Any], variant: str, assertions: list[dict[str, Any]], command: list[str], timeout_seconds: float, ) -> dict[str, Any]: request = { "case_id": str(case.get("id", "")), "variant": variant, "prompt": str(case.get("prompt", "")), "input_files": case.get("input_files", []), "metadata": case.get("metadata", {}), "fixture_output": output_for_variant(case, variant), "output_key": output_key(variant), } started = time.perf_counter() try: proc = subprocess.run( command, input=json.dumps(request, ensure_ascii=False), capture_output=True, text=True, timeout=timeout_seconds, check=False, ) duration_ms = round((time.perf_counter() - started) * 1000, 2) except subprocess.TimeoutExpired as exc: return { "case_id": request["case_id"], "variant": variant, "status": "fail", "execution_mode": "command", "model_executed": False, "command_executed": True, "duration_ms": round(timeout_seconds * 1000, 2), "provider": "", "model": "", "usage": usage_payload({}, request["prompt"], ""), "score": 0, "passed_count": 0, "failed_count": len(assertions), "failed_assertions": [str(item.get("id", "assertion")) for item in assertions], "output_sha256": "", "failure": f"runner timed out after {timeout_seconds}s: {exc}", } output, payload = parse_runner_stdout(proc.stdout) grade = grade_output(output, assertions) execution_kind = str(payload.get("execution_kind", "command")) provider = str(payload.get("provider", "")) model = str(payload.get("model", "")) model_executed = execution_kind == "model" and bool(model and provider) return { "case_id": request["case_id"], "variant": variant, "status": "pass" if proc.returncode == 0 and output else "fail", "execution_mode": execution_kind if execution_kind in {"command", "model"} else "command", "model_executed": model_executed, "command_executed": True, "duration_ms": duration_ms, "provider": provider, "model": model, "usage": usage_payload(payload, request["prompt"], output), "score": grade["score"] if output else 0, "passed_count": grade["passed_count"] if output else 0, "failed_count": grade["failed_count"] if output else len(assertions), "failed_assertions": [item["id"] for item in grade["failed"]] if output else [str(item.get("id", "assertion")) for item in assertions], "output_sha256": hashlib.sha256(output.encode("utf-8")).hexdigest() if output else "", "failure": "" if proc.returncode == 0 and output else (proc.stderr.strip() or "runner returned no output"), } def build_summary(runs: list[dict[str, Any]]) -> dict[str, Any]: variant_run_count = len(runs) case_ids = sorted({item["case_id"] for item in runs}) baseline = [item for item in runs if item["variant"] == "baseline"] with_skill = [item for item in runs if item["variant"] == "with_skill"] baseline_average = sum(item["score"] for item in baseline) / len(baseline) if baseline else 0 with_skill_average = sum(item["score"] for item in with_skill) / len(with_skill) if with_skill else 0 regression_count = sum( 1 for case_id in case_ids if max((item["score"] for item in with_skill if item["case_id"] == case_id), default=0) < max((item["score"] for item in baseline if item["case_id"] == case_id), default=0) ) failure_count = sum(1 for item in runs if item["status"] != "pass") command_executed_count = sum(1 for item in runs if item.get("command_executed")) model_executed_count = sum(1 for item in runs if item.get("model_executed")) recorded_fixture_count = sum(1 for item in runs if item.get("execution_mode") == "recorded_fixture") timing_observed_count = sum(1 for item in runs if item.get("duration_ms") is not None) token_estimated_count = sum(1 for item in runs if item.get("usage", {}).get("estimated")) token_observed_count = variant_run_count - token_estimated_count return { "case_count": len(case_ids), "variant_run_count": variant_run_count, "command_executed_count": command_executed_count, "model_executed_count": model_executed_count, "recorded_fixture_count": recorded_fixture_count, "timing_observed_count": timing_observed_count, "token_observed_count": token_observed_count, "token_estimated_count": token_estimated_count, "baseline_pass_rate": round(baseline_average, 2), "with_skill_pass_rate": round(with_skill_average, 2), "delta": round(with_skill_average - baseline_average, 2), "regression_count": regression_count, "failure_count": failure_count, "gate_pass": failure_count == 0 and with_skill_average >= baseline_average and regression_count == 0, } def render_markdown(payload: dict[str, Any]) -> str: summary = payload["summary"] lines = [ "# Output Execution Runs", "", "This report records how output-eval variants were produced and whether timing or token evidence is observed or estimated.", "", f"- Cases: `{summary['case_count']}`", f"- Variant runs: `{summary['variant_run_count']}`", f"- Command executed: `{summary['command_executed_count']}`", f"- Model executed: `{summary['model_executed_count']}`", f"- Recorded fixtures: `{summary['recorded_fixture_count']}`", f"- Timing observed: `{summary['timing_observed_count']}`", f"- Token observed: `{summary['token_observed_count']}`", f"- Token estimated: `{summary['token_estimated_count']}`", f"- Delta: `{summary['delta']}`", f"- Gate pass: `{summary['gate_pass']}`", "", ] if summary["model_executed_count"] == 0: lines.extend( [ "No model-executed runs are recorded yet.", "", "Use `python3 scripts/yao.py output-exec --provider-runner openai` or `--runner-command` with a reviewed provider-backed runner to replace recorded fixtures with real model output evidence.", "", ] ) if summary["command_executed_count"] > 0: lines.extend( [ "Command runner evidence is present. This proves the eval harness executed an external command, but it is not provider-backed model evidence unless the runner reports model metadata.", "", ] ) lines.extend( [ "## Runs", "", "| Case | Variant | Mode | Model | Duration ms | Tokens | Score | Status |", "| --- | --- | --- | --- | ---: | ---: | ---: | --- |", ] ) for item in payload["runs"]: usage = item.get("usage", {}) duration = "" if item.get("duration_ms") is None else str(item["duration_ms"]) model = item.get("model") or item.get("provider") or "" lines.append( f"| {item['case_id']} | {item['variant']} | {item['execution_mode']} | {model} | " f"{duration} | {usage.get('total_tokens', 0)} | {item['score']} | {item['status']} |" ) failures = [item for item in payload["runs"] if item.get("failure")] if failures: lines.extend(["", "## Failures", ""]) for item in failures: lines.append(f"- `{item['case_id']}` `{item['variant']}`: {item['failure']}") lines.extend( [ "", "## Next Fixes", "", "- Keep recorded fixtures as reproducible baselines, but do not describe them as model-executed evidence.", "- Use `scripts/provider_output_eval_runner.py` for provider-backed holdout cases when release confidence depends on real generation behavior.", "- Compare timing, token cost, and assertion deltas before promoting a skill to governed reuse.", ] ) return "\n".join(lines).strip() + "\n" def run_output_execution( cases_path: Path, output_json: Path, output_md: Path, runner_command: list[str], timeout_seconds: float, ) -> dict[str, Any]: cases = load_cases(cases_path) validation_failures = [failure for case in cases for failure in validate_case(case, cases_path.parent)] runs: list[dict[str, Any]] = [] if not validation_failures: for case in cases: assertions = case.get("assertions", []) if isinstance(case.get("assertions"), list) else [] for variant in VARIANTS: if runner_command: runs.append(command_run(case, variant, assertions, runner_command, timeout_seconds)) else: runs.append(recorded_fixture_run(case, variant, assertions)) summary = build_summary(runs) failures = validation_failures + [f"{item['case_id']} {item['variant']}: {item['failure']}" for item in runs if item.get("failure")] payload = { "schema_version": "1.0", "ok": not failures and summary["gate_pass"], "cases": display_path(cases_path), "runner": { "mode": "command" if runner_command else "recorded_fixture", "command": display_command(runner_command), "timeout_seconds": timeout_seconds if runner_command else None, }, "summary": summary, "runs": runs, "failures": failures, "artifacts": { "json": display_path(output_json), "markdown": display_path(output_md), }, } output_json.parent.mkdir(parents=True, exist_ok=True) output_md.parent.mkdir(parents=True, exist_ok=True) output_json.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") output_md.write_text(render_markdown(payload), encoding="utf-8") return payload def main() -> None: parser = argparse.ArgumentParser(description="Record output-eval execution evidence for static, command, or model-backed runs.") parser.add_argument("--cases", default=str(DEFAULT_CASES)) parser.add_argument("--output-json", default=str(DEFAULT_OUTPUT_JSON)) parser.add_argument("--output-md", default=str(DEFAULT_OUTPUT_MD)) parser.add_argument("--runner-command", help="Command string or JSON string list. Receives a JSON request on stdin.") parser.add_argument("--timeout-seconds", type=float, default=30.0) args = parser.parse_args() try: runner_command = parse_runner_command(args.runner_command) except (json.JSONDecodeError, ValueError) as exc: payload = { "schema_version": "1.0", "ok": False, "cases": display_path(Path(args.cases).resolve()), "runner": {"mode": "invalid", "command": [], "timeout_seconds": args.timeout_seconds}, "summary": build_summary([]), "runs": [], "failures": [str(exc)], "artifacts": {"json": display_path(Path(args.output_json).resolve()), "markdown": display_path(Path(args.output_md).resolve())}, } output_json = Path(args.output_json).resolve() output_md = Path(args.output_md).resolve() output_json.parent.mkdir(parents=True, exist_ok=True) output_md.parent.mkdir(parents=True, exist_ok=True) output_json.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") output_md.write_text(render_markdown(payload), encoding="utf-8") print(json.dumps(payload, ensure_ascii=False, indent=2)) raise SystemExit(2) payload = run_output_execution( Path(args.cases).resolve(), Path(args.output_json).resolve(), Path(args.output_md).resolve(), runner_command, args.timeout_seconds, ) print(json.dumps(payload, ensure_ascii=False, indent=2)) raise SystemExit(0 if payload["ok"] else 2) if __name__ == "__main__": main()