← Blog

Voice agents

Voice agent voor drie Rotterdamse dialecten: build log

Vrijdagavond in Rotterdam-West, vier ovens aan, de telefoon staat roodgloeiend. Zo bouwden we een voice-agent die drie Nederlandse dialecten zonder brokken verwerkt.

Jacob Molkenboer· Oprichter · A Brand New Company· 3 jun 2026· 9 min
Vintage bakelieten telefoonhoorn op leren onderlegger, gekrulde kabel, bonnetje met groen lint, koperen bel, potlood.

Vrijdagavond, Rotterdam-West. Vier pizzaovens aan, een kapsalonstation, twee bezorgers wachten bij de deur. De telefoon gaat zestig keer per uur. Een nieuwe zestienjarige neemt op, probeert in keurig Nederlands een bestelling op te schrijven van een beller die overduidelijk Rotterdams spreekt en half schreeuwt over een passerende tram heen. De bestelling gaat verkeerd in. Twee kapsalons komen op het verkeerde adres aan. De eigenaar belt ons maandag.

Dit is het build log van de voice-agent die we in april en mei uitrolden bij een Rotterdamse restaurantketen met vier vestigingen. De opdracht was kort verteld en lelijk in uitvoering: neem de telefoon op, neem de bestelling aan, duw die de POS in en bevestig hem terug in hetzelfde dialect als de beller. Drie dialecten lagen op tafel.

Het dialectprobleem dat niemand benchmarkt

Ongeveer 40% van de bestellingen van de keten komt nog altijd binnen via de telefoon. Thuisbezorgd en de eigen site doen de rest. Telefoonbestellingen zijn waar de fouten zich opstapelen, en waar de loonkosten op een vrijdag- of zaterdagavond het pijnlijkst zichtbaar worden. De eigenaar wilde een agent die tijdens de piek per locatie één telefoonkracht kon vervangen.

We scoopten drie dialecten:

  • Standaardnederlands. De nieuwslezersvariant. Whisper-large-v3 doet dit out of the box op 4 à 6% word error rate.
  • Rotterdams. Glottisslagen, geslikte slot-n, de ij die ai wordt, afgevlakte ui-tweeklanken. Whisper-baseline op ons sample: 31% word error rate.
  • Turks-Nederlands. Code-switching is gewoon in West en Zuid Rotterdam. Woorden als abi, kanka en yok vallen midden in verder Nederlandse zinnen.

Voor we iets bijschaafden hebben we vijf providers gebenchmarkt op 200 echte gesprekken uit het archief van de klant (met toestemming, AVG-geschoond, met de hand getranscribeerd door een in Rotterdam geboren stagiair). Whisper-large-v3, Deepgram Nova-3, AssemblyAI, Azure Speech en Google Chirp. Geen enkele zakte op Rotterdams onder 25% word error rate. Dat cijfer was het hele probleem in één getal.

Architectuur

De stack waar we op uitkwamen, van boven naar onder:

  • Telefonie: Twilio Programmable Voice, met een SIP-trunk naar de bestaande PBX van de keten als fallback.
  • Voice activity detection: Silero VAD, met een RNNoise pre-pass voor tram- en marktgeluid.
  • Primaire ASR: Whisper-large-v3 met een LoRA-adapter getraind op Rotterdams.
  • Fallback ASR: Deepgram Nova-3. Springt aan zodra de primaire onder 0,72 confidence zakt op een fragment.
  • Intent en slot filling: een structured-output schema voor het menu van de keten.
  • Text-to-speech: ElevenLabs Turbo v2.5, drie custom stemmen.
  • POS: Lightspeed K-Series, REST.
  • State machine: XState, één Node-worker per gesprek.

De twee-ASR-opstelling is het stuk dat ik los zou verdedigen. Elk audiofragment door beide engines halen is duur. Alleen de fragmenten met lage confidence langs de fallback sturen kost ons gemiddeld zo'n €0,04 per gesprek en redt ongeveer 8% van de bestellingen die anders aan een mens overgedragen zouden worden. De rekensom maakt zichzelf.

Latency-budget: we houden een plafond van 700 ms tussen de pauze van de beller en de reactie van de agent. Alles boven een seconde voelt aan de telefoon kapot. De grootste bijdrage kwam van de warm-up van ElevenLabs TTS, die we hebben opgevangen door de tachtig meest voorkomende bevestigingsfragmenten voor te renderen en die te streamen terwijl de slot filler de rest afmaakt.

Whisper finetunen op Rotterdams

Een Rotterdamse ASR krijg je niet uitgerold zonder Rotterdamse data. Een schoon publiek corpus is er niet. We hebben op Marktplaats een oproep geplaatst voor betaalde sprekers (€25 per uur, met script en vrij), acht uur aan verse opnamen verzameld en die gecombineerd met het belarchief van de klant. Na augmentatie (speed perturbation en gesimuleerde galm) hadden we ongeveer 14 uur gelabelde audio.

We hebben de patronen getagd die het model naar verwachting zouden breken:

