Files
km/docs/index-suche.md
eltonandClaude Fable 5.1 4986ef0575 Baue den Index als SQLite mit FTS5 und sqlite-vec, dazu die Suche
Entscheidung Andreas 2026-09-04 (spine/core#8): kein Datenbankdienst. Der
Index ist eine abgeleitete Sicht auf die Markdown-Ablage in einer Datei im
Volume, jederzeit neu baubar. image/km/index.py zerlegt die Dateien in
Chunks (Chunking aus info/ai/scripts/index_docs.py übernommen), hält
Volltext in FTS5 und Vektoren aus bge-m3 in sqlite-vec, inkrementell über
SHA-256 je Datei. image/km/search.py sucht Volltext führend, Vektor
ergänzend, mit Filtern vor dem Ranking (0 Treffer statt gesperrt).

Ingest: Kommentare bei der Erstbefüllung repo-weit statt je Vorgang —
die erste Fassung hing bei live/live nach 371 Vorgängen; jetzt 2175 in zwei
Minuten. Gemessen: Volltext über 61.000 Chunks in 3 s, Vektoren 0,31 s je
Chunk auf der CPU-Box. Kein Bytecode mehr im Repo.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-04 18:04:35 +02:00

4.1 KiB
Raw Permalink Blame History

Index und Suche — SQLite mit FTS5 und sqlite-vec

Entscheidung Andreas, 2026-09-04 (spine/core#8): kein Datenbankdienst. Der Index ist eine abgeleitete Sicht auf die Markdown-Dateien der Ablage und liegt als eine SQLite-Datei neben ihr. Er darf jederzeit gelöscht und neu gebaut werden — die Wahrheit sind die Dateien.

Code: image/km/index.py (bauen), image/km/search.py (suchen). Abhängigkeiten: requests, sqlite-vec (image/requirements.txt).

Was drin ist

Tabelle Inhalt
docs eine Zeile je Datei: Pfad, Namespace, Projekt, Klassifikation, Typ (vorgang, doku, …), Zustand, Titel, Zeitstempel, SHA-256, ob eingebettet
chunks Absatz-Chunks je Datei (max. 600 Zeichen, 60 Überlappung — dasselbe Chunking wie der Wissens-MCP); der Titel ist Chunk 0
chunks_fts FTS5 über die Chunks, Tokenizer unicode61 ohne Diakritika-Entfernung (ä bleibt ä)
chunks_vec sqlite-vec, bge-m3 mit 1024 Dimensionen, rowid = Chunk-ID
verweise Kanten aus dem Frontmatter (verweise:), beide Richtungen abfragbar

Die Filterspalten sind der Punkt, an dem die Zugriffsregel greift: Namespace, Projekt, Klassifikation, Typ, Zustand sind Spalten in docs, und die Suche filtert vor dem Ranking. Wer eine Klassifikation nicht sehen darf, bekommt 0 Treffer — nicht „vorhanden, aber gesperrt" (spine/core#18).

Bauen

# Volltext (Sekunden) — nach jedem Ingest
image/km/index.py --ablage /srv/km/ablage

# dazu Vektoren fuer neue und geaenderte Dateien
image/km/index.py --ablage /srv/km/ablage --embed

# nur ausstehende Vektoren nachziehen, z. B. nachts
image/km/index.py --ablage /srv/km/ablage --embed-only [--embed-limit 200]

Inkrementell über den SHA-256 je Datei: unveränderte Dateien werden nicht angefasst, geänderte komplett neu zerlegt (alte Chunks und Vektoren raus, neue rein), verschwundene entfernt. Der Volltext ist sofort da; die Vektoren folgen, sortiert nach aktualisiert absteigend — das Neueste zuerst.

Suchen

search.py "worktree prune"                        # hybrid: Volltext + Vektor, RRF
search.py --mode fts "live/live#2181"             # exakt
search.py --mode vec "warum braucht der bus keine persistenz mehr"
search.py --ns 42i --typ vorgang --zustand open "i18n"
search.py --classification lan --classification public "…"   # Sichtkreis
search.py --json "…"

Volltext führend, Vektor ergänzend (spine/core#1). Hybrid fusioniert beide Ranglisten per Reciprocal Rank Fusion; ausgegeben wird ein Treffer je Datei mit dem besten Chunk als Anriss. Die FTS-Anfrage macht aus jedem Wort einen Präfix (worktree*), Zeichenketten mit Sonderzeichen (live/live#2181) werden wörtlich gesucht.

Gemessen am 2026-09-04 (Mac, Ablage mit 3424 Vorgängen aus elf Repos)

Schritt Menge Dauer
Volltext-Index, Erstbau 2935 Dateien, 61.141 Chunks 3,1 s
Volltext, inkrementell 491 neue Dateien 0,3 s
Vektoren 1756 Chunks (30 Dateien) 550 s → 0,31 s je Chunk
Vektoren, hochgerechnet 64.392 Chunks ~5,5 h
FTS-Suche unter 50 ms
Vektorsuche Embedding der Anfrage + Nachbarn ~1 s, davon fast alles das Embedding

Die Vektorzeit ist der Rechner, nicht die Leitung: /api/embed mit 32 Texten im Batch liefert 0,31 s je Text, einzeln 1,0 s — die Ollama-Instanz auf dem s18-CT rechnet auf der CPU (OLLAMA_VULKAN=0, siehe info: lan/infra/wissens-mcp.md). Die Erstbefüllung fällt einmal an; danach kostet ein geändertes Ticket Sekunden. Wer es schneller braucht, hängt ein GPU-Embedding ans Gateway — das ist eine Betriebsfrage, keine am Index.

Dateigröße: 62 MB für den Volltext über 61.000 Chunks; mit Vektoren (1024 × 4 Byte je Chunk) kommen rund 260 MB dazu.

Was fehlt

  • Deutsche Wortformen. unicode61 stemmt nicht; „Sperrung" findet „gesperrt" nur über die Vektorsuche. Falls das nicht reicht: Trigramm- Tokenizer, keine neue Datenbank.
  • Kanten zu Doku-Seiten. verweise kennt heute nur Vorgang→Vorgang.
  • Der Dienst. Heute zwei Skripte; read/search/summary als HTTP-API und MCP-Endpunkt kommen darüber (Reihenfolge in image/README.md).