Log in
scripts/instruction_level_report.py 120 lines · 4.8 KB · python Blame
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())