phonetic_rules:
  - id: final_n_drop
    examples: ["lopen -> lope", "kapsalonnen -> kapsalonne"]
  - id: ij_to_ai
    examples: ["fijn -> fain", "kijken -> kaiken"]
  - id: ui_flatten
    examples: ["uitjes -> oitjes", "huis -> hois"]
  - id: glottal_stop_t
    context: "word-final t after a short vowel"
  - id: r_uvular_drop
    context: "Rotterdam West specifically"

We hebben een LoRA-adapter getraind, geen volledige finetune. Zes epochs op één A100, zo'n vier uur compute. De Rotterdamse word error rate zakte van 31% naar 9,4%. De prestatie op Standaardnederlands bleef binnen de ruis van het basismodel, en dat was belangrijk: we wilden het ene dialect niet inruilen voor het andere.

Turks-Nederlands was een ander verhaal. Het Whisper-basismodel transcribeert de meeste Turkse leenwoorden al goed. Het brak in de slot filler erachter, die abi af en toe als een naamveld interpreteerde. Dat hebben we opgelost met een klein Nederlands-Turks code-switch woordenboek als hint in de prompt, plus een regex pre-pass op het transcript voordat het de structured-output call raakt.

Het menuschema is de helft van het werk

Voice-agents in restaurants slagen of falen op hoe het menu gemodelleerd is. We hebben twee weken alleen aan het schema besteed. Het menu van de keten heeft 84 basisitems, zes toppingfamilies en modifier-regels die triviaal lijken tot je ze opschrijft (kapsalonmayo gaat over het vlees of over de friet, niet over allebei tenzij de beller daar expliciet om vraagt, en de vestiging op de Veluwseweg gebruikt een andere saus dan de andere). Het schema valideert elke uiting, en elke onopgeloste dubbelzinnigheid triggert een verduidelijkingsvraag in plaats van een gok.

Het schema codeert ook de prijzen, zodat de agent het totaal kan noemen voor de bevestiging. Ongeveer een derde van de bellers corrigeert zijn bestelling op dat moment ("nee, dan toch maar zonder kaas"). Dat is een derde van de bestellingen die zonder die prijsopgave verkeerd was binnengekomen.

De bevestigingsstem laten kloppen met het dialect van de beller

Elke bestelling wordt voorgelezen aan de beller voor de lijn valt. In zijn eigen dialect.

Takeaway

Een beller die Rotterdams spreekt en zijn bestelling terughoort in keurig Nederlands voelt zich betutteld. Een beller die Standaardnederlands spreekt en hem terughoort in stevig Rotterdams denkt dat de agent kapot is. Register matchen is geen cosmetica. Het is wat de bevestiging geloofwaardig maakt.

De eerste 2 à 3 seconden van het gesprek lopen door een kleine dialectclassifier die bovenop de Whisper-encoder-embedding zit. Drie klassen, één per dialect, plus een bak "onzeker". De classifier kiest de ElevenLabs-stem voor de rest van het gesprek. We hebben drie sprekers gekloond: één nieuwslezer-Standaardnederlands, één Rotterdamse native (een visboer uit de Markthal die de klus voor de lol oppakte) en één tweetalige Turks-Nederlandse spreker die in de stad als vertaler werkt.

Kosten per bevestiging in productie: zo'n €0,018 bij een gangbare bestellengte. Elke cent waard.

Edge cases die ons tijd kostten

Driegesprekken. Een beller die Rotterdams spreekt, met een partner die vanuit de kamer ernaast in Turks-Nederlands aanvullingen roept. Diarisatie geeft ons twee transcript-tracks; de slot filler voegt die samen met een concat op tijdvolgorde en zet de vlag "meerdere sprekers" op de bon naar de keuken, zodat de kok weet dat er een chaotische pickup aankomt.

Tramgeluid. Het RET-net rijdt op straatniveau langs twee van de vier vestigingen. Silero VAD op rauwe audio gaf false positives op het piepen van tramremmen. RNNoise als pre-pass halveerde de false-positive rate zonder meetbare schade aan de recall.

Dronken bellers na 22:00 in het weekend. Bralligheid breekt de dialectclassifier (alles wordt naar "onzeker" gerouteerd). We hebben een tijd-van-de-dag-regel toegevoegd: tussen 22:00 en 03:00, als de confidence van de classifier in het eerste venster onder 0,6 blijft, val terug op Standaardnederlands en vraag de beller beleefd om herhalen. Het werkt omdat dronken bellers register matching toch niet opmerken.

Gesprekken die halverwege wegvallen. We bewaren de gedeeltelijke bestelling 90 seconden in Redis, op caller ID. Belt iemand binnen dat venster terug, dan opent de agent met "U was bezig met drie kapsalonne, klopt dat nog?" Ongeveer 4% van de afgemaakte bestellingen loopt via dit recovery-pad.

Het pad naar overdracht aan een mens

