← Blog

Voice agents

Voice agent voor CV-storingen: 1.420 oproepen per week

Het is dinsdag 06:42 in Eindhoven, de eerste vorst van november, en 38 ketels in de regio zijn al uitgevallen. De drie planners van de coöperatie hangen niet aan de lijn.

Jacob Molkenboer· Oprichter · A Brand New Company· 15 jun 2026· 8 min
Zwarte bakelieten telefoonhoorn op ivoorkleurig leer, chartreuse lint, kaart met lakzegel, koperen bel op achtergrond.

Het is dinsdag 06:42 in Eindhoven, de eerste vorst van november, en 38 ketels in de regio zijn al uitgevallen. Zes van die meldingen kwamen binnen negentig seconden binnen. De coöperatie heeft drie planners op dienst en een wachtrij van eenenveertig. Geen van de planners hangt aan de lijn.

Die laatste zin is het punt van dit stuk. Twee jaar geleden hadden ze aan de telefoon gezeten, hetzelfde adres acht keer achter elkaar opgenomen, de klant opgezocht in een 14 jaar oude AFAS InSite portal die nog framesets gebruikt, en het modelnummer van de CV-ketel op een geeltje gekrabbeld. Vandaag doet een Nederlandse voice agent de eerste negentig seconden van dat werk, en pakt een planner alleen de hoorn op als er een CO-melder genoemd wordt of als de klant het woord "noodgeval" zegt.

Dit is de case study. We noemen het geen transformatie.

De coöperatie

De klant is een installateurscoöperatie van 29 mensen in de regio Eindhoven. Tweeëntwintig monteurs in bussen, drie planners, twee back-office medewerkers, de oprichter, en een parttime boekhouder. Ze delen een merk, een telefoonnummer, een planningssysteem en een onderdelencontract. Iedere installateur houdt zijn eigen klantenboek bij, en dat wordt later relevant.

Ze verwerken zo'n 1.420 storingsmeldingen per week in het winterkwartaal, en ongeveer 600 in de zomer. Daarvan gaat zo'n 4% over een koolmonoxidewaarde die de klant op een melder ziet of ruikt. Elk van die 4% moet binnen één keer overgaan een mens aan de lijn krijgen. De rest kan wachten tussen de vijf en vijfenveertig minuten, afhankelijk van de urgentiecode.

Wat er daarvoor stond

Twee telefoonlijnen. AFAS InSite 9 (uitgebracht in 2011, nooit geüpgraded, draait nog omdat de in-house planner module afhangt van een ActiveX-control uit 2012). Outlook. Een whiteboard met magneten. Een WhatsApp-groep genaamd "Dispatch (lees mee)".

Als alle drie de planners in gesprek waren, rolde lijn drie door naar een generieke voicemail bij KPN waar niemand naar luisterde tot de lunch. We hebben dat in de eerste week van discovery gemeten: 71 gemiste oproepen op één maandag in februari. Ongeveer een derde van die mensen belde een concurrent.

Het eerste ontwerp dat we weggegooid hebben

Onze eerste reflex was een chatbot voor de website. Verkeerde reflex. 56% van de klanten van de coöperatie is ouder dan 55, twee derde belt over een verwarming die midden in de winter net is uitgevallen, en een kwart belt vanuit een garage met een klaptelefoon. Ze willen snel een menselijke stem aan de lijn, in Brabants gekleurd Nederlands, en ze willen weten dat de monteur onderweg is voordat ze ophangen.

Een voice agent was het enige acceptabele kanaal. De vraag was wat hij veilig kon doen en wat absoluut niet.

Voice agents falen lelijk als ze falen. Een verkeerd verstane postcode oogt nog competent. Een verkeerd verstaan "ik ruik gas" oogt als nalatigheid. Kies de onderwerpen waar de prijs van een fout te herstellen is, en stuur de rest bij de eerste zin door naar een mens.

De daadwerkelijke scope van de agent

We hebben de agent precies vijf taken gegeven, en geen andere. In productie zijn ze voor de planner zichtbaar als vijf statuslampjes op het dispatch-scherm.

  1. Herken de klant aan zijn telefoonnummer, adres of ketelserienummer. Als hij dat niet alle drie met meer dan 0,92 zekerheid kan, draag over.
  2. Classificeer het gesprek in één van vier urgentiecodes (U0 tot en met U3) volgens een rubric die de coöperatie al op papier gebruikte.
  3. Plan een U2-ticket in de geografisch dichtstbijzijnde open route van een monteur, met respect voor het lunchblok en het ophaalmoment voor onderdelen.
  4. Lees het afspraakvenster in het Nederlands terug en stuur een sms met een link om zelf te verzetten.
  5. Detecteer elke vermelding van CO, gaslucht, rook of het woord "noodgeval", en verbind het gesprek binnen één keer overgaan door met een senior planner. Sluit zo'n gesprek nooit zelf af.

Hij neemt geen betalingen aan. Hij wijzigt geen adressen. Hij verwerkt geen klachten. Hij belooft geen aankomsttijd nauwkeuriger dan een venster van vijfenveertig minuten. Hij spreekt geen Engels tenzij de klant Engels begint.

Bovenop een 14 jaar oude AFAS-portal

Het lastigste van dit project was niet de spraakherkenning. Dat was de AFAS InSite 9 portal.

AFAS InSite 9 is door de coöperatie voor het laatst bijgewerkt in 2014. Hij is de bron van waarheid voor klantgegevens, ketelserienummers, contractstatus en historische monteursbezoeken. Het REST-oppervlak van de leverancier bestond voor die versie nog niet. De portal serveert HTML binnen een frameset, en de in-house planner module is een dikke ActiveX-control die niemand uit het huidige team kan hercompileren.

We hebben hem niet gemigreerd. We migreren geen dingen die werken, op een strakke planning, als de mensen die ze gebruiken rustig blijven onder druk.

Wat we wel hebben gedaan: een dunne read-only adapter bouwen die de portal scrapet via een geauthenticeerde session, het klantrecord negentig seconden cachet, en één HTTP endpoint blootstelt voor de voice agent. Writes gaan op de oude manier: de planner voert de afspraak handmatig in AFAS in bij bevestiging, zoals altijd. Het slot dat de agent voorstelt is een suggestie, geen write.

async def lookup_customer(phone: str) -> Customer | None:
    cached = await cache.get(phone)
    if cached and cached.age_seconds < 90:
        return cached.value

    session = await afas_session.ensure()
    html = await session.get(
        "/InSite/Klanten/Zoek",
        params={"telefoon": phone},
        timeout=4.0,
    )
    record = parse_klant_frame(html)
    if record is None:
        return None

    await cache.set(phone, record, ttl=90)
    return record

Vier seconden is de harde timeout. Als AFAS in vier seconden niet antwoordt, zegt de agent "een moment, ik verbind je door" en verbindt door naar een planner. Die planner heeft de klant al op het scherm omdat zijn eigen InSite-session open staat. De klant hoort één warm woord menselijke stem in plaats van negen seconden stilte.

Kernpunt

De voice agent vervangt de legacy portal niet. Hij zet er een budget van vier seconden voor, en behandelt elke miss als een routing event, niet als een storing.

De 75-secondenloop voor het inplannen

Een U2-ticket is het dagelijkse geval: de ketel doet het niet, het huis is koud, de klant wil vandaag iemand. De agent heeft 75 seconden tussen "hallo, mijn ketel doet het niet" en "een monteur is bij u tussen kwart over twee en drie uur" voordat de klant zich door een machine afgehandeld begint te voelen.

In die 75 seconden moet de agent postcode en huisnummer bevestigen, de klant in AFAS opzoeken (binnen het hierboven genoemde budget van vier seconden), drie verhelderende vragen uit het urgentierubric stellen, de live busposities en routeplanningen van de drie monteurs in het dichtstbijzijnde cluster ophalen, de goedkoopste invoeging in de route voorstellen die lunch, onderdelenpickup en de aangegeven beschikbaarheid van de klant respecteert, het venster teruglezen, bevestigen en de sms triggeren.

Het inplannen zelf is een constrained insertion-probleem: gegeven een route met stops met harde tijdvensters, voeg je een nieuwe stop in die de extra rijtijd minimaliseert. De wiskunde is eerstejaars universiteit (zie het standaard vehicle routing problem voor de leerboekversie). De kunst zit in de constraints die niemand opschrijft. Tim eet niet tussen 12:00 en 12:30. Sander neemt op woensdag geen klus aan in postcodecluster 5611, omdat zijn dochter daar naar de crèche gaat en hij even langsrijdt. De monteur op de Heeze-route haalt zijn onderdelen altijd om 14:00 op, en de groothandel sluit om 17:00.

Die hebben we opgeschreven. Ze leven in een YAML-bestand dat de planners zelf kunnen aanpassen. We laten de agent ze niet leren. Zachte voorkeuren worden harde ongelukken zodra niemand meer kan teruglezen waarom een beslissing genomen is.

Wat de agent niet optimaliseert

Hij minimaliseert niet de totale rijtijd van de coöperatie. Hij optimaliseert per ticket, tegen de routes zoals ze nu zijn, met de monteurs zoals ze nu zijn. De coöperatie heeft in 2019 een globale optimizer van een andere leverancier geprobeerd en de monteurs kwamen binnen een week in opstand, omdat het algoritme Tim de stad door stuurde om Sander vier minuten te besparen. Daar hebben we van geleerd.

Het CO-alarmpad

Er is precies één deel van dit systeem waar we ons elke week zorgen over maken. Een klant die koolmonoxide, gaslucht of "noodgeval" noemt moet binnen één keer overgaan een senior planner aan de lijn krijgen. De voice agent mag niet proberen te helpen.

De detectie is een gelaagde cascade. Eerst luistert een kleine classifier naar een woordenboek van triggerzinnen in het Nederlands, inclusief dialectvarianten ("ik rook gas", "het stinkt naar gas", "de melder gaat af", "rode lamp op de melder"). Daarna toetst het taalmodel intentie op elke uiting tegen één instructie: bij enige kans op een gas- of CO-noodgeval, verbind direct door en zeg dat hardop. Ten derde valt het gesprek bij een falende bridge door op de oude lijn drie van de coöperatie, die nog steeds rinkelt in de planningskamer. Drie onafhankelijke lagen. We gaan uit van 1% missrate per laag, wat ons een gecombineerde miss van ongeveer 1 op de miljoen geeft.

We testen de cascade wekelijks met een opgenomen set van 40 triggergesprekken, de helft in Standaardnederlands, de helft in Brabants accent. Zakt het slaagpercentage onder 100%, dan wordt de agent automatisch gepauzeerd en gaat de lijn naar mensen. Hij heeft in acht maanden vier keer gepauzeerd, altijd na een modelupdate, altijd dezelfde dag hersteld.

Als jouw voice agent enige kans heeft de eerste te zijn die "ik ruik gas" hoort, ben je hem drie onafhankelijke uitwegen en een wekelijkse geautomatiseerde test verschuldigd. Eén laag is niet genoeg.

Cijfers na acht maanden

We hebben vanaf de dag dat de agent live ging continu gemeten. De cijfers hieronder zijn van de coöperatie, niet van ons, en zijn lopende gemiddelden over de laatste twaalf weken.

  • 1.420 storingsmeldingen per week end-to-end afgehandeld zonder planner.
  • Mediane tijd van begin gesprek tot bevestigde afspraak: 71 seconden.
  • P95-tijd: 96 seconden.
  • U2-tickets bij het eerste voorstel correct naar de dichtstbijzijnde monteur gerouteerd: 94%.
  • CO- en gas-trigger gesprekken binnen één keer overgaan doorverbonden met een senior planner: 100% (op 218 geverifieerde triggers).
  • False positives op de CO-trigger: ongeveer 9 per week. De planners verkiezen die kant van de afweging.
  • Gesprekken die om wat voor reden dan ook overgedragen worden aan een mens: 17%.
  • Klanttevredenheid (door planner beoordeeld, na bezoek): ongewijzigd. We hadden een dip verwacht en die kwam niet.

