Zwei Embedding-Modelle, ein Vektorraum
Ich baue gerade einen Produktberater für meinen Shop: Der Katalog liegt als Vektoren in Qdrant, ein Sprachmodell durchsucht ihn und antwortet über Telegram. Beim Durchsehen der beiden Workflows ist mir aufgefallen, dass sie nicht zusammenpassen – und zwar auf eine Art, die man leicht übersieht.
Der Sync, der die Produkte einträgt, benutzt ein Embedding-Modell von OpenAI. Der Chatbot, der sie später sucht, benutzt ein lokales Modell über Ollama. Zwei verschiedene Modelle für dieselbe Aufgabe.
Warum das nicht funktionieren kann
Ein Embedding-Modell übersetzt Text in eine Liste von Zahlen – bei text-embedding-3-small sind das 1536 Stück. Diese Zahlen sind keine Eigenschaften des Textes, die irgendwo objektiv festliegen. Sie sind Koordinaten in einem Raum, den genau dieses Modell während seines Trainings aufgespannt hat.
Zwei Modelle spannen zwei völlig verschiedene Räume auf. Die Koordinate 0.42 an Position 7 bedeutet im einen Modell etwas anderes als im anderen – wenn sie überhaupt etwas bedeutet. Ein Abstandsmaß wie Cosine misst dann Winkel zwischen Punkten, die gar nicht im selben Koordinatensystem liegen.
Der laute und der leise Fehler
Wenn die Modelle unterschiedlich viele Dimensionen liefern, hast du Glück. Qdrant lehnt den Vektor ab, du bekommst eine Fehlermeldung, und du weißt sofort, wo du suchen musst. text-embedding-3-small liefert 1536 Werte, nomic-embed-text liefert 768 – das kracht sauber.
Der gefährliche Fall ist der andere: Zwei Modelle mit derselben Dimensionszahl. Dann passt technisch alles. Die Suche läuft, liefert zehn Treffer, und die Treffer sind Zufall.
Und jetzt kommt der Teil, der es wirklich tückisch macht: Ein RAG-System antwortet nie mit „ich habe nichts gefunden". Es bekommt zehn Produkte, gibt sie dem Sprachmodell, und das Sprachmodell formuliert daraus eine flüssige, selbstbewusste Empfehlung. Der Bot schlägt einem Kunden, der nach einem schwarzen T-Shirt fragt, mit vollkommener Überzeugung eine Kaffeetasse vor. Kein Fehler im Log, keine Ausnahme, keine rote Zeile. Nur eine schlechte Antwort, die aussieht wie eine gute.
Wie du es prüfst
Der erste Blick geht in die Collection selbst:
curl -s http://127.0.0.1:6333/collections/deine_collection | python3 -m json.tool
Dort stehen size und distance. Vergleich die Zahl mit dem, was dein Abfrage-Modell ausgibt. Bei mir: 1536 in der Collection, ein lokales Modell in der Abfrage – das passt nicht zusammen.
Der zweite Test ist noch aussagekräftiger und dauert zehn Sekunden: Nimm den exakten Titel eines Produkts, das du sicher im Index hast, und schick ihn als Suchanfrage. Landet dieses Produkt nicht auf Platz eins, ist etwas grundlegend falsch. Wörtliche Übereinstimmung ist der einfachste Fall, den es gibt – wenn der scheitert, brauchst du über schwierigere Fragen gar nicht nachzudenken.
Wie du es verhinderst
Drei Dinge haben sich für mich als sinnvoll herausgestellt:
- Ein Ort für den Modellnamen. Nicht in zwei Workflows getrennt eintragen, sondern als Umgebungsvariable, die beide lesen. Was an zwei Stellen gepflegt wird, läuft irgendwann auseinander.
- Das Modell im Namen der Collection.
produkte_openai_small_1536sieht sperrig aus, macht aber beim Draufschauen sofort klar, womit der Inhalt erzeugt wurde. - Bei einem Modellwechsel neu indexieren, nicht ergänzen. Alte und neue Vektoren in derselben Collection sind schlimmer als gar keine – ein Teil der Treffer stimmt, der andere nicht, und du suchst den Fehler an der falschen Stelle.
Der Punkt hinter alldem: Vektordatenbanken haben kein Schema im klassischen Sinn. Sie können nicht prüfen, ob deine Zahlen von demselben Modell stammen wie die schon gespeicherten – sie sehen nur, dass es die richtige Menge Zahlen ist. Diese Prüfung musst du selbst einbauen.