M1+M2: RAG-Pipeline mit verbindlichem Grounding
agent/-Paket: Ingest (601 Layer-2-Eintraege -> 3005 Chunks, FTS5-BM25 + Vektoren-Cache), Hybrid-Retrieval (RRF, Stand-Boost, cross_ref-Erweiterung), Ollama-Client (embed/chat, think-Flag-Fallback, kurzes Connect-Budget), Systemprompt mit Zitierpflicht, Post-Validierung (zitierte IDs gemaess Retrieved-Set, 1x Regenerierung, dann Verweigerung), FastAPI (/ask, /health, /reindex), CLI, Goldset (31 Fragen, IDs gegen kb.json verifiziert, inkl. ATZ-Konfliktfall + 4 Verweigerungsfaelle), Eval-Suite, Test-Chat. 41 Offline-Tests gruen. Baseline BM25-only: Hit-Rate 0,871 / Recall@8 0,855 / MRR 0,476. Hybrid-Messung, Antwortmodus-Eval und Modell-Bake-off (M3) auf dem Host ausstaendig (Ollama aus der Zed-Sandbox nicht erreichbar). MEMORY.md und planung.md Umsetzungsstand aktualisiert.
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
"""Tests: Index-Bau (Chunking, FTS, Metadaten, Schema-Gates)."""
|
||||
import sqlite3
|
||||
|
||||
import pytest
|
||||
|
||||
from agent.ingest import SCHEMA, build_index
|
||||
from agent.kb import KbValidationError
|
||||
|
||||
|
||||
def test_build_index_chunks_and_fts(mini_index):
|
||||
con = sqlite3.connect(mini_index.db_path)
|
||||
try:
|
||||
n_chunks = con.execute("SELECT COUNT(*) FROM chunks").fetchone()[0]
|
||||
n_fts = con.execute("SELECT COUNT(*) FROM chunks_fts").fetchone()[0]
|
||||
n_entries = con.execute(
|
||||
"SELECT COUNT(DISTINCT entry_id) FROM chunks"
|
||||
).fetchone()[0]
|
||||
assert n_entries == 3
|
||||
assert n_chunks == n_fts and n_chunks >= 7
|
||||
row = con.execute(
|
||||
"SELECT entry_id, section, norm FROM chunks WHERE entry_id='lb-min-01' "
|
||||
"AND section LIKE 'Kernwerte%'"
|
||||
).fetchone()
|
||||
assert row is not None
|
||||
# Umlaut-Folding im FTS-Text: "Lohnausgleich" normalisiert auffindbar
|
||||
assert "lohnausgleich" in row[2]
|
||||
meta = dict(con.execute("SELECT key, value FROM meta").fetchall())
|
||||
assert meta["n_entries"] == "3"
|
||||
assert meta["embed_model"] == "" # embed_off=True
|
||||
finally:
|
||||
con.close()
|
||||
|
||||
|
||||
def test_norm_contains_tags_and_legal_bases(mini_index):
|
||||
con = sqlite3.connect(mini_index.db_path)
|
||||
try:
|
||||
norm = con.execute(
|
||||
"SELECT norm FROM chunks WHERE entry_id='lb-min-01' "
|
||||
"AND section='Zusammenfassung'"
|
||||
).fetchone()[0]
|
||||
assert "alvg" in norm # legal_bases im FTS-Text
|
||||
assert "azg" in norm and "19e" in norm
|
||||
finally:
|
||||
con.close()
|
||||
|
||||
|
||||
def test_rebuild_is_idempotent(mini_index):
|
||||
stats = build_index(mini_index)
|
||||
assert stats.n_entries == 3
|
||||
con = sqlite3.connect(mini_index.db_path)
|
||||
try:
|
||||
assert con.execute("SELECT COUNT(*) FROM chunks").fetchone()[0] == stats.n_chunks
|
||||
finally:
|
||||
con.close()
|
||||
|
||||
|
||||
def test_build_index_aborts_on_gate_error(mini_cfg):
|
||||
"""Gate-Fehler (Registry kaputt) bricht den Ingest ab — kein halber Index."""
|
||||
from pathlib import Path
|
||||
|
||||
(Path(mini_cfg.kb_dir) / "kb.json").write_text(
|
||||
'{"n_entries": 0, "entries": []}', encoding="utf-8"
|
||||
)
|
||||
with pytest.raises(KbValidationError):
|
||||
build_index(mini_cfg)
|
||||
|
||||
|
||||
def test_vectors_table_cached_across_rebuilds(mini_index):
|
||||
"""Die Vektoren-Tabelle bleibt beim Rebuild erhalten (Cache-Garantie)."""
|
||||
con = sqlite3.connect(mini_index.db_path)
|
||||
try:
|
||||
con.execute(
|
||||
"INSERT INTO vectors(content_hash, model, dim, vec) "
|
||||
"VALUES ('deadbeef', 'bge-m3', 2, x'000000003f800000')"
|
||||
) # 0.0, 1.0
|
||||
con.commit()
|
||||
finally:
|
||||
con.close()
|
||||
build_index(mini_index)
|
||||
con = sqlite3.connect(mini_index.db_path)
|
||||
try:
|
||||
assert con.execute(
|
||||
"SELECT COUNT(*) FROM vectors WHERE content_hash='deadbeef'"
|
||||
).fetchone()[0] == 1
|
||||
finally:
|
||||
con.close()
|
||||
|
||||
|
||||
def test_schema_creates_fts5(tmp_path):
|
||||
con = sqlite3.connect(tmp_path / "s.db")
|
||||
try:
|
||||
con.executescript(SCHEMA)
|
||||
con.execute("INSERT INTO chunks_fts(rowid, norm) VALUES (1, 'testtext')")
|
||||
hits = con.execute(
|
||||
"SELECT rowid FROM chunks_fts WHERE chunks_fts MATCH '\"testtext\"'"
|
||||
).fetchall()
|
||||
assert hits == [(1,)]
|
||||
finally:
|
||||
con.close()
|
||||
Reference in New Issue
Block a user