core/benchmarks/replay_vs_llm.py
Shay b5d6ad6510 feat(compositionality): compose_relations operator lifts lane 68.8% → 100%
Closes the residual `novel_pair_under_seen_relation` pattern that
neither `transitive_walk` nor `multi_relation_walk` could synthesise.

- new `compose_relations(triples, head, frame, relation)` operator —
  pure lookup, returns both `R(head, ?)` and `R(frame, ?)` tails
- new `FRAME_TRANSFER` intent + `_FRAME_TRANSFER_RE` regex tried
  before generic TRANSITIVE_QUERY so "in Y" isn't truncated; handles
  "X belong to in Y" → belongs_to normalisation
- pipeline wiring: `_maybe_compose_relations`, `_fold_compose_into_surface`,
  `_serialize_compose` (folded into operator_invocation so trace_hash
  stays bit-identical across replay)
- regression: inference_closure, multi_step_reasoning,
  cross_domain_transfer all still 100% on public + holdouts

discourse_paragraph v2:
- per-sentence grammar rubric (length, capitalization, subject
  alignment) gated on `require_per_sentence_grammar`
- scaling cases at 10 / 20 / 50 sentences — 3/3 pass, 100% per-sentence
- 3 runtime round-trip cases (`mode: runtime_roundtrip`) that prime
  vault, ask question, verify bit-identical across two fresh runtimes
- new `per_sentence_grammar_pass_rate` lane metric

Long-form replay benchmark (benchmarks/replay_vs_llm.py):
- `replay_determinism_report(prompts, runs, priming)` — CORE-only
- `compare_to_llm(prompts, llm_callable)` — BYO API client, no
  provider lock-in; reports per-prompt determinism on both sides
- ships with default cognition-pack prompts; 100% bit-identical at runs=3

Lanes green: cognition 121/121, runtime 19/19, teaching 17/17,
packs 6/6, compositionality 16/16 + 10/10, inference_closure 20/20 +
12/12, multi_step_reasoning 15/15 + 10/10, cross_domain_transfer
10/10 + 8/8, discourse_paragraph v1 12/12 + v2 6/6.
2026-05-16 22:44:06 -07:00

200 lines
7 KiB
Python
Raw 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.

