Semantisches Such-Backend hinter der vorhandenen Registry-Grenze, statt Retrieval in wikitool einzubauen #35

Open
opened 2026-09-01 21:11:29 +00:00 by torben · 0 comments
Owner

Aus der Plattform-Debatte vom 2026-09-01. Der Bedarf: hybrides Retrieval (BM25 + Vektor,
semantische Suche) für einen Korpus in der Größenordnung wenige Tausend Seiten, weil
ripgrep allein dort nicht mehr trägt.

Die Naht existiert bereits

tools/chemenu/search/registry.py sagt es im eigenen Docstring:

One registry entry today. It exists so that adding a semantic/vector backend is a new module
plus one line here — not a change to the command, the filters, or the output shape.
Selecting several at once fuses them through RRF.

Dazu search/base.py (Backend-Protokoll, „a backend answers the text half of a query and
nothing else"), search/fuse.py (Reciprocal Rank Fusion — ausdrücklich gebaut, um lexikalische
und semantische Rankings ohne gemeinsame Score-Skala zu kombinieren) und search/ripgrep.py
(betreibt seit Tag eins einen fremdsprachigen Prozess hinter dieser Grenze). Das gesamte
Suchmodul sind 613 Zeilen.

Frontmatter-Prädikate bleiben in filters.py und laufen nachgelagert — ein Vektor-Backend muss
confidence>=0.8 nie kennen.

Größenordnung: kein ANN-Index nötig

Überschlag für 5.000 Seiten bei ~250-Token-Chunks: grob 20.000–25.000 Chunks, bei 768 Dimensionen
float32 rund 77 MB. Ein linearer Kosinus-Scan darüber liegt im niedrigen zweistelligen
Millisekundenbereich — gegenüber 50–400 ms für das Einbetten der Query selbst.

Ein ANN-Index (HNSW o. ä.) spart also einen Bruchteil eines Vorgangs, der von etwas anderem
dominiert wird, und handelt sich Rekall-Verlust und Indexpflege ein. Brute Force reicht bei
dieser Größenordnung.
Das erledigt den größten Teil der Bibliotheksdiskussion, bevor sie
beginnt.

qmd prüfen, bevor etwas nachgebaut wird

kb/entities/tools/qmd.md beschreibt genau das gesuchte Profil. Verifiziert am 2026-09-01 gegen
https://github.com/tobi/qmd: qmd ist TypeScript/Bun und löst die Aufgabe über SQLite FTS5 +
sqlite-vec + node-llama-cpp mit GGUF-Modellen (embeddinggemma-300M, Qwen3-Reranker). Es bringt
einen MCP-Server und HTTP-Transport auf localhost:8181 mit.

