| 1 | #!/usr/bin/env python3
|
| 2 | """Zero-shot vs cheat-sheet: the pretraining-tax report.
|
| 3 |
|
| 4 | Takes two agent-workflow result files β one per instruction level, never
|
| 5 | aggregated (suite contract) β and prints the per-agent, per-subject deltas
|
| 6 | the AGENTS.md story rests on: success rate, turns, provider tokens, help
|
| 7 | calls, unknown-command failures, and recovery costs.
|
| 8 |
|
| 9 | python3 scripts/instruction_level_report.py \\
|
| 10 | --zero-shot results/agent-workflow/<ts>.jsonl \\
|
| 11 | --cheat-sheet results/agent-workflow/<ts>.jsonl
|
| 12 |
|
| 13 | Provider-reported tokens only; rows with token_source != provider_reported
|
| 14 | are counted in coverage but excluded from token means (null means unmeasured,
|
| 15 | never zero).
|
| 16 | """
|
| 17 |
|
| 18 | from __future__ import annotations
|
| 19 |
|
| 20 | import argparse
|
| 21 | import json
|
| 22 | import statistics
|
| 23 | from pathlib import Path
|
| 24 | from typing import Any
|
| 25 |
|
| 26 |
|
| 27 | def row_level(row: dict[str, Any]) -> str | None:
|
| 28 | level = row.get("instruction_level")
|
| 29 | if level is None and isinstance(row.get("agent"), dict):
|
| 30 | level = row["agent"].get("instruction_level")
|
| 31 | return str(level) if level is not None else None
|
| 32 |
|
| 33 |
|
| 34 | def load(path: Path, expected_level: str) -> list[dict[str, Any]]:
|
| 35 | rows = [json.loads(line) for line in path.read_text().splitlines() if line.strip()]
|
| 36 | levels = {level for level in (row_level(row) for row in rows) if level}
|
| 37 | if levels and levels != {expected_level}:
|
| 38 | raise SystemExit(
|
| 39 | f"{path} carries instruction levels {sorted(levels)}, expected only {expected_level!r} β "
|
| 40 | "never mix levels in one input (suite contract)"
|
| 41 | )
|
| 42 | return rows
|
| 43 |
|
| 44 |
|
| 45 | def agent_name(row: dict[str, Any]) -> str:
|
| 46 | agent = row.get("agent")
|
| 47 | return str(agent.get("name")) if isinstance(agent, dict) else str(agent)
|
| 48 |
|
| 49 |
|
| 50 | def metric(rows: list[dict[str, Any]], *path: str) -> float | None:
|
| 51 | values = []
|
| 52 | for row in rows:
|
| 53 | node: Any = row
|
| 54 | for key in path:
|
| 55 | node = node.get(key) if isinstance(node, dict) else None
|
| 56 | if node is None:
|
| 57 | break
|
| 58 | if node is not None:
|
| 59 | values.append(float(node))
|
| 60 | return round(statistics.mean(values), 1) if values else None
|
| 61 |
|
| 62 |
|
| 63 | def fmt(value: Any) -> str:
|
| 64 | return "unmeasured" if value is None else str(value)
|
| 65 |
|
| 66 |
|
| 67 | def delta(zero: float | None, cheat: float | None) -> str:
|
| 68 | if zero is None or cheat is None or zero == 0:
|
| 69 | return ""
|
| 70 | return f"{(cheat - zero) / zero * 100:+.0f}%"
|
| 71 |
|
| 72 |
|
| 73 | def main() -> int:
|
| 74 | parser = argparse.ArgumentParser(description=__doc__)
|
| 75 | parser.add_argument("--zero-shot", type=Path, required=True)
|
| 76 | parser.add_argument("--cheat-sheet", type=Path, required=True)
|
| 77 | args = parser.parse_args()
|
| 78 |
|
| 79 | zero_rows = load(args.zero_shot, "zero-shot")
|
| 80 | cheat_rows = load(args.cheat_sheet, "cheat-sheet")
|
| 81 |
|
| 82 | def grouped(rows: list[dict[str, Any]]) -> dict[tuple[str, str], list[dict[str, Any]]]:
|
| 83 | groups: dict[tuple[str, str], list[dict[str, Any]]] = {}
|
| 84 | for row in rows:
|
| 85 | groups.setdefault((agent_name(row), str(row.get("subject"))), []).append(row)
|
| 86 | return groups
|
| 87 |
|
| 88 | zero, cheat = grouped(zero_rows), grouped(cheat_rows)
|
| 89 | metrics = [
|
| 90 | ("turns", ("turn_metrics", "assistant_turns_total")),
|
| 91 | ("output tokens", ("token_metrics", "output_tokens_reported")),
|
| 92 | ("input tokens", ("token_metrics", "input_tokens_reported")),
|
| 93 | ("help calls", ("tool_call_metrics", "help_calls_total")),
|
| 94 | ("unknown-cmd failures", ("tool_call_metrics", "unknown_command_failures_total")),
|
| 95 | ("failed tool calls", ("tool_call_metrics", "failed_tool_calls_total")),
|
| 96 | ("turns to recovery", ("turn_metrics", "turns_to_recovery")),
|
| 97 | ("tokens to recovery", ("turn_metrics", "tokens_to_recovery")),
|
| 98 | ]
|
| 99 |
|
| 100 | print("# Instruction-level report (zero-shot vs cheat-sheet, never aggregated)\n")
|
| 101 | print("Cheat-sheet delta is relative to zero-shot; negative = the cheat sheet")
|
| 102 | print("removed cost. The oak-vs-git gap at zero-shot is the pretraining tax;")
|
| 103 | print("how much of it the cheat sheet closes is the AGENTS.md claim.\n")
|
| 104 | for key in sorted(set(zero) | set(cheat)):
|
| 105 | zero_group, cheat_group = zero.get(key, []), cheat.get(key, [])
|
| 106 | passes_z = sum(1 for row in zero_group if row.get("outcome") == "pass")
|
| 107 | passes_c = sum(1 for row in cheat_group if row.get("outcome") == "pass")
|
| 108 | print(f"## {key[0]} Γ {key[1]}")
|
| 109 | print(f" success: zero-shot {passes_z}/{len(zero_group)} cheat-sheet {passes_c}/{len(cheat_group)}")
|
| 110 | for label, path in metrics:
|
| 111 | value_z, value_c = metric(zero_group, *path), metric(cheat_group, *path)
|
| 112 | if value_z is None and value_c is None:
|
| 113 | continue
|
| 114 | print(f" {label:22s} zero-shot {fmt(value_z):>10s} cheat-sheet {fmt(value_c):>10s} {delta(value_z, value_c)}")
|
| 115 | print()
|
| 116 | return 0
|
| 117 |
|
| 118 |
|
| 119 | if __name__ == "__main__":
|
| 120 | raise SystemExit(main())
|