Vad vi lärde oss när vi byggde vår egen supportbot-widget (chatta med ditt innehåll)
En teknisk genomgång av vad som faktiskt spelar roll när du bygger en supportchatt-widget: retrieval-fel, dokument-som-filer-navigering, caching, access control och guardrails för pålitliga svar.
Vad vi lärde oss när vi byggde vår egen supportbot-widget (chatta med ditt innehåll)
Vi byggde vår egen supportbot-widget: en chat-bubbla du bäddar in på en webbplats så att besökare kan ställa frågor och få svar från ditt innehåll (docs, knowledge base, tjänstesidor, policies).Om du någon gång har levererat en sådan vet du att pitchen är enkel och verkligheten är rörig.Problemen du stöter på är sällan “modellproblem”. Det är engineering-problem:
retrieval går sönder på förutsägbara sätt
latency dödar förtroendet innan första svaret landar
access control är lätt att göra fel
prompt injection blir “content poisoning”
svar måste vara auditerbara annars får du support-risk
Det här inlägget är en detaljerad teardown: arkitektur, datamodell, retrieval-strategi, tool-design, caching, RBAC, UI-mönster och guardrails som gjorde vår widget pålitlig.
Vad vi byggde (krav och constraints)
Vi satte några icke-förhandlingsbara krav från början:
Snabb första token: användaren ska se respons snabbt, även om assistenten behöver “söka” i bakgrunden.
Grounded answers: svar måste stödjas av innehåll vi faktiskt publicerar.
Read-only som default: assistenten får inte mutera innehåll.
Multi-locale redo: vår sajt är lokaliserad; assistenten ska inte blanda språk slarvigt.
Säker eskalering: när den inte kan svara ska den kunna lotsa vidare utan att hallucinerar.
Utifrån det landade vi i en enkel definition av “lyckas”:
Assistenten ska bete sig som en noggrann ingenjör som läser vår webbplats, inte som en kreativ skribent som improviserar.
access control blir strukturell (prune paths innan sessionen börjar)
ls/find blir snabbt och cachebart
Ingestionspipeline: gör en webbplats till en pålitlig knowledge surface
Det här tog mest tid. “Indexera dina docs” låter enkelt tills du ser hur verkligt innehåll ser ut:
marknadscopy + komponenter
MDX och rubriker som inte mappar rent till HTML
navigationssidor som upprepar content-block
locale-varianter som divergerar delvis
Vår pipeline gör:
Upptäck sidor: sitemap + känd route-lista.
Hämta renderad HTML: vad användare och crawlers faktiskt ser.
Extrahera main content: ta bort nav, footers, cookie banners, repetitiv UI.
Normalisera: kollapsa whitespace, ta bort tracking query strings från länkar.
Segmentera:
page-level record för navigation och citations
chunk-level records för retrieval
Annotera:
locale
visibility (public, client-only, internal)
headings outline
outbound internal links
Huvudlärdomen: indexera den renderade sajten, inte bara källrepon, annars missar du vad användaren faktiskt ser och riskerar att citera innehåll som inte finns i produktion.
Retrieval: kombinera lexical och vector innan du “frågar modellen”
Vi litar inte på en enda retrieval-strategi.Vi gör:
lexical search för exakta tokens (bra för identifiers, akronymer, felkoder)
vector search för semantisk match (bra för vaga frågor)
Sedan merge:ar vi kandidater och kör grundläggande sanity checks:
avvisa sidor som inte matchar användarens locale (om den inte uttryckligen ber om annat)
föredra canonical-sidor framför tag-sidor / dubblettlistningar
föredra sidor med stark rubrik-overlap med frågetermer
Först därefter låter vi assistenten öppna sidor och syntetisera.
Tool-design: vad assistenten får göra (och inte får)
Vi implementerade en liten tool-yta och gjorde den strikt.Tillåtet:
lista kataloger: ls/services
sök paths: find-name"billing"
läs en hel sida: cat/services/seo
sök i innehåll: grep-ri"canonical"/
Inte tillåtet:
skriva eller redigera innehåll
hämta godtyckliga URL:er
godtyckliga nätverksanrop
Säkerhetsvinsten är stor: prompt injection blir mest en content-kvalitetsfråga, inte en systemkompromissfråga.
Latency-lärdomen: riktiga filesystems är för långsamma för interaktiv chat
Om du startar en sandbox/container per session för att ge ett riktigt filesystem:
cold start blir synligt
chatten känns trasig
du blir frestad att lägga på komplexitet som warm pools
För en supportwidget stirrar användaren på UI:t. Du vill ha instant tools.Så vi virtualiserade filesystem-operationerna över vårt index:
ls och find slår i det cachade path tree:t
cat sätter ihop sidan från lagrade chunks (sorterade på chunk_index)
resultaten cachas per session (och delvis globalt när det är säkert)
Assistenten får illusionen av en shell över filer, men det finns inga riktiga filer.
Cacha det som upprepas: path tree, pages och “grep targets”
Att cacha “svar” är svagt eftersom frågor varierar.Det som upprepas i riktiga konversationer är:
lista samma sektioner
öppna samma 5-10 viktiga sidor
greppa efter samma tokens
Så vi cachade:
path tree:t
rekonstruerade full pages
senaste grep-kandidater
Det spelade större roll än att mikro-optimera embeddings, eftersom det förbättrade uppföljningshastighet och minskade “searching…”-loopar.
RBAC: access control måste vara strukturell, inte prompt-baserad
Om vissa docs inte är publika (utkast, interna anteckningar, client-only docs) kan du inte lita på prompting.Vi enforced RBAC innan assistenten gör ett enda tool call:
bygg ett user-scoped path tree
prune:a allt användaren inte får se
applicera samma filter på varje query och page read
Om assistenten inte kan ls en fil kan den inte cat den och den kan inte citera den.Det är den enda mental modellen som håller under press.
Guardrails: hur vi stoppade självsäkra fel-svar
Vi lade till några regler som kraftigt minskade dåliga svar.
Regel 1: Inget evidens, inget svar
Om assistenten inte kan hitta stödjande innehåll via tools ska den:
säga att den inte kunde verifiera
visa vad den kollade (sidor eller sektioner)
föreslå nästa steg (contact/support)
Regel 2: Föredra att citera framför att parafrasera för kritiska detaljer
När svaret är känsligt för exakta formuleringar (krav, begränsningar, juridisk copy):
citera relevant(a) mening(ar) från sidan den läste
håll syntesen minimal
Regel 3: Var strikt med locale och canonical-sidor
Om användaren är på en locale-route ska assistenten:
föredra den locale:ns innehåll
undvika att blanda språk i ett svar
falla tillbaka till default locale bara om locale-sidan inte finns
“Grep-problemet”: killer feature kräver en tvåfasplan
Naiv rekursiv grep är långsam om den läser allt över nätet.Vi använde en tvåfas-approach:
coarse filter med indexet (vilka sidor kan innehålla token)
fine filter i minne över cachad sidtext för att plocka exakta träffar + kontext
Det gjorde att assistenten kändes som att den kunde söka som en utvecklare, inte gissa som en chatbot.
Widget-UX: UI:t bygger eller förstör förtroende
Vi levererade flera UI-iterationer innan widgeten kändes pålitlig.Det som spelade störst roll:
streaming tokens med stabil layout (undvik jumpy reflow)
tydliga states:
“Searching…”
“Reading page…”
“Answering…”
korta, in-line citations:
“From: /services/seo”
“From: /legal/privacy”
fallbacks som inte känns som failure:
“Jag kollade X och Y men kunde inte bekräfta; här är hur du når oss.”
Det här funkar bra ihop med att göra assistentens “arbete” synligt. Användare förlåter en bot som säger “jag kunde inte bekräfta” långt mer än en som hittar på ett säkert svar.
Observability: logga läsningarna, inte bara tokens
Om du vill förbättra kvalitet måste du veta vad som hände.Vi loggade:
tool calls (paths listade/lästa/sökta)
vilka sidor som användes som evidens
svarslängd och latency buckets
“kunde inte verifiera”-andel
eskaleringsgrad (contact clicks)
Det gjorde att vi kunde svara på praktiska frågor:
vilka sidor saknar viktig information?
vilka frågor hittar aldrig evidens?
vilka sidor skapar förvirring och behöver omstrukturering?
En praktisk bygg-checklista (vad vi skulle göra igen)
Om du bygger en supportwidget som chattar med ditt innehåll börjar vi med:
indexera den renderade sajten och lagra page-level records
kombinera lexical + vector retrieval
lägg till ett exploration tool layer (filer + grep)
prune:a innehåll för RBAC innan sessioner startar
håll tools read-only som default
lägg till en “inget evidens, inget svar”-regel
gör “searching/reading/answering” synligt i UI:t
logga tool calls så du kan debugga failure
Om du vill att vi hjälper till att implementera detta (widget + indexing + säker arkitektur):