Typed tool-calls: waarom onze agents niet fantaseren
Onder de motorkap van onze actie-laag: input-schema's, output-validatie en een handvol harde regels.
Als je in 2024 of 2025 met een AI-agent hebt gebouwd, ken je het probleem: de bot besluit ineens een tool-aanroep te doen die niet bestaat, of pakt een veld dat hij verzint, of geeft als ID een random getal. De fix die iedereen toepast is reinforcement: 'gebruik alleen deze 5 tools', 'ID's zijn altijd integers', 'als je het niet zeker weet, vraag de gebruiker'. Werkt soms. Faalt soms. En je weet nooit van tevoren welke.
Bij Conveya hebben we ervoor gekozen om die ruimte voor fouten weg te ontwerpen in plaats van proberen te prompten. De agent ziet alleen tool-calls die syntactisch geldig zijn, en kan alleen output produceren die door een schema gevalideerd is. Hieronder hoe dat werkt.
Stap 1: typed tool definitions
Iedere tool heeft een JSON-schema dat de input definieert, geverifieerd op compile-time. We genereren uit TypeScript-types, geen handgeschreven schemas die uit sync raken met de implementatie.
defineTool({
name: "shopify_get_order",
description: "Look up a Shopify order by id, email, or phone.",
input: z.object({
orderId: z.string().regex(/^\d+$/).optional(),
email: z.string().email().optional(),
phone: z.string().regex(/^\+?[0-9 ]+$/).optional(),
}).refine((v) => v.orderId || v.email || v.phone, "one identifier required"),
output: ShopifyOrder, // exhaustive type from the Shopify SDK
exec: async (input, ctx) => { ... },
});Dit schema wordt naar de LLM gestuurd als JSON-schema in de native tool-use API (Anthropic's tool_use, OpenAI's function_call). De LLM krijgt dus niet 'er is een tool die orders ophaalt, bedenk maar wat je nodig hebt', maar 'er is een tool met deze exacte signature, je MOET één van deze drie velden invullen'. Zod's runtime parser vangt de rest.
Stap 2: validatie vóór executie
Tussen het moment dat de LLM zegt 'roep deze tool aan met deze args' en het moment dat onze code de tool draait, zit een validatie-laag. Drie checks:
- Schema-validatie: voldoet de input aan het Zod-schema? Zo niet → terugkoppeling naar de LLM met de validatie-fout, geen execution.
- Authorization-check: heeft deze agent permissie voor deze tool? Per agent kun je tools toestaan of blokkeren; ook write-tools krijgen alleen permissie als de merchant ze expliciet heeft geactiveerd.
- Rate-budget check: hoeveel tool-calls heeft deze sessie al gedaan? Voorkomt looping waarbij de agent eindeloos doorgaat met opzoeken zonder klantbenadering.
Als één van de drie faalt, krijgt de LLM een gestructureerde error terug. Bij schema-fouten met de exacte validatie-message ('expected integer, received string for field orderId'). De LLM corrigeert in 95% van de gevallen vanzelf bij de volgende turn. De resterende 5%, meestal door een hallucinated tool die niet bestaat, wordt geblokkeerd.
Stap 3: output validatie
Als de tool zelf data teruggeeft (een Shopify-order, een HubSpot-deal), valideren we óók dat output. Dit lijkt overkill, de Shopify SDK is toch typed, maar het vangt drie problemen:
- Externe API's schenden hun eigen schema. We hebben echt eens een 'price' veld als string i.p.v. number gehad van een 'stabiele' API.
- Nieuwe velden die de LLM in de war brengen. We strippen velden die niet in onze output-schema staan, zodat de agent een gefocuste payload krijgt.
- Encoding-issues (smart quotes, emoji-broken UTF-8). We normaliseren naar UTF-8 NFC voordat het in de context belandt.
Stap 4: tool-results als typed context
Hoe de LLM het result terugkrijgt is ook getyped. Niet als loose JSON-string die hij maar moet ontleden, maar als structured tool_result block waar de schema-keys gegarandeerd zijn. Dit voorkomt dat de LLM in latere turns velden hallucineert die niet in het result staan.
Wat dit oplevert
We hebben dit gemeten in onze interne benchmarks (3.000 echte support-conversaties van vier merchants):
- Hallucinated tool-calls: van 4.2% van turns naar 0.1%. De resterende 0.1% zijn edge cases (LLM bedenkt een tool naam die toevallig lijkt op een bestaande).
- Geblokkeerde calls: ~12% van alle tool-aanroepen wordt door validatie tegengehouden. Daarvan corrigeert de LLM 95% bij de volgende turn.
- Tijd tot eerste tool-call: 180ms gemiddeld (gemeten in apps/web/src/lib/integrations/integration-tools.ts). Schema-validatie zelf is sub-millisecond.
- Customer-impact incidenten door verkeerde tool-call: 0 in 6 maanden productie.
Wat dit NIET oplost
Typed tool-calls voorkomen dat de agent verzonnen acties uitvoert. Het voorkomt niet dat hij verzonnen ANTWOORDEN geeft op basis van correct opgehaalde data. Daar hebben we een aparte laag voor, output grounding, die ik in een volgende post behandel.
Meer lezen
Waarom 'automation rate' geen doel is, en wat dan wél?
Iedereen meet het. Weinigen begrijpen het. Een nuchtere kijk op de KPI die alles kan verbergen.
LezenPlaybookVan Intercom naar Conveya in 3 weken, zonder ticket-lekkage
Een stapsgewijs migratieplan met rollback-checkpoints. Voor iedereen die over wil zonder CSAT-verlies.
LezenResearchEU-hosting voor AI: wat niemand je vertelt
Verschil tussen 'EU-servers', 'EU-data residency' en 'EU-only processing'. En welke je écht nodig hebt voor GDPR-proof AI.
LezenBouw je eerste AI-agent.
Vandaag opzetten, vanmiddag live. Met je eigen content en je eigen tools, zonder dev.
- Gratis starten
- Geen creditcard nodig
- Live in een middag