Der Aufbau einer Retrieval-Augmented Generation (RAG)-Pipeline in TypeScript klingt einfach: Dokumente in sinnvolle Stücke zerlegen, diese in Vektoren umwandeln und bei Nutzerfragen die passenden Abschnitte an ein Sprachmodell übergeben. Doch die Praxis zeigt: Was in Tutorials oft trivial wirkt, entpuppt sich als voller Fallstricke. Ich habe bei der Entwicklung eines mehrmandantenfähigen KI-Support-Tools in Node.js und PostgreSQL mit pgvector unzählige Fehler gemacht – die mir gleichzeitig wertvolle Lehren für RAG-Systeme vermittelten. Drei dieser Fehler und ihre Lösungen möchte ich hier teilen.
Warum RAG-Systeme scheitern können: Von der Theorie zur Praxis
Sprachmodelle wie LLMs verfügen über kein internes Wissen über unternehmensspezifische Daten. Sie wurden auf öffentlich zugänglichem Textmaterial trainiert, nicht auf internen Dokumentationen oder API-Anleitungen. RAG füllt diese Lücke, indem es den LLM mit relevanten Textausschnitten aus den eigenen Daten speist. Die vier Kernschritte sind:
- Dokumentenaufnahme und -teilung: Die Inhalte werden in handhabbare Einheiten zerlegt und in numerische Vektoren (Embeddings) umgewandelt, die die Bedeutung des Textes repräsentieren.
- Vektorspeicherung: Die Embeddings werden in einer Datenbank abgelegt, die semantische Suche unterstützt – etwa PostgreSQL mit der Erweiterung pgvector oder spezialisierte Dienste wie Pinecone.
- Abrufen relevanter Inhalte: Bei einer Nutzerfrage wird diese ebenfalls in einen Vektor umgewandelt. Das System sucht dann nach den semantisch ähnlichsten Einträgen in der Datenbank.
- Generierung der Antwort: Die gefundenen Textausschnitte werden dem Sprachmodell als Kontext zur Verfügung gestellt, das daraufhin eine präzise Antwort formuliert.
Der Teufel steckt wie so oft im Detail – insbesondere in den ersten beiden Schritten. Viele Entwickler:innen unterschätzen die Bedeutung der Dokumentenaufteilung und der Suchstrategie. Doch genau hier entstehen häufige Fehlerquellen.
Fehler 1: Starre Textblöcke zerstören den Kontext
Die gängige Methode zur Dokumentenaufteilung besteht darin, Texte in feste Zeichenlängen mit Überlappung zu zerteilen. Dies erscheint auf den ersten Blick effizient, führt in der Praxis jedoch zu schwerwiegenden Problemen. Nehmen wir ein typisches Markdown-Dokument, das die Einrichtung von Stripe-Webhooks erklärt: Ein Abschnitt beginnt mit einer Überschrift wie "Stripe-Webhooks einrichten", gefolgt von einer Erklärung und einem TypeScript-Codeblock, der die Stripe-API initialisiert und einen Webhook-Handler startet.
Ein einfacher, zeichenbasierter Chunker würde diesen Text einfach an einer festen Position teilen – möglicherweise mitten im Codeblock. Das Ergebnis sind zwei unvollständige Teile:
- Chunk 1 enthält die Erklärung sowie die erste Hälfte des Codebeispiels, die Stripe initialisiert, aber ohne den Webhook-Handler.
- Chunk 2 besteht aus der zweiten Hälfte des Codes mit Fragmenten wie
req.body,sigundendpointSecret, aber ohne jeden Kontext zu deren Funktion.
Die Folgen sind verheerend: Die Embedding-Modelle verarbeiten diese fragmentarischen Textstücke und erzeugen Vektoren, die die eigentliche Bedeutung nicht erfassen. Ein Nutzer fragt nach "Wie verifiziere ich Stripe-Webhooks?", doch der relevante Codeblock wird nicht gefunden oder nur bruchstückhaft übergeben. Das Sprachmodell erhält halbfertige Informationen und kann entweder halluzinieren oder verwirrende Antworten liefern.
Die Lösung: Strukturelle Dokumentenanalyse
Menschen strukturieren Dokumente bewusst – Überschriften gliedern den Inhalt in thematische Abschnitte. Ein guter Chunker sollte diese Struktur respektieren. Mein Ansatz bestand darin, Dokumente an den Überschriften zu teilen und jeden Abschnitt als eigenständigen, kohärenten Block zu behandeln.
Die Implementierung erfolgt in mehreren Schritten:
- Extraktion der Dokumentenmetadaten: Zunächst wird der YAML-Frontmatter (sofern vorhanden) ausgelesen, um Titel und weitere Informationen zu erfassen.
- Identifikation der Überschriften: Alle Überschriften von
#bis######werden erkannt, wobei Überschriften innerhalb von Codeblöcken (umgeben von ```) ignoriert werden.
- Erstellung thematischer Blöcke: Der Text zwischen zwei Überschriften bildet einen eigenen Chunk. Die Hierarchie der Überschriften wird dokumentiert, etwa als
"Stripe-Webhooks > Synchronisationsmodus auswählen".
- Automatische Größenbegrenzung: Überschreitet ein Abschnitt die maximale Länge für Embeddings (beispielsweise 4.000 Zeichen), wird er zunächst an Absatzgrenzen (
\n\n) und im Notfall an Zeilenumbrüchen (\n) geteilt.
Das Ergebnis sind wohlgeformte Textblöcke, die genau dem entsprechen, was Autor:innen als zusammenhängende Einheit konzipiert haben. Die Embeddings spiegeln nun die tatsächliche Bedeutung wider – nicht einen zufälligen Ausschnitt.
Fehler 2: Vektorsuche allein lässt wichtige Details übersehen
Die semantische Suche findet Inhalte anhand ihrer Bedeutung – nicht ihrer exakten Wortfolge. Ein Nutzer fragt nach "Stripe-Webhook einrichten", während das Dokument den Begriff "Stripe-Integration konfigurieren" verwendet. Beide Fragen beziehen sich auf dasselbe Thema, doch reine Vektorsuche könnte den Zusammenhang übersehen.
Hier offenbart sich ein blinder Fleck: Vektorsuche ist zwar mächtig, aber sie erfasst keine exakten Wortübereinstimmungen. Besonders bei technischen Dokumentationen, in denen bestimmte Begriffe oder Funktionen exakt benannt werden müssen, führt dies zu unvollständigen Suchergebnissen.
Hybride Suchstrategien für präzisere Ergebnisse
Um diese Lücke zu schließen, kombinierte ich zwei Suchmethoden:
- Semantische Suche: Findet thematisch ähnliche Inhalte, selbst wenn die Formulierungen variieren.
- Keyword-basierte Suche: Ergänzt die semantische Suche um exakte Wortübereinstimmungen, um technische Begriffe oder spezifische Funktionen zuverlässig zu erkennen.
Die Implementierung erfolgt durch eine Gewichtung beider Methoden. Bei der Abfrage werden zunächst die semantisch ähnlichsten Chunks identifiziert. Anschließend wird überprüft, ob die Nutzerfrage bestimmte Schlüsselwörter enthält, die in den gefundenen Abschnitten vorkommen müssen. Nur wenn beide Kriterien erfüllt sind, wird der Chunk als relevant eingestuft.
Ein konkretes Beispiel: Ein Nutzer fragt nach "Fehlerbehandlung bei Stripe-Webhooks". Die semantische Suche findet Abschnitte zum Thema Fehlerbehandlung, während die Keyword-Suche sicherstellt, dass der Begriff "Stripe" im Text vorkommt. Erst die Kombination beider Methoden liefert ein vollständiges und präzises Ergebnis.
Fehler 3: Vernachlässigte Metadaten führen zu inkonsistenten Ergebnissen
Viele RAG-Systeme speichern Embeddings in einer Datenbank, ohne ausreichende Metadaten zu hinterlegen. Dies führt zu Problemen bei der Abfrage und Antwortgenerierung. Ohne Kontext weiß das Sprachmodell nicht, aus welchem Dokument oder Abschnitt ein Chunk stammt – und kann keine präzisen Antworten liefern.
Wichtige Metadaten für mehr Klarheit
In meinem Projekt führte ich folgende Metadaten ein:
- Abschnittspfad: Dokumentiert die Hierarchie der Überschriften, etwa
"Einführung > Installation > Stripe-Webhooks". Diese Information hilft dem Sprachmodell, den Kontext der Nutzerfrage besser einzuordnen.
- Mandanten-ID: Da das System mehrmandantenfähig ist, wird jeder Chunk einer bestimmten Mandantin zugeordnet. Dies ermöglicht eine mandantenspezifische Suche und vermeidet Verwechslungen zwischen verschiedenen Kund:innen.
- Deterministische IDs: Jeder Chunk erhält eine eindeutige ID, die aus dem Abschnittspfad und dem Inhalt generiert wird (beispielsweise über SHA-256). Dadurch wird sichergestellt, dass das System beim erneuten Ausführen der Pipeline keine Duplikate erzeugt, sondern bestehende Einträge aktualisiert.
Diese Metadaten sind entscheidend für die Konsistenz und Skalierbarkeit des Systems. Sie ermöglichen eine präzise Steuerung der Abfrageergebnisse und verhindern, dass das Sprachmodell auf veraltete oder unvollständige Informationen zugreift.
Fazit: RAG-Systeme brauchen mehr als nur Embeddings
Der Aufbau einer RAG-Pipeline in TypeScript ist ein komplexes Unterfangen, das weit über die bloße Erstellung von Embeddings hinausgeht. Die größten Herausforderungen liegen in der Aufteilung der Dokumente, der Auswahl der Suchstrategie und der Verwaltung von Metadaten. Durch die Implementierung struktureller Chunking-Methoden, hybrider Suchverfahren und durchdachter Metadatenstrukturen lässt sich die Qualität der Antworten deutlich verbessern.
Wer diese Fallstricke vermeidet, erhält ein robustes System, das nicht nur technisch einwandfrei funktioniert, sondern auch die Erwartungen der Nutzer:innen erfüllt. Die Investition in eine sorgfältige Implementierung zahlt sich aus – sowohl in puncto Antwortqualität als auch in der langfristigen Wartbarkeit des Systems.
Die Lektion ist klar: RAG-Systeme sind mehr als eine technische Spielerei. Sie sind ein mächtiges Werkzeug, um Sprachmodellen den Zugang zu unternehmensspezifischem Wissen zu ermöglichen – wenn sie richtig umgesetzt werden.
KI-Zusammenfassung
TypeScript kullanarak RAG pipeline oluştururken yapılan hataların çözümleri ve neden işe yaradıkları