Werkplaats / De werkplaats
In jouw eigen tempo
← Werken met LLM’s
MODULE 01 · LES 03

Laat het model een tool voorstellen en de applicatie beslissen

Valideer toolnamen en argumenten, voer twee begrensde functies uit en koppel resultaten aan hun call-ID.

75 min3 checkpoints
DIT GA JE ONTDEKKEN
  • Je maakt een expliciet onderscheid tussen een toolvoorstel en het uitvoeren van een functie.
  • Je koppelt teruggekomen resultaten op ID in plaats van op toevallige aankomstvolgorde.

Een model kan voorstellen om twee getallen te vermenigvuldigen. Bij function calling, ook tool use genoemd, beschrijft het de gewenste functie en argumenten; gewone applicatiecode controleert het voorstel en voert de functie uit. De functie draait doordat jouw programma haar aanroept.

We bouwen een eigen klein contract voor multiply en stock. Beide werken uitsluitend op getallen of een fictieve voorraaddictionary. Zo kun je de volledige route van voorstel naar resultaat onderzoeken met voorspelbare uitkomsten.

1. Controleer naam, velden en argumenten

Een call heeft exact id, name en arguments. Het ID bevat 1–32 ASCII-letters, cijfers, koppeltekens (-) of underscores (_). De naam moet bij de twee toegestane functies horen; argumenten moeten precies de afgesproken velden hebben.

call = {'id':'c1','name':'multiply',
        'arguments':{'x':6,'y':7}}
checked = validate_tool_call(call)
print(checked['name'])  # multiply
print(checked is call)  # False

Maak validate_tool_call met een nieuwe dictionary en gekopieerde argumenten. multiply vraagt twee echte gehele getallen x en y. In Python is isinstance(True,int) waar, maar ons contract weigert booleans; controleer daarvoor type(value) is int.

|x|,|y|≤106|xy|≤1012

Deze grenzen beperken zowel invoer als resultaat. stock vraagt exact sku: 1–30 kleine ASCII-letters, cijfers of koppeltekens. Gebruik volledige patroonmatching, zodat ook een verkeerde rest achter een geldig begin wordt geweigerd.

Het schema lijkt op properties, required en additionalProperties in JSON Schema. We implementeren hier alleen onze eigen afspraak in Python. Dit is geen volledige schemavalidator of berichtformaat van een specifieke modelaanbieder.

VOORSTEL → CONTROLE → HANDELINGDe applicatie bepaalt wat een toolcall mag doen

Kies een fictief voorstel. Alleen een geldige call bereikt de rekenfunctie of de eigen voorraadlijst.

{
  "id": "c1",
  "name": "multiply",
  "arguments": {
    "x": 6,
    "y": 7
  }
}
1 · VOORSTELGeldig JSON
2 · CONTRACTToegestaan
3 · UITVOERING{"value":42}

Naam en argumenten passen bij het vaste contract. Het resultaat komt uit echte lokale JavaScript-rekenlogica, zonder netwerk.

01
CHECKPOINTEen naam uit tekst is geen uitvoerrecht

2. Laat een dispatcher de bekende functie kiezen

Een dispatcher kiest op de gevalideerde naam welke functie draait. Valideer eerst; vermenigvuldig daarna x en y of lees de voorraad op. De vaste catalogus bevat notebook=12 en pen=30.

stock = {'id':'c2','name':'stock',
         'arguments':{'sku':'pen'}}
r1 = execute_tool(call)
r2 = execute_tool(stock)
print(r1['result'])  # {'value': 42}
print(r2['result'])
# {'found': True, 'quantity': 30}

Maak execute_tool met het oorspronkelijke ID als call_id. Een onbekende sku krijgt found=False en quantity=None. Nul betekent een bekend artikel zonder voorraad; None betekent dat dit artikel niet in onze catalogus is gevonden.

Deze functies hebben geen bijwerkingen. Het model-lab krijgt hiermee ook geen toolrechten: het oefent tekstgeneratie, terwijl deze les het applicatiemechanisme apart uitvoert. Een model kan later een correct toolresultaat alsnog verkeerd verwoorden, dus toon kritieke getallen zo nodig rechtstreeks uit het gecontroleerde resultaat.

02
CHECKPOINTEen kleine dispatcher voert alleen bekende functies uit

3. Herstel callvolgorde met een ID-map

Stel dat het voorraadantwoord eerder aankomt dan de vermenigvuldiging. De aankomstvolgorde zegt niets over welk resultaat bij welke vraag hoort. Een dictionary per call_id maakt de koppeling expliciet.

arrived = [r2,r1]
ordered = order_tool_results(
    [call,stock],arrived)
print([r['call_id'] for r in ordered])
# ['c1', 'c2']
print(ordered[0]['result']['value'])
# 42

Maak order_tool_results voor maximaal acht calls, ieder met precies één antwoord. Weiger dubbele, ontbrekende of onbekende IDs en extra velden. Bewaar de oorspronkelijke callvolgorde en geef nieuwe resultaatdictionaries terug.

Een multiply-resultaat heeft exact value met een echte integer binnen het productbereik. Voorraad heeft exact found en quantity: een echte bool plus bij gevonden voorraad een integer van nul tot een miljoen, anders None. De lege lijst calls met een lege lijst resultaten geeft een lege lijst terug.

Deze helper toetst vorm en koppeling, niet de waarheid van de berekening. Zelfs value=999 bij multiply(6,7) voldoet aan het resultaatschema. Betrouwbare herkomst, toegestane handelingen en controle van kritieke inhoud vragen extra applicatielogica; de agenttrack bouwt de volledige lus verder uit.

03
CHECKPOINTResultaten horen bij een ID, niet bij de snelste worker

Wat neem je mee?

  • Een toolvoorstel gaat eerst door een expliciet invoercontract.
  • De dispatcher voert alleen bekende functies uit.
  • IDs herstellen de koppeling; een geldig schema bewijst geen juiste inhoud.

Hierna reserveer je context en kosten voor gesprekken met zulke berichten.

Verder lezen & bronnen

Inhoud gecontroleerd op 2026-10-28.
DE VOLGENDE STAPReserveer ruimte voor antwoord en geschiedenis
JOUW EXPERIMENT

Probeer het zelf

Python
main.py

Python start zodra jij code uitvoert.

UITVOER

Je uitvoer verschijnt hier.
Alles rekent in je eigen browser.

Jouw checkpoints0 / 3
01
Een naam uit tekst is geen uitvoerrecht

Schrijf validate_tool_call(call), retourneer een kopie met gekopieerde arguments. Call-ID: 1–32 ASCII-letters/cijfers/_/-. Alleen multiply met exact x,y, of stock met exact sku (1–30 kleine ASCII-letters/cijfers/-). Andere input geeft ValueError.

Een hint

Gebruik re.fullmatch, type(v) is int en een vaste lijst toolnamen. Gebruik geen eval, globals() of bezoekers-URL.

02
Een kleine dispatcher voert alleen bekende functies uit

Schrijf execute_tool(call) met {call_id:ID,result:dict}. multiply geeft {value:x*y}; stock geeft {found:bool,quantity:int_of_None}, uitsluitend uit de vaste catalogus notebook=12, pen=30. Roep validate_tool_call eerst aan.

Een hint

Gebruik een if op de gevalideerde naam. Een voorgestelde toolnaam wordt nooit dynamisch als Python-code uitgevoerd.

03
Resultaten horen bij een ID, niet bij de snelste worker

Schrijf order_tool_results(calls,results), maximaal acht calls en exact evenveel resultaten. Valideer calls en unieke IDs; results heeft exact call_id/result, gekoppeld aan een bekende ID. Valideer result volgens de uitleg (voorraad 0–1.000.000) en retourneer nieuwe dictionaries in callvolgorde. Anders ValueError; lege lijsten geven [].

Een hint

Maak een ID→call-map en een ID→resultaat-map. Bewaar de gewenste volgorde apart in calls.

Vastgelopen? Bekijk de voorbeeldoplossing
import re

def validate_tool_call(call):
    if not isinstance(call, dict) or set(call) != {'id', 'name', 'arguments'}:
        raise ValueError('Toolcall heeft exact id, name en arguments')
    if not isinstance(call['id'], str) or re.fullmatch(r'[a-zA-Z0-9_-]{1,32}', call['id']) is None:
        raise ValueError('Ongeldig call-ID')
    name, args = call['name'], call['arguments']
    if not isinstance(name, str) or name not in {'multiply', 'stock'} or not isinstance(args, dict):
        raise ValueError('Onbekende tool of ongeldige argumenten')
    if name == 'multiply':
        if set(args) != {'x', 'y'} or any(type(v) is not int or abs(v) > 1_000_000 for v in args.values()):
            raise ValueError('multiply vraagt twee begrensde gehele getallen')
    else:
        if set(args) != {'sku'} or not isinstance(args['sku'], str) or re.fullmatch(r'[a-z0-9-]{1,30}', args['sku']) is None:
            raise ValueError('stock vraagt een eenvoudige sku')
    return {'id': call['id'], 'name': name, 'arguments': dict(args)}

def execute_tool(call):
    checked = validate_tool_call(call)
    if checked['name'] == 'multiply':
        args = checked['arguments']
        result = {'value': args['x'] * args['y']}
    else:
        catalog = {'notebook': 12, 'pen': 30}
        sku = checked['arguments']['sku']
        result = {'found': sku in catalog, 'quantity': catalog.get(sku)}
    return {'call_id': checked['id'], 'result': result}

def order_tool_results(calls, results):
    if not isinstance(calls, list) or not isinstance(results, list) or len(calls) > 8 or len(results) != len(calls):
        raise ValueError('Maximaal acht calls met elk precies een resultaat')
    checked = [validate_tool_call(call) for call in calls]
    by_id = {call['id']: call for call in checked}
    if len(by_id) != len(calls):
        raise ValueError('Call-IDs moeten uniek zijn')
    received = {}
    for response in results:
        if not isinstance(response, dict) or set(response) != {'call_id', 'result'}:
            raise ValueError('Ongeldig resultaatcontract')
        key, value = response['call_id'], response['result']
        if not isinstance(key, str) or key not in by_id or key in received or not isinstance(value, dict):
            raise ValueError('Onbekend/dubbel ID of ongeldig resultaat')
        if by_id[key]['name'] == 'multiply':
            if set(value) != {'value'} or type(value['value']) is not int or abs(value['value']) > 10**12:
                raise ValueError('Ongeldig multiply-resultaat')
        else:
            if set(value) != {'found', 'quantity'} or type(value['found']) is not bool:
                raise ValueError('Ongeldig stock-resultaat')
            if value['found']:
                if type(value['quantity']) is not int or not 0 <= value['quantity'] <= 10**6:
                    raise ValueError('Ongeldige voorraad')
            elif value['quantity'] is not None:
                raise ValueError('Onbekende sku heeft geen voorraadaantal')
        received[key] = {'call_id': key, 'result': dict(value)}
    return [received[call['id']] for call in checked]

Voer de code uit en verklaar voor jezelf waarom elke stap werkt.