Files
wehub-resource-sync 4b6817381b
Benchmark image — build + push to ECR (any adapter) / build + push (push) Waiting to run
CI / quality (ubuntu-latest) (push) Waiting to run
CI / test (tools-runtime) (push) Waiting to run
CI / test (e2e-general) (push) Waiting to run
CI / test (cli-runtime) (push) Waiting to run
CI / test (e2e-provider-and-openclaw) (push) Waiting to run
CI / test (integrations-and-misc) (push) Waiting to run
CI / coverage-report (push) Blocked by required conditions
CI / test-kubernetes (push) Waiting to run
CI / should-run-thorough (push) Waiting to run
CI / test-thorough (cloudwatch-demo) (push) Blocked by required conditions
CI / test-thorough (flink-ecs) (push) Blocked by required conditions
CI / test-thorough (upstream-lambda) (push) Blocked by required conditions
CI / test-thorough (prefect-ecs-fargate) (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Blocked by required conditions
Release / publish-release (push) Blocked by required conditions
Release / publish-main-release (push) Blocked by required conditions
Release / prepare (push) Waiting to run
Release / verify (push) Blocked by required conditions
Release / build-python-dist (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Blocked by required conditions
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Waiting to run
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Waiting to run
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Waiting to run
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

319 lines
13 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.
"""Abstract benchmark adapter base class.
Each benchmark suite (CloudOpsBench, ToolCallBench, etc.) implements
this interface to bridge its corpus / scoring / agent surface to the
framework. The framework calls these methods; adapters do the
benchmark-specific work.
Split out from the original ``adapters.py`` so the type contracts in
``types.py`` and the registry in ``registry.py`` can be imported without
pulling in the late-binding TYPE_CHECKING surface this module needs to
type-check ``investigation_agent_class()``-style hooks against
``ConnectedInvestigationAgent``.
This module deliberately has zero ``app.*`` imports at module load — the
framework is independent of opensre internals. The TYPE_CHECKING block
below is type-checker-only and never executes at runtime.
"""
from __future__ import annotations
from abc import ABC, abstractmethod
from collections.abc import Iterator
from typing import TYPE_CHECKING, Any, ClassVar
from pydantic import BaseModel, ConfigDict
from tests.benchmarks._framework.types import (
AlertPayload,
BenchmarkCase,
CaseFilters,
CaseScore,
MetricSchema,
RunContext,
RunResult,
)
if TYPE_CHECKING:
# Type-only import — preserves the framework's "zero ``app.*`` imports"
# constraint at runtime while still letting type-checkers validate
# that adapter overrides return an investigation-agent subclass.
from tools.investigation.stages.gather_evidence import ConnectedInvestigationAgent
# --------------------------------------------------------------------------- #
# Capability flags #
# --------------------------------------------------------------------------- #
class AdapterCapabilities(BaseModel):
"""Feature flags an adapter declares to the framework.
The framework uses these to validate config knobs without
dispatching on adapter name. Every flag defaults to ``False``: a
new adapter is locked down to the minimum surface until it opts in.
Declare as a class attribute:
class MyAdapter(BenchmarkAdapter):
capabilities = AdapterCapabilities(supports_agent_variant=True)
"""
model_config = ConfigDict(frozen=True, extra="forbid")
supports_agent_variant: bool = False
"""Adapter honors ``config.agent_variant``. If False, the framework
rejects any config with ``agent_variant != "default"`` instead of
silently running the default agent."""
supports_predictor_variant: bool = False
"""Adapter has a predictor stage and honors
``config.predictor_variant``. If False, any non-default value is
rejected. CloudOpsBench has one (paper-format triple emission);
most other benchmark types don't."""
# --------------------------------------------------------------------------- #
# Overfit-dimensions schema #
# --------------------------------------------------------------------------- #
class OverfitDimensions(BaseModel):
"""Metadata key names the overfit guards read from each case.
The guards group results by three axes — system, stratum, GT
object — to detect concentration. Adapters override this if their
cases store those values under different keys. Defaults match the
CloudOpsBench schema.
"""
model_config = ConfigDict(frozen=True, extra="forbid")
system_key: str = "system"
"""``case.metadata[<key>]`` — system / cluster name."""
stratum_key: str = "fault_category"
"""``case.metadata[<key>]`` — category / stratum for per-stratum
uniformity checks."""
gt_object_key: str = "fault_object"
"""``case.metadata["ground_truth"][<key>]`` — GT target object,
used by the cluster-concentration guard to fingerprint scenarios."""
# --------------------------------------------------------------------------- #
# The adapter interface #
# --------------------------------------------------------------------------- #
class BenchmarkAdapter(ABC):
"""One adapter per benchmark suite.
Implementations:
- ``tests/benchmarks/cloudopsbench/adapter.py`` (first)
- ``tests/benchmarks/toolcall_model_benchmark/adapter.py`` (proves reusability)
The framework calls these methods; adapters bridge to whatever the
specific benchmark needs (HF datasets, replay backends, custom scoring).
Adapters register themselves in the framework's ``adapter_registry`` so
the CLI can dispatch on ``config.benchmark`` without an if/elif chain.
See ``register_adapter`` / ``build_adapter`` / ``known_adapters`` in
``tests/benchmarks/_framework/registry.py``.
"""
name: str # e.g. "cloudopsbench"
version: str # adapter version, separate from corpus version
capabilities: ClassVar[AdapterCapabilities] = AdapterCapabilities()
"""Framework features this adapter opts into.
Default is the all-False instance: a new adapter is locked down to
the minimum surface until it explicitly declares each capability.
See :class:`AdapterCapabilities` for the available flags."""
def apply_config_overrides(self, config: Any) -> None: # noqa: ARG002 — default no-op
"""Read adapter-specific config fields before any agent runs.
Called once by the CLI after the adapter is built. Use for
config knobs only your adapter understands (CloudOpsBench reads
``min_tool_calls`` and ``agent_variant`` here). Default is
no-op.
"""
return None
def overfit_dimensions(self) -> OverfitDimensions:
"""Metadata keys the overfit guards consult for this adapter.
Override if your case metadata uses different key names than
the CloudOpsBench defaults.
"""
return OverfitDimensions()
def extend_provenance(self, provenance: dict[str, Any]) -> dict[str, Any]:
"""Add adapter-specific entries to the provenance bundle.
Called by ``capture_provenance`` after the framework assembles
its standard sections (code, config, models, environment,
run_inputs). Adapters may add top-level keys, extend existing
sections, or return the dict unchanged.
Default is identity. The hook exists so
``_framework/provenance.py`` does not need to import any
specific adapter to capture adapter-specific run inputs (e.g.
CloudOpsBench's ``min_tool_calls``).
Mutate-and-return is fine; the framework uses whatever the
hook returns.
"""
return provenance
@abstractmethod
def load_cases(self, filters: CaseFilters) -> Iterator[BenchmarkCase]:
"""Stream cases matching the filter. Seeded random selection is the
adapter's responsibility (integrity Mechanism 6).
"""
@abstractmethod
def build_alert(self, case: BenchmarkCase) -> AlertPayload:
"""Convert a case into the alert opensre / LLM consume."""
@abstractmethod
def build_opensre_integrations(self, case: BenchmarkCase) -> dict[str, Any]:
"""Return the resolved_integrations dict opensre+LLM mode passes to
``run_investigation``. For CloudOpsBench, this wires the replay
backend in place of live AWS/K8s/Datadog clients.
"""
@abstractmethod
def build_baseline_tools(self, case: BenchmarkCase) -> dict[str, Any]:
"""Return the tool surface for LLM-alone mode. Same replay backend
access as opensre+LLM (fairness) but no extract/context/diagnose
pipeline — just direct LLM with tool-calling.
"""
@abstractmethod
def score_case(self, case: BenchmarkCase, run: RunResult, context: RunContext) -> CaseScore:
"""Compute per-case metrics from the run result + per-cell context.
``context.integrations`` is the dict ``build_opensre_integrations``
returned for THIS cell — adapters use it to read runtime state
accumulated during the run (e.g., a replay backend's action_log).
Passing context explicitly (vs caching on the adapter) is what
makes the adapter thread-safe for parallel runner execution.
"""
@abstractmethod
def metric_schema(self) -> MetricSchema:
"""Declare which metrics this adapter emits, for CLI validation +
comparable reporting across adapters.
"""
def investigation_agent_class(self) -> type[ConnectedInvestigationAgent] | None:
"""Optional: which investigation agent class should the runner use?
Default ``None`` — let the production pipeline construct its standard
:class:`ConnectedInvestigationAgent`. Override when the benchmark
needs a stricter termination policy or other agent-level behavior
(e.g. CloudOpsBench's minimum-tool-call floor lives in
:class:`tests.benchmarks.cloudopsbench.bench_agent.BenchInvestigationAgent`).
Production code stays clean: the runner just passes whatever the
adapter returns to ``run_investigation``. Bench-specific agent logic
lives entirely in bench code.
"""
return None
def baseline_agent_class(self) -> type[ConnectedInvestigationAgent] | None:
"""Optional: which agent class to use for the ``llm_alone`` control arm.
Default ``None`` — the adapter does not support an in-harness baseline,
and the runner will refuse a config with ``modes=["llm_alone"]``.
Override to return an agent class that represents the matched control
for this benchmark's headline claim. The control's job is to isolate
whichever lever you're attributing lift to — typically: same tool
surface, same scoring, but no bench-specific termination policy.
The runner picks this method for ``llm_alone`` cells and
``investigation_agent_class`` for ``opensre+llm`` cells, then passes
the chosen class to ``run_investigation`` exactly the same way.
"""
return None
def pure_baseline_agent_class(self) -> type[ConnectedInvestigationAgent] | None:
"""Optional: agent class for the pure-baseline (``llm_alone_pure``) arm.
Default ``None`` — the adapter does not ship a prompt-stripped
baseline; runner refuses ``modes=["llm_alone_pure"]``.
Override to return an agent that ALSO overrides ``_build_system_prompt``
with a minimal task-specific prompt — no opensre planner / verifier /
evidence-budget instructions. The contrast (opensre+llm) (llm_alone_pure)
then isolates the lift from opensre's full structural stack, not just
the bench-specific termination policy that ``baseline_agent_class``
controls.
Same tool surface as both other arms; the methodological constant
across all three modes is the per-case integrations dict.
"""
return None
def format_final_answer(
self,
case: BenchmarkCase, # noqa: ARG002 — used by overrides
run: RunResult,
spec: Any, # noqa: ARG002 — used by overrides
) -> RunResult:
"""Optional: enrich ``run.final_diagnosis`` before ``score_case``.
Default no-op — returns the run unchanged. Override when the
benchmark's scorer expects a specific output schema the
investigation pipeline doesn't natively produce (e.g.,
CloudOpsBench requires paper-format ``top_3_predictions`` JSON
and runs a separate LLM call to emit it).
``spec`` is the framework's LLMSpec for this cell — typed as
``Any`` here to keep ``adapters.py`` free of llm_dispatch import
coupling; the override casts it to its real type.
Mode-agnostic by design: the runner calls this for every cell
regardless of mode, so the same hook serves both ``opensre+llm``
(with investigation evidence) and future ``llm_alone`` (without).
"""
return run
def select_best_run(
self,
case: BenchmarkCase, # noqa: ARG002 — used by overrides
runs: list[tuple[RunResult, CaseScore]], # noqa: ARG002 — used by overrides
) -> int | None:
"""Optional: pick the canonical run from a self-consistency batch.
Called once per (case, mode, llm) group after every run finishes.
``runs`` is the list of (RunResult, CaseScore) tuples in original
run-index order.
Return:
- ``int`` — index of the run whose metrics should be reported as
the canonical answer for this scenario. The runner emits an
additional ``consistency_selected`` stratum built from those
picks alongside the standard ``all`` (median) stratum.
- ``None`` — no selection; only the median ``all`` stratum is
reported. This is the default for adapters that don't run
multi-seed self-consistency.
Why this hook exists: paper-style A@1 averaging across N seeds
drags the median below what the agent can actually produce. The
06-05 CloudOpsBench run showed median a1=0.43 (gpt-4o) vs
ORACLE bo3=0.83 — a 0.40 consistency gap. A free selector
(majority vote on predicted root-cause taxonomy) closes 60% of
that gap with zero extra LLM calls.
The hook is opt-in per adapter so benchmarks without multi-seed
protocols are unaffected. The runner still computes the standard
median stratum so both views are reported side-by-side for
transparency — no silent metric swap.
"""
return None