← Blog

Chat agents

Triage-chatagent voor dierenartsen: 9 klinieken Nijmegen

Een paniekerige eigenaar appt op zaterdagochtend. De receptie zit vol. Een chatagent doet nu de triage, boekt een spoedplek en draagt over aan de assistent in 40 seconden.

Jacob Molkenboer· Oprichter · A Brand New Company· 8 jun 2026· 9 min
Koperen belletje, room indexkaart met groen lint, leren halsbandlabel en gevouwen linnen doek op ivoren bureau.

Het is 07:48 op een zaterdag. Een Border Collie in de Nieuwe Marktstraat heeft iets gegeten wat hij niet had moeten eten. De eigenaar typt met één hand in WhatsApp en houdt met de andere de hond vast. Aan de receptie van de centrale kliniek staan negen andere chats boven die van haar in de rij, de dienstdoende dierenarts is nog niet klaar met haar koffie, en één van de drie receptionisten die ingelogd zijn op WhatsApp Business hangt aan de telefoon met een andere eigenaar over een andere hond. Dit is het tafereel dat de wachtrij brak.

Achtentwintig mensen werken in de praktijk. Negen klinieken in en rond Nijmegen, van het centrum tot Wijchen en Beuningen. Eén gedeeld WhatsApp-nummer dat doorverdeeld werd over receptionisten die ook de telefoon opnemen, dieren inchecken en de kassa draaien. Tegen zaterdagochtend was de rijdiepte een getal waar niemand meer naar wilde kijken. De eigenaren waren nog niet boos. Dat zouden ze worden.

Dit is het playbook van wat we hebben gebouwd om dat te vervangen. Een chatagent die het binnenkomende bericht triëert, een spoedplek boekt over negen aparte Animana-agenda's, en binnen 40 seconden overdraagt aan een dierenartsassistent. De agent is geen dierenarts. Hij doet ook niet alsof. De hele clou is dat hij sneller uit de weg gaat dan een mens dat ooit kan.

Het triageprotocol dat geen dierenarts uithangt

Voordat er ook maar één regel code geschreven werd, hebben we twee dagen meegelopen met de hoofdassistent en gekeken hoe ze werkt. Het belangrijkste wat we leerden: ze vertelt een eigenaar nooit in de chat wat er met het dier aan de hand is. Ze classificeert hoe urgent het geval gezien moet worden, boekt de plek, en krijgt ze van het scherm en de praktijk in. De agent die we hebben gebouwd doet dat exact na.

Vier tiers, één-op-één gemapt op het protocol dat de praktijk al aan de telefoon gebruikte:

  • Rood: direct. Bloed dat niet stopt, een aanval, verdenking op een maagtorsie, aangereden door een auto, dystocie, verdenking op vergiftiging, ademnood. De assistent wordt opgepiept voordat de tweede vraag gesteld is.
  • Oranje: spoed dezelfde dag. Verdenking op een breuk, overgeven langer dan twaalf uur, lusteloos en al een dag geen eetlust, oogletsel zonder actieve bloeding.
  • Geel: binnen 48 uur. Verontrustend, maar niet acuut.
  • Groen: routine. Vaccinaties, herhaalrecepten, controles.

De output van de agent is een gestructureerd object dat gevalideerd wordt voordat er iets anders gebeurt. Aan de serverkant gebruiken we Pydantic, en elk modelantwoord dat niet parseert wordt één keer opnieuw geprobeerd en daarna geëscaleerd naar een mens:

class TriageDecision(BaseModel):
    tier: Literal["red", "orange", "yellow", "green"]
    confidence: float          # 0.0 to 1.0
    flags: list[str]           # e.g. ["bleeding", "ingested_foreign_object"]
    clinic_preference: str | None  # branch from owner profile if known
    needs_equipment: list[str] # ["xray", "surgery", "ultrasound"]
    rationale: str             # logged, never shown to the owner
    requires_human_within_s: int  # red = 0, orange = 60, yellow = 600

