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
126 lines
4.3 KiB
Python
126 lines
4.3 KiB
Python
"""Pure trajectory policy evaluator for the synthetic RDS benchmark suite.
|
|
|
|
This module is intentionally free of rich/console dependencies so it can be
|
|
imported and unit-tested without pulling in the full observation rendering stack.
|
|
|
|
``TrajectoryMetrics`` lives here alongside policy types so
|
|
``observations.py`` can import from this module without creating an import cycle.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TrajectoryMetrics:
|
|
"""Numeric summary of how closely an executed trajectory matched expectations."""
|
|
|
|
flat_actions: list[str]
|
|
actions_per_loop: list[int]
|
|
strict_match: bool | None
|
|
lcs_ratio: float | None
|
|
edit_distance: int | None
|
|
coverage: float | None
|
|
extra_actions: list[str]
|
|
missing_actions: list[str]
|
|
redundancy_count: int
|
|
loops_used: int
|
|
max_loops: int | None
|
|
loop_calibration_ok: bool | None
|
|
failed_action_count: int
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TrajectoryPolicy:
|
|
"""Constraints applied to the agent's execution trajectory.
|
|
|
|
Attributes:
|
|
matching: Comparison mode — one of ``"strict"``, ``"lcs"``, ``"set"``.
|
|
max_edit_distance: Maximum allowed Levenshtein distance from golden trajectory.
|
|
max_extra_actions: Maximum allowed actions beyond the golden set.
|
|
max_redundancy: Maximum allowed repeated actions.
|
|
max_loops: Maximum allowed investigation loops.
|
|
"""
|
|
|
|
matching: str
|
|
max_edit_distance: int | None = None
|
|
max_extra_actions: int | None = None
|
|
max_redundancy: int | None = None
|
|
max_loops: int | None = None
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TrajectoryPolicyResult:
|
|
"""Outcome of evaluating a trajectory against a policy.
|
|
|
|
Attributes:
|
|
passed: True when no violations were detected.
|
|
matching: The matching mode that was evaluated.
|
|
violations: Human-readable violation descriptions (empty when passed).
|
|
"""
|
|
|
|
passed: bool
|
|
matching: str
|
|
violations: list[str]
|
|
|
|
|
|
def _fmt_ratio(value: float | None) -> str:
|
|
if value is None:
|
|
return "None"
|
|
return f"{value:.2f}"
|
|
|
|
|
|
def evaluate_trajectory_policy(
|
|
metrics: TrajectoryMetrics,
|
|
golden_actions: list[str],
|
|
policy: TrajectoryPolicy | None,
|
|
) -> TrajectoryPolicyResult | None:
|
|
"""Evaluate *metrics* against *policy* and return a result.
|
|
|
|
Returns ``None`` when there is no golden trajectory or policy to check
|
|
(the caller records this as a ``not_applicable`` gate).
|
|
|
|
Args:
|
|
metrics: Computed trajectory metrics from ``compute_trajectory_metrics``.
|
|
golden_actions: The expected action sequence from the fixture answer key.
|
|
policy: The policy constraints to enforce. When ``None``, returns ``None``.
|
|
|
|
Returns:
|
|
``TrajectoryPolicyResult`` with ``passed=True`` if no violations, or
|
|
``None`` when the check is not applicable.
|
|
"""
|
|
if not golden_actions or policy is None:
|
|
return None
|
|
|
|
violations: list[str] = []
|
|
matching = policy.matching
|
|
|
|
if matching == "strict" and metrics.strict_match is not True:
|
|
violations.append("strict sequence mismatch")
|
|
elif matching == "lcs" and metrics.lcs_ratio != 1.0:
|
|
violations.append(f"lcs_ratio={_fmt_ratio(metrics.lcs_ratio)} < 1.00")
|
|
elif matching == "set" and metrics.missing_actions:
|
|
violations.append(f"missing actions: {', '.join(metrics.missing_actions)}")
|
|
|
|
if (
|
|
policy.max_edit_distance is not None
|
|
and metrics.edit_distance is not None
|
|
and metrics.edit_distance > policy.max_edit_distance
|
|
):
|
|
violations.append(f"edit_distance={metrics.edit_distance} > {policy.max_edit_distance}")
|
|
if policy.max_extra_actions is not None:
|
|
extra_count = len(metrics.extra_actions)
|
|
if extra_count > policy.max_extra_actions:
|
|
violations.append(f"extra_actions={extra_count} > {policy.max_extra_actions}")
|
|
if policy.max_redundancy is not None and metrics.redundancy_count > policy.max_redundancy:
|
|
violations.append(f"redundancy_count={metrics.redundancy_count} > {policy.max_redundancy}")
|
|
if policy.max_loops is not None and metrics.loops_used > policy.max_loops:
|
|
violations.append(f"loops_used={metrics.loops_used} > {policy.max_loops}")
|
|
|
|
return TrajectoryPolicyResult(
|
|
passed=not violations,
|
|
matching=matching,
|
|
violations=violations,
|
|
)
|