DEG-5B-E2B-1019 / source /tests /test_suite.py
gcoderw's picture
DEG-5B-E2B-1019 v0.2 (multimodal)
f8dbde1 verified
Raw History Blame Contribute Delete
8.37 kB
"""deg-suite v1 aggregation, validity gate, judge cache and training guard (CPU only)."""
from __future__ import annotations
import json
import pytest
from deg.bench import suite
from deg.bench.score import Prediction
from deg.bench.suite import SOURCES, Source, SuiteGuard, aggregate_suite, score_source
from deg.bench.suite_judge import Item, Judge, grade_score, parse_grade
def test_headline_is_mean_of_capability_means():
comps = {"decide": {"a": 1.0, "b": 0.0, "c": 0.5, "d": 0.5}, # 4 sources -> 0.5
"extract": {"a": 0.9}, # 1 source -> 0.9
"generate": {"a": 0.2, "b": 0.4}} # 2 sources -> 0.3
out = aggregate_suite(comps)
assert out["capabilities"] == pytest.approx({"decide": 0.5, "extract": 0.9, "generate": 0.3})
assert out["headline"] == pytest.approx((0.5 + 0.9 + 0.3) / 3)
def test_adding_a_source_cannot_swamp_other_capabilities():
base = {"decide": {"a": 0.8}, "extract": {"a": 0.6}, "generate": {"a": 0.4}}
more = {**base, "decide": {"a": 0.8, **{f"x{i}": 1.0 for i in range(50)}}}
h0, h1 = aggregate_suite(base)["headline"], aggregate_suite(more)["headline"]
# 50 perfect decide sources move only the DECIDE third of the headline
assert h1 - h0 <= (1.0 - 0.8) / 3 + 1e-9
assert aggregate_suite(more)["capabilities"]["extract"] == pytest.approx(0.6)
def test_missing_capability_gives_no_headline():
out = aggregate_suite({"decide": {"a": 0.9, "b": None}, "extract": {}, "generate": {}})
assert out["headline"] is None
assert out["capabilities"]["decide"] == pytest.approx(0.9)
assert out["missing_capabilities"] == ["extract", "generate"]
def test_component_out_of_range_rejected():
with pytest.raises(ValueError):
aggregate_suite({"decide": {"a": 1.5}, "extract": {"a": 0}, "generate": {"a": 0}})
def test_every_capability_has_sources():
for cap in suite.CAPABILITIES:
assert sum(cap in s.caps for s in SOURCES) >= 2
def test_jev_intelligence_chance_corrected_and_tier_weighted():
# easy: 2/2 correct on noul (chance .5) -> 1.0; hard: 1/2 on noul -> 0.0
rows = [("easy", True, 2), ("easy", True, 2), ("hard", True, 2), ("hard", False, 2)]
w = suite.JEV_TIER_WEIGHTS
assert suite.jev_intelligence(rows) == pytest.approx(w["easy"] / (w["easy"] + w["hard"]))
assert suite.jev_intelligence([("standard", False, 4)]) == 0.0 # clipped at 0
SCHEMA = {"type": "object", "properties": {
"kind": {"enum": ["a", "b"]},
"name": {"type": "string"},
"items": {"type": "array", "items": {"type": "string"}},
"note": {"type": "string", "x-generate": True, "maxLength": 20}},
"required": ["kind", "name", "items", "note"], "additionalProperties": False}
REC = {"uid": "r1", "context": "Name: Ada. Items: x, y.", "schema": SCHEMA,
"value": {"kind": "a", "name": "Ada", "items": ["x", "y"], "note": "ok"}}
class FixedJudge:
model = "fixed"
calls = 0
def score_many(self, items):
return [0.5 for _ in items]
def test_fields_scores_and_validity_gate():
src = Source("t", "fields", ("decide", "extract", "generate"))
good = Prediction("r1", value={"kind": "a", "name": "Ada", "items": ["x", "y"],
"note": "fine"})
out = score_source(src, [REC], [good], ("decide", "extract", "generate"), FixedJudge())
assert out["components"]["decide"] == 1.0
assert out["components"]["extract"] == 1.0 # mean(norm 1.0, count 1.0)
assert out["components"]["generate"] == 0.5
# the same correct fields with one schema violation (note over maxLength): all zero
bad = Prediction("r1", value={"kind": "a", "name": "Ada", "items": ["x", "y"],
"note": "x" * 30})
out = score_source(src, [REC], [bad], ("decide", "extract", "generate"), FixedJudge())
assert out["valid_rate"] == 0.0
assert out["components"] == {"decide": 0.0, "extract": 0.0, "generate": 0.0}
err = Prediction("r1", error="OutOfMemoryError: ...")
out = score_source(src, [REC], [err], ("decide", "extract", "generate"), FixedJudge())
assert out["components"] == {"decide": 0.0, "extract": 0.0, "generate": 0.0}
def test_decide_only_system_is_not_gated_by_validity():
src = Source("t", "fields", ("decide", "extract", "generate"))
out = score_source(src, [REC], [Prediction("r1", value={"kind": "a"})], ("decide",), None)
assert out["components"] == {"decide": 1.0}
def test_squad_balances_answerable_and_unanswerable():
rec = {"uid": "s", "context": "Paris is in France.",
"schema": {"type": "object", "properties": {
"q1": {"type": ["string", "null"]}, "q2": {"type": ["string", "null"]}},
"required": ["q1", "q2"], "additionalProperties": False},
"value": {"q1": "France", "q2": None}, "alternatives": {"/q1": ["France"], "/q2": []}}
src = Source("squad2", "squad", ("extract",))
always_null = Prediction("s", value={"q1": None, "q2": None})
out = score_source(src, [rec], [always_null], ("decide", "extract", "generate"), None)
assert out["components"]["extract"] == pytest.approx(0.5)
right = Prediction("s", value={"q1": "france.", "q2": None})
out = score_source(src, [rec], [right], ("decide", "extract", "generate"), None)
assert out["components"]["extract"] == pytest.approx(1.0)
def test_judge_cache_is_deterministic(tmp_path):
calls = []
def call(prompt):
calls.append(prompt)
return '{"faithful": 2, "responsive": 1}'
item = Item("ctx", "note", "a note", 40, "text")
j = Judge(tmp_path / "c.jsonl", call=call, model="m")
assert j.score_many([item, item]) == [0.75, 0.75]
j2 = Judge(tmp_path / "c.jsonl", call=lambda p: pytest.fail("cache miss"), model="m")
assert j2.score_many([item]) == [0.75]
assert len(calls) <= 2 # concurrent duplicates may both call; the cache keeps the result
other = Item("ctx", "note", "a note", 40, "other text")
assert other.key("m") != item.key("m") and item.key("m") != item.key("m2")
def test_parse_grade():
assert grade_score(parse_grade('ok {"faithful": 1, "responsive": 2}')) == 0.75
with pytest.raises(ValueError):
parse_grade('{"faithful": 3, "responsive": 2}')
def test_suite_guard_and_path_guard():
long_ctx = ("The quarterly report says revenue grew eleven percent while operating "
"margins narrowed because of freight costs and a weaker euro this year")
g = SuiteGuard(texts=[long_ctx, "How do I locate my card?"])
assert g.match("prefix text. " + long_ctx + " suffix") is not None
assert g.match("how do I locate my card") is not None
assert g.match("Where can I locate my new card after moving house last week?") is None
assert g.match("A completely unrelated document about gardening tools.") is None
with pytest.raises(ValueError):
suite.guard_path("/data/deg/suite-v1/mixed.jsonl")
suite.guard_path("/data/deg/datagen/full-off/records.jsonl")
def test_frozen_files_match_manifest_when_present():
if not (suite.RESULTS / "MANIFEST.json").exists():
pytest.skip("suite not built on this host")
for name in json.loads((suite.RESULTS / "MANIFEST.json").read_text())["sources"]:
if (suite.SUITE_DIR / f"{name}.jsonl").exists():
assert suite.load_source(name)
def test_missing_source_withholds_headline(tmp_path, monkeypatch):
"""A built source without predictions must not silently reweight its capability."""
recs = {"datagen-test": [REC]}
monkeypatch.setattr(suite, "manifest", lambda: {"sources": {
"datagen-test": {"sha256": "x"}, "extractbench": {"sha256": "y"}}})
monkeypatch.setattr(suite, "load_source", lambda name, verify=True: recs[name])
sdir = tmp_path / "sys"
sdir.mkdir()
good = Prediction("r1", value={"kind": "a", "name": "Ada", "items": ["x", "y"],
"note": "fine"})
(sdir / "datagen-test.preds.jsonl").write_text(json.dumps(good.to_json()) + "\n")
card = suite.score_system(tmp_path, "sys", ("decide", "extract", "generate"), FixedJudge())
assert card["incomplete_sources"] == ["extractbench"]
assert card["headline"] is None and card["capabilities"]["extract"] is None
assert card["capabilities"]["decide"] == 1.0