arborist/tests/test_qa_quality_live.py
russell@unturf.com 9c47d5b97b
qa/live: lock 3 bench standouts as STRICT-rate regression gates
Bench finding (2026-04-30, n=3 across 24 questions): JSON mode hit
S:3 H:0 U:0 on three questions where pointer mode landed S:0 H:3 U:0
or quote mode landed S:0 H:0 U:3 — clean modal divergence. Pinning
these as live fixtures gives us regression detection: a JSON-mode
quality drop on these questions reverts the STRICT signal first.

  test_mona_lisa_strict_and_names_leonardo  — was _grounded, tightened
    to STRICT (bench: quote 0S, pointer 0S, JSON 3S)
  test_supermans_girlfriend_strict_and_names_lois_lane (NEW)
    — relationship-shape, JSON's strong suit (quote 0S, pointer 0S,
    JSON 3S)
  test_cold_fusion_breakthrough_year_strict_and_names_1989 (NEW)
    — out-of-corpus rephrasing, atomic-claim rule unlocked grounding
    (quote 0S 3U, pointer 3S, JSON 3S)

Per fox's "scoreboard + gates" docstring: bench gives aggregate
counts; these fixtures fail by name when the scoreboard moves.
2026-04-30 19:47:07 -04:00

396 lines
17 KiB
Python

