Startseite / Artikel / Entwurf eines Grounded RAG-Pipelines: Aufteilen in Blöcke, Filtern und Streaming.

Entwurf eines Grounded RAG-Pipelines: Aufteilen in Blöcke, Filtern und Streaming.

Eine Übersicht über eine auf Grounded RAG basierende Architektur: strukturbewusstes Aufteilen in Blöcke, token-sicheres Trennen, dreistufiges Filtern bei der Informationsbeschaffung, Wiederzusammenführung von Blöcken, Prompt-Design und Streaming.

3895 Wörter

Allgemeinzweckige Sprachmodelle können hervorragend reasoning, schreiben und erklären – solange man sie nicht nach einem System fragt, das sie noch nie gesehen haben: Ihre interne Dokumentation, Ihre Arbeitsabläufe im jeweiligen Bereich oder das genaue Verhalten einer Unternehmensanwendung, die Ihr Team täglich konfiguriert. Dieses Wissen befindet sich nicht in den Trainingsdaten, und kein Trick mit Prompten kann es dorthin bringen. Noch schlimmer: Das Modell gibt selten zu, dass ein Mangel besteht; es liefert stattdessen eine flüssige, selbstsichere Schätzung. Im Folgenden wird die Grundlage eines auf Grounding basierenden Retrieval-augmented Generation (RAG)-Systems erläutert: Wie Dokumente vorbereitet werden, wie Fragmente erstellt werden, wie Kandidaten gefiltert werden, wie der Prompt gestaltet wird und wie Antworten beim Benutzer ankommen – zusammen mit den Gründen für jede Entscheidung, damit Sie dieses Konzept auf Ihre eigene Implementierung anwenden können.

Warum Grounding die Aufgabe des Modells verändert

Die Grundidee von RAG lässt sich leicht erklären. Anstatt dem Modell zu vertrauen, dass es sich eine Antwort merkt, geben Sie ihm die Antwort zum Lesen. Ihre tatsächlichen Dokumente werden in suchbare Abschnitte aufgeteilt; wenn eine Frage eintrifft, findet das System die Passagen mit der größten Wahrscheinlichkeit, sie zu beantworten, und fügt sie in den Kontext des Modells ein. Die Aufgabe wechselt von „erinnere dich an diesen Fakt“ zu „lese diesen Abschnitt und erkläre ihn gut“, was genau die Art von Aufgabe ist, die Sprachmodelle zuverlässig bewältigen können.

Das Konzept ist einfach, doch es zu einem reibungslosen Funktionieren zu bringen, ist das nicht. Die richtigen Informationen abrufen, sie kohärent halten und schnell genug antworten, sodass niemand auf einen Ladebalken starren muss – all das erfordert Entscheidungen, die leicht unterschätzt werden. Das hier beschriebene System ist absichtlich in der Grundstufe angesiedelt: Informationen abrufen und anschließend antworten. Ambitioniertere Erweiterungen wie Wissensgraphen oder agentbasierte Tools bauen genau auf diesen Elementen auf und funktionieren nur, wenn die Grundlage solide ist.

Markdown als Quelle der Wahrheit

Alle Informationen, auf die das System antwortet, befinden sich in Markdown-Dateien. Diese Wahl ist praktisch und nicht ästhetisch motiviert. Markdown bietet genügend Struktur, um die Bedeutung beizubehalten – einschließlich Überschriften, Hierarchien, Listen und Tabellen – bleibt es dennoch reiner Text, der ohne Probleme in andere Strukturen eingebettet werden kann, anstatt mit einem komplexeren Markup-Format umzugehen.

Die Dateien werden so verfasst, wie Menschen normalerweise technische Dokumentationen schreiben: ein Überschrift für jedes Thema, Untertitel für die darunterliegenden Details, Tabellen dort, wo die Struktur wichtig ist, sowie Screenshots, wo ein Bild schneller erklärt als Prosa. Der Schreibstil wird nicht dem Prozessablauf angepasst. Das ist wichtig, denn Dokumentationen, die für Suchsysteme verfasst werden müssen, entstehen oft gar nicht.

Screenshots außerhalb des Einbettungspfads halten

Screenshots erfordern eigene Gestaltungsentscheidungen. In Anwendungsdokumentationen dienen sie nicht der Dekoration: Ein großer Teil der Anleitungsfragen bezieht sich tatsächlich darauf, welchen Knopf oder Menüpunkt man verwenden soll – und Prosa allein kommt damit nur schlecht zurecht.

