2cba72aeb0
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.
43 lines
1.5 KiB
Python
43 lines
1.5 KiB
Python
"""Deutsch-affine Textnormalisierung für FTS5-Index und -Anfragen.
|
|
|
|
Konvention (einmalig, konsistent): lowercase, Diakritika via NFKD entfernen
|
|
(ä→a, ü→u), ß→ss. Dies gilt für die FTS-Spalte `norm` und die Query gleich.
|
|
Die ASCII-Slug-Konvention der Wissensbasis (Umlaute "fallen") betrifft nur
|
|
`topic`/`tags`/Dateinamen, nicht die Volltextsuche.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
import unicodedata
|
|
|
|
# Stopwörter in normalisierter Form (nach Fold), klein halten.
|
|
STOPWORDS = frozenset(
|
|
"""der die das und oder ein eine einen einem einer eines den dem des ist im in
|
|
von fur mit auf zu zum zur an am beim wie was wann wird werden kann muss
|
|
sind hat nicht man als auch aus bei sein ihre ihr es sie er doch noch nur
|
|
schon sehr mehr hier da durch fuer wird""".split()
|
|
)
|
|
|
|
_TOKEN_RE = re.compile(r"[a-z0-9]+")
|
|
|
|
|
|
def normalize_text(s: str) -> str:
|
|
"""Lowercase, Diakritika entfernen (NFKD), ß→ss."""
|
|
s = unicodedata.normalize("NFKD", s.casefold())
|
|
s = "".join(c for c in s if unicodedata.category(c) != "Mn")
|
|
return s.replace("ß", "ss")
|
|
|
|
|
|
def tokenize(s: str) -> list[str]:
|
|
return _TOKEN_RE.findall(normalize_text(s))
|
|
|
|
|
|
def fts_query(question: str, min_len: int = 2) -> str:
|
|
"""OR-verknüpfte FTS5-Phrasen aus normalisierten Termen; '' wenn leer."""
|
|
terms: list[str] = []
|
|
seen: set[str] = set()
|
|
for t in tokenize(question):
|
|
if len(t) >= min_len and t not in STOPWORDS and t not in seen:
|
|
seen.add(t)
|
|
terms.append(t)
|
|
return " OR ".join(f'"{t}"' for t in terms) |