Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

204 lines
7.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Track-2 failure-mode analysis over a finished run's per-case artifacts.
Read-only post-hoc analysis of ``run_dir/cases/*.json``. Answers the "where and
why does opensre lose?" questions the headline table can't, so the powered
full-corpus run turns into actionable next levers rather than a single number.
Four breakdowns, all scenario-clustered (seeds within a scenario averaged
first — the scenario is the independent unit):
1. Localization vs labeling — a1 (strict triple) vs object_a1 (right service)
vs partial_a1 (object+root_cause). The a1object_a1 gap is "right place,
wrong label"; the object_a1 deficit is "wrong place" (true mislocalization).
2. Per fault-category a1 — which fault families the agent fails on (the paper
reports Performance/Admission as universally hard).
3. Per system a1 — boutique (10 services) vs train-ticket (40 services); the
pilot showed train-ticket is the hard system (attention degrades at scale).
4. Control contrast — paired (opensre+llm llm_alone) and
(opensre+llm llm_alone_pure) deltas on a1, so any "opensre helps" claim
is attributable to the floor lever vs the full stack. Absent arms are
skipped gracefully (the pilot has only opensre+llm).
Usage:
python -m tests.benchmarks.cloudopsbench.failure_mode_analysis \
.bench-results/cloudopsbench_v1_openai/<run_id>/
"""
from __future__ import annotations
import argparse
import sys
from pathlib import Path
from typing import Any
from tests.benchmarks._framework.reporting import (
_cell_category,
_cell_mode,
_cells_by_llm_mode,
_load_cells,
_paired_scenario_deltas,
_scenario_means,
)
_PRIMARY_MODE = "opensre+llm"
_CONTROL_MODES = ["llm_alone", "llm_alone_pure"]
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 _metric_by_case(cells: list[dict[str, Any]], metric: str) -> dict[str, list[float]]:
"""Group one metric by case_id (for paired right-place/wrong-label counts)."""
out: dict[str, list[float]] = {}
for cell in cells:
value = cell.get("score", {}).get("metrics", {}).get(metric)
if isinstance(value, (int, float)):
case_id = cell.get("case", {}).get("case_id", "(unknown)")
out.setdefault(case_id, []).append(float(value))
return out
def _cell_system(cell: dict[str, Any]) -> str:
return cell.get("case", {}).get("metadata", {}).get("system", "(unknown)")
def _primary_cells(cells: list[dict[str, Any]]) -> list[dict[str, Any]]:
return [c for c in cells if "_load_error" not in c and _cell_mode(c) == _PRIMARY_MODE]
def _localization_vs_labeling(cells: list[dict[str, Any]], llm: str) -> None:
print(f"\n## Localization vs labeling — {llm} ({_PRIMARY_MODE})")
print(f"{'metric':<12}{'mean':>8} interpretation")
print("-" * 60)
rows = [
("a1", "strict triple match (taxonomy+object+root_cause)"),
("object_a1", "right service localized (object only)"),
("partial_a1", "object + root_cause (taxonomy ignored)"),
]
for metric, desc in rows:
m = _mean(_scenario_means(cells, metric))
print(f"{metric:<12}{_fmt(m):>8} {desc}")
# Right-place / wrong-label: object correct but strict a1 wrong, per scenario.
a1_by_case = _metric_by_case(cells, "a1")
obj_by_case = _metric_by_case(cells, "object_a1")
right_place_wrong_label = 0
wrong_place = 0
total = 0
for case_id in a1_by_case.keys() & obj_by_case.keys():
a1 = sum(a1_by_case[case_id]) / len(a1_by_case[case_id])
obj = sum(obj_by_case[case_id]) / len(obj_by_case[case_id])
total += 1
if obj >= 0.5 and a1 < 0.5:
right_place_wrong_label += 1
elif obj < 0.5:
wrong_place += 1
if total:
print(
f"\n right place / wrong label: {right_place_wrong_label}/{total} scenarios "
f"({right_place_wrong_label / total:.0%}) — fix with a labeling lever"
)
print(
f" wrong place (mislocalized): {wrong_place}/{total} scenarios "
f"({wrong_place / total:.0%}) — fix with an exploration/coverage lever"
)
def _breakdown(cells: list[dict[str, Any]], llm: str, key_fn: Any, title: str) -> None:
print(f"\n## {title}{llm} ({_PRIMARY_MODE})")
groups: dict[str, list[dict[str, Any]]] = {}
for cell in cells:
groups.setdefault(key_fn(cell), []).append(cell)
print(f"{'group':<28}{'n':>5}{'a1':>8}{'object_a1':>11}{'cov':>8}{'steps':>8}")
print("-" * 68)
for name in sorted(groups):
grp = groups[name]
n_scen = len({c.get("case", {}).get("case_id") for c in grp})
a1 = _mean(_scenario_means(grp, "a1"))
obj = _mean(_scenario_means(grp, "object_a1"))
cov = _mean(_scenario_means(grp, "cov"))
steps = _mean(_scenario_means(grp, "steps"))
print(f"{name:<28}{n_scen:>5}{_fmt(a1):>8}{_fmt(obj):>11}{_fmt(cov):>8}{_fmt(steps):>8}")
def _control_contrast(cells: list[dict[str, Any]], llm: str) -> None:
print(f"\n## Control contrast — {llm} (paired scenario deltas on a1)")
modes_present = {_cell_mode(c) for c in cells if c.get("run", {}).get("llm") == llm}
if _PRIMARY_MODE not in modes_present:
print(" (no opensre+llm cells for this LLM — skipping)")
return
any_control = False
for control in _CONTROL_MODES:
if control not in modes_present:
print(f" vs {control:<16}: (arm not run)")
continue
any_control = True
deltas = _paired_scenario_deltas(cells, llm, "a1", _PRIMARY_MODE, control)
delta = _mean(deltas)
sign = "+" if (delta or 0) >= 0 else ""
meaning = {
"llm_alone": "lift from MIN_TOOL_CALLS floor alone",
"llm_alone_pure": "lift from the full opensre stack",
}.get(control, "")
print(
f" vs {control:<16}: {sign}{_fmt(delta)} over {len(deltas)} paired "
f"scenarios — {meaning}"
)
if not any_control:
print(
" (no control arms in this run — single-arm pilot; run the v1 "
"config's llm_alone / llm_alone_pure to attribute the lift)"
)
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("run_dir", help="A finished run dir containing cases/.")
args = parser.parse_args(argv)
run_dir = Path(args.run_dir)
cases_dir = run_dir / "cases"
if not cases_dir.is_dir():
print(f" x {cases_dir} not found", file=sys.stderr)
return 1
all_cells = _load_cells(cases_dir)
by_llm_mode = _cells_by_llm_mode(all_cells)
if not by_llm_mode:
print(f" x no scorable cells under {cases_dir}", file=sys.stderr)
return 1
print(f"# Track-2 failure-mode analysis — {run_dir.name}")
print(
f"# {len([c for c in all_cells if '_load_error' not in c])} cells, "
f"{len(by_llm_mode)} LLM(s): {', '.join(sorted(by_llm_mode))}"
)
for llm in sorted(by_llm_mode):
llm_cells = [c for c in all_cells if c.get("run", {}).get("llm") == llm]
primary = _primary_cells(llm_cells)
if primary:
_localization_vs_labeling(primary, llm)
_breakdown(primary, llm, _cell_category, "Per fault-category")
_breakdown(primary, llm, _cell_system, "Per system")
_control_contrast(llm_cells, llm)
print(
"\n# Read: a1object_a1 gap = labeling problem (lever #4 cite/label); "
"low object_a1 = localization problem (coverage/exploration lever)."
)
return 0
if __name__ == "__main__":
sys.exit(main())