Anstelle von Bildern, die direkt in den Dokumentationsdateien eingebettet werden, werden sie getrennt gehostet und jede Markdown-Datei verweist über eine URL auf sie. Der Link besteht lediglich aus Text, wodurch er wie jeder andere Text beim Chunking, Einbetten und Abrufen übertragen wird. Wenn ein abgerufener Chunk mit einem solchen Link in einer Antwort landet, liest die Frontend-Plattform die URL und lädt das Bild zum Rendern. Der Benutzer sieht die Dokumentation genau so, wie sie geschrieben wurde – einschließlich der Bilder – während der Eingabeprozess und das Abrufsystem überhaupt keine Bilddaten verarbeiten.

Dieses Prinzip verdient eine Bezeichnung: Behalten Sie den Inhalt in jeder Phase, die nur Text benötigt, als Text bei und verarbeiten Sie reichhaltigere Medien erst in dem letzten möglichen Moment, wenn etwas einem Benutzer angezeigt werden soll.

Das Erstellen der Antwort als Stream

Das gleiche „letzter Schritt“-Denken gilt auch bei der Bereitstellung von Antworten. Sobald die Erzeugung beginnt, gibt es keinen Grund, den Benutzer mit der vollständigen Antwort warten zu lassen. Tokens fließen über eine persistente Verbindung zurück, sobald das Modell sie erzeugt, sodass sich die Antwort Schritt für Schritt aus einigen Worten zusammensetzt – ähnlich wie jemand eine Erklärung tippt. In Kombination mit Bildern, die asynchron geladen werden, sobald ihre URLs im Stream auftauchen, wirkt das Ganze wie ein Gespräch, obwohl es im Grunde eine Datenbankabfrage gefolgt von der Erzeugung ist.

Hierarchisches Chunking: Respekt vor der Struktur des Dokuments

Bevor etwas gesucht werden kann, muss die Dokumentation in Einheiten unterteilt werden, die jeweils unabhängig eingebettet und abgerufen werden können. An dieser Stelle gehen viele RAG-Systeme oft unerkannt Kompromisse ein, und die Folgen sind leicht zu übersehen.

Warum feste Größen bei der Aufteilung heimlich versagen

Der naive Ansatz ist mechanisch: Man wählt eine Tokenanzahl aus, teilt das Dokument in Teile von etwa dieser Größe auf und geht weiter. Er lässt sich schnell umsetzen, ist aber auf eine Weise fehlerhaft, die niemals zu einem Fehler führt. Ein Fenster fester Größe hat kein Verständnis für Satzgrenzen, geschweige denn für konzeptionelle Grenzen. Es teilt gerne ein nummeriertes Verfahren in der Mitte, trennt eine Tabelle von dem Überschriftsteil, der ihr Bedeutung verleiht, oder lässt eine Definition in einem Teil und das zugehörige Beispiel im nächsten.

Solche Teile haben die richtige Länge, aber die falsche Form, und die Qualität der Auswertung hängt von dieser Form ab. Wenn der abgerufene Text keinen vollständigen Gedanken enthält, kann sorgfältiges Nachfragen später nicht mehr Abhilfe schaffen. Das Modell handelt mit einem Fragment, und die Antwort spiegelt das wider.

Anstatt dessen die Überschriftsstruktur lesen

Die hierarchische Aufteilung geht von der Beobachtung aus, dass Dokumentationen bereits eine Struktur aufweisen und dass diese Struktur ein Vorteil ist. Gut geschriebene technische Dokumente bilden einen Überschriftenbaum: Hauptabschnitte, darunter eingebettete Unterkapitel sowie Texte auf jeder Ebene. Anstatt diesen Baum zu ignorieren, durchläuft der Aufteilungsalgorithmus ihn systematisch:

  • Jeder Hauptabschnitt wird zu einem eigenen Blöck.
  • Auch jedes Unterkapitel wird zu einem eigenen Blöck, wobei dieser den übergeordneten Abschnitt vermerkt.
  • Falls ein Unterkapitel kurz genug ist, um besser zusammen mit seinem übergeordneten Abschnitt gelesen zu werden, werden die beiden zu einem einzigen kombinierten Blöck zusammengeführt, anstatt ein winziges, kontextarmes Fragment zu erzeugen.
  • Inhalt, der nicht unter einer Überschrift steht – wie Einleitungen, lose stehende Absätze oder Anmerkungen – wird dennoch als eigenständiger Blöcktyp erfasst, anstatt weggelassen oder unpassend in einen benachbarten Blöck eingefügt zu werden.

