← Blog

Voice agents

Voice agents in de kliniek: live tegen een verouderd EPD

Vorige week ging de agenda-telefoon van een GGZ-praktijk in Hasselt 1.260 keer over. Een voice agent nam ze allemaal op, in twee talen, tegen een EPD ouder dan de receptioniste.

Jacob Molkenboer· Oprichter · A Brand New Company· 17 jun 2026· 11 min
Zwarte bakeliet telefoonhoorn op groen leer, agenda en kaart met lakzegel op ivoorkleurig bureau bij raamlicht.

De agenda-telefoon van de groepspraktijk in Hasselt ging vorige week 1.260 keer over. Een voice agent nam ze allemaal op. Driehonderd van die telefoontjes waren in het Frans. Elf waren crisissen. Geen ervan stond langer dan 45 seconden in de wacht.

Die ene zin heeft veertien maanden werk en een 13 jaar oude Daktari-installatie gekost. Hieronder het playbook, in de volgorde waarin we het schreven.

De wachtrij op maandagochtend

De praktijk telt 34 behandelaars, één voltijdse receptioniste en een parttime backup in de middag. Op een drukke maandag voor de agent live ging, stonden er om 08:25 twaalf mensen in de wacht. Het merendeel was een variant op "ik moet woensdag verzetten." Verzetjes. Een deel in het Frans. Een handvol mensen in crisis. Een paar patiënten die vroegen waarom deze consultatie €78 kost en de vorige €0, een terugkerende vraag omdat consultaties bij erkende klinisch psychologen in België BTW-vrijgesteld zijn, maar de tarieven per contractvorm verschillen.

De receptioniste kon die wachtrij niet binnen drie minuten op gehoor triëren. Een patiënt in crisis stond dus statistisch achter iemand die zijn woensdagafspraak naar donderdag wilde schuiven. Dat was het echte probleem. En dat is precies het probleem dat de meeste "we zetten er gewoon een voice bot op"-pitches niet oplossen. Een voice bot die sneller opneemt maar op FIFO routeert, helpt de patiënt die als eerste geholpen moet worden niet.

Wat live gaan tegen Daktari écht betekent

Daktari is een Belgisch EPD dat sinds 2013 onafgebroken bij deze praktijk draait. Het loopt op een Windows Server in een kast. Er is geen REST API. Er is een XML-over-HTTP endpoint dat de leverancier alleen aan partner-integrators levert, maar de partner kostte meer dan het hele projectbudget en vroeg om een onboarding van zes maanden. De documentatie die we wél hadden, was een PDF van 38 pagina's, grotendeels screenshots, grotendeels in het Nederlands, deels uit 2017.

Waar we uiteindelijk tegenaan bouwden, was een dunne interne service die we daktari-bridge noemden. Die draait op dezelfde Windows-bak, leest een queue met write-requests uit, en gebruikt hetzelfde UI-automation-patroon dat de baliemedewerkers van Daktari zelf gebruiken om afspraken in te voeren. Alleen dan scripted, audited en idempotent. We hebben Daktari niet gepatcht. We hebben Daktari niet gejailbreakt. We hebben een eerlijke, lelijke adapter geschreven die exact deed wat een typiste doet, acht keer per seconde.

Wat "acht keer per seconde" in de praktijk betekent. De adapter koppelt zich via UI Automation aan het hoofdvenster van Daktari, lokaliseert het afsprakenraster eenmalig bij opstart via accessible-id, en stuurt daarna per write een deterministische toetsenbordsequentie. We cachen de field-coordinate map in geheugen en herbouwen die bij een window-resize event. De hele bridge is ongeveer 1.400 regels C# en één operationele runbook die in feite zegt: als hij hangt, kill de service, de queue speelt zich bij herstart opnieuw af. Twee schermen, één toetsenbordlayout, geen verrassingen.

