AI Agents Claude

Claude Tool Use: Zo Geef Ik Mijn AI-Agents Echte Mogelijkheden

Alejandro Rioja
Alejandro Rioja
11 min lezen
TL;DR

Claude tool use laat uw agent acties ondernemen — niet alleen tekst genereren. U definieert tools als JSON-schema's, Claude beslist wanneer ze worden aangeroepen, en uw code voert de echte actie uit. De lus heeft drie stappen: stuur bericht → ontvang tool_use-blok → voer uit en stuur resultaat terug. Ik heb dit geïmplementeerd in 15+ productie-agents op Cloudflare Workers. Het faalpatroon ligt bijna nooit bij de AI — het zijn onduidelijke tool-resultaten die terugkomen.

Gratis nieuwsbrief

Elke woensdag. 28.400+ operators. Geen opvulling.

Inhoudsopgave

Bijgewerkt juli 2026.

TL;DR: Claude tool use laat uw agent acties ondernemen — niet alleen tekst genereren. U definieert tools als JSON-schema’s, Claude beslist wanneer ze worden aangeroepen, en uw code voert de echte actie uit. De lus heeft drie stappen: stuur bericht → ontvang tool_use-blok → voer uit en stuur resultaat terug. Ik heb dit geïmplementeerd in 15+ productie-agents op Cloudflare Workers. Het faalpatroon ligt bijna nooit bij de AI — het zijn onduidelijke tool-resultaten die terugkomen.

[Operatorperspectief] Ik beheer 30+ productie-AI-agents verspreid over een consultancymerk en Pickleland, een pickleballaccommodatie in Pflugerville, TX. Ongeveer de helft gebruikt tool use — de Claude API-functie die het model in staat stelt functies aan te roepen die uw code definieert. Dit is het patroon waar ik op ben uitgekomen na implementeren en itereren in productie.

Waarom tool use verandert wat een agent kan doen

Zonder tools kan een agent alleen tekst genereren. Dat is nuttig voor samenvattingen, opstellen en classificeren — maar dat is niet wat de meeste bedrijfsautomatiseringen echt nodig hebben. Bedrijfsautomatiseringen moeten informatie opzoeken, naar databases schrijven, API’s aanroepen, berichten sturen.

Tool use is hoe u Claude die toegang geeft. U definieert een set tools als JSON-schema’s. Claude leest de schema’s, beslist welke tool aan te roepen en met welke argumenten, en geeft een gestructureerd tool_use-inhoudsblok terug. Uw code voert de eigenlijke functie uit. Claude krijgt het resultaat en beslist wat er daarna moet gebeuren — inclusief het aanroepen van een andere tool of het produceren van een definitief tekstantwoord.

De sleutel: Claude beslist wanneer en of een tool wordt aangeroepen. U definieert de mogelijkheden. Het model redeneert over wanneer ze te gebruiken.

Hoe de API-stroom werkt

De tool use-lus heeft drie stappen. U doorloopt deze lus één of meerdere keren afhankelijk van hoeveel tool-aanroepen het model maakt.

Stap 1: Stuur uw bericht met gedefinieerde tools

typescript
const response = await anthropic.messages.create({
  model: "claude-haiku-4-5-20251001",
  max_tokens: 1024,
  tools: [
    {
      name: "check_court_availability",
      description:
        "Check if a court is available at a given date, time, and duration",
      input_schema: {
        type: "object",
        properties: {
          date: {
            type: "string",
            description: "Date in YYYY-MM-DD format",
          },
          time: {
            type: "string",
            description: "Start time in HH:MM format (24h)",
          },
          duration_minutes: {
            type: "number",
            description: "Duration of the booking in minutes",
          },
        },
        required: ["date", "time", "duration_minutes"],
      },
    },
  ],
  messages: [
    {
      role: "user",
      content: "Is a court available tomorrow at 2pm for 90 minutes?",
    },
  ],
});

Stap 2: Controleer of Claude een tool wil aanroepen

typescript
if (response.stop_reason === "tool_use") {
  const toolUseBlock = response.content.find(
    (block): block is Anthropic.ToolUseBlock => block.type === "tool_use"
  );

  if (!toolUseBlock) throw new Error("Expected tool_use block");

  // Run your actual function
  const toolResult = await checkCourtAvailability(
    toolUseBlock.input as CourtAvailabilityInput
  );

  // Step 3: Return the result to Claude
  const finalResponse = await anthropic.messages.create({
    model: "claude-haiku-4-5-20251001",
    max_tokens: 1024,
    tools: [
      /* same tools as before */
    ],
    messages: [
      {
        role: "user",
        content: "Is a court available tomorrow at 2pm for 90 minutes?",
      },
      { role: "assistant", content: response.content },
      {
        role: "user",
        content: [
          {
            type: "tool_result",
            tool_use_id: toolUseBlock.id,
            content: JSON.stringify(toolResult),
          },
        ],
      },
    ],
  });

  // finalResponse.content now has the text answer
}

