Naar inhoud
lightbulb Welkom op de nieuwe kennisbank | We hebben de docs volledig vernieuwd met meer dan 160 features. Bekijk wat nieuw isarrow_forward

Tools definiëren voor AI-agents

Hoe je effectieve tools ontwerpt voor AI-agents met duidelijke schema's, foutafhandeling en terugkoppeling die het model helpt doorwerken.

Tool-ontwerp als UX voor het model

Tools voor AI-agents zijn anders dan gewone API's. Je ontwerpt niet voor mensen die documentatie lezen, maar voor een model dat op basis van de beschrijving beslist of en hoe het een tool aanroept. Slechte beschrijvingen leiden tot verkeerd gebruik. Goede beschrijvingen maken het model effectiever.

Een tool-definitie bestaat uit drie delen: een name (patroon ^[a-zA-Z0-9_-]{1,64}$), een uitgebreide description in platte tekst, en een input_schema volgens JSON Schema 2020-12. Het model leest die beschrijving als instructie, dus behandel hem als de belangrijkste regel code in je tool.

info

Kernprincipe

Een tool is pas goed als het model hem correct gebruikt zonder extra instructies. Test dit door het model een taak te geven en te kijken of het de juiste tools kiest.

Tool-definitie voor de Anthropic SDK

import Anthropic from "@anthropic-ai/sdk";

const tools: Anthropic.Tool[] = [
  {
    name: "search_products",
    description: `Zoek producten in de catalogus op naam, categorie of kenmerk.
Gebruik dit als de gebruiker vraagt naar beschikbare producten, prijzen of productkenmerken.
Geeft een lijst van maximaal 10 producten terug met ID, naam, prijs en voorraad.`,
    input_schema: {
      type: "object",
      properties: {
        query: {
          type: "string",
          description: "Zoekterm, bijv. 'laptop onder 1000 euro' of 'draadloze muis'",
        },
        category: {
          type: "string",
          enum: ["elektronica", "kantoor", "accessoires", "alle"],
          description: "Optionele categorie om de zoekopdracht te beperken",
          default: "alle",
        },
        in_stock_only: {
          type: "boolean",
          description: "True om alleen producten op voorraad te tonen",
          default: false,
        },
      },
      required: ["query"],
    },
  },
];

Een paar regels die het verschil maken tussen een tool die werkt en een tool die het model misbruikt:

  • Zeg in de beschrijving expliciet wanneer het model de tool moet gebruiken, niet alleen wat hij doet.
  • Beschrijf wat de tool teruggeeft, zodat het model weet of het daarna nog een stap nodig heeft.
  • Geef bij elke parameter een concreet voorbeeld in de description.
  • Gebruik enum waar de waarden vastliggen, zodat het model geen ongeldige opties verzint.

Zod-schema's naar JSON Schema converteren

Met zod houd je validatie en tool-schema op één plek. De gegenereerde JSON Schema gaat rechtstreeks naar input_schema.

import { z } from "zod";
import { zodToJsonSchema } from "zod-to-json-schema";

const CreateOrderSchema = z.object({
  product_id: z.string().describe("ID van het product"),
  quantity: z.number().int().min(1).max(100).describe("Aantal stuks (1-100)"),
  shipping_address: z.object({
    street: z.string(),
    city: z.string(),
    postal_code: z
      .string()
      .regex(/^\d{4}\s?[A-Z]{2}$/)
      .describe("Nederlands postcode-formaat: 1234 AB"),
  }).describe("Bezorgadres"),
});

const createOrderTool: Anthropic.Tool = {
  name: "create_order",
  description:
    "Maak een nieuwe bestelling aan voor een klant. Controleer eerst via check_inventory of het product beschikbaar is.",
  input_schema: zodToJsonSchema(CreateOrderSchema) as Anthropic.Tool["input_schema"],
};
warning

Let op de regex-escapes

Schrijf je het schema in JSON of in een template-string, controleer dan dat backslashes overleven. \d en \s worden zonder dubbele escape soms stilletjes d en s, waardoor de validatie elke string accepteert. In een Zod-regex() zoals hierboven blijven de escapes intact.

Tool-resultaten die het model helpen doorwerken

De kwaliteit van tool-resultaten is net zo belangrijk als de tool-definitie. Geef altijd een resultaat terug dat het model verder helpt, ook als er niets gevonden is of als er een fout optreedt.

async function executeTool(
  name: string,
  input: Record<string, unknown>
): Promise<string> {
  switch (name) {
    case "search_products": {
      try {
        const products = await productService.search(input);
        if (products.length === 0) {
          return JSON.stringify({
            success: true,
            results: [],
            message:
              "Geen producten gevonden voor deze zoekopdracht. Probeer bredere zoektermen.",
          });
        }
        return JSON.stringify({
          success: true,
          results: products,
          count: products.length,
        });
      } catch (err) {
        return JSON.stringify({
          success: false,
          error: "Zoekservice tijdelijk niet beschikbaar",
          suggestion: "Probeer het over enkele seconden opnieuw",
        });
      }
    }
    default:
      return JSON.stringify({ success: false, error: `Onbekende tool: ${name}` });
  }
}

Merk op dat zelfs een fout een success: false met een suggestion teruggeeft. Een lege string of een rauwe exception laat het model gokken; een beschrijvend resultaat vertelt het precies wat de volgende stap is.

Tool-resultaten als string versus structureel

Het model verwerkt tool-resultaten als tekst. JSON is leesbaar voor het model, maar niet altijd het beste formaat. De keuze hangt af van wat het model met het resultaat gaat doen.

JSON, voor data die het model verderop nog nodig heeft:

{
  "products": [
    {"id": "P001", "name": "Laptop Pro", "price": 999.00, "in_stock": true},
    {"id": "P002", "name": "Laptop Air", "price": 799.00, "in_stock": false}
  ]
}

Beschrijvende tekst, als het model direct een antwoord aan de gebruiker formuleert:

Gevonden: 2 laptops.
- Laptop Pro (P001): 999 euro, op voorraad
- Laptop Air (P002): 799 euro, uitverkocht

Aanbeveling: Laptop Pro is de enige beschikbare optie.

Kies JSON wanneer een vervolgtool de waarden hergebruikt, en kies tekst wanneer dit het laatste resultaat is voordat het model antwoordt.

Tool-gebruik beperken

Voorkom dat het model onnodig veel tools aanroept of in een lus blijft hangen. Een korte set regels in de system-prompt helpt:

const SYSTEM_PROMPT = `Je bent een klantenservice-assistent.
Gebruik tools alleen als je de informatie nodig hebt om de gebruiker te helpen.
Roep nooit twee keer dezelfde tool aan met dezelfde parameters.
Als een zoekopdracht geen resultaten geeft, vertel dit aan de gebruiker in plaats van opnieuw te zoeken.`;

Wil je harder afdwingen dat er hoogstens één tool per beurt draait, zet dan disable_parallel_tool_use: true in het tool_choice-veld van je messages.create-aanroep.

Gevaarlijke tools afschermen

Voor tools met side-effects (schrijven, versturen, verwijderen) vraag je expliciete bevestiging voordat je de actie uitvoert. Houd de set destructieve tools apart, zodat je nooit per ongeluk iets onomkeerbaars laat lopen.

const DESTRUCTIVE_TOOLS = new Set([
  "delete_order",
  "send_email",
  "update_inventory",
]);

async function executeToolWithConfirmation(
  name: string,
  input: Record<string, unknown>,
  getUserConfirmation: (description: string) => Promise<boolean>
): Promise<string> {
  if (DESTRUCTIVE_TOOLS.has(name)) {
    const description = describeAction(name, input);
    const confirmed = await getUserConfirmation(description);
    if (!confirmed) {
      return JSON.stringify({
        success: false,
        reason: "Actie geannuleerd door gebruiker",
      });
    }
  }
  return executeTool(name, input);
}
lightbulb

Bevestiging is ook beveiliging

Een agent met een send_email- of delete_order-tool is een prima doelwit voor prompt-injectie via de data die hij verwerkt. Zet menselijke bevestiging op elke onomkeerbare actie en log wie wat heeft goedgekeurd. Vertrouw nooit blind op de system-prompt als enige rem.

Hoeveel tools mag een agent hebben?

Een praktisch maximum ligt rond 20 tot 30 tools. Bij meer tools maken modellen minder consistente keuzes. Groepeer gerelateerde functionaliteit in minder tools met meer parameters, of laad tools alleen wanneer de context erom vraagt.

Moet ik tool-resultaten cachen?

Voor idempotente tools (lezen, zoeken) is caching nuttig. Sla resultaten op met de tool-naam en de parameters als sleutel. Cache nooit resultaten van tools die de wereld wijzigen, zoals bestellen of verwijderen.

Hoe test ik tool-definities?

Schrijf tests die het model aanroepen met verschillende gebruiksvragen en die verifiëren dat het de juiste tool kiest. Simuleer ook fout-scenario's, zodat je ziet of het model netjes omgaat met een mislukte aanroep in plaats van vast te lopen.

Kan het model meerdere tools tegelijk aanroepen?

Ja. Bij tool_choice van type auto kan het model meerdere tool_use-blokken in één response teruggeven. Voer die parallelle aanroepen uit met Promise.all. Wil je dit uitschakelen, zet dan disable_parallel_tool_use: true.

Wat zet ik precies in de beschrijving van een tool?

Beschrijf wanneer het model de tool moet gebruiken, wat de parameters betekenen met een voorbeeld, en wat de tool teruggeeft. Het model leest deze tekst als instructie; vaagheid hier kost je verkeerde aanroepen.

Hoe voorkom ik dat een tool als aanvalsroute werkt?

Beperk destructieve tools tot het strikt nodige, vraag menselijke bevestiging voor onomkeerbare acties en valideer alle parameters server-side. Behandel data die het model verwerkt als mogelijk vijandig, want prompt-injectie kan een tool-aanroep proberen te sturen.