core/evals/comprehension/relational_predicate_runner.py
Shay 2a3f422783 feat(measure): put the relational reader (#596) on the capability index
A1 of the refined sequencing — the binary-relation reader was inert w.r.t. the
yardstick (contributing 0). This adds a comprehension_relational_predicate domain:
binary-relation prose scored against hand-authored independent gold (predicate,
subject, object) triples — INV-25 independent / INV-27 reader-disjoint (the reader
never produced the gold). Index breadth 8->9, capability_score 0.937258->0.944030,
wrong_total still 0; baseline.json re-frozen to digest 1ea91c1e.

Rigor split: the index lane is POSITIVE-ONLY (clean coverage, consistent with the
other 8 lanes — mixing adversarial refuse-cases into the coverage denominator would
make 'added capability' read as a score drop). The #596 fabrication-catch lives in a
dedicated falsification test (evals/relational/v1/refusals.jsonl): the trailing-
qualifier / dangling-copula / negation / verb-form cases MUST refuse — bites if the
reader ever fabricates. Honest coverage gap recorded: overlaps_event has no copular
surface form (verb-form 'A overlaps B' refuses), so 17 positives cover 15/16 predicates.
2026-06-06 10:09:15 -07:00

87 lines
3.1 KiB
Python

"""Score the relational reader (#596) on binary-relation prose against independent gold.
prose -> comprehend_relational() -> committed Relation vs hand-authored gold triple.
The gold ``(predicate, subject, object)`` is authored by reading English semantics,
INDEPENDENTLY of ``relational.py`` (INV-25 / INV-27): the reader never produced it.
A refusal is a COVERAGE miss, never a wrong; only a committed relation that disagrees
with gold — or a malformed commit (≠ exactly one relation, or a query) — is wrong, and
wrong must stay 0. This is the positive-coverage lane that puts the just-landed reader
ON the capability index (breadth 8→9); the adversarial fabrication cases that must
REFUSE live in ``evals/relational/v1/refusals.jsonl`` and the dedicated lane test.
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
from typing import Any
from generate.meaning_graph.reader import Refusal
from generate.meaning_graph.relational import (
comprehend_relational,
load_relational_pack_lemmas,
)
_CASES = Path(__file__).resolve().parent.parent / "relational" / "v1" / "cases.jsonl"
def _load_cases(path: Path = _CASES) -> list[dict[str, Any]]:
return [
json.loads(line)
for line in path.read_text(encoding="utf-8").splitlines()
if line.strip()
]
def run(path: Path = _CASES) -> dict[str, Any]:
cases = _load_cases(path)
pack = load_relational_pack_lemmas()
correct = wrong = refused = 0
wrongs: list[dict[str, Any]] = []
for case in cases:
comp = comprehend_relational(case["text"], pack)
if isinstance(comp, Refusal):
refused += 1 # coverage miss, not a wrong
continue
rels = comp.meaning_graph.relations
if comp.queries or len(rels) != 1:
# a declarative fact case must yield exactly one relation and no query;
# anything else is a malformed commit, counted as wrong (must stay 0).
wrong += 1
wrongs.append({"id": case.get("id"), "got": "malformed_commit", "text": case["text"]})
continue
rel = rels[0]
got = [rel.predicate, list(rel.arguments)]
gold = [case["predicate"], [case["subject"], case["object"]]]
if got == gold:
correct += 1
else:
wrong += 1
wrongs.append({"id": case.get("id"), "got": got, "gold": gold, "text": case["text"]})
return {
"domain": "comprehension_relational_predicate",
"total": len(cases),
"correct": correct,
"wrong": wrong,
"refused": refused,
"wrongs": wrongs,
"counts": {"correct": correct, "wrong": wrong, "refused": refused},
}
def main() -> int:
report = run()
print(json.dumps({k: v for k, v in report.items() if k != "wrongs"}, indent=2, sort_keys=True))
if report["wrong"]:
print("WRONG > 0 — relational comprehension produced a wrong committed answer:", file=sys.stderr)
print(json.dumps(report["wrongs"], indent=2), file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())