Dat is het volledige patroon. Drie API-interacties per tool-aanroep: tools definiëren → tool_use-blok ontvangen → resultaat teruggeven.

Echt voorbeeld: de Pickleland beschikbaarheidscontrole

Pickleland is een pickleballaccommodatie. We ontvangen boekingsverzoeken op Facebook Messenger, in reacties en via een chatbot. De vraag is bijna altijd een variatie van “zijn jullie zaterdag om 15:00 open?” of “kan ik een baan boeken voor mijn groep van 8 personen?”

De beschikbaarheidscontroler-agent gebruikt tool use om het echte boekingssysteem in real-time te raadplegen in plaats van een standaardantwoord te geven.

Hier is de volledige agent — vereenvoudigd maar productiegetrouw:

typescript
// workers/availability-checker.ts
import Anthropic from "@anthropic-ai/sdk";

const anthropic = new Anthropic();

const AVAILABILITY_TOOLS: Anthropic.Tool[] = [
  {
    name: "check_availability",
    description:
      "Check court availability for a date, time, and group size. Returns available courts and their prices.",
    input_schema: {
      type: "object",
      properties: {
        date: { type: "string", description: "YYYY-MM-DD" },
        start_time: { type: "string", description: "HH:MM (24h)" },
        duration_minutes: { type: "number" },
        players: { type: "number", description: "Number of players" },
      },
      required: ["date", "start_time", "duration_minutes"],
    },
  },
  {
    name: "get_pricing",
    description:
      "Get current pricing for court rentals and open play sessions",
    input_schema: {
      type: "object",
      properties: {
        session_type: {
          type: "string",
          enum: ["court_rental", "open_play", "clinics"],
        },
      },
      required: ["session_type"],
    },
  },
];

export async function handleInquiry(
  userMessage: string,
  env: Env
): Promise<string> {
  const messages: Anthropic.MessageParam[] = [
    { role: "user", content: userMessage },
  ];

  // Agentic loop — keep going until stop_reason is "end_turn"
  while (true) {
    const response = await anthropic.messages.create({
      model: "claude-haiku-4-5-20251001",
      max_tokens: 512,
      system:
        "You are the booking assistant for Pickleland, a pickleball facility in Pflugerville, TX. " +
        "Use the tools to look up real availability and pricing. Never make up availability or prices. " +
        "If the customer wants to book, direct them to pickleland.com/book.",
      tools: AVAILABILITY_TOOLS,
      messages,
    });

    // Push the assistant's response into message history
    messages.push({ role: "assistant", content: response.content });

    if (response.stop_reason === "end_turn") {
      const textBlock = response.content.find(
        (b): b is Anthropic.TextBlock => b.type === "text"
      );
      return (
        textBlock?.text ??
        "I wasn't able to answer that — please call us directly."
      );
    }

    if (response.stop_reason === "tool_use") {
      // Process ALL tool calls in this response (Claude can request multiple at once)
      const toolResults: Anthropic.ToolResultBlockParam[] = [];

      for (const block of response.content) {
        if (block.type !== "tool_use") continue;

        let result: unknown;

        switch (block.name) {
          case "check_availability":
            result = await checkAvailability(
              block.input as AvailabilityInput,
              env
            );
            break;
          case "get_pricing":
            result = await getPricing(block.input as PricingInput, env);
            break;
          default:
            result = { error: `Unknown tool: ${block.name}` };
        }

        toolResults.push({
          type: "tool_result",
          tool_use_id: block.id,
          content: JSON.stringify(result),
        });
      }

      // Return all tool results in a single user message
      messages.push({ role: "user", content: toolResults });
    }
  }
}

Twee dingen om hier te noemen.

De agentische lus. Ik ga door tot stop_reason === "end_turn". Claude kan check_availability aanroepen, beslissen dat het ook de prijs nodig heeft, get_pricing aanroepen, en dan het definitieve antwoord produceren — dat zijn drie API-aanroepen voor één gebruikersbericht. De lus handelt dit af zonder speciale logica.

Meerdere tool-aanroepen per beurt. Claude kan meerdere tool_use-blokken in één enkel antwoord retourneren. Ik verwerk ze allemaal en geef alle resultaten terug in één enkel user-bericht. Als u ze één voor één verwerkt en individueel terugstuurt, onderbreekt u de gespreksstroom en verspilt u tokens.

Echt voorbeeld: de lead-onderzoeksagent

