#58 shipped providers.py + model_registry.py for cross-provider benchmarking but never connected them to runner.py — the adapters sat unused. This PR wires them through with a clear lane split. Why a new suite instead of refactoring existing ones ----------------------------------------------------- The three existing suites (determinism / truth_lock / axis_orthogonality) pull CORE-only telemetry: trace_hash, versor_condition, register_id, register_variant_id, anchor_lens_id, register_canonical_surface. None of those fields can come from OpenAI / Anthropic / Ollama. Forcing those suites cross-provider would silently produce reports where the cross-provider rows have empty telemetry — a worse failure mode than not running them at all. So the routing is explicit: CORE-only suites → --provider must be 'core' Cross-provider suites → any provider; CORE is one adapter among many Operator asks for the wrong combo → loud error with the right alternative. New module: evals/frontier_compare/cross_provider.py ----------------------------------------------------- - ProviderObservation dataclass — provider-agnostic observation shape (prompt, surface, provider, model, elapsed_ms, error fields). No CORE-internal telemetry expected. - run_prompt_battery(adapter, *, cfg) → SuiteReport reusing existing CaseResult / SuiteReport shapes so the report viewer renders both lanes without schema branching. - _PROMPT_BATTERY: 7 fixed cases spanning definition / cause / verification / comparison / procedure / unknown intent shapes. Stable case_ids so future re-runs against the same provider produce diffable JSON. - Per-case 'passed' is loose by design (non-empty surface, no exception). Cross-provider quality is for human review — not for the runner to silently score. Updated CLI: evals/frontier_compare/__main__.py ----------------------------------------------- - --provider {core, openai, anthropic, ollama} (default: core) - --model <id> (validated via require_model_card) - --env-file <path> (default: ./.env) - Auto-persist non-CORE runs to evals/frontier_compare/results/<provider>_<model>_<utc>.json even when --report is omitted. API calls are rate-limited / paid; losing the artifact is costly. - Existing CORE-native behavior unchanged when --provider not set. Results directory: evals/frontier_compare/results/ -------------------------------------------------- Created with .gitkeep — matches the convention used by other lanes (evals/long_context_cost/results/, evals/koine_greek_fluency/results/, etc.). Distinct from reports/ which .gitignore excludes for transient debug output. Tests: tests/test_frontier_compare_cross_provider.py (9 cases) -------------------------------------------------------------- - prompt_battery runs with CORE adapter (no API needed) - adapter exceptions recorded as failed observations, never propagated - empty surfaces flagged distinctly from adapter errors - CLI default runs CORE-native (no breaking change) - CLI prompt_battery with --provider core routes through cross-provider path - CLI rejects CORE-only suite + non-CORE provider with operator-helpful error - --help surfaces both suite families - unregistered model is rejected before any benchmark cycles burn - ProviderObservation.succeeded handles error / empty / whitespace cases Live evidence ------------- $ core test --suite smoke -q 67 passed in 26.55s (no regression) $ python -m evals.frontier_compare --provider core --suite prompt_battery --json model=core-native mode=core suite=prompt_battery passed=True score=1.000 [definition_truth ] PASS Truth is a claim or state grounded by evidence... [definition_knowledge ] PASS Knowledge is justified understanding grounded... [cause_understanding ] PASS understanding — teaching-grounded (cognition_chains_v1)... [verification_evidence ] PASS evidence — teaching-grounded (cognition_chains_v1)... [comparison_knowledge_wisdom ] PASS knowledge contrasts with wisdom... [procedure_recall ] PASS To recall means to retrieve a stored state from memory... [unknown_term ] PASS I haven't learned 'xylomorphic' yet... $ python -m evals.frontier_compare --provider openai --suite determinism error: suite 'determinism' is CORE-only; pass --suite prompt_battery (the cross-provider suite) when --provider='openai'. .gitignore: adds frontier_wave1.json (stray report file repeatedly written by ad-hoc test invocations).
181 lines
5.8 KiB
Python
181 lines
5.8 KiB
Python
"""Cross-provider benchmark suite for frontier_compare.
|
|
|
|
The existing suites (``determinism``, ``truth_lock``,
|
|
``axis_orthogonality``) pull CORE-only telemetry (``trace_hash``,
|
|
``versor_condition``, ``register_id``, ``anchor_lens_id``) — they
|
|
cannot run cross-provider as-is.
|
|
|
|
This module is the cross-provider lane:
|
|
|
|
- Pure ``(prompt) -> str`` over any provider adapter.
|
|
- No CORE-internal telemetry expected.
|
|
- Per-case ``passed`` is loose by design — non-empty surface within
|
|
the elapsed-ms budget — because we are not in a position to
|
|
semantically judge GPT-4o vs Claude vs CORE here. The point of
|
|
the suite is **operator-visible, side-by-side surface evidence**
|
|
across providers on a fixed prompt battery, not automatic
|
|
quality scoring.
|
|
|
|
The prompt battery (``_PROMPT_BATTERY``) is the load-bearing data —
|
|
edit it to expand cross-provider coverage. Each entry pairs a
|
|
``case_id`` (stable across runs, used by reviewers to diff results)
|
|
with the prompt itself.
|
|
|
|
Trust boundary
|
|
--------------
|
|
- Read-only. Never writes packs, vault, or runtime state.
|
|
- Provider adapters are constructed via
|
|
``evals.frontier_compare.providers.build_adapter`` and made of a
|
|
single ``(prompt) -> str`` callable each.
|
|
- Per-call exceptions are recorded as case failures, never propagated.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import time
|
|
from dataclasses import dataclass
|
|
from typing import Callable
|
|
|
|
from .providers import ProviderConfig
|
|
from .runner import CaseResult, SuiteReport
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Prompt battery
|
|
# ---------------------------------------------------------------------------
|
|
# Stable case_ids so a future re-run on the same provider produces
|
|
# diffable JSON. Prompts span definitional / causal / verification /
|
|
# comparison / procedural / unknown shapes — enough to surface obvious
|
|
# provider behavior differences without ballooning credit cost.
|
|
|
|
_PROMPT_BATTERY: tuple[tuple[str, str], ...] = (
|
|
("definition_truth", "What is truth?"),
|
|
("definition_knowledge", "What is knowledge?"),
|
|
("cause_understanding", "What causes understanding?"),
|
|
("verification_evidence", "Does evidence ground knowledge?"),
|
|
("comparison_knowledge_wisdom", "Compare knowledge and wisdom."),
|
|
("procedure_recall", "Walk me through recall."),
|
|
("unknown_term", "What is xylomorphic?"),
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Observation shape — provider-agnostic
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class ProviderObservation:
|
|
"""One ``(prompt, provider) -> surface`` observation.
|
|
|
|
Cross-provider sibling of :class:`runner.RuntimeObservation`.
|
|
Carries only fields any provider can supply; CORE-only telemetry
|
|
deliberately omitted.
|
|
"""
|
|
|
|
prompt: str
|
|
surface: str
|
|
provider: str
|
|
model: str
|
|
elapsed_ms: float
|
|
error_type: str = ""
|
|
error_message: str = ""
|
|
|
|
@property
|
|
def succeeded(self) -> bool:
|
|
return not self.error_type and bool(self.surface.strip())
|
|
|
|
def as_dict(self) -> dict:
|
|
return {
|
|
"prompt": self.prompt,
|
|
"surface": self.surface,
|
|
"provider": self.provider,
|
|
"model": self.model,
|
|
"elapsed_ms": self.elapsed_ms,
|
|
"error_type": self.error_type,
|
|
"error_message": self.error_message,
|
|
}
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Suite runner
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _observe_one(
|
|
adapter: Callable[[str], str],
|
|
cfg: ProviderConfig,
|
|
prompt: str,
|
|
) -> ProviderObservation:
|
|
start = time.perf_counter()
|
|
try:
|
|
surface = adapter(prompt)
|
|
except Exception as exc: # noqa: BLE001 - record failure, never abort the suite
|
|
return ProviderObservation(
|
|
prompt=prompt,
|
|
surface="",
|
|
provider=cfg.provider,
|
|
model=cfg.model,
|
|
elapsed_ms=(time.perf_counter() - start) * 1000.0,
|
|
error_type=exc.__class__.__name__,
|
|
error_message=str(exc),
|
|
)
|
|
elapsed_ms = (time.perf_counter() - start) * 1000.0
|
|
return ProviderObservation(
|
|
prompt=prompt,
|
|
surface=surface,
|
|
provider=cfg.provider,
|
|
model=cfg.model,
|
|
elapsed_ms=elapsed_ms,
|
|
)
|
|
|
|
|
|
def run_prompt_battery(
|
|
adapter: Callable[[str], str],
|
|
*,
|
|
cfg: ProviderConfig,
|
|
prompts: tuple[tuple[str, str], ...] = _PROMPT_BATTERY,
|
|
) -> SuiteReport:
|
|
"""Run the cross-provider prompt battery against one adapter.
|
|
|
|
Per-case ``passed`` is loose by design (non-empty surface, no
|
|
exception). Reviewers should diff ``details.observation.surface``
|
|
side-by-side rather than rely on the boolean.
|
|
"""
|
|
cases: list[CaseResult] = []
|
|
for case_id, prompt in prompts:
|
|
obs = _observe_one(adapter, cfg, prompt)
|
|
passed = obs.succeeded
|
|
score = 1.0 if passed else 0.0
|
|
failures: tuple[str, ...] = ()
|
|
if not passed:
|
|
failures = (
|
|
("adapter_error",) if obs.error_type else ("empty_surface",)
|
|
)
|
|
cases.append(
|
|
CaseResult(
|
|
suite="prompt_battery",
|
|
case_id=case_id,
|
|
prompt=prompt,
|
|
passed=passed,
|
|
score=score,
|
|
elapsed_ms=obs.elapsed_ms,
|
|
details={"observation": obs.as_dict()},
|
|
failures=failures,
|
|
)
|
|
)
|
|
primary = (
|
|
sum(c.score for c in cases) / len(cases) if cases else 0.0
|
|
)
|
|
return SuiteReport(
|
|
suite="prompt_battery",
|
|
cases=tuple(cases),
|
|
primary_score=primary,
|
|
passed=all(c.passed for c in cases),
|
|
)
|
|
|
|
|
|
__all__ = [
|
|
"ProviderObservation",
|
|
"run_prompt_battery",
|
|
]
|