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

90 lines
4.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```bash
# 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
```bash
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`).