candidate_clauses() — NLI now runs only on the top-N source clauses by content-token overlap with the answer claim (max_candidate_clauses=6), not the whole context; records n_candidate_clauses / best_clause_overlap / recombination_risk. Synthetic sweep unchanged (28/28 recombination, 0/26 legit FP, mean 1.45 candidate clauses/record). Real-traffic smoke re-run: STRICT would-demote 30% → 20%, overall 47% → 33% — better, not fixed; recombination-risk split doesn't separate either. Residual STRICT false-contras at ~0.83-0.92 → θc would need ≈ 0.90 (vs the clean-set 0.5); at θc=0.90 the data in hand gives 27/28 synthetic recall, 0/26 legit FP, 0/10 smoke STRICT FP — but n=10 is too small to set on. Next: a fuller ARBORIST_NLI_SHADOW=1 bench-qa run → sweep θc on hundreds of STRICT cells → confirm → set it. θc stays 0.5; runtime NLI demotion stays off. Production verifier unchanged; falsification-hard stays 10/12.
This commit is contained in:
parent
f4de936ff9
commit
02f8dfec07
8 changed files with 730 additions and 124 deletions
|
|
@ -16,7 +16,7 @@ from pathlib import Path
|
|||
import pytest
|
||||
|
||||
from arborist.qa.nli import ShadowNLI, ShadowResult, shadow_check, load_manifest
|
||||
from arborist.qa.nli.shadow import clauses, _resolve_label_indices
|
||||
from arborist.qa.nli.shadow import clauses, candidate_clauses, _content_tokens, _resolve_label_indices
|
||||
|
||||
|
||||
# --- clause splitter -------------------------------------------------------
|
||||
|
|
@ -42,6 +42,58 @@ def test_clauses_empty_or_blank():
|
|||
assert clauses(None) == [] # type: ignore[arg-type]
|
||||
|
||||
|
||||
# --- candidate-clause restriction (§7 #5 step 3 / §7 #20) ------------------
|
||||
|
||||
def test_content_tokens_filters_short_and_stopwords():
|
||||
toks = _content_tokens("The Mercury is the largest planet in the Solar System.")
|
||||
assert "mercury" in toks and "largest" in toks and "planet" in toks and "solar" in toks
|
||||
assert "the" not in toks and "is" not in toks and "in" not in toks # stopword / too short
|
||||
|
||||
|
||||
def test_candidate_clauses_ranks_by_overlap_and_caps():
|
||||
claim = "Mercury is the largest planet in the Solar System."
|
||||
source = ("Jupiter is the largest planet in the Solar System. "
|
||||
"Mercury is the smallest planet in the Solar System. "
|
||||
"The Roman god Mercury was the messenger of the gods. "
|
||||
"Bananas are yellow.")
|
||||
cand = candidate_clauses(claim, source, k=6)
|
||||
# the banana clause shares no content tokens → dropped
|
||||
assert all("Bananas" not in c for c, _ in cand)
|
||||
# the two planet clauses are the highest-overlap ones
|
||||
assert "Jupiter is the largest planet" in cand[0][0] or "Mercury is the smallest planet" in cand[0][0]
|
||||
assert cand[0][1] > cand[-1][1] or len(cand) == 1
|
||||
# cap respected
|
||||
assert len(candidate_clauses(claim, source, k=1)) == 1
|
||||
|
||||
|
||||
def test_candidate_clauses_empty_when_no_overlap():
|
||||
assert candidate_clauses("Bananas are yellow.", "Mercury is the smallest planet.", k=6) == []
|
||||
assert candidate_clauses("", "anything at all here", k=6) == []
|
||||
|
||||
|
||||
def test_check_restricts_to_candidate_clauses_not_whole_haystack():
|
||||
# a long source where only 2 clauses touch the claim — the result must
|
||||
# report n_candidate_clauses << n_clauses (this is the §7 #20 fix).
|
||||
haystack = " ".join(f"Unrelated fact number {i} about pottery." for i in range(40))
|
||||
source = "Jupiter is the largest planet. " + haystack + " Mercury is the smallest planet."
|
||||
res = shadow_check("Mercury is the largest planet.", source)
|
||||
assert res.n_clauses >= 40
|
||||
assert res.n_candidate_clauses <= 6
|
||||
assert res.n_candidate_clauses < res.n_clauses
|
||||
|
||||
|
||||
def test_check_no_candidate_clauses_is_no_demote():
|
||||
res = shadow_check("Mercury is the largest planet.",
|
||||
"Bananas are yellow. Pottery is ancient. The weather is fine.")
|
||||
assert res.would_demote is False
|
||||
if res.available:
|
||||
assert res.reason == "no_candidate_clauses" and res.n_candidate_clauses == 0
|
||||
|
||||
|
||||
def test_manifest_has_candidate_clause_cap():
|
||||
assert load_manifest()["max_candidate_clauses"] == 6
|
||||
|
||||
|
||||
# --- manifest --------------------------------------------------------------
|
||||
|
||||
def test_manifest_has_required_fields():
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue