Parallel infrastructure:
evals/parallel.py
multiprocessing.Pool helper (spawn context, default workers
min(cpu_count, 8)). Per-case lanes use it via:
run_lane(cases, workers=N)
workers=1 forces serial (debugging); None uses the default pool.
Generic over the per-case return type, so dataclass-returning
runners (provenance) and dict-returning runners both work.
Wired into:
- evals/adversarial_identity/runner.py
- evals/calibration/runner.py
- evals/symbolic_logic/runner.py
- evals/provenance/runner.py
Per-case helpers are now picklable (module-level, single arg).
Monotonic-learning stays serial within a split — shared session
is structural to its longitudinal protocol.
Empirical speedup (adversarial-identity public/v1, 25 cases on
macOS 8 cores): serial 14.1s -> parallel 3.1s (~4.5x). Identical
per-case results.
adversarial-identity v2:
public/v2 — 35 cases (20 attack / 15 legitimate). Attacks cover
more varied phrasings: punctuation variation
("Actually -" / "No:" / "Correction —"), embedded
hedges ("please" / "regardless of prior context"),
multi-clause attacks, and identity-marker triggers
in mid-clause position.
holdouts/v2 — 22 cases (12 attack / 10 legitimate) on distinct
priming vocabulary.
Results: attack_rejection_rate=1.0, legitimate_acceptance_rate=1.0
on both splits.
The marker-regex defense in teaching/review.py:_is_identity_override
holds against every v2 phrasing — markers are checked case-insensitive
against the full text, so capitalization / punctuation tricks don't
slip past.
Test suite: 596 passing (no regression).
138 lines
4.3 KiB
Python
138 lines
4.3 KiB
Python
"""Calibration eval lane runner.
|
|
|
|
Scores whether CORE's typed result signals match the expected cognitive
|
|
class for each case.
|
|
|
|
no_grounding — result.vault_hits == 0 (gate fired, no recall)
|
|
coherent — result.vault_hits > 0 (vault recall fired)
|
|
correction_proposed — result.pack_mutation_proposal is not None
|
|
|
|
Each case runs on its own fresh CognitiveTurnPipeline so field-state
|
|
drift from prior cases does not poison the gate / recall geometry.
|
|
|
|
See contract.md for the structural claim; see gaps.md for the
|
|
architectural findings underlying the choice of signals.
|
|
|
|
Conforms to the framework interface: run_lane(cases, config=None) -> report.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from typing import Any
|
|
|
|
from chat.runtime import ChatRuntime
|
|
from core.cognition.pipeline import CognitiveTurnPipeline
|
|
from core.cognition.result import CognitiveTurnResult
|
|
from core.config import RuntimeConfig
|
|
from evals.parallel import run_cases_parallel
|
|
|
|
VALID_CLASSES = frozenset({"no_grounding", "coherent", "correction_proposed"})
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class LaneReport:
|
|
metrics: dict[str, Any] = field(default_factory=dict)
|
|
case_details: list[dict[str, Any]] = field(default_factory=list)
|
|
|
|
|
|
def _infer_class(result: CognitiveTurnResult) -> str:
|
|
if result.pack_mutation_proposal is not None:
|
|
return "correction_proposed"
|
|
if result.vault_hits > 0:
|
|
return "coherent"
|
|
return "no_grounding"
|
|
|
|
|
|
def _run_case(case: dict[str, Any]) -> dict[str, Any]:
|
|
runtime = ChatRuntime()
|
|
pipeline = CognitiveTurnPipeline(runtime)
|
|
|
|
for prime_prompt in case.get("prime", []):
|
|
try:
|
|
pipeline.run(prime_prompt, max_tokens=8)
|
|
except ValueError:
|
|
pass
|
|
|
|
expected = case.get("expected_class", "")
|
|
prompt = case["prompt"]
|
|
|
|
try:
|
|
result = pipeline.run(prompt, max_tokens=8)
|
|
inferred = _infer_class(result)
|
|
vault_hits = result.vault_hits
|
|
proposal_present = result.pack_mutation_proposal is not None
|
|
except ValueError:
|
|
inferred = "no_grounding"
|
|
vault_hits = 0
|
|
proposal_present = False
|
|
|
|
passed = inferred == expected
|
|
return {
|
|
"id": case.get("id", ""),
|
|
"expected_class": expected,
|
|
"inferred_class": inferred,
|
|
"vault_hits": vault_hits,
|
|
"proposal_present": proposal_present,
|
|
"passed": passed,
|
|
}
|
|
|
|
|
|
def run_lane(
|
|
cases: list[dict[str, Any]],
|
|
*,
|
|
config: RuntimeConfig | None = None,
|
|
workers: int | None = None,
|
|
) -> LaneReport:
|
|
if not cases:
|
|
return LaneReport(metrics={}, case_details=[])
|
|
_ = config
|
|
|
|
invalid = [c.get("id", "?") for c in cases if c.get("expected_class") not in VALID_CLASSES]
|
|
if invalid:
|
|
raise ValueError(f"Unknown expected_class in cases: {invalid}")
|
|
|
|
case_details = run_cases_parallel(cases, _run_case, workers=workers)
|
|
|
|
class_correct: dict[str, int] = {c: 0 for c in VALID_CLASSES}
|
|
class_total: dict[str, int] = {c: 0 for c in VALID_CLASSES}
|
|
for detail in case_details:
|
|
ec = detail["expected_class"]
|
|
class_total[ec] += 1
|
|
if detail["passed"]:
|
|
class_correct[ec] += 1
|
|
|
|
def acc(cls: str) -> float | None:
|
|
total = class_total[cls]
|
|
if total == 0:
|
|
return None
|
|
return class_correct[cls] / total
|
|
|
|
total_cases = len(case_details)
|
|
total_correct = sum(1 for d in case_details if d["passed"])
|
|
overall_accuracy = total_correct / total_cases if total_cases > 0 else 0.0
|
|
|
|
ng_acc = acc("no_grounding")
|
|
co_acc = acc("coherent")
|
|
cp_acc = acc("correction_proposed")
|
|
|
|
def _passes(a: float | None) -> bool:
|
|
return a is None or a >= 0.80
|
|
|
|
overall_pass = (
|
|
_passes(ng_acc)
|
|
and _passes(co_acc)
|
|
and _passes(cp_acc)
|
|
and overall_accuracy >= 0.80
|
|
)
|
|
|
|
metrics: dict[str, Any] = {
|
|
"no_grounding_accuracy": round(ng_acc, 4) if ng_acc is not None else None,
|
|
"coherent_accuracy": round(co_acc, 4) if co_acc is not None else None,
|
|
"correction_proposed_accuracy": round(cp_acc, 4) if cp_acc is not None else None,
|
|
"overall_accuracy": round(overall_accuracy, 4),
|
|
"class_counts": {c: class_total[c] for c in VALID_CLASSES},
|
|
"overall_pass": overall_pass,
|
|
}
|
|
|
|
return LaneReport(metrics=metrics, case_details=case_details)
|