Semesterprojekt (solo): AI-drevet kundeservice til Kirppu — loppesupermarked med standleje og ca. 34 butikker i Danmark. Det er et rigtigt kundeprojekt, ikke kun en øvelse: målet er at hjælpe sælgere og besøgende hurtigere med priser, booking, nærmeste butik og app-support, uden at chatbotten gætter sig til fakta.
Hvad og hvorfor#
Kirppus information ligger spredt på hjemmesiden: priser hentes per butik via formular, butiksdata ligger i HTML, og ord som bod, stand og standleje betyder det samme for mennesker — men ikke nødvendigvis for en generisk chatmodel. Hvis en bot opfinder en pris eller skriver «Kirppu Lemvig», når der ingen butik er der, taber man tillid med det samme.
Jeg byggede derfor to ting, der hænger sammen: en vedligeholdelsesvenlig videns-pipeline (scraping, struktureret markdown, upload til Dify med RAG) og en intern demo af kirppu.dk, hvor den færdige chat er indlejret som en del af oplevelsen. Demo’en viser, at chatten ikke er en fremmed blå widget, men føles som Kirppu.
Behovet — set fra demo og produktion#
Primær bruger er den potentielle eller nuværende sælger, der vil forstå standleje, finde nærmeste filial og booke. Værdien for Kirppu er selvbetjening på dansk, færre gentagne henvendelser og korrekte booking-links — forudsat at svarene kun kommer fra verificeret indhold på kirppu.dk, ikke fra modellens generelle viden.
I demo-perspektivet var det lige så vigtigt at tilliden til UI’et matchede tilliden til botten: hvis forsiden lignede en generisk webshop med forkert farve, troede ingen på chatten heller.
Hvad jeg byggede#
Videnslaget følger en klar kæde: kirppu.dk scrapes med Python (WordPress API, HTML på find-min-butik, POST til prissider per butik) → mange små markdown-filer (én butik, én prisliste, guides) → afstandsberegning for 605 danske bynavne via DAWA og Haversine → upload til Dify Knowledge med embeddings og en lang system-prompt (dify_instructions.txt), der fungerer som en «kontrakt»: kun tal og URL fra hentet viden, ellers et fast FALLBACK til telefon og mail.
Demo-siden er en React/Vite-app, der loader Kirppus egne stylesheets fra kirppu.dk og bruger samme HTML-klasser som det rigtige site. Brugeren kan browse forsiden, marketplace med stande og varer, og butikker — med mock-data (30 produkter, 8 stande, 4 butikker). Chatten er ikke en React-komponent: den er Difys officielle embed (embed.min.js) med Kirppu-grøn tema, eget logo på den flydende knap og et stort chatvindue.
Begge dele adresserer samme problem fra forskellige vinkler: botten skal kunne svare rigtigt, og omgivelserne skal føles rigtige.
Udfordringer undervejs#
Design først, virkelighed bagefter. Min første mock brugte en «rød primary accent» fra en generisk prompt — ikke Kirppus grønne #31aa47 og orange CTA’er. Først da jeg sammenlignede med kirppu.dk og hentede deres CSS, blev demo’en troværdig. Det er et godt eksempel på, at AI accelererer scaffolding, men domæne og brand kræver menneskelig verifikation.
Fra API til embed. Jeg startede med en egen React-chat og API-nøgle i frontend. Det gav fin kontrol over header og reload-knap, men var en sikkerheds- og vedligeholdelsesbyrde. Skiftet til Dify embed fjernede nøglen fra browseren og overlod samtaler og RAG til Dify Studio — til gengæld kom cross-origin iframe-begrænsninger.
Shell-wrapper mod «Powered by Dify». Man kan ikke fjerne footeren via difyChatbotConfig. Et forsøg med clip-path gav en grå bjælke over chat-teksten. Løsningen blev en wrapper (#dify-chatbot-shell) med overflow: hidden og en iframe, der er 60 px højere end shell’en, så footeren klippes væk uden artefakter.
Data og prompts. Priser findes kun ved at POST’e per butik; Dify crashede engang på alle spørgsmål, fordi [slug] i instructions blev tolket som Jinja-variabler. Hallucinationer som «priser i Lemvig» løste jeg med regler om nærmeste butik og strenge FALLBACK-tekster. Stavefejl i bynavne krævede både prompt og en dedikeret guide-fil — ren embedding-søgning rammer sjældent «espegærder» → Esbjerg.
Platformbegrænsninger. Dify søger ofte kun på den seneste brugerbesked, ikke hele tråden — så «hvad koster en bod?» efterfulgt af «i Helsingør» kan give skæve chunks. Det delvist løses i instructions, men fuld kontrol kræver enten bedre flows (spørg altid om by) eller højere plan/features.
Modelstørrelse, instructions og tokenforbrug. En vigtig erfaring var, at en mindre LLM havde svært ved at følge lange, detaljerede instructions og derfor oftere faldt tilbage på generel træningsviden. Da jeg skiftede til en stærkere model, blev adfærden bedre, men tokenforbruget steg markant, hvilket er en reel kundebekymring. Jeg testede også en idé om at skrive instructions på kinesisk for at reducere tokens, men det gav ikke den ønskede effekt i praksis. Min nuværende retning er i stedet: kortere og mere præcise instructions kombineret med mere detaljeret og dækkende data i vector-databasen, så modellen behøver mindre «styring» i prompten.
Stavefejl før retrieval. Lige nu klarer retrieval typisk små stavefejl, fordi ord stadig ligger tæt i embedding-rummet, men ved 2–3 fejl i samme ord falder træfsikkerheden hurtigt. Derfor er et konkret næste udviklingstrin at indføre stavekorrektion/normalisering før chunk-søgning (fx fuzzy matching eller leksikon-baseret rettelse), fordi Kirppus kundedata viser, at mange spørgsmål bliver stavet meget upræcist.
AI to steder#
| Rolle | Værktøj | Hvad det gjorde |
|---|---|---|
| Slutprodukt | Dify (RAG + LLM) | Danske svar ud fra knowledge-chunks |
| Udvikling (kode) | Cursor | Scraper, scripts, embed-integration, fejlsøgning |
| Konsultation og læring | Claude og ChatGPT | Snak om arkitektur, RAG/chunking, Dify-fælder, GDPR og «hvordan griber jeg X an?» — ikke primær kode-editor, men sparring til forståelse undervejs |
| Indhold | Whisper (dansk) | Video om prismærker → søgbar guide i RAG |
| Design-asset | Gemini (billede) | Logo på chat-knappen |
Under udviklingen arbejdede jeg spec-drevet i Cursor: stor initial prompt, implementering, verifikation mod kirppu.dk, omspec (API → embed), og dokumentation så embed-tricks kan vedligeholdes. Claude og ChatGPT brugte jeg parallelt som konsulenter — fx da jeg skulle forstå Jinja i Dify-prompts, vælge chunk-strategi, eller diskutere tradeoffs mellem embed og egen API. Det hjalp mig at lære hurtigere, men beslutningerne og det endelige tjek mod kirppu.dk lå stadig hos mig.
Hvad jeg lærte#
En pålidelig kundechatbot er i praksis et data- og grænse-projekt med en chatflade ovenpå — ikke bare et modelvalg. Cursor sparede timer på implementering; Claude og ChatGPT gav mig et ekstra lag af forklaring og refleksion, når jeg sad fast på koncepter — men AI i produktet skulle begrænses til det, scraperen og instructions tillader. Domæner med faste priser, geo og booking-URL’er tåler ikke «kreativ» LLM.
Teknisk lærte jeg, at integration og brand er halvdelen af arbejdet: postMessage for at skjule expand-knap i iframe, Vite-proxy til same-origin i dev, og ærlighed om, hvad mock-data på demo-siden ikke kan (live lager, rigtig checkout).
Samtidig lærte jeg, at den billigste model ikke nødvendigvis er billigst i praksis: hvis prompten bliver lang, svarene upræcise og man skal genkalde flere gange, kan totalforbruget stadig blive højt. Derfor er målet at gøre instructions kortere og skarpere, mens kvaliteten flyttes over i bedre knowledge-data og retrieval.
Næste skridt mod rigtig produktion#
- Pilot på kirppu.dk med embed og Kirppu-godkendt tema.
- Proces for opdatering — re-scrape, re-upload prisfiler, publicér app i Dify (kun «gem» er ikke nok).
- Testpakke med 50+ rigtige spørgsmål, faktatjek mod officielle tekster, mobil og FALLBACK-rate.
- Menneskelig eskalering ved booking, klager og tvivl.
- Analytics — hvilke spørgsmål ender i FALLBACK?
- Evt. reverse-proxy i produktion, hvis citation-UI skal styles som i dev.
- Token-optimering: kortere, mere præcise instructions + mere detaljerede chunks i knowledge.
- Stavefejlshåndtering før retrieval: normalisering/fuzzy-korrektion inden vector search.
Største risiko forbliver et forkert pris- eller booking-svar — én gang er nok til at underminere tilliden.
Afslutning#
Projektet startede som «lav en chatbot», men blev til infrastruktur for viden, en troværdig demo og en masse små beslutninger om, hvad AI må og ikke må. Fra rød accent til grøn, fra API-nøgle i browseren til shell-wrapper om embed’et: det meste af læringen lå i detaljerne, ikke i at vælge den nyeste model.
Hvis du vil dykke ned i embed-teknikken, ligger der separat teknisk dokumentation i projektmappen; her er pointen den samme: AI hjælper os at bygge hurtigt — produktværdien kommer af korrekt viden, tydelige grænser og en brugerflade, man tør stole på.