Mijn consultancymerk gebruikt een onderzoeksagent die inkomende leads verrijkt voordat ik met ze praat. Wanneer iemand het contactformulier invult, onderzoekt de agent het bedrijf en extraheert wat ik moet weten voor het gesprek.

De tooldefinities hiervoor bevatten een schrijftool — en hier wordt het patroon interessant:

typescript
const RESEARCH_TOOLS: Anthropic.Tool[] = [
  {
    name: "search_company",
    description: "Search for information about a company",
    input_schema: {
      type: "object",
      properties: {
        company_name: { type: "string" },
        website: { type: "string", description: "Company website if known" },
      },
      required: ["company_name"],
    },
  },
  {
    name: "save_research",
    description:
      "Save the completed research summary to Airtable. Call this when all research is complete.",
    input_schema: {
      type: "object",
      properties: {
        company_summary: { type: "string" },
        estimated_size: {
          type: "string",
          enum: ["1-10", "11-50", "51-200", "200+"],
        },
        likely_use_case: { type: "string" },
        priority: { type: "string", enum: ["high", "medium", "low"] },
        notes: { type: "string" },
      },
      required: [
        "company_summary",
        "estimated_size",
        "likely_use_case",
        "priority",
      ],
    },
  },
];

save_research is wat ik een schrijftool noem — het doel is niet informatie ophalen, maar de output van Claude in gestructureerde vorm in een database vastleggen. Ik gebruik dit patroon in plaats van te proberen JSON te parsen uit een tekstantwoord. Claude weet wanneer het onderzoek klaar is en roept save_research aan met correct getypeerde velden. Ik schrijf nooit een parser.

Dit is de schoonste toepassing van tool use: definieer een tool voor een “definitieve actie” met het exacte schema dat u wilt, en Claude levert gestructureerde output via de tool-aanroep. Geen tekstparsing, geen regex, geen JSONSchema-validatie van vrije tekstoutput.

Één tool vs. meerdere

Het instinct bij het starten met tool use is een grote tool te bouwen die alles doet. Weersta dit. Kleine, gefocuste tools zijn beter om drie redenen:

  1. Claude redeneert beter over kleine tools. Een tool genaamd get_court_status die beschikbaarheid retourneert is gemakkelijker voor het model te verwerken dan een tool genaamd manage_facility die een mode-parameter neemt en intern aftakt.

  2. Kleine tools zijn makkelijker te testen. Elke tool is een TypeScript-functie die u onafhankelijk van de LLM unit kunt testen. Dat moet u doen — tool-bugs zijn moeilijk te debuggen in een live gesprek.

  3. Claude kan kleine tools parallelliseren. Als twee tools niet van elkaar afhankelijk zijn, kan Claude ze in hetzelfde antwoord aanroepen en verwerkt u ze parallel. Dit werkt alleen als de tools echt onafhankelijk zijn.

De uitzondering: tools die toegang nodig hebben tot veel gedeelde interne state. Als de functie 10 variabelen van dezelfde gegevensbron nodig heeft, klopt één tool met een rijker schema beter dan 10 tools die elk afzonderlijk de database benaderen.

Mijn vuistregel: begin met één tool per afzonderlijke mogelijkheid. Voeg tools samen alleen wanneer u ziet dat Claude ze bij elk verzoek samen aanroept.

Kostenimplicaties

Tool use voegt tokens toe. Elke tooldefinitie gaat in de systeempromptcontext. Elk tool_use- en tool_result-blok verbruikt tokens in de gespreksgeschiedenis. Voor een agentische multi-beurt-lus stapelt dit snel op.

Voor de Pickleland beschikbaarheidscontrole voert een typisch gesprek in totaal 3–4 API-aanroepen uit (initieel bericht + 1–2 tool-aanroepen + eindantwoord), elk met 600–900 tokens. Tegen Haiku-prijzen kost dit minder dan $0,001 per verzoek. Zoals ik uitleg in de post over AI-agenten kostenberekening, verwerkt Haiku goed gedefinieerde tool-aanroepopdrachten betrouwbaar en is het 10× goedkoper dan Sonnet voor hetzelfde tokenvolume.

De lead-onderzoeksagent draait op Sonnet omdat de beoordelingsbeslissingen — een lead prioriteren, fit inschatten — meer redeneerkapaciteit vereisen dan Haiku betrouwbaar levert op open invoer. De berekening werkt nog steeds omdat het zelden wordt uitgevoerd (een paar keer per week, niet duizenden per dag). De modelkeuze volgt de taakcomplexiteit, niet persoonlijke voorkeur.

Het faalpatroon waar niemand over spreekt

Het meest voorkomende faalpatroon dat ik zie bij tool use in productie is niet Claude die de verkeerde tool aanroept. Het is de tool die iets teruggeeft waar Claude niet duidelijk over kan redeneren.