Een agent die niet kan zeggen "ik verbind u door met iemand" verliest het vertrouwen de eerste keer dat hij iets fout doet. We bouwden drie handover-triggers: de beller die een variant zegt van "kan ik een mens spreken", de dialectclassifier die twee keer achter elkaar "onzeker" teruggeeft, en de slot filler die onder 0,55 confidence zakt op een verplicht veld. Alle drie gaan naar de mobiel van de dienstdoende manager, met een blurb van één regel als sms op het moment van overdracht: "Klant belt over een eerdere bestelling, agent verstond locatie niet." De blurb scheelt de manager telkens een minuut "waar hadden we het ook alweer over".

Ongeveer 11% van de gesprekken raakt een van deze triggers. De handover-blurb heeft de gemiddelde managerbel-tijd met zo'n 40 seconden verkort, iets wat de eigenaar in week vier ongevraagd opmerkte. Dat detail telde voor hem zwaarder dan al onze accuracy-cijfers bij elkaar.

Resultaten na zes weken live

De agent neemt sinds half april alle vier de lijnen op. De cijfers uit de POS-audit van de klant:

  • 73% van de telefoonbestellingen rondt door de agent af zonder overdracht aan een mens.
  • Gemiddelde gesprekslengte: 1 minuut 12 seconden, was 2 minuten 40 seconden toen een mens opnam.
  • Bestelnauwkeurigheid bij bezorging: 96,4%, tegenover 91% voor handmatig opgenomen telefoonbestellingen in hetzelfde venster een jaar eerder.
  • Twee vestigingen zetten tijdens de vrijdag- en zaterdagpiek geen vaste telefoonkracht meer in.

Dat cijfer van 91% voor mensen verraste de eigenaar meer dan al onze cijfers. Mensen gaan ervan uit dat een mens de nauwkeurigheidsbaseline is. Dat is niet zo, zeker niet om half tien op zaterdagavond met vier pizza's in de oven en een rij aan de balie.

Wat we anders zouden doen

We hebben te lang aan de Whisper-finetune gewerkt voor we de fallback-router gebenchmarkt hadden. Hadden we de confidence-routing tussen twee ASR's eerst aangesloten, dan waren we eerder op een uitstuurbare error rate uitgekomen en hadden we tijd overgehouden om de set dialecten uit te breiden. De finetune deed ertoe, maar de routinglogica is wat het systeem productieklaar maakte.

We hadden het AVG-werk ook te krap begroot. De richtlijn van de Autoriteit Persoonsgegevens over inkomende belopnames is specifiek over toestemmingsprompts, bewaartermijnen en het aanbieden van een niet-opgenomen lijn. We hebben de toestemmingsprompt als eerste beurt van elk gesprek gebouwd en routeren weigeringen naar een menselijke wachtrij, wat de privacy officer van de klant goedkeurde. Dat kostte een week die we niet hadden ingepland.

De slotstap

Toen we deze voice-agent bouwden voor de Rotterdamse keten, was het ding waar we steeds tegenaan liepen het gat tussen wat generieke spraakherkenning belooft en wat ze daadwerkelijk doet op de tongval van een echte stad. Het is opgelost met een hybride stack: finetune wat je kunt finetunen, route om wat je niet kunt finetunen, en laat het model nooit een bestelling bevestigen waar het minder dan 90% zeker van is.

Het kleinste nuttige wat je vandaag kunt doen, als je op een operatie zit waar veel gebeld wordt: trek twintig echte gespreksopnames eruit, haal die zonder aanpassingen door Whisper-large-v3 en lees de transcripten naast de daadwerkelijke bestellingen. Het foutpatroon vertelt je of een voice-agent een tooling-probleem of een trainingsprobleem is voor je een euro uitgeeft.

Kern

Laat de bevestigingsstem het dialect van de beller volgen. Een Rotterdammer die nieuwslezer-Nederlands terughoort voelt zich betutteld, en betuttelde bellers corrigeren geen fouten.

FAQ

Kan standaard spraakherkenning Nederlandse dialecten aan?

Standaardnederlands transcribeert prima op 4 à 6% word error rate. Rotterdams en vergelijkbare regionale dialecten komen rond 25 tot 35% WER uit, en dat is onbruikbaar voor bestellingen aannemen zonder gerichte finetuning.

Hoe lang duurt het om Whisper te finetunen op een Nederlands dialect?

Met 10 tot 15 uur gelabelde audio, een LoRA-adapter en één A100 trek je de dialect-WER van boven de 30% naar onder de 10% in ongeveer vier uur compute. Het verzamelen van de data is het lange stuk.

Wat is het meest risicovolle deel van een voice-agent voor bestellingen?

Verzonnen orderbevestigingen. De agent moet elk item teruglezen aan de beller voor de lijn valt, en dat moet in een register dat de beller vertrouwt. Sla die stap over en bestellingen belanden op het verkeerde adres.

Heb je toestemmingsprompts nodig op opgenomen telefoonbestellingen in Nederland?

Ja. De richtlijn van de Autoriteit Persoonsgegevens vereist een heldere toestemmingsprompt aan het begin van het gesprek, een genoemde bewaartermijn en de optie om door te bellen zonder opname. Bouw dat als de eerste beurt van de belstroom.

voice agentsai agentscase studyintegrationsarchitecture

Iets bouwen?

Start een project