Het rationale-veld is de audit trail. Elke triagebeslissing wordt gelogd met de redenering van het model, zodat de praktijkmanager ze op maandagochtend kan reviewen. We laten de classifier overhellen naar over-triage. Twijfelt het model tussen oranje en rood, dan kiest het rood. Een vals-positief kost een assistent twee minuten. Een vals-negatief kost een hond.

De arbiter tussen negen agenda's

Negen klinieken, negen aparte agenda's in Animana, het praktijkmanagementsysteem dat IDEXX aan de meeste Nederlandse en Belgische klinieken verkoopt. Elke kliniek heeft andere openingstijden, andere apparatuur, andere specialismen. De vestiging in Wijchen heeft de operatiekamer. De binnenstadvestiging is op woensdag alleen voor katten. Beuningen heeft als enige na 18:00 een echo.

De slot finder draait nadat de triagebeslissing is gevallen. Hij onderhandelt niet met het model. Het model heeft al verteld welke apparatuur het geval nodig heeft en welke vestiging de voorkeur van de eigenaar heeft. De arbiter doet alleen een query op Animana en sorteert:

def find_emergency_slot(triage: TriageDecision, owner_postcode: str) -> Slot | None:
    horizon_min = {"red": 60, "orange": 240, "yellow": 2880}[triage.tier]
    candidates = animana.list_open_slots(
        from_ts=now_amsterdam(),
        within_minutes=horizon_min,
        clinics=eligible_clinics(triage.needs_equipment),
    )
    if not candidates:
        return None
    candidates.sort(key=lambda s: (
        drive_minutes(owner_postcode, s.clinic.postcode),
        s.start_ts,
    ))
    return candidates[0]

De plek wordt door de agent gereserveerd onder een serviceaccount, en daarna als platte tekst aan de eigenaar aangeboden. Altijd platte tekst. In het begin stuurden we slot-aanbiedingen als interactieve WhatsApp-knoppen. Eigenaren met oudere KaiOS-telefoons konden er niet op tikken. Nu zijn die knoppen een extraatje bovenop een kopie van dezelfde aanbieding in helder Nederlands. Renderen de knoppen, prima. Doen ze het niet, dan antwoordt de eigenaar 'ja' en parseren wij dat. De WhatsApp Cloud API-docs documenteren beide oppervlakken; we gebruiken ze allebei, maar de tekst is leidend.

Het overdrachtsbudget van 40 seconden

Het hele spel zit in dat budget. Veertig seconden, vanaf het eerste binnenkomende bericht in de rode tier tot een menselijke assistent die aan de andere kant zit te typen. In het ideale geval ziet de verdeling er zo uit:

  • 0s: bericht komt binnen. De agent bevestigt binnen één seconde in het Nederlands. In de rode tier is die bevestiging bewust niet warm. Geen 'wat vervelend!'. Gewoon 'Ik help je nu meteen. Eerste vraag:'.
  • 1 tot 12s: twee gestructureerde vragen, nooit meer dan twee voordat een tier-beslissing valt. Het model mag de tweede vraag overslaan als het eerste antwoord al een rode vlag is.
  • 12 tot 14s: gestructureerde output gevalideerd, slot finder doet zijn werk op Animana.
  • 14 tot 18s: plek aangeboden aan de eigenaar, met adres en tijd. Eigenaar bevestigt.
  • 18 tot 32s: assistent wordt opgepiept. We piepen parallel via twee kanalen: een Slack-melding met het chattranscript en een telefoonmelding via de bestaande alerting van Animana. Wie het eerst bevestigt, krijgt het gesprek.
  • 32 tot 40s: assistent opent WhatsApp Business, de agent zet een briefing van één regel in dezelfde thread voor haar, en stapt dan in observe-mode.
Onthoud