Metadaten, die spätere Schritte ermöglichen

Jeder Datenblock enthält mehr als nur seinen Text:

  • Eine Navigationsleiste: die Kette der übergeordneten Überschriften. Ein Datenblock zu einer einzelnen Konfigurationsoption weiß auch dann noch, dass er zu einem umfassenderen Arbeitsablauf gehört, wenn er allein abgerufen wird.
  • Eine Typbezeichnung: zur Unterscheidung zwischen obersten Abschnitten, verschachtelten Details, zusammengeführten Eltern- und Kind-Datenblöcken sowie eigenständigen Notizen.
  • Ein stabiler Identifikator: der den Datenblock mit seiner genauen Herkunft im Quellfile verbindet.

Diese Metadaten sind keine Dekoration. Sie ermöglichen es später, die Reihenfolge zu ändern, getrennte Abschnitte wieder zusammenzufügen sowie kohärente, zuordnende Antworten zu liefern. Eine einfache Liste von gleich großen Textblöcken kann all das nicht unterstützen; ein Datenblock, der seinen Platz im Strukturbaum kennt, schon.

Der zugrundeliegende Kompromiss besteht darin, der bereits vorhandenen Struktur des Dokuments zu folgen anstatt eine willkürliche hinzuzufügen. Das zahlt sich schnell aus: Die Abschnitte lesen wie vollständige Gedanken, weil es tatsächlich vollständige Gedanken sind, die genau so strukturiert sind, wie der Dokumentenschreiber es vorgesehen hat.

Eine anpassbare zweite Durchgangsphase für die Embedding-Token-Limitierung

Hierarchisches Aufteilen sorgt für die richtige Struktur, ignoriert jedoch eine grundlegende Einschränkung: Das Embedding-Modell akzeptiert pro Eingabe nur eine begrenzte Anzahl an Token. Die Struktur eines Dokuments kümmert sich nicht um diese Grenze. Eine lange FAQ, eine umfangreiche Referenztabelle oder ein Abschnitt, der einfach zu lang ist, kann trotzdem ein vollkommen kohärenter Abschnitt sein und dennoch die vom Modell akzeptierte Anzahl an Token überschreiten.

Dieser Fehler ist gefährlich unauffällig. Je nach Client wird ein zu großer Datenblock entweder abgelehnt oder vor der Einbettung stillschweigend gekürzt, und in beiden Fällen verschwindet der Inhalt ohne dass jemand es zum Zeitpunkt der Aufnahme bemerkt. Die Kürzung ist das heimtückischere Ergebnis, denn der Block existiert weiterhin und wird weiterhin abgerufen; sein Vektor repräsentiert einfach nicht mehr den Text, für den man ihn hielt.

Die Lösung besteht darin, nach dem hierarchischen Verarbeitungsschritt einen weiteren Schritt einzufügen. Jeder Datenblock wird anhand eines Sicherheitsschwellenwerts überprüft, der deutlich unter dem tatsächlichen Maximum des Modells liegt, sodass es einen Puffer statt einer steilen Grenze gibt. Die Anzahl der Token variiert zwischen den Tokenisierern, und ein Puffer absorbiert diese Unsicherheit. Blöcke unterhalb des Schwellenwerts gelangen unverändert weiter. Blöcke darüber werden weiter aufgeteilt, und jedes entstehende Stück wird:

  • ausdrücklich als Teil eines größeren Ganzen gekennzeichnet, damit spätere Schritte seine „Geschwister“ finden können
  • Da es nur eine geringe Überschneidung des Textes mit dem benachbarten Abschnitt gibt, stößt niemand, der ihn liest – weder Mensch noch Modell – auf einen plötzlichen, kontextlosen Bruch mitten in einem Gedanken.
  • Wo genau geschnitten werden soll und warum eine naive Aufteilung auch hier nicht ausreicht, ist ein eigenständiges, wichtiges Designthema. Im Grunde sorgt der hierarchische Durchlauf für die richtige Form und der adaptive Durchlauf dafür, dass alles passt – sodass eine gute Form niemals zu einem Verlust an Inhalt führt.

    Speichern von Vektoren zusammen mit ihren Metadaten

    Eingebettete Datenblöcke benötigen eine Struktur, die einer einzigen Frage dient: Welche dieser vielen Vektoren sind dem neuen am nächsten? Diese Frage muss schnell und in großem Umfang beantwortet werden. Relationale Datenbanken sind nicht dafür konzipiert, daher übernimmt ein speziell entwickelter Vektor-Speicher diese Aufgabe.

    Die Datenbank läuft in einem Container statt direkt auf dem Host-System. Die Gründe sind praktischer Natur: Eine containerisierte Instanz lässt sich leicht starten, löschen oder auf ein anderes Gerät verschieben, und sie bringt keine Abhängigkeiten mit sich auf das Host-System.

    Neben jedem Vektor speichert der Speicher eine vollständige Metadatenmenge mit dem Text des Datensatzes, den Navigationshinweisen, der Art sowie dem Quellidentifikator. Jeder Treffer bei der Ähnlichkeitssuche enthält somit sofort alle notwendigen Kontextinformationen, anstatt nur als anonymer Vektor vorliegen zu müssen. Die Kombination aus schneller Ähnlichkeitssuche und umfangreichen Metadaten zu jedem Ergebnis ermöglicht es, Filterungen durchzuführen, Ranglisten neu zu ordnen sowie verwandte Ergebnisse wieder zusammenzusetzen – ohne erneute Abfragen bei einer anderen Quelle.

    Der Anfrage-Lebenszyklus: von der Frage bis zur gestreamten Antwort

    Hier erledigt das System seine eigentliche Arbeit, weshalb es sinnvoll ist, sie genau genug zu beschreiben, damit die Logik wiederverwendet werden kann.

    Trennung von Einreichung und Streaming

    Die API stellt zwei Endpunkte mit unterschiedlichen Aufgaben bereit. Der erste empfängt die Frage und gibt sofort einen Konversationsidentifikator zurück. Der zweite ist ein Streaming-Endpunkt, an den sich die Frontend-Anwendung mit diesem Identifikator anschließt. Eine Frage entgegenzunehmen und eine Antwort bereitzustellen sind getrennte Aufgaben, und ihre Trennung ermöglicht es, die Antwort schrittweise zurückzusenden, anstatt eine einzige Anfrage offen zu halten, bis eine monolithische Antwort fertig ist. Zudem bietet sie dem Client eine klare Möglichkeit, sich erneut anzuschließen oder Logs einer bestimmten Konversation zu korrelieren.

    Zwischen diesen beiden Endpunkten werden die folgenden Schritte in dieser Reihenfolge ausgeführt.

    Schritt 1: Einfügen der Frage mithilfe des Einpflegungsmodells

    Der Text des Benutzers wird mit genau demselben Modell eingebettet, das bei der Erfassung verwendet wurde. Das ist ein Detail, das nicht geändert werden kann. Ähnlichkeitsvergleiche sind nur dann sinnvoll, wenn der Abfragenvektor und die gespeicherten Vektoren sich im selben Vektorraum befinden. Wenn sich das Einbettungsmodell jemals ändert, müssen alle gespeicherten Teile erneut eingebettet werden, denn Vektoren aus zwei verschiedenen Modellen sind selbst dann nicht vergleichbar, wenn sie denselben Text beschreiben.

    Schritt 2: breites Netz auswerfen

    Der Abfragenvektor wird mit den gespeicherten Vektorfragmenten verglichen, und es werden ungefähr die zwölf nächsten Übereinstimmungen ermittelt. Als Maß dient die Kosinusähnlichkeit, die im Grunde den Winkel zwischen zwei Vektoren misst: Je kleiner der Winkel, desto ähnlicher ist die Bedeutung. Diese Phase ist absichtlich großzügig gestaltet. Ihr Zweck besteht darin, sicherzustellen, dass nichts Relevantes ausgeschlossen wird, bevor mit der eigentlichen Auswahl begonnen wird.

    Schritt 3: Eingrenzung der Kandidaten durch drei Filter

    Drei Filter werden nacheinander angewendet, um die Dutzend Kandidaten auf die wenigen relevanten zu reduzieren:

    1. Eine Mindestähnlichkeitsgrenze. Kandidaten mit einem Wert unter dem Minimum werden entfernt. Es handelt sich dabei um Abschnitte, die nur aufgrund fehlender besserer Alternativen in die Liste gekommen sind; ihr Beibehalten würde nur Störungen verursachen.
    2. Eine Neubewertungsschleife. Die verbleibenden Kandidaten werden erneut von einem Modell bewertet, das anders arbeitet als die erste Suche. Anstatt die Frage und jeden Abschnitt getrennt einzubetten und die Vektoren zu vergleichen, nimmt das Modell Frage und einen Abschnitt gemeinsam als Eingabe und prüft, ob dieser Abschnitt tatsächlich die Frage beantwortet. Dadurch werden bestimmte falsch positive Ergebnisse ausgeschlossen: Abschnitte, die im Vektorraum nahe der Abfrage liegen, aber beim Zusammenlesen mit dieser keine Antwort enthalten.
  • Eine strenge Untergrenze und ein fester Obergrenzwert. Nach der Neubewertung schränkt ein weitaus strengeres Kriterium das Übrige ein, und was übrig bleibt, ist auf einen kleinen, festgelegten Maximalwert beschränkt.
  • Jeder Schritt hat eine bestimmte Aufgabe: Der erste schützt die Ergebnisse vor Störungen, der zweite sorgt für Präzision und der dritte setzt eine feste Obergrenze dafür, wie viel Kontext das Modell erhält. Was übrig bleibt, ist das beste verfügbare Material – nicht nur der oberste Eintrag einer langen Liste.

    Der Grund für diese Reihenfolge ist der Kostenfaktor. Der Paar-Les-Neubewerter ist zwar viel genauer als der Vektorvergleich, kostet aber auch deutlich mehr, da er jedes Frage-Anteil-Paar gemeinsam verarbeiten muss. Wenn man ihn anstelle des gesamten Korpus an einer Handvoll vorfilterter Kandidaten einsetzt, erhält man die gewünschte Präzision zu einem geringeren Preis.

    Schritt 4: Getrennte Abschnitte wiederherstellen

    Einige überlebende Abschnitte sind nur Teile einer Einheit, die beim adaptiven Verarbeitungsschritt geteilt werden mussten. Wenn das Modell beispielsweise den zweiten von drei Teilen erhält, ohne irgendwelche Hinweise darauf, dass die anderen existieren, basiert seine Antwort auf unvollständigen Informationen. Deshalb wird vor der Zusammenstellung des Prompts jeder überlebende Abschnitt auf „Geschwister“ – also die anderen Teile derselben ursprünglichen Einheit – überprüft. Wenn solche Teile vorhanden sind, werden sie heruntergeladen und in der richtigen Reihenfolge angeordnet, wobei jeder mit seiner Position versehen wird. Anschließend liest das Modell die gesamte Einheit statt nur einen Teil davon. Genau hier zeigen sich die Vorteile der bei der Eingabe hinzugefügten Teilbezeichnungen und stabilen Identifikatoren.

    Schritt 5: Zusammenstellung des Prompts

    Sämtliche abgerufenen Teile zusammen mit ihren wiederhergestellten „Geschwistern“ bilden den vollständigen Kontext. Die Frage des Benutzers wird zusammen damit übertragen, und ein Systemprompt, der im nächsten Abschnitt beschrieben wird, weist das Modell darauf hin, welche Rolle es spielen soll und wie es das Material nutzen muss.

    Schritt 6: Die Antwort als Stream zurücksenden

    Der generierte Text wird über denselben Streaming-Endpunkt zurückgesendet, an den sich der Client ursprünglich verbunden hat – und zwar in kleinen Schritten statt als eine einzige, blockierende Antwort. Der Benutzer kann so beobachten, wie sich die Antwort in Echtzeit formt.

    Zusammenfassend: Die Anfrage wird vektorisiert, breit gefasst abgerufen, durch drei Filter geschleust, fehlende Teile ergänzt, das Modell instruiert und dessen Ausgabe als Stream übertragen. Keine einzelne Phase ist besonders komplex; die Qualität entsteht durch saubere Übergänge zwischen den Phasen sowie durch die Zuweisung genau einer Aufgabe pro Phase.

    Entwurf des Prompts: Format, Modell und Ton

    Sobald der Prompt zusammengestellt ist, sind die schwierigen Probleme bei der Informationsabrufung gelöst: Die richtigen Teile wurden gefunden, gefiltert und neu zusammengesetzt. Was übrig bleibt, ist genauso leicht falsch zu machen: Das Material so darzustellen, dass das Modell eine gute Antwort liefert – und nicht nur eine technisch korrekte.

    Den Prompt in Markdown schreiben

    Der Prompt selbst ist in Markdown geschrieben. Auch hier liegt der Grund im Praktischen: Modelle haben große Mengen an Markdown in Dokumentationen, README-Dateien und technischen Texten gesehen, wodurch Anweisungen in einem vertrauten Format zuverlässiger verarbeitet werden können als solche in einer benutzerdefinierten Struktur, die das Modell entschlüsseln muss. Überschriften trennen die Abschnitte des Prompts voneinander, der abgerufte Kontext ist klar von den umliegenden Anweisungen abgegrenzt, und jedes aus getrennten Teilen wieder zusammengesetzte Inhaltsstück trägt einen sichtbaren Marker, sodass das Modell zwischen „einer aus Teilen zusammengesetzten kontinuierlichen Idee“ und gewöhnlichem abgerufenen Text unterscheiden kann.

    Wahl eines kleineren, schnelleren Modells für die Synthese

    Das Modell, das die endgültige Antwort erstellt, ist ein kleineres, schnelleres Modell und nicht das größte verfügbare. Das erscheint gegenintuitiv, bis man sich die Aufgabe ansieht, die es tatsächlich erledigt. Es soll nichts von Grund auf ableiten, sich seltene Details merken oder fehlendes Wissen ausgleichen; die schwierige Arbeit fand bereits früher bei der Suche und Filterung statt. Seine verbleibende Aufgabe besteht darin, sorgfältig ausgewählten, ordentlich strukturierten Kontext in eine klare, schnelle Antwort im richtigen Tonfall umzuwandeln.

    Das ist eine Syntheseaufgabe. Wenn der Kontext bereits vorhanden ist, nähert sich ein kompaktes Modell in Bezug auf die Antwortqualität einem viel größeren Modell an, reagiert dabei schneller und kostet deutlich weniger. Die Denkfähigkeit eines größeren Modells lohnt sich, wenn dieses etwas herausfinden muss. Sie ist hingegen weitaus weniger wertvoll, wenn die Antwort bereits vorliegt und die Aufgabe darin besteht, sie gut zu erklären. Der Kompromiss hängt von der Qualität der Informationsbeschaffung ab: Je schwächer der Kontext, desto wichtiger wird die Fähigkeit eines größeren Modells, mit Mehrdeutigkeiten umzugehen – was ein weiterer Grund ist, in Filterstufen zu investieren.

    Eine bewusste Struktur der Anweisung

    Die Systemanweisung folgt einer festen Struktur statt einer improvisierten:

    • Rolle. Sie beginnt damit, dem Modell mitzuteilen, um welche Art von Assistenten es sich handelt und in welchem Bereich es tätig ist, sodass Tonfall und Annahmen bereits in der ersten Zeile festgelegt werden.
  • Aufgabe. Hier wird die Aufgabe ausdrücklich beschrieben: Der abgerufte Kontext muss gelesen und ausschließlich darauf basierend geantwortet werden, ohne auf allgemeines Wissen oder Vermutungen zurückzugreifen – selbst dann nicht, wenn eine Antwort offensichtlich erscheint.
  • Sicherheit und Lücken. Ein kurzer Einleitungsteil legt fest, wie zuversichtlich oder vorsichtig die Antworten klingen sollten und was zu tun ist, wenn der Kontext tatsächlich keine Antwort enthält: Man sollte dies klar aussprechen, anstatt etwas zu erfinden.
  • Ton und Stil. Formalität, typische Länge der Antworten sowie ob mit der direkten Antwort begonnen oder erst Schritt für Schritt darauf hingewiesen werden soll, sind alle genau festgelegt und nicht dem Modell überlassen.
  • Nichts bleibt dem Standardverständnis des Modells darüber überlassen, wie ein hilfreicher Assistent klingen sollte. Jedes Verhalten wird schriftlich festgelegt, ähnlich wie bei der Einarbeitung eines neuen Teammitglieds, indem zunächst die Kommunikationsnormen des Teams erklärt werden, bevor Aufgaben zugewiesen werden.

    Der Vorteil ist ein Assistent, der zuverlässig antwortet, wenn er Grundlagen hat, ehrlich zugibt, wenn nicht, und in jedem Gespräch eine konsequente Stimme beibehält. Diese Konstanz stammt nicht vom Modell selbst, sondern von der darumherum formulierten Anweisung.

    Routing nach Absicht: reguläre Blöcke und Bildschirmblöcke

    Fragen, die ähnlich aussehen, können nach völlig unterschiedlichen Dingen fragen. Eine Frage wie „Wie wird in diesem Workflow die Preiskalkulation vorgenommen?“ ist konzeptionell und erfordert eine Erklärung. Eine ähnliche Frage wie „Welches Feld auf diesem Bildschirm enthält den Preis?“ ist navigativ und verlangt nach einem bestimmten Feld, Knopf oder Bereich der Benutzeroberfläche. Beide können Abschnitte aus demselben Dokumentationsbereich enthalten, doch die idealen Antworten haben fast nichts miteinander zu tun. Sie gleich zu behandeln führt dazu, dass jemandem, der nur die Position suchte, eine konzeptionelle Erläuterung gegeben wird – oder schlimmer noch, dass ein Benutzeroberflächenelement in Abstraktion beschrieben wird, obwohl der Nutzer seine genaue Position benötigte.

    Die Kenntnisse bei der Aufnahme trennen

    Die Unterscheidung wird auf Ebene der Datenblöcke vorgenommen, nicht erst zum Zeitpunkt der Antwort. Neben regulären Prose-Dokumentationsblöcken gibt es eine zweite Kategorie, die Inhalte enthält, die Bildschirme und ihre Felder beschreiben: wie jedes Feld benannt ist, welche Funktion es hat und wo es im Verhältnis zu den anderen Feldern auf demselben Bildschirm liegt. Diese werden nicht nachträglich gekennzeichnet. Die Klassifizierung erfolgt bereits beim Einlesen des Inhalts, sodass zum Zeitpunkt einer Anfrage zwei klar getrennte Wissensbestände vorliegen anstelle eines ununterschiedlichen Haufens.

    Auswahl der Antwortstrategie zum Zeitpunkt der Anfrage

    Wenn eine Frage eintrifft, wird geprüft, um welche Art von Datenblock es sich tatsächlich handelt: konzeptionelle Dokumentation oder detaillierte Informationen zu bestimmten Bildschirmen und Feldern. Diese Klassifizierung bestimmt, welche Datenblöcke bei der Suche priorisiert werden und welche Antwort das Modell erhält:

    • Bei einer auf das Bildschirmdisplay ausgerichteten Frage erhält man einen Prompt, der auf Präzision bei der Beschreibung der Benutzeroberflächenelemente ausgelegt ist: wörtlich, spezifisch und auf genau den Ort sowie das Element fokussiert.
    • Bei einer konzeptionellen Frage erhält man einen Prompt, der auf Erklärungen ausgelegt ist: umfassender und bereit, Ideen miteinander zu verbinden.

    Sowohl im einen als auch im anderen Fall sind der Abrufprozess sowie das Erzeugungsmodell identisch; nur die Herangehensweise bei der Beantwortung ändert sich und wird je nach Fragebedarf gewählt, anstatt jede Antwort durch ein generisches Muster zu leiten.

    Dies ist wichtiger, als es klingt. Ein Assistent mit nur einem Antwortstil wirkt letztendlich allgemein, egal wie gut seine Informationsabruffunktion ist. Das Erkennen der Absicht – und nicht nur des Themas – sorgt dafür, dass der Assistent den Eindruck vermittelt, auf die tatsächlich gestellte Frage zu achten. Wenn Sie dieses Muster anwenden, sollten Sie für unklare Fragen eine Rückfalllösung bereithalten – beispielsweise indem Sie auf die konzeptionelle Herangehensweise zurückgreifen und alle stark passenden Bildschirmabschnitte einbeziehen – damit eine Fehlklassifizierung sanft abgefedert wird, anstatt zu einem falschen Antwortstil zu führen.

    Die Erinnerung im Griff halten

    Eine sorgfältige Abruf- und Erstellungsprozedur ist nutzlos, wenn der Dienst allmählich seinen Speicher erschöpft. Ein Prozess, der Texte einbettet, Teile im Speicher behält, Anfragen zusammenstellt und die Ausgabe ständig – manchmal sogar gleichzeitig – streamt, erzeugt leise einen Speicherdruck, es sei denn, etwas kümmert sich aktiv darum. Objekte, die nur für eine Anfrage benötigt werden, bleiben oft länger im Speicher, als es nötig wäre, wenn niemand sie bereinigt.

    Deshalb wird die Bereinigung nicht dem Zufall überlassen. An natürlichen Grenzen, wie zum Beispiel am Ende einer Anfrage oder nach Abschluss eines Eingabepakets, wird der Speicher explizit zurückgewonnen, anstatt darauf zu vertrauen, dass er sich irgendwann von selbst leert. In der Praxis bedeutet das, Verweise auf große Zwischenobjekte zu löschen, Cache-Daten pro Anfrage zu bereinigen und bei lokal gehosteten Modellen den gesamten Speicher freizugeben, den der Laufzeitumgebung noch zur Verfügung steht.

    Es handelt sich um eine unscheinbare Arbeit, die niemals in einer Demo zu sehen ist und nur dann auffällt, wenn sie fehlt: ein Service, dessen Leistung im Laufe einer langen Betriebszeit nachlässt, anstatt auf seine tausendste Anfrage genauso schnell zu reagieren wie auf die erste. Um das richtig umzusetzen, geht es weniger um raffinierte Ingenieurskunst als vielmehr um Disziplin – man muss den Speicher als etwas betrachten, das jede Phase selbst verwalten muss, und nicht als etwas, das der Laufzeit sicherlich bewältigen wird. Die Überwachung des Speicherverbrauchs während eines langen Belastungstests, und nicht nur bei einem kurzen Benchmark, ist der einfachste Weg, um zu überprüfen, ob die Disziplin funktioniert.

    Kernpunkte

    Die hier beschriebene Grundlage ist ein vollständiger, fundierter RAG-Pipeline-Prozess: strukturbewusstes Aufteilen in Blöcke, token-sicheres Einbetten, mehrstufiges Filtern, Wiederzusammenfügen geteilter Abschnitte, eine disziplinierte Prompt-Formulierung, die das Modell dazu bringt, ehrlich über sein Wissen Auskunft zu geben, sowie eine Echtzeit-Lieferung. Die entscheidendsten Faktoren sind folgende:

    • Halten Sie Dokumente in einem strukturierten Plain-Text-Format und verarbeiten Sie Bilder sowie andere Medien erst zum Zeitpunkt der Darstellung.
    • Teilen Sie den Inhalt entlang des Überschriftenbaums auf und fügen Sie zu jedem Teil Pfade zum Überfliegen, Typenbezeichnungen sowie stabile Identifikatoren hinzu.
    • Führen Sie einen zweiten Durchlauf durch, bei dem die Token-Limit des Einbettungsmodells mit einem Sicherheitspuffer sowie durch beschriftete Teile und geringe Überschneidungen eingehalten werden.
    • Einbetten Sie stets Abfragen mit demselben Modell, das bei der Eingabe verwendet wurde, und einbetten Sie alles erneut, wenn sich dieses Modell ändert.
    • Holen Sie zunächst umfangreich Daten ab und beschränken Sie die Ergebnisse anschließend durch eine Mindestähnlichkeitsgrenze, einen erneuten Rangieralgorithmus sowie eine strenge Endgrenze.
    • Fügen Sie die geteilten Abschnitte vor der Anfrage wieder zusammen, damit das Modell niemals über einen unmarkierten Fragment arbeiten muss.
    • Lassen Sie ein kleineres, schnelleres Modell die Synthese durchführen, nachdem die Datenerfassung bereits die schwierige Arbeit erledigt hat, und definieren Sie dabei explizit seine Rolle, Aufgabe, Handhabung von Lücken sowie Tonfall.
  • Klassifizieren Sie die Absicht, nicht nur das Thema, und weisen Sie jeder Frageart eine eigene Antwortmethode zu.
  • Erholen Sie den Speicher auf Anfrage sowie an Grenzen von Batch-Verarbeitungen, damit der Service nach stundenlanger Betriebszeit genauso schnell bleibt wie beim Start.
  • Jede dieser Optionen für sich genommen ist bescheiden. Gemeinsam ergeben sie ein System, dessen Antworten fundiert, kohärent und schnell sind, und sie bieten Ihnen eine stabile Grundlage für anspruchsvollere Suchtechniken in Zukunft.

    Verwandte Literatur