Als uw tool een ruwe database-object met 40 velden teruggeeft, raakt Claude in de war over welke velden belangrijk zijn. Als uw tool een uitzondering gooit (die verschijnt als een Worker-crash in plaats van een tool-resultaat), breekt de lus stil af. Als uw tool null retourneert wanneer het “geen resultaten” bedoelt, weet Claude niet of het opnieuw moet proberen of opgeven.

Drie regels voor tool-resultaten:

Geef compacte, expliciete resultaten terug. { available: true, courts: ["Court 3", "Court 5"], price_per_hour: 20 } — niet de volledige databaserij.

Vang fouten op binnen de tool-functie en geef ze terug als gestructureerde resultaten. { error: "booking system timeout", retry: true } — geen gegooide uitzondering die de Worker laat crashen.

Maak “geen resultaten” expliciet. { available: false, next_available: "2026-07-23T14:00:00Z" } — niet null of een lege array zonder context.

Claude redeneert veel beter over duidelijke signalen dan over onduidelijke retourwaarden. Elk uur dat ik heb besteed aan het debuggen van tool use in productie ging over onduidelijke resultaten, niet over het redeneren van het model.

De conclusie van de operator

Tool use is de functie die Claude transformeert van een tekstgenerator in een operator. Definieer gefocuste tools met duidelijke invoerschema’s. Verwerk alle tool_use-blokken in één enkel antwoord aan het model. Voer de agentische lus uit totdat stop_reason === "end_turn". Geef schone, compacte resultaten terug van uw tool-functies — geen ruwe data-objecten, geen gegooide uitzonderingen, geen onduidelijke nulls.

Het model handelt het redeneren af. Uw code handelt de echte acties af. Houd die twee taken duidelijk gescheiden en de architectuur blijft onderhoudbaar zelfs als u tools toevoegt.

Als u uw eerste tool use-agent bouwt, begin dan met het bovenstaande patroon van de beschikbaarheidscontrole — één tool, één doel, één agentische lus. Zet dat in productie. Voeg dan de tweede tool toe.


Gerelateerd: De agent-stack die ik gebruik om 30+ productie-agents te draaien · Haiku vs Sonnet: de kostenberekening voor agent-taken · Event-getriggerde vs geplande agents: welk patroon voor welke taak

Bouwt u een tool use-agent en loopt u vast? Neem contact op — ik ontwerp en bouw productie-agentarchitecturen voor operatorteams.

FAQ

Werkt Claude tool use met alle modellen?

Ja — tool use wordt ondersteund door alle huidige Claude-modellen. Claude Haiku verwerkt goed gedefinieerde tools met duidelijke schema’s betrouwbaar en is de goedkoopste optie voor taaktypen met hoog volume. Sonnet verwerkt meer ambigue of open tool-aanroepbeslissingen beter. Begin met Haiku; ga hoger als de outputkwaliteit onvoldoende is.

Wat is het verschil tussen Claude tool use en OpenAI function calling?

Mechanisch identiek. OpenAI bedacht “function calling”; Anthropic noemt het “tool use”. In beide gevallen: u definieert JSON-schema’s, het model geeft gestructureerde aanroepen terug, uw code voert de functie uit. De API-vorm verschilt maar het concept is hetzelfde.

Kan Claude meerdere tools aanroepen in één enkel antwoord?

Ja. Claude kan meerdere tool_use-blokken in één enkel assistant-antwoord retourneren. Verwerk ze allemaal en geef alle resultaten terug in één enkel user-bericht. Zie het agentische luspatroon in het Pickleland-voorbeeld hierboven — de for-lus over response.content handelt dit correct af.

Hoeveel tools moet ik definiëren per agent?

Ik blijf onder 8–10 tools per agent. Daarboven heb ik Claude soms de verkeerde tool zien kiezen bij de eerste poging, wat tokens verspilt in een correctielus. Als u meer dan 10 mogelijkheden nodig heeft, splits de agent in meerdere agents met gespecialiseerde toolsets in plaats van één agent te bouwen die alles weet.

Moet ik tool use gebruiken voor gestructureerde output?

Ja — het save_research-schrijftool-patroon is schoner dan Claude te vragen JSON in een tekstblok te retourneren en dat vervolgens te parsen. Definieer een tool voor een “definitieve actie” met het exacte schema dat u wilt. Claude roept het aan met correct getypeerde velden wanneer het klaar is. Geen parser nodig.

Lees verder

Gerelateerde berichten

Lees verder

Ontvang het AI-playbook in je inbox

Elke woensdag. 28.400+ operators. Geen opvulling.

↵ alle resultaten bekijken esc esc om te sluiten