De dag dat je het EPD patcht, is de dag dat de leverancier je mails niet meer beantwoordt. Adapter, geen chirurgie. Elke minuut die je probeert slim te zijn binnen de EPD-database, verlies je bij de volgende leveranciersupdate.

Taalherkenning vóór de eerste zin

Hasselt ligt in Belgisch Limburg, Nederlandstalig, maar de praktijk trekt patiënten van over de taalgrens. Ongeveer 24% van de inkomende oproepen is Franstalig. We hebben drie aanpakken getest.

  1. De beller om een taal vragen. Traag. Mensen haken af.
  2. Taal detecteren uit de eerste zin. Niet slecht, maar de eerste zin van een GGZ-telefoontje is vaak "euh, bonjour, c'est Madame X." Twee woorden. Dubbelzinnige toon.
  3. Taal afleiden uit het telefoonnummerprefix en het tijdstip, en daarna verfijnen op basis van de eerste audio.

We hebben de derde live gezet. Een nummer met prefix +32 4 (regio Luik) krijgt een Franstalige opening. Een nummer met +32 11 (regio Hasselt) krijgt een Nederlandstalige opening. Onbekende nummers openen in het Nederlands met een Franstalige follow-up als het eerste antwoord niet aansluit. De hit rate op taalmatch bij de eerste begroeting is 91%. De 9% die we missen, corrigeert de agent in de tweede beurt zonder daar een scène van te maken.

Het zelfgeknutselde signaal is klein en dom en werkt. Wat je niet moet doen: een tweetalig model fine-tunen op je gespreksopnames. Je hebt niet genoeg data, je data is medisch, en je data is sowieso niet van jou om op te fine-tunen.

Het 45-secondenbudget voor crisis

De harde randvoorwaarde van de klinisch leidinggevende was deze. Een beller in crisis bereikt binnen 45 seconden na verbinding een behandelaar. Geen 45 seconden wacht. 45 seconden van inbellen tot een menselijke stem.

Dat getal was niet onderhandelbaar. Alles in de architectuur van de agent volgt eruit.

We hebben het audiopad in tweeën gesplitst. De eerste stream draait de conversationele LLM op normale latency. De tweede stream draait een kleine, snelle classifier op een glijdend venster van 4 seconden, op zoek naar crisis-indicatoren in beide talen. De classifier is niet het brein van de operatie. Het is de rookmelder. False positives zijn prima, want de kostprijs van een false positive is dat de praktijkmanager een niet-crisisoproep opneemt. De kostprijs van een false negative is een echte.

Als de classifier afgaat, gebeuren er drie dingen in dezelfde tick.

  • De conversationele agent schakelt over op een rustig, low-affect script: "Ik blijf bij u aan de lijn. Ik haal een collega erbij."
  • Een SIP-transfer naar de wachtrotatie van de GZ-psycholoog wordt parallel met (niet ná) de verbale overdracht gestart.
  • De daktari-bridge registreert nog niets. Agenda-mutaties tijdens een crisisoproep zijn verboden tot een menselijke behandelaar de afhandeling bevestigt.

End-to-end mediaan van verbinden tot behandelaar aan de lijn: 31 seconden. p95: 42 seconden. We hebben de 45-seconden-SLA in 19 weken niet één keer gemist.

Waarom betalingen langs een mens moeten voor de agenda vastligt

De andere categorie oproepen waarvoor we weigeren naar de agenda te schrijven, is alles wat met geld te maken heeft. In België is een psychotherapeutische consultatie door een erkend klinisch psycholoog BTW-vrijgesteld onder artikel 44, §2, 1° van het BTW-wetboek. Maar BTW-vrijgesteld betekent niet gratis. Het betekent geen BTW op de factuur. Patiënten halen die twee constant door elkaar, en de helft van de doorverwijzende huisartsen ook.