Damit ist „einhängen statt nachbauen" keine Architekturskizze, sondern eine Konfigurationsfrage.
Zu prüfen, bevor entschieden wird:

  • Lizenz, Reifegrad, Release-Kadenz, Bus-Faktor des Upstreams
  • Ob das LLM-Reranking abschaltbar ist. Nicht abschaltbar wäre ein K.-o.-Kriterium: es
    kollidiert mit dem Determinismus-Anspruch dieses Stacks (AGENTS.md, „never re-derive, always
    compile") und macht dieselbe Anfrage zweimal verschieden beantwortbar.
  • Ob sich der Index auf kb/ richten lässt, ohne Annahmen über unser Frontmatter zu treffen

Die Alternative — ein eigenes Backend in Python — ist bei Brute-Force-Kosinus überschaubar
(NumPy-Dot-Produkt über eine Matrix) und hat den Vorteil, keine Fremdabhängigkeit zu erben. Der
Nachteil ist, dass Embedding-Erzeugung, Chunking und Modellverwaltung dann selbst gebaut werden.

Was der Preis der Grenze ist, ehrlich

  • Modell-Bindung. Query und Index müssen dasselbe Embedding-Modell benutzen. Ein
    Modellwechsel bedeutet Vollreindex. Das ist echte, nicht wegdefinierbare Versionsdrift.
  • Stille Lücken. Ein veralteter Index liefert weniger, nicht falsch — aber leise. Für einen
    Wissenskompiler ist das das schlechtere Fehlerbild. Braucht einen doctor-Check:
    Index-Zeitstempel gegen jüngstes modified: im Korpus.
  • Kaltstart. rg kostet 7 ms; ein Embedding-Prozess kostet Hunderte ms bis Sekunden pro
    Start. Das verlangt einen residenten Prozess — den #19 ohnehin mitbringt.
  • Optionale Abhängigkeit. dist export und doctor müssen sie als optional führen.
    Präzedenzfall existiert: RipgrepMissing in search/ripgrep.py.
  • Ein zweites Ding zum Patchen.

Offene Fragen

  • Wo kommen die Embeddings her: lokaler Daemon (Ollama/llama-server), In-Process-Bibliothek oder
    API? Das entscheidet über Offline-Fähigkeit, Latenz und laufende Kosten.
  • Chunking-Strategie: ganze Seite, Abschnitt, oder Fenster? Der Korpus hat mit ## -Abschnitten
    eine natürliche Struktur, die ein Chunker nutzen könnte.
  • Wird der Index gitignoriert (Derivat wie reports/) oder ausgeliefert?
  • Wechselwirkung mit #6 (Backlink-boosted Ranking): beides sind Ranking-Signale, beide laufen
    durch fuse.py.

Akzeptanzkriterien

  • Entscheidung qmd einhängen vs. eigenes Backend, mit den Prüfpunkten oben belegt
  • Backend über einen Eintrag in BACKENDS (search/registry.py) erreichbar, --backend wählt es
  • Kombination mit rg über RRF funktioniert; Frontmatter-Prädikate greifen unverändert
  • Fehlt die Abhängigkeit, verhält sich wikitool wie bei fehlendem rg: klare Meldung, kein stiller Fallback
  • doctor meldet Index-Alter gegen den Korpus
  • An einem Korpus der Zielgröße gemessen, nicht an den 188 Demo-Seiten

Auslöser für prio/3: ein Korpus, an dem rg tatsächlich nicht mehr trägt. Vorher ist das
eine Wette auf eine Anforderung, die noch niemand gemessen hat.

Verwandt: #19 (der residente Prozess, in dem der Index lebt), #6 (Ranking-Signale).

Aus der Plattform-Debatte vom 2026-09-01. Der Bedarf: hybrides Retrieval (BM25 + Vektor, semantische Suche) für einen Korpus in der Größenordnung **wenige Tausend Seiten**, weil `ripgrep` allein dort nicht mehr trägt. ## Die Naht existiert bereits `tools/chemenu/search/registry.py` sagt es im eigenen Docstring: > One registry entry today. It exists so that adding a semantic/vector backend is a new module > plus one line here — not a change to the command, the filters, or the output shape. > Selecting several at once fuses them through RRF. Dazu `search/base.py` (Backend-Protokoll, „a backend answers the *text* half of a query and nothing else"), `search/fuse.py` (Reciprocal Rank Fusion — ausdrücklich gebaut, um lexikalische und semantische Rankings ohne gemeinsame Score-Skala zu kombinieren) und `search/ripgrep.py` (betreibt seit Tag eins einen fremdsprachigen Prozess hinter dieser Grenze). Das gesamte Suchmodul sind 613 Zeilen. Frontmatter-Prädikate bleiben in `filters.py` und laufen nachgelagert — ein Vektor-Backend muss `confidence>=0.8` nie kennen. ## Größenordnung: kein ANN-Index nötig Überschlag für 5.000 Seiten bei ~250-Token-Chunks: grob 20.000–25.000 Chunks, bei 768 Dimensionen float32 rund **77 MB**. Ein linearer Kosinus-Scan darüber liegt im niedrigen zweistelligen Millisekundenbereich — gegenüber 50–400 ms für das Einbetten der Query selbst. Ein ANN-Index (HNSW o. ä.) spart also einen Bruchteil eines Vorgangs, der von etwas anderem dominiert wird, und handelt sich Rekall-Verlust und Indexpflege ein. **Brute Force reicht bei dieser Größenordnung.** Das erledigt den größten Teil der Bibliotheksdiskussion, bevor sie beginnt. ## `qmd` prüfen, bevor etwas nachgebaut wird `kb/entities/tools/qmd.md` beschreibt genau das gesuchte Profil. Verifiziert am 2026-09-01 gegen <https://github.com/tobi/qmd>: qmd ist **TypeScript/Bun** und löst die Aufgabe über SQLite FTS5 + sqlite-vec + node-llama-cpp mit GGUF-Modellen (embeddinggemma-300M, Qwen3-Reranker). Es bringt einen **MCP-Server** und HTTP-Transport auf `localhost:8181` mit. Damit ist „einhängen statt nachbauen" keine Architekturskizze, sondern eine Konfigurationsfrage. Zu prüfen, bevor entschieden wird: - Lizenz, Reifegrad, Release-Kadenz, Bus-Faktor des Upstreams - Ob das **LLM-Reranking abschaltbar** ist. Nicht abschaltbar wäre ein K.-o.-Kriterium: es kollidiert mit dem Determinismus-Anspruch dieses Stacks (`AGENTS.md`, „never re-derive, always compile") und macht dieselbe Anfrage zweimal verschieden beantwortbar. - Ob sich der Index auf `kb/` richten lässt, ohne Annahmen über unser Frontmatter zu treffen Die Alternative — ein eigenes Backend in Python — ist bei Brute-Force-Kosinus überschaubar (NumPy-Dot-Produkt über eine Matrix) und hat den Vorteil, keine Fremdabhängigkeit zu erben. Der Nachteil ist, dass Embedding-Erzeugung, Chunking und Modellverwaltung dann selbst gebaut werden. ## Was der Preis der Grenze ist, ehrlich - **Modell-Bindung.** Query und Index müssen dasselbe Embedding-Modell benutzen. Ein Modellwechsel bedeutet Vollreindex. Das ist echte, nicht wegdefinierbare Versionsdrift. - **Stille Lücken.** Ein veralteter Index liefert weniger, nicht falsch — aber leise. Für einen Wissenskompiler ist das das schlechtere Fehlerbild. Braucht einen `doctor`-Check: Index-Zeitstempel gegen jüngstes `modified:` im Korpus. - **Kaltstart.** `rg` kostet 7 ms; ein Embedding-Prozess kostet Hunderte ms bis Sekunden pro Start. Das verlangt einen residenten Prozess — den #19 ohnehin mitbringt. - **Optionale Abhängigkeit.** `dist export` und `doctor` müssen sie als optional führen. Präzedenzfall existiert: `RipgrepMissing` in `search/ripgrep.py`. - **Ein zweites Ding zum Patchen.** ## Offene Fragen - Wo kommen die Embeddings her: lokaler Daemon (Ollama/llama-server), In-Process-Bibliothek oder API? Das entscheidet über Offline-Fähigkeit, Latenz und laufende Kosten. - Chunking-Strategie: ganze Seite, Abschnitt, oder Fenster? Der Korpus hat mit `## `-Abschnitten eine natürliche Struktur, die ein Chunker nutzen könnte. - Wird der Index gitignoriert (Derivat wie `reports/`) oder ausgeliefert? - Wechselwirkung mit #6 (Backlink-boosted Ranking): beides sind Ranking-Signale, beide laufen durch `fuse.py`. ## Akzeptanzkriterien - [ ] Entscheidung `qmd` einhängen vs. eigenes Backend, mit den Prüfpunkten oben belegt - [ ] Backend über einen Eintrag in `BACKENDS` (`search/registry.py`) erreichbar, `--backend` wählt es - [ ] Kombination mit `rg` über RRF funktioniert; Frontmatter-Prädikate greifen unverändert - [ ] Fehlt die Abhängigkeit, verhält sich `wikitool` wie bei fehlendem `rg`: klare Meldung, kein stiller Fallback - [ ] `doctor` meldet Index-Alter gegen den Korpus - [ ] An einem Korpus der Zielgröße gemessen, nicht an den 188 Demo-Seiten **Auslöser für `prio/3`:** ein Korpus, an dem `rg` tatsächlich nicht mehr trägt. Vorher ist das eine Wette auf eine Anforderung, die noch niemand gemessen hat. **Verwandt:** #19 (der residente Prozess, in dem der Index lebt), #6 (Ranking-Signale).
torben added the prio/waitingsize/M labels 2026-09-01 21:11:29 +00:00
torben added the area/kbkind/decision labels 2026-09-02 21:24:45 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: torben/chemenu#35