qa: drop manual-quote rule from pointer verifier; port G0 policy to runner
Removes the strict no-double-quote check (`MANUAL_QUOTE_VIOLATION`) from
verify_claim_lattice. The rule was rejecting factually correct,
source-grounded claims for cosmetic punctuation: Hermes-3-8B paraphrases
prose but copies named-quoted phrases verbatim from source (e.g.
`"Constitution State"` lifted from a Connecticut chunk). Pre-fix,
`make query Q="tell me about connecticut"` reported `0/13 verified` on
a paragraph where every claim was correct and source-supported, just
because the model preserved source quote marks.
The two checks that remain handle the cases the manual_quote rule was
nominally meant to catch:
- claim_lattice_min_citation_coverage (Rule 5, default 0.30)
catches lazy-anchor citations whose only overlap with the cited
span is a single topical token
- claim_lattice_max_pointers_per_claim (Rule 6, default 2)
catches the encyclopedic-mega-claim where the model emits
`[E1,E2,...,E26]` after one sentence
Bench n=3 across 8 fixed questions vs pre-G0-hardening baseline (also
n=3): pointer-mode grounded count (STRICT+HYBRID) goes 15 → 21,
UNGROUNDED 9 → 3, broad-descriptive failures (connecticut, python)
fully cured. STRICT count goes 12 → 9 because previously-bogus STRICTs
(microsoft cited transit chunks that just shared one token, supermans
lazy-anchor magnet at 32/32) are now honestly reclassified to HYBRID.
Side changes:
- Ports max_pointers / min_citation_coverage policy fields and
verify_claim_lattice call into runner.py so single-document `ask`
matches multi-source `query` semantics (query.py already had them
via b39e79b).
- Worked Example 2 (broad-descriptive) added to pointer-mode prompt
so "tell me about X" has a template, plus Rules 7 (anti-echo) and
8 (max-2-pointers).
- Two manual_quote tests in tests/test_claim_lattice.py inverted to
document the new behavior (quotes-in-claim no longer block).
- `_has_manual_quote` retained — still used by verify_claim_lattice_json.
459 tests pass.
This commit is contained in:
parent
2601575f15
commit
224bfd6a2b
4 changed files with 107 additions and 57 deletions
|
|
@ -227,25 +227,35 @@ def test_source_role_blocked_violation():
|
|||
assert v["claim_statuses"][0]["status"] == "SOURCE_ROLE_BLOCKED"
|
||||
|
||||
|
||||
def test_strict_no_quote_rule_any_double_quote_violates():
|
||||
"""Even a 4-char quoted span triggers — model is forbidden to type
|
||||
quote characters at all in pointer mode."""
|
||||
def test_double_quote_in_claim_text_no_longer_blocks_verification():
|
||||
"""Pre-2026-04-30: any `"` in claim text was a hard MANUAL_QUOTE_VIOLATION
|
||||
that blocked every pointer on the claim — even when the claim was
|
||||
factually correct and source-grounded. Hermes-3-8B paraphrases prose
|
||||
but copies named-quoted phrases verbatim from source (e.g.
|
||||
`"Constitution State"` from a Connecticut chunk), so the rule was
|
||||
rejecting good claims for cosmetic punctuation. Removed in favor
|
||||
of the coverage-threshold check (Rule 5) and pointer cap (Rule 6).
|
||||
|
||||
The claim below would previously have failed with MANUAL_QUOTE_VIOLATION;
|
||||
now it stands or falls on whether the cited evidence actually supports
|
||||
it — same as any quote-free claim.
|
||||
"""
|
||||
em = build_evidence_map(_sample_chunks())
|
||||
answer = 'The "T-rex" appears. [E1]\n'
|
||||
v = verify_claim_lattice(answer, em)
|
||||
assert v["audit_mode"] == "UNGROUNDED"
|
||||
assert any(viol["kind"] == "MANUAL_QUOTE_VIOLATION"
|
||||
for viol in v["violations"])
|
||||
assert v["claim_statuses"][0]["status"] == "MANUAL_QUOTE_VIOLATION"
|
||||
# No MANUAL_QUOTE_VIOLATION emitted anywhere.
|
||||
assert not any(viol["kind"] == "MANUAL_QUOTE_VIOLATION"
|
||||
for viol in v["violations"])
|
||||
assert v["claim_statuses"][0]["status"] != "MANUAL_QUOTE_VIOLATION"
|
||||
|
||||
|
||||
def test_curly_quotes_also_violate():
|
||||
def test_curly_quotes_also_no_longer_block():
|
||||
"""Mirrors the ASCII-quote case for curly typographic quotes."""
|
||||
em = build_evidence_map(_sample_chunks())
|
||||
answer = "The “T-rex” appears. [E1]\n"
|
||||
v = verify_claim_lattice(answer, em)
|
||||
assert v["audit_mode"] == "UNGROUNDED"
|
||||
assert any(viol["kind"] == "MANUAL_QUOTE_VIOLATION"
|
||||
for viol in v["violations"])
|
||||
assert not any(viol["kind"] == "MANUAL_QUOTE_VIOLATION"
|
||||
for viol in v["violations"])
|
||||
|
||||
|
||||
def test_no_evidence_pointer_downgrades():
|
||||
|
|
@ -749,7 +759,16 @@ def test_per_chunk_evidence_map_query_path(tmp_path):
|
|||
"Velociraptor is a theropod. [E2]\n"
|
||||
"T-rex is apex. [E3]\n"
|
||||
))
|
||||
policy = dict(DEFAULT_QUERY_POLICY, answer_mode="claim_lattice_pointer")
|
||||
# Override the per-source chunk cap so all three chunks of this
|
||||
# single test source surface as E1/E2/E3. Production default caps
|
||||
# at 2 chunks per source to keep the evidence catalog small for
|
||||
# broad-descriptive questions; this test is asserting the per-chunk
|
||||
# mapping itself, so it opts into the unbounded path.
|
||||
policy = dict(
|
||||
DEFAULT_QUERY_POLICY,
|
||||
answer_mode="claim_lattice_pointer",
|
||||
claim_lattice_max_chunks_per_source=8,
|
||||
)
|
||||
result = query(
|
||||
question="dinosaur paragraphs",
|
||||
qa_db=qa_db,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue