Chat agents
WhatsApp chat agent voor transport: overdracht in 45 sec
Een chauffeur in een uitwijkstrook bij Venray stuurt om 04:53 één WhatsApp-zin. Om 04:54:38 is de load geboekt, de klant ingelicht, en dispatch slaapt nog. Zo zit dat.

De chauffeur rijdt een grindstrook in ten noorden van Venray, zet de motor af en typt één regel in WhatsApp: "load 88421 collected, scan coming." De timestamp is 04:53. Om 04:54:38 staat de pickup geregistreerd in het TMS van de makelaar, is de klant ingelicht, staat het volgende ritdeel klaar in de chat van de chauffeur, en is de CMR-foto via OCR gekoppeld aan consignment 88421. Er is nog geen dispatcher wakker. De eerste begint pas om 05:30.
De makelaar rijdt met 60 trekkers vanuit Venlo, vooral NL naar NRW met een handvol ritten Beieren in en back-haul werk richting Groot-Polen. Voor wij gebeld werden, deden veertien dispatchers het inkomende chauffeursverkeer over WhatsApp, telefoon en e-mail. Twee waren al opgestapt omdat hun avondshifts tot na middernacht doorliepen om de queue leeg te krijgen. De eigenaar noemde het "het WhatsApp-probleem". Terecht. Bellen is op zijn retour in het wegtransport. E-mail is nooit doorgedrongen. Chauffeurs, elke nationaliteit op de vloer, sturen alles via WhatsApp.
Dit is wat we bouwden, waarom die 45 seconden echt zijn, en waar het bijna stuk ging.
Het 04:53-probleem
Het werk van een transportmakelaar is ladingen aan trekkers koppelen en beide kanten tevreden houden. Een dispatcher neemt duizend micro-beslissingen per dag. Een chauffeur meldt 40 minuten vertraging, en dispatch moet weten of dat het tijdslot bij Aldi Geseke breekt of alleen opschuift. Een chauffeur haalt een load op en de klant wil binnen een uur een bevestiging in het portaal. Een chauffeur opent zijn trailer bij de consignee en het zegelnummer klopt niet met de CMR. Elk van die gevallen begint als een WhatsApp-bericht.
De inbox is bimodaal verdeeld. Ongeveer 80% van de berichten zijn routine status-updates die netjes op een handvol TMS-acties vallen. De andere 20% loopt uiteen van een verkeerde document-scan tot een chauffeur die meldt dat de load in zijn trailer niet de load op de papieren is. Bucket één hoeft nooit langs een mens. Bucket twee moet binnen seconden bij een mens liggen, niet pas na de 14 minuten die de makelaar gemiddeld haalde.
Dat is de vorm van de agent die wij hebben opgeleverd.
Vijf intents, gekoppeld aan vijf TMS-acties
De eerste week lazen we 2.300 gearchiveerde berichten van de makelaar (met toestemming, geanonimiseerd bij binnenkomst) en clusterden die op intent. De long tail is echt, maar de kop is kort:
- Load-acceptatie. "loaded 88421", "collected", "got it", met of zonder referentie.
- ETA-update. "file A2, +40", "vertraging brücke leverkusen", "stuck at consignee".
- Document-upload. Een foto van een CMR, POD of weegbon, soms met een onderschrift van één woord.
- Reactie op nieuwe load. De agent biedt een back-haul aan, de chauffeur zegt ja, nee, of stelt een vraag.
- Geschil of anomalie. Alles wat niet bij bovenstaande past, met de zwaarste categorie "deze load klopt niet".
De eerste vier zijn deterministische round-trips met het TMS van de makelaar, een custom Symfony-stack met een dunne REST-laag die we al in kaart hadden. De vijfde is degene met scherpe randjes.
Wat er echt onder de motorkap zit
De agent is bewust klein. We hebben geen general-purpose agent gebouwd met een sandbox en een planning-loop. Wegtransport is daarvoor te smal en te onverbiddelijk.
Inbound: Meta's WhatsApp Cloud API raakt een webhook op onze Hetzner-bak, die het bericht naar Postgres schrijft en een job op een Redis-queue zet. Een worker pakt de job, laadt de context van de chauffeur (laatste 20 berichten, huidige load, truck-toewijzing, taalvoorkeur, dialectnotities) en roept een classifier aan. De classifier is een LLM met een system prompt van zo'n 700 tokens en een JSON-schema response. Geen tools, geen recursie. Hij geeft één van de vijf intents terug, plus de uitgepakte slots.
Vanaf daar is het saaie code. Eén Symfony-controller per intent. De load-acceptatie-handler roept het TMS aan, markeert de consignment als geladen, vuurt de klant-webhook af, en zet de outbound WhatsApp-template in de queue. De document-handler stuurt het beeld naar een vision-model, draait OCR voor de load-referentie, en koppelt 'm aan de juiste consignment. Saai is precies het punt.
Het 24-uurs customer service window van WhatsApp breekt je antwoorden geruisloos als je de API behandelt als SMS. Outbound berichten naar een chauffeur die je in 24 uur niet heeft gemaild, moeten via een vooraf goedgekeurde template, niet via vrije tekst. Wij kwamen daar op een zondagavond om 23:00 achter, toen de queue stikte in 180 follow-ups voor chauffeurs.
Hoe een geschil binnen 45 seconden bij dispatch ligt
Dit is het deel waar de eigenaar wakker van lag. De andere intents zijn fijn om te automatiseren. Bij geschillen lekt het geld weg.
Zodra de classifier intent: dispute teruggeeft met een confidence boven 0,6, doet de agent vier dingen tegelijk:
- Antwoordt de chauffeur: "Begrepen. Eric van dispatch komt erbij, max 5 min." De chauffeur weet dat er een mens op zit. Hij stopt met dezelfde boodschap in drie varianten te sturen.
- Bouwt een gestructureerd handoff-pakket: naam chauffeur, truck, load-referentie, samenvatting van het geschil in één zin, de laatste zes berichten letterlijk, eventuele foto's, klantcontact, openingstijden van de consignee, en de marge van de makelaar op de load.
- Plaatst het pakket in de dispatch-room op Microsoft Teams, met een @-mention van degene die op dat moment rooster heeft voor die lane.
- Opent een TMS-case met hetzelfde pakket aan de consignment vastgeniet, zodat de volgende klik van de dispatcher op het juiste scherm uitkomt.
De dispatcher die op de oproep zit, leest één Teams-bericht en heeft het hele plaatje. Geen WhatsApp scrollen. Geen vragen aan de chauffeur of hij het wil herhalen. Hij opent het TMS, ziet het pakket, en beslist. De mediaan van het eerste geschil-bericht van een chauffeur tot het antwoord van dispatch op WhatsApp is 41 seconden. Die 45 is de 75e percentiel.
Het 45-seconden budget, regel voor regel
Tijd is wat de eigenaar in de gaten houdt. Zo verantwoorden we 'm:
webhook ingest 0.4 s
queue + hydrate context 1.2 s
classifier round-trip 2.8 s
TMS write or read 1.5 s
outbound WhatsApp 0.9 s
----------------------------
routine intent total 6.8 s p50
dispute branch adds:
structured packet build 1.1 s
Teams post + @-mention 0.7 s
TMS case open 1.4 s
dispatcher first reply ~30 s (human)
----------------------------
dispute total 41 s p50 / 45 s p75
De mens doet nog steeds het werk. De agent schrapt alleen die 13 minuten latency die voorheen kwamen van een dispatcher die het bericht ontdekte, drie dagen context las, tabs switchte en de juiste consignment opzocht.
Waar het bijna stuk ging
Niks hiervan was zo schoon als het architectuurplaatje. Drie dingen lieten ons struikelen in productie.
Poolse chauffeurs die mengtaal schrijven. De classifier was getuned op het archief van de makelaar, dat overhelt naar Nederlands en Duits. Een Poolse chauffeur schreef "loaded ale brak dokumentu", een mix van Pools met Nederlands logistiek-jargon, en de classifier dumpte het in dispute. False positives kosten de dispatcher net zoveel tijd als false negatives de chauffeur kosten. We voegden een few-shot blok met meertalige mixed-code voorbeelden toe, hertuneden de confidence-drempel per taal, en de false-dispute rate zakte van 7% naar onder de 1%.
Berichten met alleen een foto, zonder bijschrift. Chauffeurs sturen een foto van een CMR en verder niks. De agent moet de consignment afleiden uit het beeld, niet uit het bericht. Eerst lieten we de LLM dat in één keer doen. Die hallucineerde load-referenties die geloofwaardig leken maar niet bestonden. We switchten naar een pipeline in twee stappen: OCR op de zichtbare tekst, dan de load-referentie opzoeken tegen de op dat moment actieve consignments van die chauffeur. Geen match? Dan antwoordt de agent "welk loadnummer?" in plaats van te gokken.
Het 24-uurs service window. Zie de callout hierboven. Templates zijn de belasting die je betaalt voor het privilege om over de rails van WhatsApp te rijden. Plan je template-inventaris vóór je live gaat, niet erna. We houden nu elf goedgekeurde templates aan die de meeste outbound-patronen van de makelaar dekken, inclusief degene die Eric vanuit zijn Teams-sidebar verstuurt als hij een geschil privé wil voeren.
Waarom een kleine agent het hier wint van een grote
Er is op dit moment mode voor general-purpose agents die plannen, browsen en zichzelf corrigeren. Op sommige plekken werkt dat prima. In de WhatsApp-inbox van een transportmakelaar zou het een aansprakelijkheid zijn. De kosten van een verkeerde booking zijn echt geld. De kosten van een gehallucineerde load-referentie zijn een chauffeur die om 03:00 voor de verkeerde loods staat.
Het werk van het systeem rond het model is zorgen dat "soms fout" niet ongecontroleerd in productie komt. Voor ons betekent dat: korte prompts, JSON-schema's, deterministische handlers, en een mens in de loop zodra de confidence zakt. Het model is een onderdeel. Als het model het systeem is, is het systeem zo goed als de slechtste dag van het model.
Cijfers, vier weken later
De makelaar meet drie dingen, en niets anders.
De dispatcher-tijd per dag op WhatsApp, over het hele team, ging van 38 uur naar 9. Er staan twee nachtdiensten minder op het rooster. Het aantal klachten van chauffeurs over dispatch, gemeten via het kwartaaltevredenheidsonderzoek van de makelaar, daalde met 31% (steekproef van 47 chauffeurs, neem die omvang serieus). De gemiddelde tijd van het eerste geschil-bericht van een chauffeur tot het eerste antwoord van dispatch op WhatsApp ging van 14 minuten 22 seconden naar 41 seconden.
Eén getal dat niet bewoog: booking-nauwkeurigheid. We hielden elke drift in load-toewijzingen in de gaten. Er is geen drift. De agent boekt geen loads waar hij niet zeker van is. Hij vraagt door.
Wat dit betekent als jij een makelaar bent die dit om 23:00 leest
Je hebt waarschijnlijk geen general agent nodig. Je hebt een kleine, smalle agent nodig die de kop van je inbox netjes afhandelt en de staart aan een mens overdraagt met alle context al ingeladen. Dat is een build van vier weken, geen vier kwartalen, mits je een TMS met een API hebt en een eigenaar die een middag met ons wil zitten om berichten te labelen.
Toen we deze agent bouwden voor de Venlose makelaar, was het bijna niet het model dat het project nekte. Het was het TMS dat voor de helft van de foutmodi een 200 teruggaf met een Nederlandse foutstring in de body. We losten het op door een dunne adapter te schrijven die de body parseert en die responses behandelt als de fouten die ze zijn. Hoe we AI-agents zoals deze aanpakken, staat uitgebreider op de servicepagina.
Het kleinste wat je vandaag kunt doen: open de WhatsApp van dispatch, scroll 24 uur terug, en tel hoeveel verschillende intents je ziet. Als de top vijf meer dan 75% van het volume dekt, heb je een chat-agent-vormig probleem.
Kern
Kleine smalle agents winnen het in de logistiek van grote algemene. Geef de rommelige 20% aan een mens met alle context al ingeladen, en je schrapt minuten, geen seconden.
FAQ
Waarom WhatsApp en niet een eigen app voor chauffeurs?
Chauffeurs hebben WhatsApp al. Ze naar een nieuwe app dwingen betekent training, supporttickets en weerstand. Makelaars die dit proberen, draaien bijna altijd uiteindelijk beide kanalen en verdubbelen hun inbox.
Wat gebeurt er als de classifier een bericht verkeerd leest?
Classificaties met lage confidence vallen door naar een menselijke queue met de volledige context erbij, zodat een dispatcher het bericht ziet en de routing kan corrigeren. False positives worden gelogd en wekelijks teruggevoerd in de few-shot voorbeelden.
Hoe lang duurde de bouw van begin tot eind?
Vier weken van kickoff tot productie voor de routine intents, plus twee weken hardening voor de dispute-tak en de meertalige edge cases. De TMS-adapter was het langste losse stuk werk.
Boekt de agent ooit zelfstandig een load?
Ja, voor de 'reactie op nieuwe load'-intent tegen de al toegewezen shortlist van de chauffeur. Hij boekt niets dat de dispatcher niet vooraf heeft vrijgegeven voor die chauffeur en die dag. Die grens wordt afgedwongen in het TMS, niet in de prompt.