Welches Embedding-Modell? Messen statt raten beim Blog-Chatbot
Welches Embedding-Modell?
Messen statt raten: wie der Blog-Chatbot ein neues Suchmodell bekam
- Der Chatbot auf dieser Seite fand zu einer einfachen Frage den passenden Beitrag erst auf Platz 10. Die naheliegende Reparatur, die Aufgabenpräfixe aus der Modellkarte, hat gemessen nicht geholfen.
- Statt weiter an einzelnen Fragen zu probieren, habe ich einen Testsatz mit 44 Fragen und ein kleines Messwerkzeug gebaut, das die Suche des Chatbots exakt nachbildet, ohne die Produktion anzufassen.
- Vier Modelle traten an. Gewonnen hat
nomic-embed-text-v2-moe: alle 38 Inhaltsfragen in den Top 5 (vorher 33), Platz 1 bei 32 statt 21 Fragen, und es ist das schnellste der Kandidaten. - Die eigentliche Falle beim Wechsel war die Score-Skala. Die alte Schwelle hat nie etwas gefiltert, mit dem neuen Modell hätte sie die Hälfte der Treffer weggeworfen.
Inhalt
Der Auslöser: Platz 10
Nach jedem neuen Blogbeitrag stelle ich dem Chatbot ein paar Fragen dazu. Beim letzten Beitrag über eine Woche mit Sicherheitsupdates lautete eine davon: „Wie sind Passwörter ins SIEM gelangt?“ Die Antwort steht ausführlich im Beitrag. Der Chatbot fand den passenden Abschnitt trotzdem erst auf Platz 10, und weil er nur die besten fünf Beiträge an das Sprachmodell weitergibt, kam die Antwort nie an. Mit Stichworten, die wörtlich im Text stehen, landete derselbe Abschnitt auf Platz 1.
Das ist das typische Verhalten einer Suche, die Wörter vergleicht statt Bedeutung. Genau dafür setzt man aber ein Embedding-Modell ein. Irgendetwas lief also nicht wie gedacht.
Was ein Embedding-Modell hier tut
Der Chatbot arbeitet nach dem üblichen RAG-Muster. Ein nächtlicher Workflow in n8n zerlegt alle Beiträge in Abschnitte von höchstens 1000 Zeichen, lässt jeden Abschnitt von einem Embedding-Modell in einen Vektor mit 768 Zahlen übersetzen und speichert ihn in der Vektordatenbank Qdrant. Kommt eine Frage, wird sie mit demselben Modell übersetzt, und Qdrant liefert die Abschnitte, deren Vektoren am ähnlichsten sind (Kosinus-Ähnlichkeit). Diese Abschnitte bekommt das Sprachmodell als Kontext.
Das Embedding-Modell läuft lokal über Ollama, angesprochen über LiteLLM, und zwar bewusst nur auf der CPU: Die
Grafikkarten gehören dem großen Sprachmodell. Bisher war das nomic-embed-text in Version 1, ein
verbreitetes, kleines Modell, das vor allem auf englischen Texten trainiert wurde. Der Blog ist deutsch.
Die Suche selbst hatte ich kurz vorher umgebaut: Sie liefert je Beitrag nur noch den besten Abschnitt, bei Inhaltsfragen die besten fünf Beiträge, bei Listenfragen („Welche Beiträge gibt es zu …?“) acht, dann einschließlich der Themenübersichten. Diese Logik musste jede Messung später genau nachbilden.
Erste Idee: Aufgabenpräfixe
Die Modellkarte von nomic-embed-text verlangt Aufgabenpräfixe: Dokumente sollen mit
search_document: eingebettet werden, Fragen mit search_query: . Weder der
Import noch der Chatbot setzten sie. Das sah nach der Ursache aus, und ein schneller Versuch mit 40
Kandidaten-Abschnitten bestätigte es scheinbar: Die SIEM-Frage sprang von Platz 10 auf Platz 3.
Eine einzelne Frage beweist aber wenig. Ich wollte vor einer Änderung an der Produktion wissen, was die Präfixe über viele Fragen hinweg bewirken, und vor allem, ob sie anderswo etwas verschlechtern.
Ein Testsatz statt Einzelfragen
Der Testsatz besteht aus 38 Inhaltsfragen und 6 Listenfragen. Jede Inhaltsfrage nennt den oder die Beiträge, in denen die Antwort steht; einer davon unter den Treffern genügt. Die Listenfragen zielen auf die Übersichtsdokumente, etwa „Themenübersicht: Lokale KI“. Wichtig war die Formulierung: Die Fragen sind bewusst umschreibend, so wie ein Leser fragt, der den Text nicht vor sich hat. Wer Fragen aus dem Text abschreibt, misst die Wortsuche und bekommt geschönte Zahlen.
Einige Beispiele:
- „Wie wurde der Arbeitsspeicher des Servers fast halbiert?“
- „Was passiert, wenn der nächtliche Import die Wissensdatenbank leert?“
- „Warum verschwinden bei nginx die Sicherheitsheader, wenn man Cache-Control setzt?“
- „Wie legt man fest, welche Geräte im VPN miteinander sprechen dürfen?“
Das Messwerkzeug
Das Werkzeug ist ein Python-Skript ohne Abhängigkeiten außer der Standardbibliothek. Es arbeitet in zwei Schritten:
- Dump: Es liest alle Abschnitte der Live-Collection samt gespeicherter Vektoren aus Qdrant und legt sie als Datei ab. An Qdrant und n8n ändert es nichts.
- Lauf: Für jede Variante (Modell plus Präfix) bettet es alle Abschnitte neu ein, speichert die Vektoren zwischen, bettet die Testfragen ein und rechnet die Suche lokal nach: gleiche Filter, je Beitrag der beste Abschnitt, Top 5 bzw. Top 8.
Gezählt wird, wie oft der richtige Beitrag auf Platz 1, in den Top 3 und in den Top 5 landet, dazu der Mean
Reciprocal Rank (MRR, der Mittelwert von 1/Platz) und der Score, mit dem die richtigen Treffer gefunden
werden. Mit --detail zeigt es je Frage den Platz und, falls nicht Platz 1, was davor lag.
Die Basismessung
Der Ist-Zustand mit nomic v1 ohne Präfixe: 21 von 38 Inhaltsfragen auf Platz 1, 33 in den Top 5, MRR 0,694. Alle 6 Listenfragen fanden ihre Übersicht. Mit Präfixen wurde es nicht besser, sondern gemischt: Platz 1 stieg auf 23, die Top 5 sanken aber auf 31. Für den Chatbot zählen die Top 5, denn nur diese Abschnitte bekommt das Sprachmodell zu sehen. Die Präfixe hätten also zwei Fragen mehr ins Leere laufen lassen.
Die Detailansicht zeigte außerdem, dass vier Fragen bei allen v1-Varianten danebenlagen, am deutlichsten die nach dem halbierten Arbeitsspeicher: Der richtige Beitrag kam erst auf Platz 29 bis 33. Das lag nicht an Präfixen. Das Modell verstand die deutschen Umschreibungen schlicht nicht gut genug.
Vier Modelle im Vergleich
Damit war die Frage eine andere: nicht „Präfix ja oder nein“, sondern „welches Modell“. Ich habe drei
mehrsprachige Kandidaten ausgewählt, die über Ollama verfügbar sind und auf der CPU in vertretbarer Zeit
laufen. Jeder bekam einen eigenen Alias in LiteLLM, eine Ollama-Variante mit num_gpu 0, damit
er keinen Grafikspeicher belegt, und dann denselben Testsatz.
| Modell | Platz 1 | Top 5 | MRR | ms je Abschnitt | Dimensionen |
|---|---|---|---|---|---|
| nomic-embed-text v1 (bisher) | 21 | 33/38 | 0,694 | 140 | 768 |
| nomic-embed-text v1 mit Präfixen | 23 | 31/38 | 0,725 | 140 | 768 |
| bge-m3 | 34 | 37/38 | 0,930 | 262 | 1024 |
| qwen3-embedding:0.6b | 28 | 37/38 | 0,847 | 613 | 1024 |
| nomic-embed-text-v2-moe | 33 | 38/38 | 0,919 | 181 | 768 |
bge-m3 hat knapp den besten Platz-1-Wert und den besten MRR. Ich habe mich trotzdem für nomic-embed-text-v2-moe entschieden, aus drei Gründen:
- Es ist das einzige Modell, bei dem keine Frage aus den Top 5 fällt. Für den Chatbot ist das die Kennzahl, die zählt: Platz 2 statt Platz 1 merkt kein Leser, Platz 6 schon.
- Es ist mit 181 ms je Abschnitt der schnellste Kandidat. Der nächtliche Import bettet jedes Mal alles neu ein, die Laufzeit wächst mit jedem Beitrag.
- Es hat wie das alte Modell 768 Dimensionen. Die Collections in Qdrant konnten bleiben, wie sie sind; bei 1024 Dimensionen hätte ich sie neu anlegen müssen.
Die vier Dauer-Fehlgriffe von v1 landen mit v2 alle auf Platz 1 bis 4, die Frage nach dem Arbeitsspeicher eingeschlossen. Ein Hinweis zur Einordnung: 38 Fragen sind ein kleiner Testsatz. Der Unterschied zwischen 33 und 38 Treffern ist deutlich, der zwischen 37 und 38 kann Zufall sein. Für die Entscheidung gab deshalb nicht die eine Frage den Ausschlag, sondern die Summe aus Qualität, Tempo und Aufwand.
- Das Modell verarbeitet höchstens 512 Token je Text. Unsere Abschnitte mit maximal 1000 Zeichen liegen deutlich darunter. Wer größere Abschnitte einbettet, verliert sonst still das Ende.
- Ollama nutzt standardmäßig die Grafikkarte. Für den reinen CPU-Betrieb braucht es eine eigene Modelfile-Variante mit
PARAMETER num_gpu 0, sonst verdrängt es beim ersten Aufruf Teile des Sprachmodells aus dem Grafikspeicher.
Die Falle: Schwellen hängen am Modell
Beim Vergleich fiel eine Zahl auf, die in keiner Rangliste steht: Die richtigen Treffer von nomic v2 hatten einen Score von minimal 0,414 und im Median 0,569. Bei v1 waren es 0,615 und 0,753. Die Rangfolge ist besser, die absoluten Werte liegen aber viel tiefer. Das ist bei Embedding-Modellen normal, jedes Modell hat seine eigene Skala.
Der Chatbot filtert jedoch nach absoluten Werten. Qdrant lieferte nur Treffer ab 0,55, und der Code, der den Kontext für das Sprachmodell zusammenbaut, verwarf zusätzlich alles bis 0,50. Mit dem neuen Modell wäre etwa die Hälfte der richtigen Treffer an dieser Schwelle hängengeblieben. Der Modellwechsel hätte die Suche dann schlechter gemacht, obwohl das Modell besser ist.
Um neue Schwellen festzulegen, habe ich Fragen gestellt, auf die der Blog keine Antwort hat, etwa nach einem Rezept für Apfelstrudel, und die Scores der besten Treffer mit denen der echten Treffer verglichen. Bei v2 lagen fremde Fragen bei höchstens 0,343, echte Treffer bei mindestens 0,415. Dazwischen liegt eine Lücke, und in diese Lücke kam die neue Schwelle: 0,36 für Inhaltsfragen, 0,30 für Listenfragen, die gern etwas lockerer formuliert sind.
Präfix nur für die Frage
Bei nomic v2 sind die Aufgabenpräfixe ebenfalls vorgesehen. Die Messung zeigte: Das Präfix an der Frage bringt viel, das am Dokument kaum etwas. Mit beiden Präfixen landeten 33 Fragen auf Platz 1, nur mit dem Frage-Präfix 32, die Top 5 blieben in beiden Fällen bei 38 von 38.
Das war wichtig, weil der Import das Dokument-Präfix nicht sauber setzen kann. Er nutzt in n8n die
LangChain-Kette aus Document Loader, Text Splitter und Embeddings-Node. Diese Kette bettet die Abschnitte
direkt ein, ein Präfix je Abschnitt ist darin nicht vorgesehen. Man hätte es in den Text schreiben müssen,
dann stünde es aber auch im gespeicherten Abschnitt und im Kontext des Sprachmodells. Für einen Treffer mehr
auf Platz 1 war mir dieser Umbau zu viel. In Produktion bekommt deshalb nur die Frage
search_query: vorangestellt, die Dokumente bleiben ohne Präfix.
Die Umsetzung
Ein Modellwechsel betrifft zwei Workflows, die zusammenpassen müssen: den Import, der die Abschnitte einbettet, und den Chatbot, der die Frage einbettet. Vektoren aus verschiedenen Modellen sind nicht vergleichbar. Steht der Chatbot schon auf v2, während in der Datenbank noch v1-Vektoren liegen, liefert die Suche Zufallsergebnisse, ohne Fehlermeldung.
Die Wissensbasis läuft deshalb schon länger nach dem Blue/Green-Prinzip: Es gibt zwei Collections und einen Alias, der auf die aktive zeigt. Der Import baut immer die inaktive neu und schwenkt erst danach den Alias. Damit sah der Ablauf so aus:
- Beide Workflows exportieren und als Rückfall ablegen.
- Im Import das Modell tauschen, im Chatbot Modell, Frage-Präfix und die beiden Schwellen. Beide Workflows importieren und veröffentlichen.
- Den Import sofort von Hand starten. Er brauchte 206 Sekunden und schwenkte danach den Alias auf die neue Collection. Bis dahin beantwortet der Chatbot Fragen mit v2 gegen v1-Vektoren, das Fenster ist also so kurz wie möglich zu halten.
- Die alte Collection bleibt bis zum nächsten nächtlichen Lauf unverändert. Bis dahin ist der Rückweg ein Alias-Schwenk plus die alten Workflows.
- Den Testsatz gegen die neue Live-Collection laufen lassen, mit Kontrolle der Nachbildung.
- Auf der Website echte Fragen stellen, auch fremde.
Ergebnis und Grenzen
| Kennzahl | vorher (v1) | nachher (v2, live gemessen) |
|---|---|---|
| Inhaltsfragen auf Platz 1 | 21 von 38 | 32 von 38 |
| Inhaltsfragen in den Top 5 | 33 von 38 | 38 von 38 |
| MRR | 0,694 | 0,909 |
| Listenfragen gefunden | 6 von 6 | 6 von 6 |
| Quellen bei fremden Fragen | immer welche | keine |
Die Kontrolle der Nachbildung ergab wieder 1,0000, die Zahlen gelten also für das, was der Chatbot tatsächlich tut. Auf der Website beantwortet er die Frage nach dem halbierten Arbeitsspeicher jetzt inhaltlich statt mit einem Verweis auf einen unpassenden Beitrag, und zum Apfelstrudel bekommt er keine Quellen mehr, statt einen fremden Beitrag als Kontext zu erhalten.
Ganz gelöst ist der Auslöser allerdings nicht. Die vage Frage „Wie sind Passwörter ins SIEM gelangt?“ trifft weiter zuerst einen älteren Beitrag über die Einrichtung von Wazuh, und der handelt tatsächlich auch von SIEM-Passwörtern. Das ist keine falsche Antwort im engeren Sinn, nur nicht die gemeinte. Etwas genauer gefragt steht der richtige Beitrag klar auf Platz 1. An dieser Stelle hilft kein besseres Modell mehr, die Frage ist schlicht mehrdeutig.
Checkliste für den eigenen RAG-Chatbot
- Gibt es einen Testsatz mit umschreibend formulierten Fragen und bekannten Zielen? Ohne ihn ist jede Änderung an der Suche ein Ratespiel.
- Bildet die Messung die echte Suche nach, mit denselben Filtern, Gruppierungen und Grenzen, und gibt es eine Kontrolle, dass sie das tut?
- Welche Kennzahl zählt für den eigenen Chatbot? Wer fünf Abschnitte weitergibt, sollte die Top 5 messen, nicht nur Platz 1.
- Erwartet das Embedding-Modell Aufgabenpräfixe, und wurde gemessen, ob sie helfen?
- Filtert die Score-Schwelle tatsächlich etwas? Ein paar Fragen, auf die die Wissensbasis keine Antwort hat, zeigen es sofort.
- Wurden die Schwellen nach einem Modellwechsel neu bestimmt? Sie gehören zum Modell, nicht zur Suche.
- Werden Import und Abfrage im selben Zug umgestellt, und gibt es einen Rückweg, solange die alten Vektoren noch da sind?
- Passt das Token-Limit des Modells zur Abschnittsgröße, und reicht der Timeout des Imports für das langsamere Modell?