De succesmaatstaf van je triage-agent is geen conversie of sentiment. Het is de overdrachtslatency in de meest urgente tier. Kies dat getal eerst en ontwerp van daaruit terug.

Foutscenario's die we hebben afgevangen

Dit is het stuk dat in de meeste 'we hebben een AI-agent gebouwd'-stukken wordt weggesneden. In productie is het het enige stuk dat ertoe doet.

Eigenaren sturen foto's

Ongeveer één op de zeven berichten bevat een afbeelding. We draaien geen vision model op de afbeelding om de wond te classificeren. We draaien sowieso geen vision model op die afbeelding. De foto wordt doorgestuurd in dezelfde thread zodat de assistent hem ziet, en de agent bevestigt in platte tekst dat hij is ontvangen. De dag dat we een vision model medisch-aandoend commentaar laten geven op een foto van een hondenoog, is de dag dat we worden aangeklaagd. Die grens ligt hard en blijft liggen.

Eigenaren vragen om advies

Default weigeren, maar warm weigeren. De system prompt verbiedt elke zin die een diagnose stelt, een behandeling adviseert of de ernst inschat. Vraagt een eigenaar 'denk je dat het ernstig is?', dan antwoordt de agent: 'Dat kan onze assistent veel beter beoordelen. Ik haal haar er nu bij.' Koel, bruikbaar, eerlijk.

Jailbreaks

Eigenaren met stress proberen de agent niet te jailbreaken. Verveelde mensen wel. We behandelen elke chatthread met rollenspel-prompts, pogingen om de system prompt te extraheren, of instructies om 'het bovenstaande te negeren' als kwaadwillend en sturen die met een vlag naar een mens. Publieke incidenten waarbij chatbots in producten misbruikt werden om dingen te doen die niet de bedoeling waren, herinneren je eraan wat er gebeurt als een chatbot mag schrijven en niet wordt ingeperkt op wat hij mag. Onze agent heeft precies één schrijfmogelijkheid: een plek boeken waarvan het Animana-ID in dezelfde request cycle door de calendar service is teruggegeven. Dat is het volledige aanvalsoppervlak.

Model verzint een plek

De agent kan structureel geen tijd verzinnen. Hij kan alleen plekken aanbieden waarvan de calendar service in dezelfde beurt het Animana-ID heeft teruggegeven. Het slot-ID wordt round-trip gecheckt op het moment dat de eigenaar bevestigt. Stelt het model 'morgen om 10:00' voor zonder onderliggend ID, dan wordt het aanbod onderdrukt en vraagt de agent de eigenaar te wachten op een assistent.

Taal

De praktijk bedient een grotendeels Nederlandstalig verzorgingsgebied, maar Nijmegen heeft veel internationale studenten en expats. We ondersteunen Nederlands, Engels, Duits en Pools. Alles daarbuiten triggert directe overdracht aan een mens, waarbij het oorspronkelijke bericht woordelijk in de thread blijft staan.

Waarschuwing

Is het enige contact tussen je chatagent en het medische systeem een screenshot van het gesprek in een gedeeld Slack-kanaal, dan heb je geen agent. Dan heb je een aansprakelijkheidsprobleem. Koppel het audit log vanaf dag één aan het praktijkmanagementsysteem.

Wat we fout deden

Drie dingen, allemaal in de eerste maand.

De vriendelijke toon in de rode tier. Versie één van de agent opende elk gesprek met 'Wat vervelend om dit op een zaterdag mee te maken!'. Eigenaren met bloedende dieren haatten het. We hebben warmte volledig uit de opener van de rode tier gehaald. Geel en groen houden hem. Toon is geen globale instelling, hij is per tier.

