De voicemailteller op de balietelefoon van een fysiopraktijk in Utrecht stond op 11 onbeluisterde berichten toen de deur op een dinsdag in april om 08:00 van het slot ging. Om 08:47 stond hij op 17. Marieke, de receptioniste, was nog niet eens aan haar koffie toegekomen. Twee berichten kwamen van dezelfde patiënt, drie minuten na elkaar, omdat ze niet zeker wist of het eerste was aangekomen. De praktijk vroeg of een voice agent het werk kon opvangen zonder de 78-plussers af te schrikken.
In die praktijk hebben we eind april 2026 een week lang een Nederlandstalige voice agent in het wild getest. Drie therapeuten, ongeveer 220 actieve patiënten, flinterdunne marges op receptionistentijd, en een agenda die in software (Intramed) leefde zonder bruikbare publieke API. Dit is wat we bouwden, wat op dag één stuk ging, en de cijfers na zeven dagen.
De opdracht
Inkomende gesprekken vallen in een normale week voorspelbaar uiteen. Ongeveer 60% verzettingen, 20% nieuwe-patiëntvragen, 10% verzekeringsvragen, 10% 'mag ik mijn therapeut spreken'. Het volume piekte op maandagochtend (om 10:00 een wachtrij van negen) en laat op vrijdagmiddag, als mensen bedachten dat ze zaterdag iets te doen hadden.
De opdracht van de praktijkhouder was smal en helder. Vang verzettingen op. Boek geen nieuwe patiënten. Laat een oudere beller nooit het gevoel krijgen dat hij met een snoepautomaat in discussie is. Geef netjes door als iets klinisch niet pluis klinkt.
Stack en wat we hebben uitgesloten
De basis van het systeem:
- Nederlands geografisch nummer op Twilio Media Streams, doorgeschakeld vanaf de bestaande vaste lijn van de praktijk na drie keer overgaan.
- Speech-to-speech redenering via de OpenAI Realtime API over WebSocket.
- Text-to-speech overgezet naar ElevenLabs Multilingual v2 met een Nederlandse stem. De standaardstemmen van Realtime waren verstaanbaar, maar klonken in de eerste testgesprekken bij oudere bellers Amerikaans.
- Agenda via een gedeelde Google Calendar die Marieke al spiegelde vanuit Intramed. Een directe integratie met Intramed had de hele week opgevreten.
- SMS-bevestiging via Messagebird, zodat de beller een schriftelijke vastlegging van de afspraak had voor het ophangen.
- Een reconciler die elke 60 seconden draait, de agenda controleert op dubbele boekingen en die naar een Slack-kanaal vlagt.
We hebben ook een volledig lokale stack overwogen (Whisper plus een 70B-model plus een open-weights Nederlandse TTS). De latency was haalbaar op een enkele H100, maar de kosten per gesprek versloegen de hosted route niet bij dit volume, en we wilden niet de eersten zijn die op een dinsdagmiddag een open-weights Nederlandse stemkloon zaten te debuggen op het moment dat de praktijkhouder belde.
Dag één en wat er stuk ging
De eerste acht uur leverden drie problemen op die achteraf allemaal voorspelbaar waren.
De voice activity detection was te gretig. Standaard ziet VAD een pauze van 200 ms als einde van de beurt. Prima voor iemand die een Slack-bericht dicteert. Niet voor mevrouw De Vries, 78, die haar afspraakkaartje uit haar tas opdiept. We zetten server-side VAD op 700 ms aanhoudende stilte en voegden een zachte instructie toe aan het systeemprompt: als de beller langzaam praat, niet onderbreken, gewoon wachten.
Nederlandse getallen werden als twee waarden geparsed. Uitgesproken Nederlandse data zijn een mijnenveld. 'Vierentwintig mei' werd correct getranscribeerd, maar het model spuugde JSON uit als {"day": 4, "extra": 20} voordat het herstelde. We hebben een Nederlands-specifiek tool schema toegevoegd en een few-shot blok met vijf voorbeelden van uitgesproken data die de ergste gevallen afdekken (vierentwintigste, eenendertigste, half drie, kwart voor tien). De foutmarge op datum-extractie zakte van één op vijf naar één op vijftig in de rest van de week.
De agenda had een race. Twee bellers wilden dezelfde donderdag om 14:30. Beiden kregen een bevestiging. Eén van hen was de schoonmoeder van de praktijkhouder, en dat is het soort bug dat je je herinnert. We hebben een advisory lock per slot in Redis toegevoegd rond het read-check-write window van de boekingstool, plus de reconciler die elke minuut draait en conflicten naar boven haalt voordat de betreffende patiënt binnenstapt.
Als jouw boekingstool beschikbaarheid leest en een bevestiging schrijft als twee aparte calls, dan heb je een race condition. Hij vuurt op de dag dat een familielid belt. Bouw de lock eerst.
Beleidskeuzes die belangrijker waren dan het model
Het model was niet het interessante stuk. Het beleid wel.
De voice agent mocht onder geen beding een nieuwe patiënt boeken. Nieuwe patiënten triggeren een verzekeringscheck, intakeformulieren en een eerste-bezoek slot dat langer is dan standaard. We routeerden elke 'ik ben hier nog nooit geweest'-beurt naar een wachtrij voor een terugbel binnen vier werkuren.
De voice agent escaleerde direct bij een vaste lijst zinnen: 'pijn na een val', 'kon niet meer bewegen', 'gevoelloos', 'sinds het ongeluk', 'sinds de operatie', en alles waarbij de beller om een afspraak op dezelfde dag vroeg. Same-day verzoeken gingen naar Marieke, omdat die bijna altijd een afweging vragen of een therapeut zelf de telefoon moet oppakken.
Iedere bevestiging las datum, dag van de week, tijd en naam van de therapeut hardop terug. ('Donderdag 24 mei, om half drie, bij Bram.') Die ene regel halveerde meer dan het aantal conflicten dat de reconciler vlagde, omdat bellers verkeerde data oppikten voordat de boeking weggeschreven werd.
We logden STT-confidence per beurt. Als de confidence twee beurten achter elkaar onder de 0,7 zakte, vertraagde de agent zijn TTS-rate met 15% en bood hij: 'Hoort u mij goed?' Die ene heuristiek correleerde met oudere bellers en lijnen met ruis, en bracht het aantal mid-call hangups in dat segment terug van acht op maandag naar twee op vrijdag. De praktijk werkt ook onder Nederlandse medische gegevensregels (AVG plus NEN 7510), dus de policy-laag was tegelijk de plek waar we logden wat de agent wel en niet in transcripts bewaarde. De Autoriteit Persoonsgegevens kijkt met een schuin oog naar een spraakzame AI op een telefoonlijn zonder bewaarbeleid.
De cijfers na zeven dagen
Zeven kalenderdagen. 312 inkomende gesprekken. De praktijk was open van maandag tot en met zaterdagochtend.
- 198 gesprekken (63%) volledig zelfstandig afgehandeld zonder menselijke tussenkomst.
- 84 gesprekken (27%) netjes geëscaleerd naar Marieke, hetzij via beleid (nieuwe patiënt, klinische zin, same-day), hetzij omdat de beller om een mens vroeg.
- 30 gesprekken (10%) afgebroken tijdens of voor de openingsregel van de agent.
- Gemiddelde gespreksduur: 1:42. Mediaan: 1:28.
- Slagingspercentage op geprobeerde verzettingen: 89%.
- Door de agent gemaakte nieuwe-patiëntafspraken: 0 (zoals bedoeld).
- Door de reconciler gevangen dubbele boekingen: 2. Beide opgelost voordat de patiënt aankwam.
- Officiële klachten bij de praktijk: 1, van een 71-jarige die een mens wilde. We hebben dat gehonoreerd en haar nummer toegevoegd aan een lijst die de agent overslaat.
De terugbelstapel van de receptioniste (voicemails die ze moest terugbellen) zakte van een maandagochtend-gemiddelde van 14 naar 2 aan het eind van week één. Dat is het cijfer dat telt. Het is geen benchmark, het is de middag van iemand terug.
De interessante metric was niet 'gesprekken afgehandeld door de agent'. Het was 'voicemails die de receptioniste niet meer hoefde terug te bellen op maandagochtend'. Optimaliseer voor de mens die je beschermt.
Een stukje van de echte config
Voor wie de Realtime API koppelt aan een Nederlandstalige praktijk: twee dingen in de session config deden het meeste werk. Server-side VAD met een langere stiltedrempel, en een korte, uitgesproken system prompt die de praktijk benoemt en alles buiten scope weigert. Schets:
{
"type": "session.update",
"session": {
"modalities": ["audio", "text"],
"instructions": "Je bent de telefonische assistent van Praktijk X in Utrecht. Spreek Nederlands, rustig en beknopt. Je plant alleen vervolgafspraken voor bestaande patienten. Nieuwe patienten en spoed verbind je door. Lees iedere bevestiging hardop terug: datum, dag, tijd, therapeut.",
"voice": "external",
"input_audio_format": "g711_ulaw",
"output_audio_format": "g711_ulaw",
"turn_detection": {
"type": "server_vad",
"threshold": 0.5,
"prefix_padding_ms": 300,
"silence_duration_ms": 700
},
"tools": [
{ "type": "function", "name": "find_slots" },
{ "type": "function", "name": "book_slot" },
{ "type": "function", "name": "escalate_to_human" }
]
}
}
De TTS-audio komt van ElevenLabs en wordt teruggestreamd naar Twilio als g711 mu-law. De Realtime API doet de transcriptie en het redeneren; de stem wordt extern gerenderd. Die splitsing kost je ongeveer 250 ms aan gevoelde latency op first-token, en die milliseconden zijn elke ene waard wanneer een oudere beller een stem hoort die hij eerst als Nederlands en pas daarna als synthetisch parseert.
Wat we de volgende keer anders doen
Drie dingen, op volgorde van hoeveel pijn ze hadden bespaard.
Eén. Bouw de reconciler voordat je live gaat, niet na de derde dubbele boeking. Een script van dertig regels dat elke minuut de agenda diff't en conflicten naar Slack post, had de schoonmoeder-case binnen 90 seconden gevlagd. Bij ons draaide het pas op woensdag. Het had op zondag moeten draaien.
Twee. Voice clone eerst, default stem nooit. Het percentage afgeronde gesprekken bij oudere bellers steeg merkbaar toen we naar een Nederlandse ElevenLabs-stem overstapten. De standaardstemmen op de meeste realtime API's klinken default Amerikaans, en oudere Nederlandse bellers horen dat binnen twee seconden.
Drie. Lever vanaf het eerste gesprek een 'druk 9 voor een mens'-affordance. Wij deden dat niet, omdat we schone data wilden over de prestaties van de agent. Hadden wel gemoeten. De ene klacht die we kregen had niet bestaan, en we waren niets meetbaars kwijt geweest.
De praktijk heeft het systeem aan gehouden. Het contract voor week twee en verder bevat een rustige uitbreiding van de scope: verzekeringsvragen deflecteren (read-only), en een uitgaand herinneringsgesprek de avond voor elke afspraak. Zelfde architectuur, andere prompt.
Toen we deze voice agent bouwden voor de Utrechtse praktijk, bleef het ons opvallen dat het model bijna nooit de bottleneck was. Beleid wel. We schreven uiteindelijk meer regels escalatieregels dan promptinstructies, en dat is de juiste verhouding voor elke voice agent die een medische balie raakt. Wil je er zelf een proberen? Begin met het opschrijven van de vijf zinnen die nooit bij het model mogen aankomen.




