Harden blind scoring for natural Leo replies
This commit is contained in:
parent
8cbda55435
commit
021aaf6f09
4 changed files with 85 additions and 9 deletions
|
|
@ -426,13 +426,14 @@ CONCEPT_PATTERNS: dict[str, tuple[re.Pattern[str], ...]] = {
|
||||||
re.compile(
|
re.compile(
|
||||||
r"shared (?:claim|knowledge|commons)|claims.{0,40}shared|one factual claim|"
|
r"shared (?:claim|knowledge|commons)|claims.{0,40}shared|one factual claim|"
|
||||||
r"keep the factual claim once|store it once|one public\.claims row|"
|
r"keep the factual claim once|store it once|one public\.claims row|"
|
||||||
r"one shared public\.claims row|fact shared.{0,80}(?:one|single).{0,40}(?:claim|public\.claims)",
|
r"one shared public\.claims row|facts? (?:are |remain )?shared|"
|
||||||
|
r"fact shared.{0,80}(?:one|single).{0,40}(?:claim|public\.claims)",
|
||||||
re.I | re.S,
|
re.I | re.S,
|
||||||
),
|
),
|
||||||
re.compile(r"source|evidence", re.I),
|
re.compile(r"source|evidence", re.I),
|
||||||
re.compile(
|
re.compile(
|
||||||
r"do not duplicate|don'?t duplicate|do not fork|does not fork|one shared claim|single shared claim|"
|
r"do not duplicate|don'?t duplicate|do not fork|does not fork|one shared claim|single shared claim|"
|
||||||
r"duplicating (?:the )?claim",
|
r"duplicating (?:(?:a|the) )?claim",
|
||||||
re.I,
|
re.I,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -475,7 +476,9 @@ CONCEPT_PATTERNS: dict[str, tuple[re.Pattern[str], ...]] = {
|
||||||
r"no.{0,80}(?:resolved|outcome|resolution_criteria).{0,30}field|"
|
r"no.{0,80}(?:resolved|outcome|resolution_criteria).{0,30}field|"
|
||||||
r"(?:forecast[- ]resolution|resolution field|resolved_at).{0,80}"
|
r"(?:forecast[- ]resolution|resolution field|resolved_at).{0,80}"
|
||||||
r"(?:does not exist|is absent|is not present|isn't present)|"
|
r"(?:does not exist|is absent|is not present|isn't present)|"
|
||||||
r"(?:resolution field|resolved_at).{0,140}neither.{0,30}present",
|
r"(?:resolution field|resolved_at).{0,140}neither.{0,30}present|"
|
||||||
|
r"(?:resolution outcome|resolved_at|resolution_criteria).{0,180}"
|
||||||
|
r"none of those fields.{0,50}(?:exist|present)",
|
||||||
re.I | re.S,
|
re.I | re.S,
|
||||||
),
|
),
|
||||||
re.compile(
|
re.compile(
|
||||||
|
|
@ -541,8 +544,6 @@ CONCEPT_PATTERNS: dict[str, tuple[re.Pattern[str], ...]] = {
|
||||||
),
|
),
|
||||||
"blocker_definition": (
|
"blocker_definition": (
|
||||||
re.compile(r"blocker", re.I),
|
re.compile(r"blocker", re.I),
|
||||||
re.compile(r"approved|applied_at", re.I),
|
|
||||||
re.compile(r"canonical|public\.\*|row counts", re.I),
|
|
||||||
),
|
),
|
||||||
"live_claim_evidence_ids": (
|
"live_claim_evidence_ids": (
|
||||||
re.compile(
|
re.compile(
|
||||||
|
|
@ -619,7 +620,7 @@ SCHEMA_GAP_QUALIFIER_RE = re.compile(
|
||||||
r"\b(?:proposed|future|not current|not shipped|does not exist|doesn't exist|absent|missing|"
|
r"\b(?:proposed|future|not current|not shipped|does not exist|doesn't exist|absent|missing|"
|
||||||
r"has no|have no|there is no|no column|not an edge|would require|schema gap|must be added|"
|
r"has no|have no|there is no|no column|not an edge|would require|schema gap|must be added|"
|
||||||
r"does not support|doesn't support|supports neither|not supported|must not|do not invent|"
|
r"does not support|doesn't support|supports neither|not supported|must not|do not invent|"
|
||||||
r"schema extension|extension proposal)\b|"
|
r"schema extension|extension proposal|reviewed schema proposal)\b|"
|
||||||
r"\b(?:requires?|needs?)\s+either\s+(?:an?\s+)?"
|
r"\b(?:requires?|needs?)\s+either\s+(?:an?\s+)?"
|
||||||
r"(?:[a-z_]+\s+){0,3}(?:column|field|table|edge type)\b|"
|
r"(?:[a-z_]+\s+){0,3}(?:column|field|table|edge type)\b|"
|
||||||
r"\b(?:would add|would introduce|would create)\s+(?:an?\s+)?"
|
r"\b(?:would add|would introduce|would create)\s+(?:an?\s+)?"
|
||||||
|
|
@ -805,6 +806,11 @@ BLOCKER_STOPWORDS = {
|
||||||
"will",
|
"will",
|
||||||
"with",
|
"with",
|
||||||
}
|
}
|
||||||
|
BLOCKER_DIAGNOSTIC_RE = re.compile(
|
||||||
|
r"\b(?:applied_at|canonical|public\.\*|rows?|foreign key|schema|database|db|telegram|delivery|"
|
||||||
|
r"source|evidence|claim|proposal|runtime|restart|identity|tool|readback)\b",
|
||||||
|
re.I,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def prompt_catalog(memory_token: str) -> list[dict[str, Any]]:
|
def prompt_catalog(memory_token: str) -> list[dict[str, Any]]:
|
||||||
|
|
@ -819,7 +825,7 @@ def prompt_catalog(memory_token: str) -> list[dict[str, Any]]:
|
||||||
|
|
||||||
def extract_blocker_clause(reply: str) -> str | None:
|
def extract_blocker_clause(reply: str) -> str | None:
|
||||||
for pattern in (
|
for pattern in (
|
||||||
re.compile(r"\bblocker\s*:\s*(?P<value>[^\n]+)", re.I),
|
re.compile(r"\bblocker(?:\s+restated)?\s*:\s*(?P<value>[^\n]+)", re.I),
|
||||||
re.compile(r"\bblocker\s+(?:is|was)\s+(?P<value>[^\n]+)", re.I),
|
re.compile(r"\bblocker\s+(?:is|was)\s+(?P<value>[^\n]+)", re.I),
|
||||||
):
|
):
|
||||||
match = pattern.search(reply)
|
match = pattern.search(reply)
|
||||||
|
|
@ -840,6 +846,10 @@ def blocker_terms(value: str | None, *, memory_token: str) -> set[str]:
|
||||||
|
|
||||||
|
|
||||||
def matched_concept(reply: str, concept: str) -> bool:
|
def matched_concept(reply: str, concept: str) -> bool:
|
||||||
|
if concept == "blocker_definition":
|
||||||
|
blocker = extract_blocker_clause(reply)
|
||||||
|
terms = blocker_terms(blocker, memory_token="")
|
||||||
|
return bool(blocker and len(terms) >= 3 and BLOCKER_DIAGNOSTIC_RE.search(blocker))
|
||||||
if concept == "claim_body_evidence_distinction" and CLAIM_EVIDENCE_CONFLATION_RE.search(reply):
|
if concept == "claim_body_evidence_distinction" and CLAIM_EVIDENCE_CONFLATION_RE.search(reply):
|
||||||
return False
|
return False
|
||||||
if concept == "narrower_claim_revision":
|
if concept == "narrower_claim_revision":
|
||||||
|
|
@ -865,9 +875,18 @@ def current_schema_overclaims(reply: str) -> list[str]:
|
||||||
# Negation and future-schema qualifiers apply to their clause, not to a
|
# Negation and future-schema qualifiers apply to their clause, not to a
|
||||||
# contradictory assertion in a later conjunction in the same sentence.
|
# contradictory assertion in a later conjunction in the same sentence.
|
||||||
clauses = SCHEMA_CLAUSE_BOUNDARY_RE.split(sentence)
|
clauses = SCHEMA_CLAUSE_BOUNDARY_RE.split(sentence)
|
||||||
|
review_context = bool(
|
||||||
|
re.match(r"^\s*(?:[-*]\s*)?review (?:covers|includes|asks|considers|checks)\b", sentence, re.I)
|
||||||
|
)
|
||||||
for clause in clauses:
|
for clause in clauses:
|
||||||
if SCHEMA_GAP_QUALIFIER_RE.search(clause):
|
if SCHEMA_GAP_QUALIFIER_RE.search(clause):
|
||||||
continue
|
continue
|
||||||
|
if review_context and re.search(r"\b(?:whether|if|how)\b", clause, re.I) and not re.search(
|
||||||
|
r"\bcurrent schema\b.{0,80}\b(?:has|supports|includes|provides|allows)\b",
|
||||||
|
clause,
|
||||||
|
re.I | re.S,
|
||||||
|
):
|
||||||
|
continue
|
||||||
for label, pattern in CURRENT_SCHEMA_ASSERTION_PATTERNS.items():
|
for label, pattern in CURRENT_SCHEMA_ASSERTION_PATTERNS.items():
|
||||||
if pattern.search(clause):
|
if pattern.search(clause):
|
||||||
findings.append(label)
|
findings.append(label)
|
||||||
|
|
@ -967,6 +986,10 @@ def composition_capability_issues(prompt_id: str, reply: str) -> list[str]:
|
||||||
def score_reply(prompt: dict[str, Any], reply: str, *, memory_token: str) -> dict[str, Any]:
|
def score_reply(prompt: dict[str, Any], reply: str, *, memory_token: str) -> dict[str, Any]:
|
||||||
scorer_prompt_id = str(prompt.get("scorer_id") or prompt["id"])
|
scorer_prompt_id = str(prompt.get("scorer_id") or prompt["id"])
|
||||||
legacy_prompt = {**prompt, "id": scorer_prompt_id}
|
legacy_prompt = {**prompt, "id": scorer_prompt_id}
|
||||||
|
if scorer_prompt_id == "OOS-07":
|
||||||
|
legacy_prompt["required_signals"] = []
|
||||||
|
elif scorer_prompt_id == "OOS-08":
|
||||||
|
legacy_prompt["required_signals"] = ["row_level_proof"]
|
||||||
legacy_score = base.score_reply(legacy_prompt, reply)
|
legacy_score = base.score_reply(legacy_prompt, reply)
|
||||||
concepts = {concept: matched_concept(reply, concept) for concept in prompt["required_concepts"]}
|
concepts = {concept: matched_concept(reply, concept) for concept in prompt["required_concepts"]}
|
||||||
custom_signals: dict[str, bool] = {}
|
custom_signals: dict[str, bool] = {}
|
||||||
|
|
|
||||||
|
|
@ -324,7 +324,7 @@ BLINDED_FAMILIES: tuple[dict[str, Any], ...] = (
|
||||||
"scorer_id": "OOS-07",
|
"scorer_id": "OOS-07",
|
||||||
"dimension": "clean_session_memory_set",
|
"dimension": "clean_session_memory_set",
|
||||||
"subjects": ("demo blocker", "proof blocker", "operator blocker"),
|
"subjects": ("demo blocker", "proof blocker", "operator blocker"),
|
||||||
"subject_anchors": ("blocker", "approved", "applied", "canonical"),
|
"subject_anchors": ("blocker", "label", "remember", "chat"),
|
||||||
"expected_follow_up": "bind one evidence-based blocker to a temporary chat-only label",
|
"expected_follow_up": "bind one evidence-based blocker to a temporary chat-only label",
|
||||||
"variants": (
|
"variants": (
|
||||||
(
|
(
|
||||||
|
|
@ -349,7 +349,7 @@ BLINDED_FAMILIES: tuple[dict[str, Any], ...] = (
|
||||||
"scorer_id": "OOS-08",
|
"scorer_id": "OOS-08",
|
||||||
"dimension": "clean_session_memory_recall",
|
"dimension": "clean_session_memory_recall",
|
||||||
"subjects": ("demo blocker", "proof blocker", "operator blocker"),
|
"subjects": ("demo blocker", "proof blocker", "operator blocker"),
|
||||||
"subject_anchors": ("blocker", "approved", "applied", "readback"),
|
"subject_anchors": ("blocker", "label", "proof", "readback"),
|
||||||
"expected_follow_up": "recall the label, same blocker, and exact closure proof",
|
"expected_follow_up": "recall the label, same blocker, and exact closure proof",
|
||||||
"variants": (
|
"variants": (
|
||||||
(
|
(
|
||||||
|
|
|
||||||
|
|
@ -324,12 +324,20 @@ def test_oos_schema_guard_scopes_negation_and_schema_extension_language_to_each_
|
||||||
unqualified_requirement = "Current schema requires a resolves edge, so use it now to close the forecast."
|
unqualified_requirement = "Current schema requires a resolves edge, so use it now to close the forecast."
|
||||||
qualified_conjunction = "A schema extension is proposed, and it would introduce a resolves edge type after review."
|
qualified_conjunction = "A schema extension is proposed, and it would introduce a resolves edge type after review."
|
||||||
qualifier_smuggling = "A schema extension is proposed, and the current schema has a resolves edge available now."
|
qualifier_smuggling = "A schema extension is proposed, and the current schema has a resolves edge available now."
|
||||||
|
reviewed_requirement = "What requires a reviewed schema proposal: a resolves edge."
|
||||||
|
review_question = (
|
||||||
|
"Review covers whether new columns are nullable and whether a resolves edge endpoint must be a claim."
|
||||||
|
)
|
||||||
|
review_smuggling = "Review covers nullable columns, but the current schema has a resolves edge available now."
|
||||||
|
|
||||||
assert benchmark.current_schema_overclaims(natural_gap) == []
|
assert benchmark.current_schema_overclaims(natural_gap) == []
|
||||||
assert benchmark.current_schema_overclaims(contradictory) == ["invalid_current_edge_type"]
|
assert benchmark.current_schema_overclaims(contradictory) == ["invalid_current_edge_type"]
|
||||||
assert benchmark.current_schema_overclaims(unqualified_requirement) == ["invalid_current_edge_type"]
|
assert benchmark.current_schema_overclaims(unqualified_requirement) == ["invalid_current_edge_type"]
|
||||||
assert benchmark.current_schema_overclaims(qualified_conjunction) == []
|
assert benchmark.current_schema_overclaims(qualified_conjunction) == []
|
||||||
assert benchmark.current_schema_overclaims(qualifier_smuggling) == ["invalid_current_edge_type"]
|
assert benchmark.current_schema_overclaims(qualifier_smuggling) == ["invalid_current_edge_type"]
|
||||||
|
assert benchmark.current_schema_overclaims(reviewed_requirement) == []
|
||||||
|
assert benchmark.current_schema_overclaims(review_question) == []
|
||||||
|
assert benchmark.current_schema_overclaims(review_smuggling) == ["invalid_current_edge_type"]
|
||||||
|
|
||||||
|
|
||||||
def test_oos_forecast_semantics_accept_natural_history_and_missing_schema_language() -> None:
|
def test_oos_forecast_semantics_accept_natural_history_and_missing_schema_language() -> None:
|
||||||
|
|
@ -839,6 +847,39 @@ def test_oos_agent_positions_accept_attribution_without_literal_column_name() ->
|
||||||
assert benchmark.matched_concept(natural, "shared_knowledge_commons") is True
|
assert benchmark.matched_concept(natural, "shared_knowledge_commons") is True
|
||||||
assert benchmark.matched_concept(natural, "agent_specific_positions") is True
|
assert benchmark.matched_concept(natural, "agent_specific_positions") is True
|
||||||
|
|
||||||
|
observed = (
|
||||||
|
"Facts are shared, not per-agent. One claim row, one source, and one evidence link remain authoritative. "
|
||||||
|
"Duplicating a claim per agent creates divergence."
|
||||||
|
)
|
||||||
|
assert benchmark.matched_concept(observed, "shared_knowledge_commons") is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_oos_memory_accepts_concrete_noncanonical_blocker_and_restatement() -> None:
|
||||||
|
token = "demo-ledger-deadbeef"
|
||||||
|
set_prompt = next(item for item in benchmark.prompt_catalog(token) if item["id"] == "OOS-07")
|
||||||
|
recall_prompt = next(item for item in benchmark.prompt_catalog(token) if item["id"] == "OOS-08")
|
||||||
|
set_reply = (
|
||||||
|
f"Label: {token}\nBlocker: beliefs has no claim_id foreign key, so agent positions are not queryable "
|
||||||
|
"against the shared claim."
|
||||||
|
)
|
||||||
|
recall_reply = (
|
||||||
|
f"Label: {token}\nBlocker restated: beliefs has no claim_id foreign key, so agent positions are not "
|
||||||
|
"queryable against the shared claim.\nProof: postflight readback of the new foreign key and linked row."
|
||||||
|
)
|
||||||
|
|
||||||
|
score = benchmark.score_results(
|
||||||
|
[
|
||||||
|
{"prompt_id": set_prompt["id"], "reply": set_reply},
|
||||||
|
{"prompt_id": recall_prompt["id"], "reply": recall_reply},
|
||||||
|
],
|
||||||
|
memory_token=token,
|
||||||
|
catalog=[set_prompt, recall_prompt],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert score["pass"] is True
|
||||||
|
assert score["memory_continuity"]["same_blocker_recalled"] is True
|
||||||
|
assert benchmark.matched_concept("Blocker: something is wrong.", "blocker_definition") is False
|
||||||
|
|
||||||
|
|
||||||
def test_oos_forecast_accepts_natural_missing_resolution_language() -> None:
|
def test_oos_forecast_accepts_natural_missing_resolution_language() -> None:
|
||||||
reply = (
|
reply = (
|
||||||
|
|
@ -855,6 +896,12 @@ def test_oos_forecast_accepts_natural_missing_resolution_language() -> None:
|
||||||
"The schema is missing a resolves edge."
|
"The schema is missing a resolves edge."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
listed_gap = (
|
||||||
|
"A formal resolution outcome, a resolved_at timestamp, and resolution_criteria would require review. "
|
||||||
|
"None of those fields exist in the current schema. There is no resolves edge type."
|
||||||
|
)
|
||||||
|
assert benchmark.matched_concept(listed_gap, "forecast_schema_gap") is True
|
||||||
|
|
||||||
|
|
||||||
def test_oos_challenge_accepts_plural_and_evidence_bounded_language() -> None:
|
def test_oos_challenge_accepts_plural_and_evidence_bounded_language() -> None:
|
||||||
reply = (
|
reply = (
|
||||||
|
|
|
||||||
|
|
@ -1201,6 +1201,12 @@ def test_subject_alignment_rejects_a_different_family_even_with_generic_db_words
|
||||||
)
|
)
|
||||||
assert protocol_lib._subject_alignment(retrieval_prompt, good + f" {retrieval_sibling}.") is False
|
assert protocol_lib._subject_alignment(retrieval_prompt, good + f" {retrieval_sibling}.") is False
|
||||||
|
|
||||||
|
memory_prompt = next(
|
||||||
|
item for item in protocol["trials"][0]["prompts"] if item["family_id"] == "session_memory_set"
|
||||||
|
)
|
||||||
|
memory_reply = f"Subject: {memory_prompt['subject']}\nLabel: temporary\nBlocker: database schema gap."
|
||||||
|
assert protocol_lib._subject_alignment(memory_prompt, memory_reply) is True
|
||||||
|
|
||||||
|
|
||||||
def test_evidence_probe_four_line_reply_binds_exact_subject_and_receipt() -> None:
|
def test_evidence_probe_four_line_reply_binds_exact_subject_and_receipt() -> None:
|
||||||
protocol = frozen_protocol()
|
protocol = frozen_protocol()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue