Erstellung eines Slack RAG-Assistenten ohne separate Vektordatenbank
Ein Slack RAG-Assistent ohne separate Vektordatenbank: Die Dokumente befinden sich in einer einzigen StarRocks-Tabelle, die Abfrage erfolgt über herkömmlichen SQL, und sechs Lektionen behandeln Prompts und Chunking.
Quellen: Lessons We Learned Building a RAG Assistant Without a Separate Vector Database
StarRocks ist eine schnelle, open-source-basierte Analyse-Datenbank mit einem einheitlichen Ausführungsmotor, flexibler Bereitstellung sowie hoher Leistung bei realen Arbeitslasten. Mit dem Wachstum der Community ist auch die Unterstützungsbelastung gestiegen: Die Betreuer müssen weiterhin immer wieder dieselben Fragen zu den Dokumenten, GitHub-Issues, Veröffentlichungshinweisen und alten Slack-Threads beantworten.
Rocky ist der für diese Aufgabe entwickelte Slack-Assistent. Seine Aufgabe ist einfach: Er übernimmt die wiederholten Fragen und Antworten von den Betreuern, wobei jede Antwort auf der Dokumentation des Projekts sowie verwandten Quellen beruht.
Interessant ist dabei die Architektur. Rocky läuft selbst auf StarRocks. Dokumententeile, Schlüsselwortabfragen, Vektorabrufe sowie Ähnlichkeitsbewertungen befinden sich alle in einer einzigen OLAP-Tabelle – somit läuft der Assistent, der Fragen zur Datenbank beantwortet, ebenfalls auf derselben Datenbank. Insgesamt umfasst das Projekt etwa 600 Zeilen Python, eine Tabelle sowie einen Gemini-API-Schlüssel.
Die Grundlage von RAG
Der übliche erste Schritt bei der Entwicklung von retrieval-augmented Generation besteht darin, eine spezielle Vektordatenbank einzusetzen. Jedes zusätzliche Komponente erfordert eine eigene Implementierung, einen Backup-Plan sowie ein eigenes Konsistenzmodell – für einen kleinen Community-Bot ist dieser Aufwand schwer zu rechtfertigen.
Deshalb blieb alles in StarRocks. Die Dokumentteile sowie ihre 768-dimensionalen Gemini-Embeddings befinden sich in einer gewöhnlichen OLAP-Tabelle unter Verwendung des PRIMARY KEY-Modells, und die Abfrage erfolgt über eine SQL-Anfrage. Das Prinzip lautet: Wenn Ihre analytische Datenbank bereits Array-Spalten sowie Funktionen zur Berechnung der Kosinusähnlichkeit unterstützt, benötigen Sie kein zweites Datenbanksystem für die Vektorabfrage.
Die Tabelle sieht aus wie jede andere:
CREATE TABLE docs (
id BIGINT NOT NULL,
path VARCHAR(512),
`index` INT,
`text` STRING,
vector ARRAY<FLOAT> -- 768-dim Gemini embedding
) ENGINE = OLAP
PRIMARY KEY(id)
DISTRIBUTED BY HASH(id) BUCKETS 1;
Und die Abfrage erfolgt mit einer einzigen Anfrage unter Verwendung der Kosinusähnlichkeit:
SELECT path, `index`, `text`,
approx_cosine_similarity([0.012, -0.034, ...], vector) AS similarity
FROM docs
ORDER BY similarity DESC
LIMIT 8;
Es gibt kein SDK, keine Client-Bibliothek und kein zweites System. Von Rockys Seite aus ist die „Vektordatenbank“ einfach nur eine weitere Tabelle, die bereits abgefragt werden kann.
Von einem Slack-Ereignis bis zur fundierten Antwort
Der Ablauf ist absichtlich minimal gehalten. Eine @Rocky-Erwähnung in Slack aktiviert den Bot, der anschließend mithilfe von nativen Funktionsaufrufen die Auswertung an Gemini 3 Flash weiterleitet. Das Modell entscheidet, ob es in der Dokumentation suchen oder Google abfragen soll, und kann pro Turnus bis zu zehn Schritte mit Tool-Aufrufen durchführen.
- Slack übermittelt die Erwähnung. Die slack_bolt-Bibliothek für Python erhält das Ereignis über Socket Mode und extrahiert die Anfrage des Benutzers zusammen mit dem Thread-Kontext.
- Gemini begründet und ruft Tools auf. Das Modell erhält einen Systemhinweis mit strengen Ehrlichkeitsvorschriften sowie zwei Tools:
search_starrocks_doc, eine clientseitige Vektorabfragefunktion, undgoogle_search, eine serverseitige Verifizierung über Gemsinis integrierte Websuche. - Die Abfrage wird innerhalb von StarRocks ausgeführt. Das Suchtool fügt die Anfrage mit gemini-embedding-001 unter Verwendung von task_type=RETRIEVAL_QUERY ein und führt anschließend die zuvor gezeigte SQL-Anfrage zur Kosinusähnlichkeit aus.
- Das Modell schreibt die Antwort. Gemini kombiniert die abgerufenen Teile zu einer Antwort im Markdown-Format, die Rocky vor dem Veröffentlichen in das mrkdwn-Format von Slack umwandelt.
- Telemetriedaten gehen an das Überwachungssystem. Jeder Modellaufruf, jede Toolnutzung sowie jede Tokenzählung wird zu einem OpenTelemetry-Span, wobei
thread_tsals Session-ID dient.
Der Bot besteht aus etwa 600 Zeilen Python in einer Datei. Die zugehörigen Werkzeuge für das Aufteilen in Blöcke, die Erstellung von Embeddings und den Aufbau eines Indexes fügen nur noch ein paar Tausend Zeilen hinzu, wobei Speicherung, Abruf und Ähnlichkeitsbewertung vollständig von StarRocks übernommen werden.
Warum dieser Stack für eine leichte RAG-App geeignet ist
Drei Optionen sorgen dafür, dass der Betriebsaufwand gering bleibt, während dennoch nützliche Antworten erzeugt werden.
Primärschlüsseltabelle zusammen mit Stream Load für austauschbare Dokumente
Das Korpus stellt keine strömende Arbeitslast dar. Wenn sich die Dokumente ändern, wird der gesamte docs/en/-Ordner erneut in Blöcke aufgeteilt, die Embeddings neu generiert und die gesamte Tabelle über Stream Load neu geladen:
curl --location-trusted -u root: \
-H "format: json" \
-H "strip_outer_array: true" \
-T docs_with_embedding.json \
-XPUT http://host:8030/api/starsage/docs/_stream_load
Eine HTTP-Anfrage lädt etwa 5.000 Zeilen, wobei jede Zeile einen 768-dimensionalen Float-Array enthält. In diesem Umfang eignet sich Stream Load hervorragend: Es muss kein Broker betrieben werden, es müssen keine Flink-Pipelines ausgeführt werden, und es ist keine laufende Eingabeanlage erforderlich. Für Tausende bis Zehntausende von Zeilen mit eingebetteten Vektoren ist dies der einfachste Ansatz.
Das Primary Key-Modell garantiert eine atomare Ersetzung. Abfragen, die während des Neuladens eintreffen, erhalten ein konsistentes Snapshot und niemals ein halb aktualisiertes Korpus. Das ist der Hauptgrund dafür, warum man statt einer Tabelle mit Duplicate Key eine Tabelle mit Primary Key wählt. Es geht dabei nicht um Upserts auf Zeilenebene, da die Arbeitslast in vollständigen Neubauten besteht. Vielmehr sorgt eine PK-Tabelle in Kombination mit Stream Load dafür, dass jedes Neuladen atomar abläuft – Rocky liefert entweder das alte Korpus oder das vollständig neue und niemals eine Mischung aus beidem.
Auswertung als Werkzeug, nicht als Kontextfüllung
Dokumente werden nicht im Systemprompt vorgegeladen. search_starrocks_doc wird als aufrufbare Funktion bereitgestellt, und das Modell entscheidet selbst, wann und wie oft es verwendet wird.
In der Praxis löst eine Nachricht wie „hi Rocky“ keinerlei Abrufaktion aus und kostet fast nichts. Eine differenzierte Frage, wie sich das Verhalten von GROUP_CONCAT zwischen 3.5 und 4.0 unterscheidet, führt zu mehreren Aufrufen: Das Modell fragt jeweils pro Version nach und überprüft die Ergebnisse gegeneinander. Die Anzahl der Abrufe ist auf acht pro Turn begrenzt. Geminis Serverseitige Funktion google_search kümmert sich um Release-Notes und GitHub-Issues, die über die Dokumentation hinausgehen, während Rocky ein breites Spektrum abdeckt, ohne einen separaten Web-Scraping-Prozess zu benötigen.
Ein Embedding-Cache für vorhersehbare Kosten
Die Fragen der Community bilden Cluster. Wie man einen Bitmap-Index hinzufügt und wie man auf Kubernetes bereitstellt, werden mehrmals pro Woche gestellt. Ein lokaler SQLite-Cache, der nach dem Abfragedtext und task_type sortiert ist (da RETRIEVAL_QUERY und RETRIEVAL_DOCUMENT unterschiedliche Vektoren erzeugen), beseitigt überflüssige Embedding-Aufrufe, sodass beliebte Abfragen keine Kosten verursachen.
Auf der Seite der LLMs kosten die in Gemini gespeicherten Eingabetoken 25 Prozent des Standardpreises. Mit dem Embedding-Cache liegt die Kosten pro Antwort bei etwa 0,001 US-Dollar, was für ein von der Community finanziertes Projekt, das unbegrenzt läuft, tragfähig ist.
Sechs Lektionen aus echtem Traffic
Diese stammen aus der tatsächlichen Nutzung durch die Community und nicht aus Benchmarks, und die meisten gelten für jeden Domänen-Assistenten auf einer Chat-Plattform.
1. Prompt-Änderungen sind wirksamer gegen Halluzinationen als Anpassungen der Abfragedaten
Frühe Versionen erfanden falsche Namen für Red-Hat-Spiegel, konstruierten erfundene URLs zu Release-Hinweisen und erzeugten falsche Versionennummern, wodurch Community-Mitglieder auf 404-Seiten gelangten. Für einen Q&A-Bot ist eine selbstbewusste falsche Antwort schlimmer als keine Antwort überhaupt.
Die Lösung bestand nicht in einer besseren Informationsbeschaffung. Es handelte sich um eine Anpassung der Prompt-Struktur: eine ausdrückliche Ehrlichkeitsklausel, die dem Modell vorschreibt, keine Fakten zu erfinden, und falls es keine zuverlässige Antwort geben kann, dies anzugeben, auf autoritative Quellen zu verweisen und vorzuschlagen, die Community zu befragen. Die Wirkung war sofort spürbar. Die Informationsbeschaffung ist zwar wichtig, doch Prompt-Engineering ist oft der schnellere und günstigere Weg, Halluzinationen einzudämmen.
2. Beheben Sie das Chunking, bevor Sie sich der Informationsbeschaffung widmen
Die StarRocks-Dokumentation enthält viele MDX-Importzeilen, Markdown-Tabellen, die über verschiedene Chunks reichen, sowie sehr lange SQL-DDL-Anweisungen. Wenn man das Rohmaterial direkt in Embeddings einsetzt, erhält man nur minderwertige Ergebnisse. In die Chunking-Pipeline wurden daher erhebliche Investitionen gesteckt:
- Hierarchische Aufteilung nach Überschriften. Die Blöcke werden an den Markdown-Überschriften geteilt, wobei der gesamte Überschriftpfad als Metadaten gespeichert wird, sodass das Modell erkennt, wo ein Block in der Dokumenthierarchie steht.
- Störungsfilterung. MDX-Importzeilen werden entfernt, und Codeblöcke mit mehr als 4.000 Zeichen werden weggelassen, da sie das semantische Signal schwächen, ohne zur Verbesserung der Antworten beizutragen.
- Einfachere Ausgabetabellen. Beispielausgaben, die ursprünglich als MySQL-ähnliche Tabellen dargestellt wurden, werden durch strukturierte Beschreibungen anstelle von rohen ASCII-Tabellen zusammengefasst.
Die Aufteilung in Blöcke erforderte etwa zehnmal so viel Arbeit wie die Integration einer Vektorsuche. Wenn die Trefferquote enttäuschend ist, prüfen Sie zunächst, wie der Text aufgeteilt wurde, bevor Sie Embedding-Modelle wechseln oder Indexeinstellungen anpassen.
3. RAG ist ein Werkzeug, keine Pipeline
Wenn die Abfrage eine Funktionsaufruf ist und nicht in jeder Anfrage integriert ist, kann das Modell sie überspringen, wenn sie nicht benötigt wird. Begrüßungen, nachfolgende Klärungen sowie einfache faktische Fragen berühren niemals den Vektorindex. Die Kombination eines Client-Seit-Tools (search_starrocks_doc) und eines Server-Seit-Tools (google_search), die vom Gemini SDK seit Version 1.70 vollständig unterstützt wird, bietet Rocky außerdem eine Alternative für Inhalte außerhalb des Korpus, ohne dass ein eigener Scraper erforderlich ist.
4. Befolgen Sie das Sitzungsmodell der Plattform
Slack-Threads verfügen über Identifikatoren vom Typ thread_ts. Rocky verwendet diesen direkt als Sitzungs-ID, speichert die Historie in einer entsprechend benannten JSON-Datei und enthält in jeder Anfrage die sechs neuesten Nachrichten:
contents = []
for turn in history[-MAX_CONTEXT_MESSAGES:]:
contents.append({"role": turn["role"], "parts": [{"text": turn["text"]}]})
contents.append({"role": "user", "parts": [{"text": query}]})
Das sind etwa zwanzig Zeilen Code – weitaus einfacher als die Verwaltung einer Sitzungsdatenbank.
Eine häufige Kritik der Nutzer lautet: „Rocky hat kein Gedächtnis“. Der Grund dafür ist, dass Rocky in Slack nur dann antwortet, wenn es direkt angesprochen wird, was den Eindruck von Vergesslichkeit erweckt. Es handelt sich dabei um ein Problem der Benutzerinteraktion und nicht um ein technisches Problem.
5. Frühzeitig instrumentieren
Nach dem Start suchte das Team nach Antworten auf grundlegende Fragen. Wie viele Anfragen hat Rocky diese Woche bearbeitet? Wo konzentrierten sich die Tool-Aufrufe nach Themen? Wie viel kostete eine typische Antwort? Welche Antworten wurden zurückgewiesen, korrigiert oder weiterverfolgt? Ohne Aufzeichnungen war das System eine Black Box, weshalb später eine vollständige OpenTelemetry-Schicht eingeführt wurde. Die Lektion daraus ist, bereits vor dem Start Tracing hinzuzufügen. Ohne dieses sind die Token-Nutzung, die Kosten sowie die Muster der Tool-Aufrufe unsichtbar, wodurch die Kosteneffizienzoptimierung, die Zuordnung von Fehlern sowie die Analyse schwacher Fragen erheblich erschwert werden.
6. Cache aggressiv und jeden Cent verfolgen
Embedding-APIs scheinen pro Aufruf günstig zu sein, doch häufige Fragen aus der Community summieren sich schnell. Der SQLite-Embedding-Cache in Kombination mit Geminis Preismodell für gekachelte Token hält die Einheitskosten bei etwa 0,001 US-Dollar pro Antwort. Für ein von der Community betriebenes Projekt macht das den Unterschied zwischen „endlos nachhaltig“ und „wir müssen über das Budget sprechen“ aus.
Ehrliche Kompromisse
Rocky ist ein leichtgewichtiger RAG-Assistent und kein Produktionsunterstützungssystem, weshalb einige Einschränkungen erwähnt werden sollten:
- Halluzinationen werden reduziert, aber nicht beseitigt. Prompt-Schutzmechanismen helfen, garantieren aber keine Korrektheit. Gut dokumentierte Themen funktionieren am besten, während Randfälle und versionsspezifische Besonderheiten weiterhin Probleme verursachen.
- Lange Antworten. Wie die meisten RAG-Bots erklärt er zu ausführlich. Für kurze, zusammenfassende Antworten ist ein Schalter geplant.
Alle diese Punkte sind aktive Planungsitems, die auf Feedback der Community basieren.
Was Rocky zeigt
Rocky ist ein kleines Projekt – 600 Zeilen Python plus eine Tabelle und ein API-Schlüssel – doch es verfolgt ein größeres Ziel. Für leichte KI-Anwendungen kann eine analytische Datenbank mit Array-Spalten und Ähnlichkeitsfunktionen den Bedarf an einer separaten Vektor-Datenbank eliminieren. Eine Tabelle kümmert sich um die Speicherung von Dokumenten, die Suchfunktion nach Schlüsselwörtern sowie das Abrufen von Vektoren, sodass die zu verwaltende Infrastruktur klein bleibt und es nur ein System statt zwei gibt.
Laut dem Projekt bewältigt der Bot täglich echten Community-Traffic, und die Nutzer bewerten ihn im Durchschnitt mit 4,2 von 5 Sternen. Jede Antwort kostet weniger als ein Zehntel eines Cent. Für alle, die einen ähnlichen Domain-Assistenten entwickeln möchten und einfache Infrastruktur suchen, lohnt sich das Muster mit einer einzigen Tabelle, insbesondere wenn StarRocks bereits im Einsatz ist.
Verwandte Artikel
- Hybride Suche in Postgres für einen Gesundheitsassistenten: Schema, Chunking, RRF und Bewertung — Aufbau der Abrufschicht eines Gesundheitsassistenten in Postgres: strukturbezogenes Chunking, hybride Suche mit pgvector und RRF, binäre Quantisierung, Schwellenwerte und Bewertung.
- Spring AI Rezept: Filtern von RAG-Ergebnissen mithilfe von Metadaten nach Spieltitel — Eine Anleitung zu Spring AI, bei der Dokumente mit einem Titel versehen werden, ein LLM zur Auswahl des richtigen Wertes aus der Frage verwendet wird und anschließend die Vektorsuche darauf gefiltert wird.