Als de beller een vraag stelt over betaling, doet de agent drie dingen. Hij beantwoordt de feitelijke laag waar hij antwoord op mag geven (ja, de consultatie is BTW-vrijgesteld; de praktijk doet geen derdebetalersregeling). Hij legt geen agenda-write vast. En hij plant binnen vier werkuren een terugbelafspraak van de praktijkmanager in, geschreven in een aparte intent-queue, niet in Daktari.

Waarom geen agenda-write op een oproep met een betaalvlag? Omdat oproepen met een betaalvraag in acht maanden pilotdata een no-show ratio hadden van 31%, tegenover 6% voor schone boekingsoproepen. Mensen die niet weten wat ze betalen, komen niet opdagen. Dus boeken we ze niet. Het tijdslot blijft open voor iemand die wél komt.

Onthouden

Laat de voice agent geen write doen op een oproep waarin de beller nog niet weet wat hij gaat betalen. Stel de agenda-mutatie uit. Altijd.

De agenda-write in twee fasen

Zelfs bij een schone herboeking schrijven we niet live naar Daktari. We schrijven naar een staging table. Een tweede proces, draaiend op dezelfde Windows-bak en lezend uit dezelfde queue, speelt de write om de zes seconden af in Daktari. De replay is idempotent, met als sleutel een UUID die we genereren op het moment van de intent.

create table agenda_intent (
  id              uuid primary key default gen_random_uuid(),
  call_id         uuid not null,
  intent_kind     text not null check (intent_kind in ('book','reschedule','cancel')),
  patient_ref     text not null,
  slot_start      timestamptz not null,
  clinician_id    text not null,
  created_at      timestamptz default now(),
  applied_at      timestamptz,
  conflict_at     timestamptz,
  conflict_reason text
);
create unique index agenda_intent_idem
  on agenda_intent (call_id, intent_kind, slot_start);

Dat levert ons vier dingen op.

  • Als Daktari plat ligt (en dat is in veertien maanden vier keer gebeurd, telkens tijdens de kwartaalpatch van de leverancier), wacht de queue en speelt zich later af. De patiënt merkt er niets van.
  • Als een behandelaar het tijdslot intussen handmatig in Daktari wijzigt, detecteren we het conflict bij de replay en leggen we het voor aan de receptioniste in plaats van overschrijven.
  • We kunnen auditen. Elke write heeft een opname, een transcript, een intent-payload en een eindstand in Daktari. Na vijfduizend oproepen hebben we een schoon logboek.
  • We kunnen rollbacken. Een verkeerd geclassificeerde intent kunnen we terugdraaien voor die ooit het EPD raakt.

Er is een vijfde voordeel van de staging table dat we niet hadden voorzien. Het geeft de receptioniste een queue die ze kan superviseren. Elke ochtend opent ze een klein dashboard en ziet de intents van gisteren, het aantal toegepast, het aantal conflicten en de gemiddelde tijd-tot-toepassen. Dat dashboard is het eerste waar ze 's ochtends naar kijkt, nog voor de mail. Ze pakt er edge cases uit die geen enkele eval-suite zou hebben opgepikt, omdat zij weet welke behandelaars lange lunches nemen en welke patiënten nooit bevestigen per sms.

De receptioniste zat vroeger ongeveer 70% van haar ochtend aan de telefoon. Nu nog ongeveer 30%, voor de oproepen die de agent escaleert. Dat zijn ook precies de oproepen waaraan zij wíl meedraaien.

De guardrails die de receptionistes zelf schreven

De nuttigste tests in de suite zijn niet door ons geschreven. Ze zijn geschreven door de receptioniste en de twee behandelaars die zich opgaven voor de pilot. Zij hebben 84 voorbeeldgesprekken opgeschreven in het Nederlands, Frans, gemengd, met gemompel, met een blaffende hond, met krijsende kinderen op de achtergrond, en bij elk een verwachte uitkomst in één regel. Die 84 spelen we af tegen elke model- of promptwijziging voordat die staging verlaat.

