"""Integration: the name-hit lexical signal against real Postgres (the 2026-09-05 "Qwen 3.8" incident). The unit suite (``tests/unit/test_retriever.py``) covers the pure mapping with fake rows; this suite covers the SQL side on real Postgres: the document-projection scan, the LATERAL representative- chunk fetch (the ``is_summary`` chunk wins, chunk 0 otherwise, and a chunk-less name match is EXCLUDED — the ``c.id IS NOT NULL`` guard), the (matched-token count, catalog) ranking (phase 119, LOCKED A2 — the two-class component rule: digit-bearing prefix, digitless exact, titles never matched), the name-hits-lead-the-lexical-list union with the FTS rows (chunk-id dedup), and the full ``retrieve()`` → ``select_documents()`` path putting the versioned-name document into the seeded top-N. Requires: ``podman compose up -d db``. """ from __future__ import annotations import uuid from collections.abc import Iterator from datetime import UTC, datetime import pytest from sqlalchemy import text from sqlalchemy.orm import Session from app.models import Chunk, Document from app.rag.retriever import ( NAME_HIT_LIMIT, _lexical_candidates, _name_hit_chunks, retrieve, select_documents, ) INCIDENT_QUESTION = "What are the correct llama.cpp arguments for Qwen 3.8?" #: 768-dim test vectors (the pgvector column's dimension) — axis unit #: vectors so the cosines are exact (1.0 parallel, 0.0 orthogonal, #: 0.7071 half-parallel). D = 768 def _vec(axis: int, second: bool = False) -> list[float]: v = [0.0] * D v[axis] = 1.0 if second: v[axis + 1] = 1.0 return v def _doc(db: Session, source: str, path: str, title: str, content: str) -> Document: doc = Document( id=uuid.uuid4(), source=source, path=path, full_path=f"/tmp/{source}/{path}", title=title, content=content, content_hash="0" * 64, indexed_at=datetime.now(UTC), ) db.add(doc) return doc def _chunk( db: Session, doc: Document, position: int, content: str, is_summary: bool = False ) -> Chunk: chunk = Chunk( id=uuid.uuid4(), document_id=doc.id, position=position, content=content, is_summary=is_summary, ) db.add(chunk) return chunk @pytest.fixture() def kb(db) -> Iterator[None]: """A fresh KB with the incident shape: the qwen3.8 quadlet (the name hit, with a summary chunk + an ordinary chunk), a qwen3.6 quadlet (same family, different version — NOT a hit), an unrelated document (FTS-only candidate), and a chunk-less document whose name DOES carry the token (the exclusion guard).""" db.execute(text("TRUNCATE chunks, documents")) db.commit() q38 = _doc( db, "deploy", "reeseapps/ai/deployments/juggernaut/quadlets/qwen3.8-27b-juggernaut-vulkan.container", "qwen3.8-27b-juggernaut-vulkan", "# llama.cpp juggernaut\nExec=--port 8000 -ctk q8_0 -ctv q8_0 --jinja\n" "-m /models/qwen3.8-27b/Qwen3.8-27B-UD-Q6_K.gguf\n", ) _chunk( db, q38, -1, "Podman quadlet: llama.cpp server for Qwen 3.8 27B (juggernaut).", is_summary=True, ) _chunk(db, q38, 0, "# llama.cpp juggernaut\nExec=--port 8000 -ctk q8_0") db.flush() # A vector the question vector (below) cosines with — non-NULL so # the chunk is eligible for the vector list too. for c in q38.chunks: c.embedding = _vec(1) db.commit() q36 = _doc( db, "deploy", "reeseapps/ai/deployments/juggernaut/quadlets/qwen3.6-27b-juggernaut-vulkan.container", "qwen3.6-27b-juggernaut-vulkan", "# llama.cpp juggernaut\n-m /models/qwen3.6-27b/model.gguf\n", ) c36 = _chunk(db, q36, 0, "# llama.cpp juggernaut\n-m /models/qwen3.6-27b/model.gguf") c36.embedding = _vec(0) # orthogonal to the question vector db.commit() other = _doc( db, "homelab", "notes/llama.cpp.md", "llama.cpp notes", "llama cpp server arguments notes\n" ) c_other = _chunk(db, other, 0, "llama cpp server arguments notes") c_other.embedding = _vec(1, second=True) # half-parallel to the question db.commit() # Name carries the token, ZERO chunks — the exclusion guard. _doc(db, "deploy", "qwen3.8-empty.container", "qwen3.8-empty", "(empty file)") db.commit() yield db.execute(text("TRUNCATE chunks, documents")) db.commit() def test_name_hit_chunks_real_sql(kb, db) -> None: """Real Postgres: the projection scan finds the qwen3.8 quadlet (the ``qwen38`` stem prefix — the incident's original case) AND the ``notes/llama.cpp.md`` doc (the question names "llama.cpp" — the dotted token ``llamacpp`` exact-matches the file stem, the phase-119 two-class rule). The qwen3.6 sibling (``qwen38`` is not a prefix of ``qwen36…``) and the chunk-less name match are excluded; the LATERAL fetch hands back the SUMMARY chunk as the representative for the quadlet (position −1, is_summary) and chunk 0 for the single-chunk notes doc.""" out = _name_hit_chunks(db, INCIDENT_QUESTION) assert [rc.document.path for rc in out] == [ "reeseapps/ai/deployments/juggernaut/quadlets/qwen3.8-27b-juggernaut-vulkan.container", "notes/llama.cpp.md", # the question names it — stem exact (A2) ] rc = out[0] assert rc.position == -1 # the summary chunk wins the LATERAL order assert rc.is_summary is True assert rc.fts_hit is True # the lexical signal — the A8 gate answers assert rc.cosine == 0.0 # no vector rank on the name-hit row rc_notes = out[1] assert rc_notes.position == 0 # chunk 0 (no summary chunk) assert rc_notes.is_summary is False assert all(rc.name_hit is True for rc in out) # phase 119 — D2 bonus input assert "qwen3.8-empty.container" not in [r.document.path for r in out] # chunk-less guard def test_lexical_candidates_name_hit_leads_real_sql(kb, db) -> None: """The full lexical list on real Postgres: the name hit leads, the FTS rows follow (the qwen3.6 and llama.cpp docs both match the OR-tsquery on llama|cpp|arguments|… — the pre-incident pollution — but the name hit still ranks them behind it).""" out = _lexical_candidates(db, INCIDENT_QUESTION, limit=30) paths = [rc.document.path for rc in out] assert paths[0] == ( "reeseapps/ai/deployments/juggernaut/quadlets/qwen3.8-27b-juggernaut-vulkan.container" ) assert paths[1] == "notes/llama.cpp.md" # the second name hit (stem exact) # The FTS pollution is still present (the incident's shape) — but # behind the name hits, no longer ahead of them. assert ( "reeseapps/ai/deployments/juggernaut/quadlets/qwen3.6-27b-juggernaut-vulkan.container" in paths ) assert all(rc.fts_hit is True for rc in out) # Phase 119: the two name-hit representative rows are flagged, the # plain FTS rows are not. assert out[0].name_hit is True assert out[1].name_hit is True assert all(not rc.name_hit for rc in out[2:]) def test_retrieve_selects_name_hit_doc_into_top_n(kb, db) -> None: """The product path: hybrid ``retrieve()`` (vector ∪ lexical, RRF fused) → ``select_documents`` puts the qwen3.8 quadlet in the seeded top-N — the incident's seed miss (the two overview docs only) is fixed. The question vector is parallel to the q38 chunk embeddings (cosine 1.0), orthogonal to q36 (0.0). Phase 119: the ``name_hit`` flag survives the fusion — the two name-hit representative chunks are double hits (vector ∪ lexical), and the double-hit merge ORs the flag into the surviving row; the plain q36 vector+FTS row stays False.""" question_vec = _vec(1) chunks = retrieve(db, INCIDENT_QUESTION, question_vec) docs = select_documents(chunks, n=2) assert [d.path for d in docs] == [ "reeseapps/ai/deployments/juggernaut/quadlets/qwen3.8-27b-juggernaut-vulkan.container", "notes/llama.cpp.md", ] by_path: dict[str, list[bool]] = {} for rc in chunks: by_path.setdefault(rc.document.path, []).append(rc.name_hit) # The q38 doc's summary chunk is the double-hit name hit (flagged); # its plain chunk 0 (vector + FTS) is not — the doc has a flagged row. assert any(by_path[ "reeseapps/ai/deployments/juggernaut/quadlets/qwen3.8-27b-juggernaut-vulkan.container" ]) assert any(by_path["notes/llama.cpp.md"]) # its only chunk is the name hit assert not any(by_path[ "reeseapps/ai/deployments/juggernaut/quadlets/qwen3.6-27b-juggernaut-vulkan.container" ]) # plain vector+FTS — never a name hit def test_name_hit_limit_real_sql(db) -> None: """Twelve tied name hits (one matched token each — the ``qwen38`` stem PREFIX; the token must lead the stem, the old mid-stem containment no longer matches) — the LATERAL fetch (and the output) carries exactly ``NAME_HIT_LIMIT`` winners, catalog order.""" db.execute(text("TRUNCATE chunks, documents")) db.commit() for i in range(12): doc = _doc( db, "S", f"quadlets/qwen38-m{i:02d}.container", f"qwen38-m{i:02d}", "llama cpp qwen38\n" ) db.flush() c = _chunk(db, doc, 0, f"llama cpp qwen38 doc {i}") c.embedding = _vec(2) db.commit() out = _name_hit_chunks(db, "what are the llama.cpp arguments for qwen 3.8") assert len(out) == NAME_HIT_LIMIT assert [rc.document.path for rc in out] == [ f"quadlets/qwen38-m{i:02d}.container" for i in range(NAME_HIT_LIMIT) ] assert all(rc.name_hit is True for rc in out)