4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
140 lines
4.6 KiB
Python
140 lines
4.6 KiB
Python
"""Summarize a MIN_TOOL_CALLS floor sweep into one floor-vs-metric table.
|
|
|
|
Run after sweeping ``cloudopsbench_floorsweep_openai.yml`` over several
|
|
``BENCH_MIN_TOOL_CALLS`` values. Each run wrote a timestamped subdir under the
|
|
config's ``output_dir`` with a ``provenance.json`` (the floor lives at
|
|
``run_inputs.min_tool_calls``) and a ``cases/`` dir of per-case scores.
|
|
|
|
The question this answers: does lowering the floor lift the process metrics
|
|
opensre is weak on (rel/exact) and cut over-exploration (steps), WITHOUT
|
|
dropping the outcome metric (a1)? Each cell is a scenario-clustered mean (seeds
|
|
within a scenario averaged first), matching the report's headline statistic.
|
|
|
|
Usage:
|
|
|
|
python -m tests.benchmarks.cloudopsbench.analyze_floor_sweep \
|
|
.bench-results/cloudopsbench_floorsweep_openai/
|
|
|
|
Add ``--paper gpt-4o`` to print the paper Table-4 Base row alongside as the
|
|
reference target.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
from tests.benchmarks._framework.reporting import (
|
|
_PAPER_BASELINE,
|
|
_load_cells,
|
|
_scenario_means,
|
|
)
|
|
|
|
# Process + outcome metrics the sweep is meant to move. a1 is the guardrail
|
|
# (must not drop); rel/exact/steps/cov are the over-exploration signal.
|
|
_SWEEP_METRICS = [
|
|
"a1",
|
|
"a3",
|
|
"exact",
|
|
"in_order",
|
|
"any_order",
|
|
"rel",
|
|
"cov",
|
|
"steps",
|
|
"iac",
|
|
"mtti",
|
|
]
|
|
|
|
|
|
def _floor_for_run(run_dir: Path) -> int | None:
|
|
"""Read the resolved MIN_TOOL_CALLS floor from a run's provenance.json."""
|
|
prov_path = run_dir / "provenance.json"
|
|
if not prov_path.exists():
|
|
return None
|
|
try:
|
|
prov = json.loads(prov_path.read_text(encoding="utf-8"))
|
|
except json.JSONDecodeError:
|
|
return None
|
|
value = prov.get("run_inputs", {}).get("min_tool_calls")
|
|
return value if isinstance(value, int) else None
|
|
|
|
|
|
def _run_dirs(sweep_dir: Path) -> list[Path]:
|
|
"""Every immediate subdir that looks like a completed run."""
|
|
return sorted(d for d in sweep_dir.iterdir() if d.is_dir() and (d / "cases").is_dir())
|
|
|
|
|
|
def _mean(values: list[float]) -> float | None:
|
|
return sum(values) / len(values) if values else None
|
|
|
|
|
|
def _fmt(value: float | None) -> str:
|
|
return f"{value:.3f}" if value is not None else " - "
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("sweep_dir", help="Directory holding the per-floor run subdirs.")
|
|
parser.add_argument(
|
|
"--paper",
|
|
default=None,
|
|
help="LLM key (e.g. gpt-4o) to print the paper Table-4 Base row as a target.",
|
|
)
|
|
args = parser.parse_args(argv)
|
|
|
|
sweep_dir = Path(args.sweep_dir)
|
|
if not sweep_dir.is_dir():
|
|
print(f" x {sweep_dir} is not a directory", file=sys.stderr)
|
|
return 1
|
|
|
|
rows: list[tuple[int | None, str, dict[str, float | None], int]] = []
|
|
for run_dir in _run_dirs(sweep_dir):
|
|
cells = _load_cells(run_dir / "cases")
|
|
if not cells:
|
|
continue
|
|
floor = _floor_for_run(run_dir)
|
|
means = {m: _mean(_scenario_means(cells, m)) for m in _SWEEP_METRICS}
|
|
n_scen = len({c.get("case", {}).get("case_id") for c in cells if "_load_error" not in c})
|
|
rows.append((floor, run_dir.name, means, n_scen))
|
|
|
|
if not rows:
|
|
print(f" x no completed runs found under {sweep_dir}", file=sys.stderr)
|
|
return 1
|
|
|
|
rows.sort(key=lambda r: (r[0] is None, r[0]))
|
|
|
|
header = ["floor", "n", *_SWEEP_METRICS]
|
|
widths = [max(7, len(h)) for h in header]
|
|
print(" ".join(h.ljust(w) for h, w in zip(header, widths, strict=True)))
|
|
print(" ".join("-" * w for w in widths))
|
|
for floor, _name, means, n_scen in rows:
|
|
cells_str = [
|
|
(str(floor) if floor is not None else "?"),
|
|
str(n_scen),
|
|
*[_fmt(means[m]) for m in _SWEEP_METRICS],
|
|
]
|
|
print(" ".join(c.ljust(w) for c, w in zip(cells_str, widths, strict=True)))
|
|
|
|
if args.paper:
|
|
paper = _PAPER_BASELINE.get(args.paper.strip().lower())
|
|
if paper:
|
|
cells_str = [
|
|
"paper",
|
|
"452",
|
|
*[_fmt(paper.get(m)) for m in _SWEEP_METRICS],
|
|
]
|
|
print(" ".join(c.ljust(w) for c, w in zip(cells_str, widths, strict=True)))
|
|
else:
|
|
print(f" (no paper Base row for {args.paper!r})", file=sys.stderr)
|
|
|
|
print()
|
|
print("Read: a1 is the guardrail (must NOT drop as floor falls); rel/exact up")
|
|
print("and steps down = less over-exploration. Pick the lowest floor that holds a1.")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|