Overdracht buiten kantooruren. We gingen ervan uit dat weekenddiensten betrouwbare bezetting hadden. Meestal klopte dat. Op zondagavond na 22:00 niet altijd. De fix was deterministisch: bevestigt geen assistent binnen 90 seconden in de rode tier, dan belt de agent via Twilio het persoonlijke mobiel van de dienstdoende dierenarts met een gesynthetiseerde samenvatting. We hebben dat in week drie zes keer getest. Vijf van de zes ging het goed. De zesde keer stond de telefoon van de dierenarts op Niet storen. We hebben een tweede nummer toegevoegd.

De reserveringstermijn. Animana laat een vastgehouden plek los als die niet binnen vier minuten wordt bevestigd. Onze agent kon een plek vasthouden, de assistent kon traag zijn met bevestigen, en de plek viel dan geluidloos terug in de publieke pool. We hebben een dunne reserveringslaag buiten Animana gebouwd die de plek acht minuten onafhankelijk vasthoudt en elke zestig seconden reconcilieert. Het is geen sexy code. Het heeft de launch gered.

Wat de praktijkmanager echt veranderde

De agent heeft het personeelsbestand niet verkleind. Dat wilde de praktijk ook niet. Wat hij wel deed: drie receptionisten uit de WhatsApp-triage halen en terugzetten aan de balie, waar mensen met manden stonden te wachten om in te checken. De agent verwerkte de nieuwe spoedaanmeldingen. De receptionisten hielpen de mensen die al in het pand stonden. Beide rijen krompen.

De cijfers die we meten leven op een dashboard dat de praktijkmanager opent bij haar ochtendkoffie. Mediane overdrachtstijd in de rode tier. Aantal oranje plekken dat halverwege het gesprek rood werd (gebeurt vaker dan je zou denken). Aantal weigeringen. Aantal menselijke escalaties op jailbreak-vlaggen. Het dashboard is saai. Saai is goed.

Wat je vandaag kunt doen

Toen we de triage-agent voor deze Nijmeegse groep bouwden, was het de slot-reservation race die ons telkens beet. We hebben die opgelost met de dunne reserveringslaag hierboven, plus een reconciliatie job die elke minuut draait. Kijk je naar AI-agents voor een proces waarin mensen onder stress contact zoeken, dan is de saaie infrastructuur (locks, reconciliatie, audit logs, deterministische escalatie) belangrijker dan de modelkeuze.

Open vandaag je bestaande wachtrij. Pak één gespreksthread, eender welke. Klok hem end to end met een stopwatch: van het eerste binnenkomende bericht tot het moment dat de klant heeft waarvoor hij kwam. Dat getal is je echte benchmark. De rest is mening.

Kern

De succesmaatstaf van je triage-agent is geen conversie of sentiment, maar de overdrachtslatency in de meest urgente tier.

FAQ

Geeft de chatagent medisch advies?

Nee. Hij classificeert de urgentie, boekt een plek en draagt over aan een assistent. Medische vragen weigert hij expliciet en in de rode tier worden ze binnen één seconde naar een mens gerouteerd.

Hoe kiest hij welke van de negen klinieken de boeking krijgt?

Op basis van de urgentie-tier, de benodigde apparatuur (röntgen voor breuken, echo voor buikgevallen), de postcode van de eigenaar voor de reistijd, en de vroegste passende plek. Animana is de bron van waarheid.

Wat voorkomt dat het model een tijd verzint?

De agent kan alleen plekken aanbieden waarvan de calendar service in dezelfde beurt het Animana-ID heeft teruggegeven. Slot-ID's worden round-trip gecheckt op de bevestiging van de eigenaar. Het model stelt nooit een vrij ingetypte tijd voor.

Hoe ga je om met jailbreak-pogingen?

Een aparte classifier vlagt rollenspel, prompt-extractie en 'negeer het bovenstaande'-patronen. Gevlagde threads gaan met het originele transcript naar een mens. De enige schrijfmogelijkheid van de agent is het boeken van een al bestaande plek.

chat agentsai agentsautomationintegrationsworkflowcase study

Iets bouwen?

Start een project