← Blog

Chat agents

Meertalige chat agent voor Mews: build voor Brugs hotel

Het is 23:00 bij een Brugs hotel. Drie gasten in de rij, één Duitse die vraagt over een no-show fee. De chat agent die we in Mews koppelden helpt er twee tegelijk.

Jacob Molkenboer· Oprichter · A Brand New Company· 10 jun 2026· 10 min
Koperen hotelbel op leren gastenboek, drie genummerde sleutelhangers, één met groen label, lakzegelkaart, ivoor linnen.

De avond die de build rechtvaardigde

Het is na 23:00 bij een hotelgroep met vier vestigingen in Brugge. Eén receptionist op de balie. Drie gasten in de rij: een Nederlands stel dat hun verblijf met één nacht wil verlengen, een Franse solo-reiziger die discussieert over een late-checkout-toeslag, en een Duitse gast die naar zijn telefoon staart en probeert te begrijpen waarom hij een no-show fee betaald heeft voor een boeking die hij naar eigen zeggen geannuleerd had.

De groep draait met negentien mensen verdeeld over vier kleine hotels. Receptionisten, housekeeping, een operationeel lead, de eigenaar. 's Nachts krimpt de balie naar één persoon per vestiging. Die persoon kan één gesprek tegelijk voeren. De andere twee gasten in de rij verliezen hun geduld en schrijven later in de week een review van één ster.

De briefing was kort en specifiek. Bouw een chat widget op de hotelwebsite die drie dingen kan, in drie talen, van begin tot eind, zonder dat een receptionist het toetsenbord aanraakt. Herboeken (datum wijzigen binnen een bestaande reservering). Late checkout aanvragen (vertrektijd verschuiven, met de juiste fee toegepast). Een no-show fee betalen voor een gemiste boeking. Alles buiten die drie flows wordt rustig doorgegeven aan een mens, met het volledige gesprek erbij.

Hieronder de build, stap voor stap.

Drie flows, gekoppeld aan Mews-calls

We zijn begonnen met het uitschrijven van de exacte Mews-endpoints die elke flow zou raken. Mews stelt de Connector API beschikbaar op vestigingsniveau. Elke vestiging geeft een access token uit aan de partner-integratie, met scope beperkt tot de data van die vestiging. De drie flows vielen als volgt uiteen.

Herboeken (datumwijziging). De agent leest de reservering, controleert de beschikbaarheid van het rate plan voor de nieuwe data, geeft het prijsverschil door, vraagt de gast om bevestiging, en past dan de reservering aan. De betrokken endpoints zijn reservations/getAll om de boeking te vinden, services/getPriceUpdates voor de quote, en reservations/update om door te voeren. De agent bedenkt nooit een prijs. Faalt de quote-call om welke reden ook, dan escaleert de agent.

Late checkout. De agent leest de reservering, controleert het late-checkout-beleid van de vestiging voor dat rate plan, past de fee toe als het beleid dat vereist, en boekt die op de rekening van de gast. Endpoints: reservations/getAll, accountingItems/addAdjustments voor de fee, en daarna reservations/update om EndUtc te verlengen.

No-show fee. De agent leest de no-show-clausule uit het annuleringsbeleid van de reservering, geeft het exacte bedrag door, vraagt om toestemming, verwerkt de kaartbetaling op de opgeslagen betaalmethode van de gast, en legt de betaling vast op de account. Endpoints: reservations/getAll, payments/charge voor de opgeslagen kaart, en payments/add om vast te leggen.

Voor elke flow schreven we een acceptatiescript van één pagina: wat de agent moet zeggen, wat hij moet doen, en wat de receptionist daarna in Mews moet zien. De acceptatie werd uitgevoerd door de operationeel lead, niet door ons. Dezelfde les als bij elke andere agent die we hebben opgeleverd: als je klant het acceptatietest niet zelfstandig kan draaien, is de agent niet af.

Het Mews tool-oppervlak

Mews authentiseert elke Connector API-call met twee tokens. De ClientToken identificeert de integratie. De AccessToken identificeert de vestiging die toegang verleent. Beide staan in de JSON body van elk request, niet in HTTP headers, wat mensen die gewend zijn aan OAuth bearer tokens vaak verrast.

curl -sS https://api.mews.com/api/connector/v1/reservations/getAll \
  -H 'Content-Type: application/json' \
  -d '{
    "ClientToken": "'"$MEWS_CLIENT_TOKEN"'",
    "AccessToken": "'"$MEWS_ACCESS_TOKEN"'",
    "Client": "ABN Chat Agent 1.0",
    "Extent": { "Reservations": true, "Customers": true },
    "Numbers": ["BR-260608-0042"]
  }'

Het patroon is hetzelfde voor elk endpoint: POST, JSON body, twee tokens, payload. Geen path parameters, geen GETs. Zodra je dat accepteert, lost de SDK-vraag zichzelf op: je hebt er geen nodig. Een dunne wrapper van drie functies (get, update, charge) is genoeg voor een agent van deze omvang. De volledige Mews Connector API reference documenteert de rest.

Twee gotchas waar we tegenaan liepen.

Ten eerste: tijd. Mews wil overal UTC-strings. Het hotel draait op Europe/Brussels. De agent toont gasten hun lokale tijd. Alle conversies deden we in de orchestrator, nooit in de prompt. Het model tijdzonerekenwerk laten doen is hoe je bugs verscheept die geen QA-ronde vangt.

Ten tweede: idempotentie. De Connector API dedupliceert requests niet voor je. Als de gast dubbeltapt op de bevestigknop en je orchestrator twee reservations/update-calls afvuurt, krijg je twee updates. We hebben per gesprek een idempotency key toegevoegd die elke dubbele tool call binnen een venster van dertig seconden blokkeert. Eén regel state, en het heeft elke double-write in productie tot nu toe voorkomen.

De taallaag

Drie talen, één widget. Detectie gebeurt twee keer.

De eerste ronde is de browser. We lezen navigator.language en de Accept-Language header voor een eerste gok. Die bepaalt of het eerste bericht van de widget in het Nederlands, Frans, Duits of Engels (fallback) uitgaat. De gok klopt ongeveer acht van de tien keer.

De tweede ronde is het eerste bericht van de gast. Een kleine classificatiecall vergrendelt de taal van het gesprek. Eenmaal vergrendeld, wisselt de taal niet meer tenzij de gast er expliciet om vraagt.

De reden voor de vergrendeling: gasten in Brugge schrijven vaak in hun eigen taal met een paar Engelse woorden ertussen ("hallo, I want to extend my stay"). Zonder vergrendeling krijg je een agent die midden in een zin omschakelt en leest als een taalgidsje. Met vergrendeling blijft hij in één stem.

Het derde stuk is een woordenlijst. Hotelspecifieke termen (late checkout, no-show fee, OTA, rate plan, aanbetaling, toeristenbelasting) hebben per taal afgesproken vertalingen, gepind in de system prompt. Dit weegt zwaarder mee dan mensen denken. "No-show fee" letterlijk in het Nederlands is onhandig. De eigenaar wilde vergoeding bij niet-verschijnen. We hebben de woordenlijst hardcoded en de agent stopte met varianten verzinnen.

De woordenlijst staat in een klein JSON-bestand. We hebben de editor in het ops-dashboard gezet zodat de operationeel lead zonder ons een term kan toevoegen of hernoemen. Toen een gast vroeg om een pax-restitutie (een per-persoon term die Booking.com gebruikt), escaleerde de agent, voegde de ops lead diezelfde middag de Nederlandse en Franse entries toe, en kreeg de volgende gast die het woord gebruikte een antwoord. Die feedbackloop is wat een meertalige agent eerlijk houdt. Als alleen engineers de woordenlijst kunnen bijwerken, raakt die binnen een maand verouderd.

Agent tools en de prompt

Zes tools, niet minder.

const tools = [
  { name: 'lookup_reservation',  /* by confirmation number or last name + arrival */ },
  { name: 'quote_date_change',   /* services/getPriceUpdates */ },
  { name: 'apply_date_change',   /* reservations/update */ },
  { name: 'apply_late_checkout', /* accountingItems/addAdjustments + reservations/update */ },
  { name: 'charge_no_show_fee',  /* payments/charge + payments/add */ },
  { name: 'escalate_to_human',   /* posts transcript to ops Slack */ },
];

De system prompt is twee schermen lang en het meeste daarvan is regels, geen persoonlijkheid. Persoonlijkheid is één alinea bovenaan. De regelsectie behandelt: wanneer welke tool aanroepen, wat nooit verzonnen mag worden (prijzen, beleid, beschikbaarheid), hoe te escaleren, wat te zeggen als een tool een fout teruggeeft, en hoe toestemming te bevestigen vóór elke betaling of reserverings-aanpassing.

Elke tooldefinitie is strikt over wat het model moet produceren. Hier is de late-checkout-tool, ingekort:

{
  "name": "apply_late_checkout",
  "description": "Extend the guest's departure time. Always quote the fee before calling. Never call without an explicit yes from the guest.",
  "input_schema": {
    "type": "object",
    "required": ["reservation_id", "new_departure_local", "fee_eur_cents", "guest_consent"],
    "properties": {
      "reservation_id":      { "type": "string" },
      "new_departure_local": { "type": "string", "description": "ISO local time, Europe/Brussels" },
      "fee_eur_cents":       { "type": "integer", "minimum": 0 },
      "guest_consent":       { "type": "boolean" }
    }
  }
}

We kozen voor tool-use boven een multi-step prompt chain omdat elke actie die het boekingssysteem raakt voor de receptionist in real time zichtbaar moet zijn. De orchestrator logt elke tool call in een kleine Postgres-tabel en stuurt een notificatie naar de Slack van de nachtbalie wanneer een flow eindigt of faalt. De receptionist kan door de audit log scrollen zonder de transcriptie van de agent te openen.

Overdracht aan mensen

Het escalatiepad is de belangrijkste non-feature in de build.

De agent escaleert wanneer een van deze waar is:

  • De gast vraagt om iets buiten de drie flows.
  • De gast gebruikt taal die een kleine classifier opmerkt (klacht, restitutie, brandalarm, ziekte, dispuut, advocaat).
  • De taaldetector spreekt zichzelf twee keer tegen in dezelfde sessie.
  • Een Mews-call faalt twee keer achter elkaar.
  • De gast vraagt expliciet om een mens.

De escalatie is een warme overdracht, geen dump. De agent zet het volledige transcript, het gematchte reservation ID, de vergrendelde taal van de gast en een samenvatting van één regel in een Slack-kanaal dat de nachtreceptionist in de gaten houdt. Daarna vertelt hij de gast, in hun taal, dat een collega zo aansluit in de chat. De receptionist antwoordt vanuit dezelfde Slack-thread en dat bericht komt terug in de widget. De gast ziet niets van de kanaalwissel.

Waarschuwing

Als escaleren ook maar een beetje onhandig is voor de receptionist, krijgt de agent de schuld van elk ongemakkelijk gesprek dat hij terecht heeft doorgegeven. Maak de overdracht de makkelijkste actie in de hele stack.

Live gaan zonder ongelukken

Eerst shadow mode. Twee weken. De agent draaide parallel met de receptionist, maar berichtte geen gasten. Elk gesprek genereerde een conceptantwoord dat werd gelogd. De operationeel lead bekeek de concepten elke ochtend. Op basis van die review hebben we de prompt en de tool-beschrijvingen twee keer bijgesteld.

Daarna een zachte launch op één vestiging, tussen 22:00 en 07:00. De receptionisten op de andere drie vestigingen waren het vangnet. De agent verwerkte in de eerste week drieëntwintig gesprekken. Achttien losten op zonder mens. Twee waren escalaties die de agent correct opving. Drie waren edge cases die de prompt nog niet had gezien, en die werden de maandag erna nieuwe test fixtures.

Uit week één kwamen twee patronen die we niet hadden voorspeld. Ten eerste: gasten die in het Engels begonnen, schakelden over naar Nederlands zodra de agent in het Nederlands antwoordde. De browser-taalgok zat er vaker naast dan we hadden verwacht, maar de classifier op het eerste bericht corrigeerde dat elke keer al binnen de openingswisseling. Ten tweede: late checkout liep ongeveer vier op één voor op herboeken. De eigenaar had ons verteld dat herboeken zou domineren. We sneden de herboek-voorbeelden uit de system prompt en voegden twee voorbeelden voor late-checkout edge cases toe, wat de prompt korter maakte en in dezelfde wijziging de tool-call-accuratesse verbeterde.

De volledige uitrol kwam in week vier. De maatstaf waar de eigenaar om gaf was niet de deflectie-ratio. Het waren minuten per incident aan de balie tussen 22:00 en 02:00. Dat getal zakte naar een niveau dat het project binnen de eerste maand terugverdiende. De eigenaar vroeg ons het getal niet te publiceren. De vorm ervan: één nachtreceptionist dekt nu vier vestigingen zonder backup op een dinsdag in maart, en de rij vormt zich niet.

Wat je ook vindt van het recente argument dat grote taalmodellen vertragen, het agentische werk dat bovenop stabiele APIs als Mews zit, is geen stukje langzamer gegaan. Het model is het makkelijke deel. Het moeilijke deel is de woordenlijst, de escalatieregels, en het acceptatiescript dat de operationeel lead op een zondagochtend zelf kan draaien.

De audit van vijf minuten

Run je een klein hotel en wil je weten of dit voor jou de moeite waard is, dan is dit de audit. Trek de mails, WhatsApp-berichten en chats van vorige maand buiten kantooruren. Tel de verzoeken per type. Als meer dan de helft een variant is van herboeken, late checkout, of betaling, dan heb je een agent-vormig probleem, en is een Mews API key genoeg om te beginnen.

Toen we de chat agent voor de Brugse groep bouwden, was het langste stuk werk niet de Mews-integratie. Het waren de woordenlijst in drie talen en de escalatieregels die de nachtreceptionist genoeg vertrouwde om de agent met rust te laten. Dit soort werk doen we als AI-agents voor operators die te klein zijn om een 24-uurs balie te bemensen, en te groot om vrijdagnacht zelf de rij weg te werken.

Kern

Een hotel chat agent verdient zijn plek door escalatieregels en een strakke woordenlijst, niet door het model. De Mews API is het makkelijke deel; de warme overdracht is het moeilijke.

FAQ

Waarom niet gewoon de chat widget gebruiken die met Mews meekomt?

Mews heeft een product voor gast-messaging, en dat is prima om mee te berichten. Het roept de API niet aan om reserveringen aan te passen of betalingen te innen, en dát is het hele punt van deze build.

Hoe blijf je bij de no-show betaling buiten de PCI-scope?

De agent ziet het kaartnummer nooit. Hij belast de opgeslagen betaalmethode op het klantprofiel via het Mews payments endpoint. De kaartdata blijft in Mews en de receptionist bevestigt de keten daarna in Mews.

Wat kost dit per maand om te draaien?

De token-uitgaven per opgelost gesprek liggen ruim onder een euro tegen de huidige API-tarieven. Hosting is een kleine Postgres database en een enkele worker. De dominante kost zijn de engineering-uren om de woordenlijst en de escalatieclassifier actueel te houden.

Welk model hebben jullie gebruikt?

We hebben twee algemene modellen vergeleken en gekozen voor het model dat tools betrouwbaarder aanriep op Nederlandse en Franse input. Modelkeuze verandert elke zes maanden. De tooldefinities en escalatieregels niet.

Kan dezelfde agent ook talen buiten Nederlands, Frans en Duits aan?

Ja, zodra de woordenlijst vertaald is en de escalatieclassifier seed-frases heeft in die taal. Een vierde taal toevoegen aan dezelfde agent kostte ons ongeveer een dag per vestiging.

ai agentschat agentsintegrationsworkflowautomationcase study

Iets bouwen?

Start een project