Die suite heeft meer regressies gevangen dan welke synthetische eval we ook hebben geschreven. Reden is simpel. De receptioniste heeft een model van hoe een herboekoproep op dinsdagavond klinkt, dat wij als engineers niet hebben. Zij schreef tests op dat model. Toen we afgelopen maart de conversationele prompt wijzigden en de nieuwe versie te vaak om het rijksregisternummer begon te vragen bij Franstalige oproepen, ving haar suite het diezelfde namiddag.

De vraag die we van collega's blijven krijgen, is of een lokaal, self-hosted model het gehoste kan vervangen voor zo'n workload. Zes maanden geleden hebben we op de hardware van de praktijk twee lokale Whisper-varianten getest voor transcriptie. Goed genoeg voor de rookmelder-classifier, nog niet goed genoeg voor de conversationele laag. We draaien die vergelijking volgend kwartaal opnieuw, want het gat wordt snel kleiner.

Wat je vandaag kunt doen

Als jij een praktijk runt met een verouderd EPD en een telefoonwachtrij, is dit het kleinste nuttige eerste ding. Instrumenteer de queue. Log van elke oproep de wachttijd, de taal, de uitkomstcategorie en of de patiënt is komen opdagen. Twee weken van die data vertellen je of een voice agent de juiste vorm van oplossing is, of dat je gewoon een halve dag extra receptie-uren nodig hebt.

Toen we de voice agent bouwden voor de Hasseltse praktijk, was het deel dat we onderschatten hoe weinig van het werk eigenlijk in het model zat. Het zat in de queue, de staging table en de receptioniste die naast ons zat tests te schrijven. We hebben uiteindelijk een kleinere agent live gezet dan we hadden gepland, en een veel grotere adapter, en de 45-seconden-SLA hield het.

Kern

Triëer voor je transcribeert, laat elke write via een staging table lopen, en weiger oproepen te boeken waarin de beller nog niet weet wat hij gaat betalen.

FAQ

Waarom geen gebruik van het XML-endpoint uit het partnerprogramma van Daktari in plaats van UI-automation?

Het partnerprogramma kostte meer dan het projectbudget en vroeg om een onboarding van zes maanden. UI-automation tegen dezelfde schermen die de balie al gebruikt, was eerlijk, auditeerbaar en in weken live te krijgen.

Wat houdt de voice agent tegen om een beller in crisis in een verkeerd tijdslot te boeken?

Bij elke oproep die de crisis-classifier markeert, blokkeren we agenda-writes volledig. De SIP-transfer gaat door, de agent blijft aan de lijn, en er gebeurt geen agenda-mutatie tot een menselijke behandelaar de afhandeling bevestigt.

Hoe ga je om met Franstalige bellers uit Wallonië die een Nederlandstalige praktijk bellen?

Het telefoonnummerprefix geeft de eerste gok. +32 4-nummers openen in het Frans, +32 11 in het Nederlands. Onbekende nummers openen in het Nederlands met een Franstalige follow-up als het eerste antwoord niet aansluit. De hit rate bij de eerste begroeting ligt op 91%.

Waarom betaalvragen doorverwijzen naar de praktijkmanager in plaats van ze direct te beantwoorden?

Oproepen met een betaalvraag hadden in de pilot een no-show ratio van 31% tegen 6% voor schone boekingsoproepen. Een verwarde patiënt boeken verbrandt een tijdslot. Een terugbel binnen vier uur door een mens lost de verwarring op voor het slot vastligt.

Kan een lokaal model hier de conversationele LLM vervangen?

Zes maanden geleden niet. Lokale Whisper-varianten waren goed genoeg voor de rookmelder-classifier, nog niet voor de conversationele laag. We hertesten elk kwartaal. Het gat wordt kleiner.

voice agentsai agentsintegrationscase studyarchitectureworkflow

Iets bouwen?

Start een project