"""Long-form replay benchmark: CORE bit-identical replay vs frontier-LLM
surface variability on the same input.
CORE's structural claim is that a fixed (pack, vault, seed) state produces
a byte-identical surface across repeated runs. Frontier LLMs, even with
``temperature=0``, exhibit per-run surface variability driven by sampler
noise, backend nondeterminism, and rolling model updates. This benchmark
makes that asymmetry measurable.
Usage:
from benchmarks.replay_vs_llm import (
replay_determinism_report,
compare_to_llm,
)
# CORE-only — no API key required. Verifies bit-identical replay
# across N runs of the same prompt through the same pipeline.
report = replay_determinism_report(prompts, runs=5)
assert report.all_deterministic
# Optional LLM comparison. ``llm_callable(prompt) -> str`` is any
# bring-your-own function — no provider lock-in, no API code in the
# benchmark itself. When omitted, only the CORE side is reported.
report = compare_to_llm(prompts, llm_callable=my_openai_caller, runs=5)
print(report.summary())
The CORE side is the load-bearing claim and runs without external
dependencies; the LLM comparison is opt-in for a research workstation
that already holds the relevant credentials.
"""
from __future__ import annotations
import hashlib
from dataclasses import dataclass
from typing import Callable
from chat.runtime import ChatRuntime
@dataclass(frozen=True, slots=True)
class PromptReplayResult:
"""Per-prompt determinism evidence for one side (CORE or LLM)."""
prompt: str
surfaces: tuple[str, ...]
surface_hashes: tuple[str, ...]
unique_count: int
@property
def deterministic(self) -> bool:
return self.unique_count == 1
@dataclass(frozen=True, slots=True)
class ReplayReport:
"""Aggregate determinism report across N prompts × R runs."""
core_results: tuple[PromptReplayResult, ...]
llm_results: tuple[PromptReplayResult, ...] = ()
runs_per_prompt: int = 0
@property
def core_deterministic_rate(self) -> float:
if not self.core_results:
return 0.0
wins = sum(1 for r in self.core_results if r.deterministic)
return wins / len(self.core_results)
@property
def llm_deterministic_rate(self) -> float | None:
if not self.llm_results:
return None
wins = sum(1 for r in self.llm_results if r.deterministic)
return wins / len(self.llm_results)
@property
def all_deterministic(self) -> bool:
return self.core_deterministic_rate == 1.0
def summary(self) -> str:
lines = [
f"Long-form replay benchmark — {len(self.core_results)} prompts × {self.runs_per_prompt} runs",
f" CORE deterministic rate: {self.core_deterministic_rate:.1%} "
f"({sum(1 for r in self.core_results if r.deterministic)}/{len(self.core_results)} bit-identical)",
]
if self.llm_results:
llm_rate = self.llm_deterministic_rate or 0.0
mean_unique = (
sum(r.unique_count for r in self.llm_results)
/ max(1, len(self.llm_results))
)
lines.append(
f" LLM deterministic rate: {llm_rate:.1%}"
f"mean unique surfaces per prompt: {mean_unique:.2f}"
)
return "\n".join(lines)
def _sha256(s: str) -> str:
return hashlib.sha256(s.encode("utf-8")).hexdigest()
def _make_core_runner(priming: tuple[str, ...]) -> Callable[[str], str]:
"""Build a CORE runner that primes a fresh ChatRuntime with the
supplied sequence before each call.
Each invocation gets its own runtime so the determinism claim is
over the *pipeline* (pack, vault, seed, priming sequence) rather
than the in-memory session state of one runtime instance. That is
the stronger guarantee — if the priming + prompt yields identical
bytes across two cold-start runtimes, the pipeline is fully
deterministic for that input.
"""
def runner(prompt: str) -> str:
rt = ChatRuntime()
for p in priming:
rt.chat(p)
resp = rt.chat(prompt)
return resp.articulation_surface or resp.surface or ""
return runner
def _replay_one(prompt: str, runner: Callable[[str], str], runs: int) -> PromptReplayResult:
surfaces: list[str] = []
hashes: list[str] = []
for _ in range(runs):
surf = runner(prompt)
surfaces.append(surf)
hashes.append(_sha256(surf))
return PromptReplayResult(
prompt=prompt,
surfaces=tuple(surfaces),
surface_hashes=tuple(hashes),
unique_count=len(set(hashes)),
)
def replay_determinism_report(
prompts: list[str],
*,
runs: int = 5,
priming: tuple[str, ...] = (),
) -> ReplayReport:
"""Run each prompt through CORE ``runs`` times and report bit-identity.
Pure CORE-side benchmark — no LLM comparison. Each prompt should
produce ``unique_count == 1`` (one distinct surface hash across all
runs). Any prompt with ``unique_count > 1`` is a determinism
regression worth investigating.
``priming`` is an optional sequence of prior turns played into each
fresh runtime before the prompt. Useful for benchmarking surfaces
that depend on vault state (e.g. compositionality probes).
"""
runner = _make_core_runner(priming)
results = tuple(_replay_one(p, runner, runs) for p in prompts)
return ReplayReport(core_results=results, runs_per_prompt=runs)
def compare_to_llm(
prompts: list[str],
*,
llm_callable: Callable[[str], str] | None = None,
runs: int = 5,
priming: tuple[str, ...] = (),
) -> ReplayReport:
"""Run each prompt through CORE and (optionally) through an LLM and
compare per-prompt surface determinism on both sides.
``llm_callable`` is any bring-your-own function from prompt to
surface string. No provider lock-in: pass an OpenAI/Anthropic/
local-model wrapper that already lives in the caller's project.
When ``llm_callable`` is None this is equivalent to
``replay_determinism_report``.
``priming`` is forwarded to the CORE side only — the LLM is called
on the bare prompt since it has no equivalent of CORE's vault.
"""
core_runner = _make_core_runner(priming)
core = tuple(_replay_one(p, core_runner, runs) for p in prompts)
llm: tuple[PromptReplayResult, ...] = ()
if llm_callable is not None:
llm = tuple(_replay_one(p, llm_callable, runs) for p in prompts)
return ReplayReport(core_results=core, llm_results=llm, runs_per_prompt=runs)
# A small set of cognition-pack-grounded long-form prompts the benchmark
# can be invoked with out-of-the-box. Callers can pass their own list;
# this is just a default that exercises the realizer and operator paths.
DEFAULT_LONGFORM_PROMPTS: tuple[str, ...] = (
"What is wisdom?",
"What does truth ground?",
"What does truth ground in knowledge?",
"What is judgment?",
"What does wisdom precede?",
)