"""Live QA-quality tests against the real Hermes endpoint.
These tests are deliberately gated — they hit
``hermes.ai.unturf.com/v1`` and require a populated shard set under
``~/.aborist/shards`` (default) or the path passed via
``ABORIST_LIVE_SHARDS_DIR``. Default `make test` does NOT run these;
opt in via ``ABORIST_LIVE_TESTS=1`` (the ``make test-live`` target
sets it).
Why this exists:
The unit tests in ``test_qa.py`` / ``test_query.py`` use ``StubClient``
to validate plumbing (cache lookups, document mapping, schema, audit
chain). They cannot tell you if the system answers ``"who founded
Apple Computer?"`` correctly — that's a model+retrieval+verifier
behavior, not a unit-tested code path. The QA-quality bench
(``make bench-qa``) measures aggregate verdict counts but doesn't
assert specific content. These live fixtures sit between those two
concerns: each test runs ONE known-good question, asserts the
audit_mode is at least HYBRID, and checks for entity-level content
the answer should mention.
"Benchmax" rationale: the bench gives us a scoreboard
(STRICT/HYBRID/UNGROUNDED counts); these tests give us *gates*. When
a future change improves the system, the strict-rate climbs in the
bench AND every fixture here keeps passing or gets stricter
assertions. When a change regresses, the bench number falls AND
specific fixtures fail by name, telling you where the regression
landed.
Every assertion accepts a tolerance band — Hermes is non-deterministic
at temperature > 0, so single-sample tests are inherently flaky on
boundary cases. The bar is "the right entity is present in the
answer", not "the answer is byte-identical to last run". When a
fixture starts failing intermittently, treat it as evidence of
quality drift on that question shape and tighten retrieval / verifier
upstream rather than loosen the test.
"""
from __future__ import annotations
import os
from pathlib import Path
import pytest
_LIVE_OPT_IN = os.environ.get("ABORIST_LIVE_TESTS") == "1"
_SHARDS_DIR = Path(
os.environ.get("ABORIST_LIVE_SHARDS_DIR")
or (Path.home() / ".aborist" / "shards")
)
_QA_DB = _SHARDS_DIR / "qa.db"
_ENDPOINT = os.environ.get(
"ABORIST_LLM_ENDPOINT", "https://hermes.ai.unturf.com/v1"
)
_MODEL = os.environ.get(
"ABORIST_LLM_MODEL",
"adamo1139/Hermes-3-Llama-3.1-8B-FP8-Dynamic",
)
pytestmark = pytest.mark.skipif(
not _LIVE_OPT_IN,
reason="live QA tests gated by ABORIST_LIVE_TESTS=1; "
"run via `make test-live` or set the env var",
)
def _ask(question: str, *, mode: str = "claim_lattice", top_k: int = 8) -> dict:
"""One live query against the configured shard set + Hermes endpoint."""
if not _SHARDS_DIR.exists():
pytest.skip(f"shards dir not found: {_SHARDS_DIR}")
if not _QA_DB.parent.exists():
pytest.skip(f"qa.db parent not found: {_QA_DB.parent}")
# Lazy import so collecting the file doesn't pull qa modules in
# default-skip mode.
from aborist.qa.client import OpenAICompatibleClient
from aborist.qa.query import DEFAULT_QUERY_POLICY, query
api_key = os.environ.get("ABORIST_LLM_API_KEY")
client = OpenAICompatibleClient(base_url=_ENDPOINT, api_key=api_key)
policy = dict(DEFAULT_QUERY_POLICY)
policy["answer_mode"] = mode
return query(
question=question,
qa_db=_QA_DB,
chat_client=client,
model_id=_MODEL,
shards_dir=_SHARDS_DIR,
top_k=top_k,
policy=policy,
burn_existing=True, # forces fresh inference per test
)
def _answer_lower(result: dict) -> str:
return (result.get("answer_text") or "").lower()
def _grounded(result: dict) -> bool:
"""STRICT or HYBRID — at least one pointer/quote pair verified."""
return result.get("audit_mode") in ("STRICT", "HYBRID")
# ---------------------------------------------------------------- narrow factoid
def test_france_capital_strict_and_names_paris():
"""Capital-of-X is the textbook narrow-factoid; we expect STRICT
and the literal city name to appear in the cited or rendered span."""
r = _ask("what is the capital of france?")
assert r.get("audit_mode") == "STRICT", \
f"expected STRICT, got {r.get('audit_mode')}"
assert "paris" in _answer_lower(r), \
f"answer missing 'Paris': {_answer_lower(r)[:200]}"
def test_linux_kernel_grounded_and_names_torvalds():
r = _ask("who wrote the linux kernel?")
assert _grounded(r), f"audit_mode={r.get('audit_mode')!r}"
assert "torvalds" in _answer_lower(r), \
f"answer missing 'Torvalds': {_answer_lower(r)[:200]}"
def test_mona_lisa_strict_and_names_leonardo():
"""Bench standout (2026-04-30, n=3): JSON mode hit S:3 H:0 U:0
while quote landed 0S and pointer 0S. Tightened from _grounded
to STRICT to lock in the JSON-mode unique win as a regression
gate — drop in JSON-mode quality on this question reverts the
STRICT signal first."""
r = _ask("who painted the mona lisa?")
assert r.get("audit_mode") == "STRICT", \
f"expected STRICT (bench 3/3 STRICT in JSON), got {r.get('audit_mode')}"
assert "leonardo" in _answer_lower(r), \
f"answer missing 'Leonardo': {_answer_lower(r)[:200]}"
def test_supermans_girlfriend_strict_and_names_lois_lane():
"""Bench standout (2026-04-30, n=3): JSON mode hit S:3 H:0 U:0
while pointer landed S:0 H:3 (every grounded answer demoted by
lazy-anchor or pointer-saturation). The relationship-shape
question 'who is X's girlfriend' is JSON's strong suit — atomic
claims, single-name target. Gate: STRICT + 'lois' or 'lane'."""
r = _ask("who is supermans girlfriend?")
assert r.get("audit_mode") == "STRICT", \
f"expected STRICT (bench 3/3 STRICT in JSON), got {r.get('audit_mode')}"
txt = _answer_lower(r)
assert "lois" in txt or "lane" in txt, \
f"answer missing Lois/Lane: {txt[:200]}"
def test_cold_fusion_breakthrough_year_strict_and_names_1989():
"""Bench standout (2026-04-30, n=3): originally classified
out-of-corpus but JSON mode hit S:3 H:0 U:0 — the corpus has
Pons & Fleischmann's 1989 announcement covered. Quote mode
landed 0S 0H 3U (UNGROUNDED) so this fixture also catches
quote/JSON divergence. Gate: STRICT + a 1989-era marker
(year, Pons, Fleischmann, or Utah)."""
r = _ask("what year does our cold fusion breakthrough happen?")
assert r.get("audit_mode") == "STRICT", \
f"expected STRICT (bench 3/3 STRICT in JSON), got {r.get('audit_mode')}"
txt = _answer_lower(r)
cold_fusion_markers = ("1989", "pons", "fleischmann", "utah")
assert any(m in txt for m in cold_fusion_markers), \
f"answer missing 1989-era cold-fusion markers: {txt[:300]}"
# ---------------------------------------------------------------- retrieval disambiguation
def test_red_fish_blue_fish_identifies_seuss_book():
"""Stress-tests retrieval disambiguation: "red fish blue fish" partially
matches many noise titles (Red Dwarf, Blue whale, Toronto Blue Jays,
Red Sea, Detroit Red Wings, marine aquarium fish list...). The right
answer is the Dr. Seuss book ``One Fish Two Fish Red Fish Blue Fish``.
The model should anchor on the Seuss article and the answer should
contain a Seuss-specific token.
Pinned to claim_lattice_pointer mode: JSON mode hits a token-
budget runaway on "plot of X" prose-summary shapes for this
question (~2/3 of samples produce malformed JSON with whitespace
spam after the closing brace, blowing the lenient parser's
bounds). Pointer mode reliably hits STRICT 1/1 at 4.5s. Until
JSON-mode max_tokens / prompt discipline addresses the runaway,
this fixture documents that prose-summary questions are
pointer-mode's strength.
"""
r = _ask("plot of red fish blue fish?", mode="claim_lattice_pointer")
assert _grounded(r), f"audit_mode={r.get('audit_mode')!r}"
txt = _answer_lower(r)
seuss_markers = ("seuss", "rhyming", "children's book", "creatures")
assert any(m in txt for m in seuss_markers), \
f"answer missing Seuss-book markers: {txt[:300]}"
# ---------------------------------------------------------------- entity disambiguation
def test_veronica_ballestrini_singer_october_birth():
"""Multi-part question against a niche personal-bio article.
Retrieval surfaces 8 different Veronicas (Landers, Guerin,
Belmont, Wadley, Gamba, Doran, the disambiguation page, and
Ballestrini herself). The model should anchor on Ballestrini's
article and answer both parts: who she is (country singer /
songwriter / Connecticut) and when she was born (October).
Tests entity-disambiguation among same-first-name candidates
plus multi-part-question handling on a small-bio source.
"""
r = _ask(
"who is veronica ballestrini when what month was she born?"
)
assert _grounded(r), f"audit_mode={r.get('audit_mode')!r}"
txt = _answer_lower(r)
assert "ballestrini" in txt, \
f"answer doesn't even mention 'Ballestrini': {txt[:300]}"
# The "who is" half — accept any of the canonical descriptors
# from her bio span.
bio_markers = ("country", "singer", "songwriter", "connecticut", "italian")
assert any(m in txt for m in bio_markers), \
f"answer missing bio markers: {txt[:300]}"
# The "when was she born" half — accept "october" or the full
# date "october 29, 1991" or any 1991 mention as evidence the
# model picked up the birth date span.
birth_markers = ("october", "1991")
assert any(m in txt for m in birth_markers), \
f"answer missing birth markers: {txt[:300]}"
def test_lara_croft_mentions_tomb_raider():
"""Lara Croft (correctly spelled) should pull the Tomb Raider
article; the answer should mention either the franchise or her
role/profession."""
r = _ask("who is lara croft?")
assert _grounded(r), f"audit_mode={r.get('audit_mode')!r}"
txt = _answer_lower(r)
assert (
"tomb raider" in txt
or "archaeologist" in txt
or "video game" in txt
), f"answer missing Tomb Raider context: {txt[:200]}"
def test_laura_croft_finds_at_least_one_real_entity():
"""`Laura Croft` (an extra `u`) is BOTH a real Playmate and a
common misspelling of `Lara Croft`. The system should land on at
least one real entity from the corpus, not hallucinate a third
person."""
r = _ask("who is laura croft?")
assert _grounded(r), f"audit_mode={r.get('audit_mode')!r}"
txt = _answer_lower(r)
# One of: the actual Laura Croft (model) markers, or the Lara
# Croft (character) markers. Either grounding is honest.
# Broad marker net to absorb Hermes single-sample variance — if
# the system grounded at all, ANY of these will land. A failure
# of all of them implies the answer didn't anchor on a real
# corpus entity.
real_entity_markers = (
"playmate", "playboy", "magazine", "model", # Playmate context
"tomb raider", "archaeologist", "video game", # Character context
"fictional", "character", "british", # Generic fallback
)
assert "croft" in txt, f"answer doesn't even mention 'Croft': {txt[:300]}"
assert any(m in txt for m in real_entity_markers), \
f"answer doesn't ground on any known entity: {txt[:300]}"
# ---------------------------------------------------------------- multi-part list
def test_ninja_turtles_lists_all_four_plus_splinter():
"""Multi-part question: all four turtle names + the master's name
must appear in the answer. Tests that the system handles a
conjunctive `&` query and produces a complete list."""
r = _ask("what are the names of the ninja turtles & their master name.")
assert _grounded(r), f"audit_mode={r.get('audit_mode')!r}"
txt = _answer_lower(r)
for turtle in ("leonardo", "donatello", "michelangelo", "raphael"):
assert turtle in txt, f"missing turtle '{turtle}': {txt[:300]}"
assert "splinter" in txt, f"missing master 'Splinter': {txt[:300]}"
def test_homer_simpson_boss_is_mr_burns():
"""Cross-document relationship: Homer's boss is named in the Homer
Simpson article (and several episode articles that reference the
Springfield Nuclear Power Plant). The model needs to land on
`Mr. Burns` and cite a chunk from one of the Simpsons articles.
Used to be pinned to claim_lattice_pointer mode: pre-2026-04-30
the JSON mode prompt presented content-addressed evidence_ids
(e.g. ``Eed1b6e396``) and Hermes-3-8B was fabricating
plausible-looking near-miss IDs (``E1b6e396``) → UNGROUNDED on
factually correct answers. Now JSON mode uses the same short
pointer IDs as the pointer variant (E1, E2, …) — the model
can't fabricate ``E27`` if only ``E1``-``E10`` were shown — so the
fixture runs against the default (JSON) mode and grounds
cleanly.
"""
r = _ask("who is homer simpson's boss?")
assert _grounded(r), f"audit_mode={r.get('audit_mode')!r}"
txt = _answer_lower(r)
assert "burns" in txt, f"answer missing 'Burns': {txt[:300]}"
# ---------------------------------------------------------------- forensic / leading
def test_great_wall_elevation_north_side_higher():
"""Forensic-shape question: which side of the Great Wall has higher
ground elevation? Expected answer: north (per geographic surveys).
The corpus may not directly contain the elevation fact, in which
case Hermes-3-8B can lazy-anchor a "north" claim onto any
Great-Wall span by token coincidence. The system today returns
STRICT 1/1 with the right answer ("northern side higher") cited
to a chunk about "Outer China beyond the Great Wall" — coverage
threshold passes via topical-token overlap (north / Great Wall /
China) without the cited span actually containing elevation
information.
Until the verifier grows a semantic-frame check, this fixture
gates on entity-presence only: the answer must mention "north"
(or "northern" / "northward"). STRICT or HYBRID is fine; the
model sometimes deflects to other Great-Wall facts.
"""
r = _ask(
"which side is the ground elevation highest throughout the "
"span of the great wall of china, the north or south?"
)
txt = _answer_lower(r)
# Either grounded with a directional answer, OR honestly UNGROUNDED.
# The fail mode is: grounded WITHOUT mentioning a direction.
if r.get("audit_mode") == "UNGROUNDED":
return
direction_markers = ("north", "northern", "northward")
assert any(m in txt for m in direction_markers), (
f"audit={r.get('audit_mode')} but answer doesn't name a "
f"direction: {txt[:300]}"
)
# ---------------------------------------------------------------- honest refusal
def test_mars_benevolent_dictator_refuses_or_ungrounded():
"""Mars has no benevolent dictator for life. The system has three
acceptable behaviors on this adversarial-premise question:
1. UNGROUNDED (refused outright — no grounded claim emitted)
2. STRICT/HYBRID + mentions "mars" + includes a refutation marker
(model explicitly refuted the false premise)
3. STRICT/HYBRID + does not mention "mars" at all (deflection to
an adjacent grounded fact — e.g. "Guido van Rossum is BDFL for
Python" — model declined the false-premise question and
answered a grounded related one instead)
The hallucination case (FAIL) is: the answer mentions Mars AND
affirmatively names a Mars BDFL with no refutation. Empirically
seen 2026-04-30: claim_lattice mode deflected to Guido/Python
(option 3) and the verifier returned STRICT correctly — claims
grounded, premise sidestepped.
Adversarial shape: "benevolent dictator for life" is a real
term-of-art (open-source governance — Linus, Guido) so the model
is tempted to fluently project it onto Mars. The corpus has no
such anchor; STRICT/HYBRID with an affirmative Mars-BDFL claim
= fail."""
r = _ask("who is a benevolent dictator for life for mars?")
audit = r.get("audit_mode")
txt = _answer_lower(r)
# 1) Outright refusal — honest UNGROUNDED.
if audit == "UNGROUNDED":
return
# 3) Deflection — answer is about something else entirely, no
# Mars mention. This is acceptable: the system grounded its
# claims (STRICT/HYBRID) but didn't assert the false premise.
if "mars" not in txt:
return
# 2) Explicit refutation — answer mentions Mars but rejects the
# premise. Markers cover the language patterns we've seen the
# 8B-class model use on similar no-such-thing questions.
refutation_markers = (
"no benevolent dictator",
"does not have",
"is not a",
"no government",
"not a sovereign",
"no such",
"no one",
"no person",
"no recognized",
"no known",
"fictional", # accepts answers that frame Mars-BDFL as scifi
)
assert any(m in txt for m in refutation_markers), \
f"audit={audit} but answer asserts a Mars BDFL: {txt[:300]}"