Was wir beim Bau unseres eigenen Support-Bot-Widgets gelernt haben (Chat mit deinem Content)
Ein technischer Deep-Dive in das, was beim Bau eines Support-Chat-Widgets wirklich zaehlt: Retrieval-Failure-Modes, Docs-als-Files-Navigation, Caching, Access Control und Guardrails fuer verlaessliche Antworten.
Was wir beim Bau unseres eigenen Support-Bot-Widgets gelernt haben (Chat mit deinem Content)
Wir haben unser eigenes Support-Bot-Widget gebaut: die Chat-Blase, die du auf einer Website einbettest, damit Besucher Fragen stellen und Antworten aus deinem Content bekommen (Docs, Knowledge Base, Service-Seiten, Policies).Auf dem Papier klingt das simpel. In der Praxis ist es messy.Die Probleme sind selten "Model-Probleme". Es sind Engineering-Probleme:
Retrieval bricht vorhersehbar
Latenz zerstoert Vertrauen, bevor die erste Antwort landet
Access Control ist leicht falsch zu machen
Prompt Injection wird zu "Content Poisoning"
Antworten muessen auditierbar sein, sonst wird der Bot zum Support-Risiko
Dieser Beitrag ist ein detaillierter Teardown: Architektur, Datenmodell, Retrieval-Strategie, Tool-Design, Caching, RBAC, UI-Patterns und die Guardrails, die unser Widget verlaesslich gemacht haben.
Was wir gebaut haben (Requirements und Constraints)
Wir hatten ein paar nicht verhandelbare Anforderungen:
Schnelles First Token: Nutzer sollen schnell eine Reaktion sehen, auch wenn der Assistant im Hintergrund erst "suchen" muss.
Grounded Answers: Antworten muessen durch Content gestuetzt sein, den wir wirklich ausliefern.
Read-only by default: der Assistant darf Content nicht mutieren.
Multi-Locale ready: unsere Site ist lokalisiert; der Assistant soll Locales nicht beliebig mischen.
Safe Escalation: wenn er nicht sicher antworten kann, soll er sauber eskalieren (Contact/Support), ohne zu halluzinieren.
Aus diesen Requirements haben wir eine einfache Definition von Erfolg abgeleitet:
Der Assistant soll sich wie ein vorsichtiger Engineer verhalten, der unsere Website liest, nicht wie ein kreativer Writer, der improvisiert.
Synthesize: eine kurze, korrekte Antwort liefern, mit Pointern, woher sie kommt.
Viele Implementierungen machen nur (1) und (3). Sie ueberspringen (2). Genau dort stirbt Accuracy.
Lesson 1: Chunk-only RAG faellt auseinander, wenn die Antwort ueber mehrere Seiten geht
Top-K Chunk Retrieval bricht in konstanten Mustern:
die Antwort ist auf zwei Seiten verteilt
die "exakte Syntax" steckt in einem Codeblock, der nicht gerankt wurde
die Frage ist underspecified, Embedding Match ist "close enough"
die richtige Seite hat wenig lexikalische Ueberschneidung
Wenn ein Model nur ein paar Chunks sieht, kann es sehr sicher klingen und trotzdem unvollstaendig sein.Unser Takeaway: Retrieval reicht nicht. Du brauchst Exploration.
Lesson 2: Gib dem Assistant deterministische Primitives (Docs-as-Files)
Menschen beantworten Docs-Fragen nicht, indem sie fuenf zufaellige Absaetze lesen. Wir:
find die Seite
open sie
scannen Headings
suchen nach exakten Tokens
folgen internen Links
wiederholen, bis wir die Antwort beweisen koennen
Darum haben wir die Tool-Schnittstelle wie ein kleines Filesystem ueber unserem Content gestaltet:
Pages bevorzugen, deren Headings gut zur Query passen
Erst dann darf der Assistant Pages oeffnen und synthesizen.
Tool-Design: was der Assistant darf (und was nicht)
Wir haben eine kleine Tool-Oberflaeche implementiert und sie strikt gemacht:Erlaubt:
Directories listen: ls/services
Pfade suchen: find-name"billing"
eine ganze Seite lesen: cat/services/seo
innerhalb von Content suchen: grep-ri"canonical"/
Nicht erlaubt:
Content schreiben oder editieren
beliebige URLs fetchen
beliebige Network Calls
Der Safety-Benefit ist gross: Prompt Injection wird viel eher zu einem Content-Qualitaetsproblem als zu einem System-Compromise.
Latenz: echte Filesystems sind zu langsam fuer interaktiven Chat
Wenn du pro Session einen Sandbox/Container startest, um ein echtes Filesystem zu bieten:
Cold Start wird sichtbar
Chat fuehlt sich kaputt an
du wirst zu Komplexitaet wie Warm Pools verleitet
Fuer ein Widget willst du instant Tools.Darum haben wir Filesystem-Operationen ueber unseren Index virtualisiert:
ls und find laufen ueber den gecachten Path Tree
cat setzt eine Page aus gespeicherten Chunks zusammen (sortiert nach chunk_index)
Ergebnisse werden per Session gecacht (und teilweise global, wenn safe)
Der Assistant bekommt die Illusion von Files, aber ohne echte Files.
Cache, was sich wiederholt: path tree, pages und "grep targets"
Antworten cachen ist schwach, weil Fragen variieren.Was sich in echten Conversations wiederholt:
dieselben Sections listen
dieselben 5-10 Kernseiten oeffnen
dieselben Tokens greppen
Darum haben wir gecacht:
den Path Tree
rekonstruierte Full Pages
recente Grep-Kandidaten
Das hat mehr gebracht als Embeddings zu micro-optimieren, weil es Follow-ups beschleunigt und "searching..."-Loops reduziert.
RBAC: Access Control muss strukturell sein, nicht prompt-basiert
Wenn ein Teil deiner Docs nicht public ist (Drafts, interne Notes, client-only Docs), kannst du nicht auf Prompting vertrauen.Wir enforce RBAC, bevor der Assistant einen einzigen Tool Call macht:
user-scoped Path Tree bauen
alles prunen, was der User nicht sehen darf
denselben Filter auf jede Query und jeden Page Read anwenden
Wenn der Assistant ein File nicht lsen kann, kann er es nicht caten und nicht zitieren.
Guardrails: wie wir "confident wrong" Antworten reduziert haben
Ein paar Regeln haben bad answers drastisch reduziert:
Regel 1: Kein Evidence, keine Antwort
Wenn der Assistant keinen stuetzenden Content findet, soll er:
sagen, dass er es nicht verifizieren konnte
zeigen, was er geprueft hat (Pages/Sections)
einen naechsten Schritt anbieten (Contact/Support)
Regel 2: Bei kritischen Details lieber zitieren als paraphrasieren
Wenn exakte Formulierungen zaehlen (Requirements, Limits, rechtliche Copy):
relevante Saetze aus der gelesenen Page zitieren
Synthesis minimal halten
Regel 3: Strikt bei Locale und canonical Pages bleiben
Wenn der User in einer Locale ist, soll der Assistant:
die Locale bevorzugen
Sprachen nicht mischen
nur auf Default-Locale fallbacken, wenn die Locale-Page nicht existiert
Das "grep problem": Killer-Feature, aber nur mit Plan
Naives recursive Grep ist langsam, wenn du alles ueber das Netzwerk lesen musst.Wir nutzen einen Two-Phase-Ansatz:
coarse filter ueber den Index (welche Pages koennten das Token enthalten)
fine filter in memory ueber gecachten Page Text, um exakte Treffer + Kontext zu extrahieren
So fuehlt sich der Assistant an wie "Docs durchsuchen wie ein Developer", nicht wie "raten".
Widget-UX: die UI entscheidet ueber Vertrauen
Wir haben mehrere UI-Iterationen gebraucht, bis das Widget sich verlaesslich anfuehlte.Was am meisten zaehlte:
Streaming Tokens mit stabilem Layout (keine jumpy Reflows)
klare States:
"Searching..."
"Reading page..."
"Answering..."
kurze, inline Zitate:
"From: /services/seo"
"From: /legal/privacy"
Fallbacks, die nicht wie ein Fail wirken:
"Ich habe X und Y geprueft, konnte es aber nicht bestaetigen; hier ist, wie du uns erreichst."
Das passt gut dazu, die "Arbeit" sichtbar zu machen. Nutzer verzeihen "konnte nicht bestaetigen" viel eher als eine erfundene, selbstsichere Antwort.
Observability: logge Reads, nicht nur Tokens
Wenn du Qualitaet verbessern willst, musst du wissen, was passiert ist.Wir loggen: