Naar de inhoud
NLEN
Illustratie: Function Calling Nauwkeurigheid Testen bij Schema's

Function Calling Nauwkeurigheid Testen bij Complexe Schema's

Door Ivo Donker — samengesteld met AI-ondersteuning (Claude & Gemini)

Het betrouwbaar aanroepen van externe functies vormt de ruggengraat van moderne AI-applicaties. Wanneer een taalmodel platte parameters zoals een zoekterm of een datum moet genereren, halen de meeste modellen hoge slagingspercentages. De situatie verandert drastisch zodra een JSON-schema diepe nestings, afhankelijke objecten, arrays met specifieke restricties of polymorfe structuren bevat. Met deze meting neem je een cruciale architectuurbeslissing: kan een lichter of goedkoper model dit specifieke functieschema foutloos vullen, of is een zwaarder redeneermodel noodzakelijk om runtime-fouten in backend-systemen te voorkomen?

Dit artikel onderscheidt zich nadrukkelijk van algemene API-handleidingen en algemene stresstests. Waar het artikel over JSON-validiteit bij belasting zich richt op netwerklatency en syntaxiscorruptie onder zware concurrency, analyseert deze evaluatiemethode de semantische en structurele parsingkracht van modellen bij complexe interfaces. We onderzoeken niet alleen of de JSON valideert, maar of de gegenereerde waarden logisch correct aansluiten op het schema en de gebruikersinstructie.

De anatomie van structureel functiefalen

Wanneer een model faalt bij function calling met complexe schema's, gebeurt dit zelden met een ongeldig leesteken. De introductie van constrained decoding en grammar-based sampling bij toonaangevende inference engines zorgt ervoor dat de ruwe uitvoer vrijwel altijd syntactisch correcte JSON is. De echte fouten treden op een abstracter niveau op:

Ten eerste zien we type-confusie binnen arrays. Wanneer een array polymorfe objecten toestaat (zoals een lijst van filters waarin elk filtertype een ander waardetype vereist), injecteert het model regelmatig eigenschappen van het ene objecttype in het andere. Ten tweede treden er hallucinaties van optionele velden op: het model vult niet-verplichte eigenschappen in met aannames in plaats van deze leeg te laten wanneer de brontekst geen informatie bevat. Ten derde ontstaat contextverlies in diepe nestings. Vanaf het derde niveau van objectstructuren neigen modellen ernaar om globale velden te herhalen binnen subobjecten of verplichte sleutels in diepere lagen simpelweg over te slaan.

Om te begrijpen hoe dit mechanisme onder de motorkap werkt, biedt de gids over hoe een model een functie aanroept diepgaande achtergrondinformatie over tokenbeperkingen en functiedefinities. De onderstaande evaluatiemethode bouwt hierop voort door systematische meetcriteria te definiëren.

De meetdimensies: van syntaxis tot semantische trouw

Een robuuste benchmark voor function calling hanteert vier afzonderlijke meetdimensies. Een samengestelde binaire score (slaagt/faalt) verbergt namelijk waar het integratierisico zit.

Dimensie Wat wordt gemeten Foutvoorbeeld Beoordelingsmethode
Schema-conformance Voldoet de payload aan alle types, enums en required-velden? Een stringwaarde teruggeven waar een integer met minimum 1 vereist is. JSON Schema Validator (geautomatiseerd).
Argument-extractie Zijn alle feiten uit de gebruikersprompt exact overgenomen? Een bedrag verkeerd overnemen of een foutieve valutacode kiezen. Exact-match en numerieke tolerantietests.
Negatieve selectie Blijven velden leeg waarvoor geen brondata bestaat in het bericht? Een optioneel bezorgadres invullen op basis van aannames. Null/Undefined constraint verificatie.
Functiekeuze Kiest het model de juiste functie uit een set van overlappende tools? update_user_address aanroepen in plaats van create_shipping_label. Functienaam-match op de ground truth.

Door deze vier dimensies strikt gescheiden te registreren, wordt direct inzichtelijk of een model structureel struikelt over JSON-definitielogica of simpelweg moeite heeft met begrijpend lezen van de inputtekst.

Een benchmarkschema definiëren

Om een model echt op de proef te stellen, is een triviale functiebeschrijving onvoldoende. We hanteren een samengesteld schema waarin geneste entiteiten, conditionele velden, enums en unieke array-items samenkomen. Denk aan een financieel transactieschema voor een hypotheekofferteaanvraag:

{
  "name": "verwerk_hypotheek_aanvraag",
  "description": "Verwerkt een samengestelde hypotheekaanvraag met meerdere onderpanden en leningdelen.",
  "parameters": {
    "type": "object",
    "properties": {
      "dossier_id": { "type": "string", "pattern": "^DOS-[0-9]{5}$" },
      "aanvragers": {
        "type": "array",
        "minItems": 1,
        "items": {
          "type": "object",
          "properties": {
            "rol": { "type": "string", "enum": ["hoofdaanvrager", "partner", "borgsteller"] },
            "persoonsgegevens": {
              "type": "object",
              "properties": {
                "achternaam": { "type": "string" },
                "geboortedatum": { "type": "string", "format": "date" },
                "bruto_jaarinkomen": { "type": "number", "minimum": 0 }
              },
              "required": ["achternaam", "geboortedatum", "bruto_jaarinkomen"],
              "additionalProperties": false
            }
          },
          "required": ["rol", "persoonsgegevens"],
          "additionalProperties": false
        }
      },
      "leningdelen": {
        "type": "array",
        "minItems": 1,
        "items": {
          "type": "object",
          "properties": {
            "aflosvorm": { "type": "string", "enum": ["annuitair", "lineair", "aflossingsvrij"] },
            "hoofdsom": { "type": "integer", "minimum": 1000 },
            "looptijd_maanden": { "type": "integer", "minimum": 12, "maximum": 360 }
          },
          "required": ["aflosvorm", "hoofdsom", "looptijd_maanden"],
          "additionalProperties": false
        }
      }
    },
    "required": ["dossier_id", "aanvragers", "leningdelen"],
    "additionalProperties": false
  }
}

Voor het interactief valideren en analyseren van soortgelijke definities gebruik je de JSON schema output validator tool, waarmee schema-eisen direct getoetst worden tegen voorbeeldoutputs.

De toetsverzameling opbouwen: synthese en valstrikken

Een betrouwbare testset voor complexe schema's telt bij voorkeur minimaal 100 tot 200 unieke testgevallen. Een effectieve richtlijn voor de opbouw van deze dataset is om de prompts te verdelen over drie specifieke categorieën. Deze percentages vormen een praktische vuistregel die je naar eigen inzicht kunt afstemmen op de risico's van jouw specifieke domein:

1. Expliciete, volledige opdrachten (richtwaarde circa 60%): Berichten waarin alle noodzakelijke velden ondubbelzinnig worden vermeld. Hier test je de basisvaardigheid om gestructureerde data op te vangen zonder permutaties in veldnamen.

2. Berichten met ruis en afleiding (richtwaarde circa 25%): Berichten waarin overbodige details staan die verleidelijk lijken voor optionele parameters, maar niet in het schema thuishoren. Denk aan een fictief verbouwingsscenario ter illustratie: "De aanvrager wil graag een dakkapel plaatsen van 12.000 euro, maar we registreren nu alleen de hoofdaanvraag van 350.000 euro." Een model dat ruis niet kan filteren, zal proberen het fictieve bedrag voor de dakkapel alsnog ergens in het schema te forceren.

3. Randgevallen en ontbrekende data (richtwaarde circa 15%): Berichten waarin een verplicht veld opzettelijk ontbreekt of waarin een typefout staat die botst met een regex-patroon. Hier meet je of het model de functie alsnog met ongeldige dummy-data aanroept, of weigert en terugvalt op een toelichtende tekstboodschap.

Let op: Gebruik in de testset nooit voorbeelden die rechtstreeks overeenkomen met demonstratiedata uit publieke API-documentatie. Dit voorkomt dat je traint of test op data die via pre-training in het geheugen van het model is opgeslagen.

Reproduceerbare meetopzet en testuitvoering

Om een vergelijking tussen modelversies wetenschappelijk zuiver te houden, moeten alle niet-functionele parameters strikt worden vastgezet. Pas de volgende randvoorwaarden toe:

Zet de samplingtemperatuur op temperature = 0.0 (of de laagst toegestane waarde van de provider) en fixeer de seed indien ondersteund. Schakel het model in de officiële function-calling modus van de API, en vermijd het handmatig instrueren van JSON in een standaard systeemprompt. Wie dieper wil duiken in de API-parameters en implementatiedetails vindt in het artikel over function calling via de API de exacte architectuurpatronen.

Voer elk testgeval minimaal 3 keer uit om stochastische variatie uit te sluiten. Zelfs bij temperatuur nul kan floating-point afronding op geclusterde GPU-infrastructuur minimale verschillen in token-keuzes veroorzaken.

import json
import jsonschema
from typing import Dict, Any, List

def evalueer_function_call(
    schema: Dict[str, Any],
    model_output_args: str,
    ground_truth: Dict[str, Any]
) -> Dict[str, Any]:
    resultaat = {
        "valid_json": False,
        "schema_conform": False,
        "exact_match": False,
        "fouten": []
    }
    
    # Stap 1: Valideer JSON syntaxis
    try:
        parsed_payload = json.loads(model_output_args)
        resultaat["valid_json"] = True
    except json.JSONDecodeError as e:
        resultaat["fouten"].append(f"JSONDecodeError: {str(e)}")
        return resultaat

    # Stap 2: Valideer tegen JSON Schema
    try:
        jsonschema.validate(instance=parsed_payload, schema=schema)
        resultaat["schema_conform"] = True
    except jsonschema.ValidationError as e:
        resultaat["fouten"].append(f"ValidationError: {e.message} op pad: {list(e.path)}")

    # Stap 3: Semantische vergelijking met Ground Truth
    if resultaat["schema_conform"]:
        if parsed_payload == ground_truth:
            resultaat["exact_match"] = True
        else:
            resultaat["fouten"].append("Payload wijkt af van verwachte waarden.")
            
    return resultaat

Typische Nederlandse taalvalkuilen bij functieroepen

Bij het evalueren van Nederlandstalige prompts tegen gestructureerde schema's treden specifieke taalkundige fricties op die in Engelstalige benchmarks onzichtbaar blijven:

Decimale notaties en getalscheiders: In Nederlandse teksten worden punten gebruikt als duizendtallen en komma's als decimalen. Neem een fictief bedrag als voorbeeld: bij de notatie 150.000,50 euro vertalen modellen die primair getraind zijn op Engelstalige data dit regelmatig naar het getal 150 in plaats van 150000.50. Dit leidt tot catastrofale fouten in administratieve payloads.

Samengestelde woorden en veldnamen: Wanneer een schema Engelstalige veldnamen gebruikt (zoals employment_type) en de prompt spreekt over "loondienstverband voor onbepaalde tijd", moet het model een vertaalslag én abstractieslag maken naar de juiste enum-waarde (bijv. PERMANENT_CONTRACT). Modellen met een zwakkere Nederlandse woordenschat laten dit veld vallen of vullen de letterlijke Nederlandse term in als ongeldige string.

Adres- en persoonsnaamconventies: Nederlandse tussenvoegsels (zoals "van der", "de", "ten") worden door modellen vaak onjuist gesplitst over velden als first_name en last_name, tenzij het schema hier expliciet rekening mee houdt of het model voldoende getoetst is op Nederlandse naamstructuren.

Meetresultaten aggregeren en analyseren

Na het doorlopen van de volledige testset aggregeren we de uitkomsten naar heldere KPI's. Kijk hierbij niet naar het gemiddelde succespercentage over de gehele dataset, maar segmenteer de resultaten op basis van schemadiepte en complexiteitsgraad.

Rekenvoorbeeld (illustratief): Onderstaande cijfers dienen uitsluitend als verzonnen voorbeeld om de berekeningswijze en presentatievorm te verduidelijken. Het betreft geen empirische ranglijst of productiescore.

Complexiteitsniveau Aantal tests Syntactisch valide Schema conform Exact match
Niveau 1: Vlak schema (1-5 velden) 50 100% 98% 96%
Niveau 2: Geneste objecten (2 lagen) 50 100% 92% 86%
Niveau 3: Arrays met objecten 50 98% 82% 74%
Niveau 4: Polymorfie + Regex validatie 50 94% 68% 58%

Een dergelijke uitsplitsing toont haarscherp waar de betrouwbaarheidsgrens van een specifiek model ligt. Wanneer een systeem in productie minimaal 99% schema-conformance vereist, toont de tabel direct aan dat dit model vanaf Niveau 3 niet autonoom kan functioneren zonder extra fallback-mechanismen of repair-prompts.

Kosten en doorlooptijd van de evaluatie

Het testen van function calling op grote schaal vraagt om een bewuste begroting. Doordat functiebeschrijvingen en JSON-schema's bij elke API-aanroep in de context moeten worden meegestuurd, ligt het tokenverbruik per testgeval aanzienlijk hoger dan bij standaard tekstprompts.

Een gemiddeld schema van 50 regels JSON verbruikt inclusief functiedefinities al snel 400 tot 800 systeemtokens per aanroep. Gecombineerd met een gebruikersprompt van 150 tokens en een gegenereerde payload van 250 tokens, kost één testgeval circa 1.000 tokens. Een testcyclus van 200 unieke prompts met 3 herhalingen (600 calls in totaal) verbruikt daarmee ongeveer 600.000 tokens per geteste modelversie. Bij commerciële API-endpoints blijft de financiële investering hiervoor doorgaans beperkt tot enkele euro's per run, terwijl het potentiële productierisico van brekende integraties hiermee volledig wordt afgedekt.

Om te waarborgen dat nieuwe modelversies of wijzigingen in functieschema's geen onverwachte regressie veroorzaken, integreer je deze testsuite direct in het continuous integration proces. Lees in de gids over evaluaties in de pijplijn automatiseren hoe je breekpunten en drempelwaarden instelt voor geautomatiseerde builds.

Structurele betrouwbaarheid borgen

Function calling bij complexe schema's mag nooit worden beoordeeld op basis van anekdotische tests of oppervlakkige demonstraties. Alleen door schema-conformance, argument-extractie en randgevallen systematisch te kwantificeren, ontstaat een helder beeld van de operationele robuustheid. Door continue evaluatie op te nemen in de ontwikkelcyclus, transformeer je kwetsbare modeluitvoer naar een voorspelbare en betrouwbare softwarecomponent.