Het getal dat we niet hadden verwacht: het overwerk van planners is met 38% gedaald. Niet omdat de agent planningswerk wegnam. Maar omdat de agent de eerste negentig seconden van elk gesprek wegnam, en negentig seconden keer 1.420 gesprekken is 36 uur per week.

Wat we niet zullen claimen

We claimen niet dat dit een technisch zwaar project was. De voice-stack is de standaardstack (een Nederlandse ASR, een kleine classifier voor een taalmodel, een TTS die klinkt als een echt mens uit Eindhoven en niet als een Belgische nieuwslezer). Het inplannen is eerstejaars algoritmiek. De AFAS-adapter is een vermoeide HTML-scraper met een session-cookie en een cache.

Wat zwaar was, was drie weken in de planningskamer zitten, kijken wat Tim, Sander en de planners daadwerkelijk doen, de ongeschreven regels opschrijven, en de coöperatie ervan overtuigen dat de juiste zet was om een budget van vier seconden voor hun 14 jaar oude portal te zetten in plaats van hem te herbouwen.

Toen we de voice agent voor deze coöperatie bouwden, was het ding waar we steeds tegenaan liepen het gat tussen wat de AFAS-portal veilig kon antwoorden en wat de klant in de eerste tien seconden wilde horen. We hebben het opgelost door de portal te behandelen als een trage oracle, de agent als een snelle begroeter, en de planner als de enige writer. Als je naar vergelijkbaar werk kijkt, past dezelfde vorm waarschijnlijk: lees de legacy, schrijf niets, draag snel over. Dat is het grootste deel van ons werk aan AI-agents voor installateurs en field-service teams.

Het kleinste wat je vandaag kunt doen: open je gespreksopnames van vorige week dinsdagochtend, tel de gesprekken die in de eerste 30 minuten binnenkwamen, en vraag je af wat er anders was geweest als een menselijke stem ze allemaal binnen één keer overgaan had opgenomen.

Kern

Zet een budget van vier seconden voor je legacy portal, behandel elke miss als een routing event, en laat een voice agent nooit een CO-alarmgesprek afsluiten.

FAQ

Waarom hebben jullie de AFAS InSite portal niet eerst van versie 9 af gemigreerd?

Omdat hij werkt, de planners hem vertrouwen, en het project van acht weken naar acht maanden zou zijn uitgelopen. Een read-only adapter met een timeout van vier seconden was goedkoper en veiliger dan een herbouw.

Wat gebeurt er als de voice agent een postcode verkeerd verstaat?

Hij leest terug wat hij gehoord heeft voordat hij boekt, en gaat alleen door als de klant bevestigt. Lukt bevestiging twee keer niet, dan gaat het gesprek naar een planner. Verkeerd geboekte slots zijn zeldzaam genoeg dat de coöperatie ze niet apart bijhoudt.

Had een chatbot dit ook gekund?

Nee. Twee derde van deze gesprekken komt van mensen met een koud huis en een telefoon in de hand. Ze willen een stem, geen tikformulier. Het spraakkanaal is het product.

Hoe trainen en testen jullie de CO-alarm trigger?

We houden een verzegelde testset van 40 Nederlandse opnames bij, de helft Standaardnederlands en de helft Brabants accent. De cascade draait er wekelijks tegenaan. Zakt het slaagpercentage onder 100%, dan wordt de agent automatisch gepauzeerd en valt de lijn terug op mensen.

Op welke stack draait dit?

Een Nederlandse ASR voor transcriptie, een kleine intent-classifier voor een taalmodel voor de dialoog, een Nederlandse TTS voor de output, en een Python-service die met AFAS InSite en de routing engine praat. Het zware werk zit in de rubrics en de YAML met operator-voorkeuren, niet in de modellaag.

voice agentsai agentsautomationcase studyintegrationslegacy sites

Iets bouwen?

Start een project