SPB Git

Repository Public

build-a-tool-using-agent.md 159.5 KB 4,675 lines markdown

1---2title: "Tutorial: Build a tool-using agent"3url: https://platform.claude.com/docs/en/agents-and-tools/tool-use/build-a-tool-using-agent4description: A guided walkthrough from a single tool call to a production-ready agentic loop.5---67This tutorial builds a calendar-management agent in five concentric rings. Each ring is a complete, runnable program that adds exactly one concept to the ring before it. By the end you will have written the agentic loop by hand and then replaced it with the Tool Runner SDK abstraction.89The example tool is `create_calendar_event`. Its schema uses nested objects, arrays, and optional fields, so you will see how Claude handles realistic input shapes rather than a single flat string.1011<Note>12  Every ring runs standalone. Copy any ring into a fresh file and it will run without the code from earlier rings.13</Note>1415## Ring 1: Single tool, single turn1617The smallest possible tool-using program: one tool, one user message, one tool call, one result. The code is heavily commented so you can map each line to the [tool use lifecycle](https://platform.claude.com/docs/en/agents-and-tools/tool-use/how-tool-use-works).1819The request sends a `tools` array alongside the user message. When Claude determines that a tool call is needed, the response comes back with `stop_reason: "tool_use"` and a `tool_use` content block containing the tool name, a unique `id`, and the structured `input`. Your code runs the tool, then sends the result back in a `tool_result` block whose `tool_use_id` matches the `id` from the call.2021<CodeGroup>22  ```bash cURL23  #!/bin/bash24  # Ring 1: Single tool, single turn.2526  # Define one tool as a JSON fragment. The input_schema is a JSON Schema27  # object describing the arguments Claude should pass when it calls this28  # tool. This schema includes nested objects (recurrence), arrays29  # (attendees), and optional fields, which is closer to real-world tools30  # than a flat string argument.31  TOOLS='[32    {33      "name": "create_calendar_event",34      "description": "Create a calendar event with attendees and optional recurrence.",35      "input_schema": {36        "type": "object",37        "properties": {38          "title": {"type": "string"},39          "start": {"type": "string", "format": "date-time"},40          "end": {"type": "string", "format": "date-time"},41          "attendees": {42            "type": "array",43            "items": {"type": "string", "format": "email"}44          },45          "recurrence": {46            "type": "object",47            "properties": {48              "frequency": {"enum": ["daily", "weekly", "monthly"]},49              "count": {"type": "integer", "minimum": 1}50            }51          }52        },53        "required": ["title", "start", "end"]54      }55    }56  ]'5758  USER_MSG="Schedule a 30-minute sync with alice@example.com and bob@example.com on Monday, March 30, 2026 at 10am."5960  # Send the user's request along with the tool definition. Claude decides61  # whether to call the tool based on the request and the tool description.62  RESPONSE=$(curl -s https://api.anthropic.com/v1/messages \63    -H "x-api-key: $ANTHROPIC_API_KEY" \64    -H "anthropic-version: 2023-06-01" \65    -H "content-type: application/json" \66    -d "$(jq -n \67      --argjson tools "$TOOLS" \68      --arg msg "$USER_MSG" \69      '{70        model: "claude-opus-5",71        max_tokens: 1024,72        tools: $tools,73        tool_choice: {type: "auto", disable_parallel_tool_use: true},74        messages: [{role: "user", content: $msg}]75      }')")7677  # When Claude calls a tool, the response has stop_reason "tool_use"78  # and the content array contains a tool_use block alongside any text.79  echo "stop_reason: $(echo "$RESPONSE" | jq -r '.stop_reason')"8081  # Find the tool_use block. A response may contain text blocks before the82  # tool_use block, so filter by type rather than assuming position.83  TOOL_USE=$(echo "$RESPONSE" | jq '.content[] | select(.type == "tool_use")')84  TOOL_USE_ID=$(echo "$TOOL_USE" | jq -r '.id')85  echo "Tool: $(echo "$TOOL_USE" | jq -r '.name')"86  echo "Input: $(echo "$TOOL_USE" | jq -c '.input')"8788  # Execute the tool. In a real system this would call your calendar API.89  # Here the result is hardcoded to keep the example self-contained.90  RESULT='{"event_id": "evt_123", "status": "created"}'9192  # Send the result back. The tool_result block goes in a user message and93  # its tool_use_id must match the id from the tool_use block above. The94  # assistant's previous response is included so Claude has the full history.95  ASSISTANT_CONTENT=$(echo "$RESPONSE" | jq '.content')96  FOLLOWUP=$(curl -s https://api.anthropic.com/v1/messages \97    -H "x-api-key: $ANTHROPIC_API_KEY" \98    -H "anthropic-version: 2023-06-01" \99    -H "content-type: application/json" \100    -d "$(jq -n \101      --argjson tools "$TOOLS" \102      --arg msg "$USER_MSG" \103      --argjson assistant "$ASSISTANT_CONTENT" \104      --arg tool_use_id "$TOOL_USE_ID" \105      --arg result "$RESULT" \106      '{107        model: "claude-opus-5",108        max_tokens: 1024,109        tools: $tools,110        tool_choice: {type: "auto", disable_parallel_tool_use: true},111        messages: [112          {role: "user", content: $msg},113          {role: "assistant", content: $assistant},114          {role: "user", content: [115            {type: "tool_result", tool_use_id: $tool_use_id, content: $result}116          ]}117        ]118      }')")119120  # With the tool result in hand, Claude produces a final natural-language121  # answer and stop_reason becomes "end_turn".122  echo "stop_reason: $(echo "$FOLLOWUP" | jq -r '.stop_reason')"123  echo "$FOLLOWUP" | jq -r '.content[] | select(.type == "text") | .text'124  ```125126  ```bash CLI127  #!/usr/bin/env bash128  # Ring 1: Single tool, single turn.129  # Uses jq for cross-turn message-array state — building an agentic loop in shell130  # requires JSON manipulation beyond ant's single-call --transform scope.131  set -euo pipefail132133  USER_MSG="Schedule a 30-minute sync with alice@example.com and bob@example.com on Monday, March 30, 2026 at 10am."134  MESSAGES=$(jq -n --arg msg "$USER_MSG" '[{role: "user", content: $msg}]')135136  # Define one tool. The input_schema is a JSON Schema object describing137  # the arguments Claude should pass when it calls this tool. This schema138  # includes nested objects (recurrence), arrays (attendees), and optional139  # fields, which is closer to real-world tools than a flat string argument.140  call_api() {141    # ant reads the request body as YAML on stdin: no auth headers, no142    # hand-built JSON envelope. The static keys (model, tools, tool_choice)143    # live in a quoted heredoc; the growing messages array is appended as144    # JSON, which YAML accepts as flow syntax.145    {146      cat <<'YAML'147  model: claude-opus-5148  max_tokens: 1024149  tool_choice: {type: auto, disable_parallel_tool_use: true}150  tools:151    - name: create_calendar_event152      description: Create a calendar event with attendees and optional recurrence.153      input_schema:154        type: object155        properties:156          title: {type: string}157          start: {type: string, format: date-time}158          end: {type: string, format: date-time}159          attendees:160            type: array161            items: {type: string, format: email}162          recurrence:163            type: object164            properties:165              frequency: {enum: [daily, weekly, monthly]}166              count: {type: integer, minimum: 1}167        required: [title, start, end]168  YAML169      printf 'messages: %s\n' "$MESSAGES"170    } | ant messages create --format json171  }172173  # Send the user's request along with the tool definition. Claude decides174  # whether to call the tool based on the request and the tool description.175  RESPONSE=$(call_api)176177  # When Claude calls a tool, the response has stop_reason "tool_use"178  # and the content array contains a tool_use block alongside any text.179  echo "stop_reason: $(jq -r '.stop_reason' <<<"$RESPONSE")"180181  # Find the tool_use block. A response may contain text blocks before the182  # tool_use block, so filter by type rather than assuming position.183  TOOL_USE=$(jq '.content[] | select(.type == "tool_use")' <<<"$RESPONSE")184  TOOL_USE_ID=$(jq -r '.id' <<<"$TOOL_USE")185  echo "Tool: $(jq -r '.name' <<<"$TOOL_USE")"186  echo "Input: $(jq -c '.input' <<<"$TOOL_USE")"187188  # Execute the tool. In a real system this would call your calendar API.189  # Here the result is hardcoded to keep the example self-contained.190  RESULT='{"event_id": "evt_123", "status": "created"}'191192  # Send the result back. The tool_result block goes in a user message and193  # its tool_use_id must match the id from the tool_use block above. The194  # assistant's previous response is included so Claude has the full history.195  MESSAGES=$(jq \196    --argjson assistant "$(jq '.content' <<<"$RESPONSE")" \197    --arg tool_use_id "$TOOL_USE_ID" \198    --arg result "$RESULT" \199    '. + [200      {role: "assistant", content: $assistant},201      {role: "user", content: [202        {type: "tool_result", tool_use_id: $tool_use_id, content: $result}203      ]}204    ]' <<<"$MESSAGES")205206  FOLLOWUP=$(call_api)207208  # With the tool result in hand, Claude produces a final natural-language209  # answer and stop_reason becomes "end_turn".210  echo "stop_reason: $(jq -r '.stop_reason' <<<"$FOLLOWUP")"211  jq -r '.content[] | select(.type == "text") | .text' <<<"$FOLLOWUP"212  ```213214  ```python Python215  # Ring 1: Single tool, single turn.216217  import json218219  import anthropic220221  # Create a client. It reads ANTHROPIC_API_KEY from the environment.222  client = anthropic.Anthropic()223224  # Define one tool. The input_schema is a JSON Schema object describing225  # the arguments Claude should pass when it calls this tool. This schema226  # includes nested objects (recurrence), arrays (attendees), and optional227  # fields, which is closer to real-world tools than a flat string argument.228  tools = [229      {230          "name": "create_calendar_event",231          "description": "Create a calendar event with attendees and optional recurrence.",232          "input_schema": {233              "type": "object",234              "properties": {235                  "title": {"type": "string"},236                  "start": {"type": "string", "format": "date-time"},237                  "end": {"type": "string", "format": "date-time"},238                  "attendees": {239                      "type": "array",240                      "items": {"type": "string", "format": "email"},241                  },242                  "recurrence": {243                      "type": "object",244                      "properties": {245                          "frequency": {"enum": ["daily", "weekly", "monthly"]},246                          "count": {"type": "integer", "minimum": 1},247                      },248                  },249              },250              "required": ["title", "start", "end"],251          },252      }253  ]254255  # Send the user's request along with the tool definition. Claude decides256  # whether to call the tool based on the request and the tool description.257  response = client.messages.create(258      model="claude-opus-5",259      max_tokens=1024,260      tools=tools,261      tool_choice={"type": "auto", "disable_parallel_tool_use": True},262      messages=[263          {264              "role": "user",265              "content": "Schedule a 30-minute sync with alice@example.com and bob@example.com on Monday, March 30, 2026 at 10am.",266          }267      ],268  )269270  # When Claude calls a tool, the response has stop_reason "tool_use"271  # and the content array contains a tool_use block alongside any text.272  print(f"stop_reason: {response.stop_reason}")273274  # Find the tool_use block. A response may contain text blocks before the275  # tool_use block, so scan the content array rather than assuming position.276  tool_use = next(block for block in response.content if block.type == "tool_use")277  print(f"Tool: {tool_use.name}")278  print(f"Input: {tool_use.input}")279280  # Execute the tool. In a real system this would call your calendar API.281  # Here the result is hardcoded to keep the example self-contained.282  result = {"event_id": "evt_123", "status": "created"}283284  # Send the result back. The tool_result block goes in a user message and285  # its tool_use_id must match the id from the tool_use block above. The286  # assistant's previous response is included so Claude has the full history.287  followup = client.messages.create(288      model="claude-opus-5",289      max_tokens=1024,290      tools=tools,291      tool_choice={"type": "auto", "disable_parallel_tool_use": True},292      messages=[293          {294              "role": "user",295              "content": "Schedule a 30-minute sync with alice@example.com and bob@example.com on Monday, March 30, 2026 at 10am.",296          },297          {"role": "assistant", "content": response.content},298          {299              "role": "user",300              "content": [301                  {302                      "type": "tool_result",303                      "tool_use_id": tool_use.id,304                      "content": json.dumps(result),305                  }306              ],307          },308      ],309  )310311  # With the tool result in hand, Claude produces a final natural-language312  # answer and stop_reason becomes "end_turn".313  print(f"stop_reason: {followup.stop_reason}")314  final_text = next(block for block in followup.content if block.type == "text")315  print(final_text.text)316  ```317318  ```typescript TypeScript319  // Ring 1: Single tool, single turn.320321  import Anthropic from "@anthropic-ai/sdk";322323  // Create a client. It reads ANTHROPIC_API_KEY from the environment.324  const client = new Anthropic();325326  // Define one tool. The input_schema is a JSON Schema object describing327  // the arguments Claude should pass when it calls this tool. This schema328  // includes nested objects (recurrence), arrays (attendees), and optional329  // fields, which is closer to real-world tools than a flat string argument.330  const tools: Anthropic.Tool[] = [331    {332      name: "create_calendar_event",333      description:334        "Create a calendar event with attendees and optional recurrence.",335      input_schema: {336        type: "object",337        properties: {338          title: { type: "string" },339          start: { type: "string", format: "date-time" },340          end: { type: "string", format: "date-time" },341          attendees: {342            type: "array",343            items: { type: "string", format: "email" },344          },345          recurrence: {346            type: "object",347            properties: {348              frequency: { enum: ["daily", "weekly", "monthly"] },349              count: { type: "integer", minimum: 1 },350            },351          },352        },353        required: ["title", "start", "end"],354      },355    },356  ];357358  // Send the user's request along with the tool definition. Claude decides359  // whether to call the tool based on the request and the tool description.360  const response = await client.messages.create({361    model: "claude-opus-5",362    max_tokens: 1024,363    tools,364    tool_choice: { type: "auto", disable_parallel_tool_use: true },365    messages: [366      {367        role: "user",368        content:369          "Schedule a 30-minute sync with alice@example.com and bob@example.com on Monday, March 30, 2026 at 10am.",370      },371    ],372  });373374  // When Claude calls a tool, the response has stop_reason "tool_use"375  // and the content array contains a tool_use block alongside any text.376  console.log(`stop_reason: ${response.stop_reason}`);377378  // Find the tool_use block. A response may contain text blocks before the379  // tool_use block, so scan the content array rather than assuming position.380  const toolUse = response.content.find(381    (block): block is Anthropic.ToolUseBlock => block.type === "tool_use",382  )!;383  console.log(`Tool: ${toolUse.name}`);384  console.log(`Input: ${JSON.stringify(toolUse.input)}`);385386  // Execute the tool. In a real system this would call your calendar API.387  // Here the result is hardcoded to keep the example self-contained.388  const result = { event_id: "evt_123", status: "created" };389390  // Send the result back. The tool_result block goes in a user message and391  // its tool_use_id must match the id from the tool_use block above. The392  // assistant's previous response is included so Claude has the full history.393  const followup = await client.messages.create({394    model: "claude-opus-5",395    max_tokens: 1024,396    tools,397    tool_choice: { type: "auto", disable_parallel_tool_use: true },398    messages: [399      {400        role: "user",401        content:402          "Schedule a 30-minute sync with alice@example.com and bob@example.com on Monday, March 30, 2026 at 10am.",403      },404      { role: "assistant", content: response.content },405      {406        role: "user",407        content: [408          {409            type: "tool_result",410            tool_use_id: toolUse.id,411            content: JSON.stringify(result),412          },413        ],414      },415    ],416  });417418  // With the tool result in hand, Claude produces a final natural-language419  // answer and stop_reason becomes "end_turn".420  console.log(`stop_reason: ${followup.stop_reason}`);421  for (const block of followup.content) {422    if (block.type === "text") {423      console.log(block.text);424    }425  }426  ```427428  ```csharp C#429  // Ring 1: Single tool, single turn.430431  using System;432  using System.Collections.Generic;433  using System.Linq;434  using System.Text.Json;435  using System.Threading.Tasks;436  using Anthropic;437  using Anthropic.Models.Messages;438439  // Create a client. It reads ANTHROPIC_API_KEY from the environment.440  AnthropicClient client = new();441442  // Define one tool. The input schema is a JSON Schema object describing443  // the arguments Claude should pass when it calls this tool. This schema444  // includes nested objects (recurrence), arrays (attendees), and optional445  // fields, which is closer to real-world tools than a flat string argument.446  List<ToolUnion> tools =447  [448      new ToolUnion(new Tool()449      {450          Name = "create_calendar_event",451          Description = "Create a calendar event with attendees and optional recurrence.",452          InputSchema = new InputSchema()453          {454              Properties = new Dictionary<string, JsonElement>455              {456                  ["title"] = JsonSerializer.SerializeToElement(new { type = "string" }),457                  ["start"] = JsonSerializer.SerializeToElement(new { type = "string", format = "date-time" }),458                  ["end"] = JsonSerializer.SerializeToElement(new { type = "string", format = "date-time" }),459                  ["attendees"] = JsonSerializer.SerializeToElement(new460                  {461                      type = "array",462                      items = new { type = "string", format = "email" },463                  }),464                  ["recurrence"] = JsonSerializer.SerializeToElement(new465                  {466                      type = "object",467                      properties = new468                      {469                          frequency = new { @enum = new[] { "daily", "weekly", "monthly" } },470                          count = new { type = "integer", minimum = 1 },471                      },472                  }),473              },474              Required = ["title", "start", "end"],475          },476      }),477  ];478479  // Ask for at most one tool call per turn so the single-turn flow below480  // stays predictable.481  var toolChoice = new ToolChoice(new ToolChoiceAuto { DisableParallelToolUse = true });482483  const string userPrompt =484      "Schedule a 30-minute sync with alice@example.com and bob@example.com on Monday, March 30, 2026 at 10am.";485486  // Send the user's request along with the tool definition. Claude decides487  // whether to call the tool based on the request and the tool description.488  var response = await client.Messages.Create(new MessageCreateParams489  {490      Model = Model.ClaudeOpus5,491      MaxTokens = 1024,492      Tools = tools,493      ToolChoice = toolChoice,494      Messages = [new() { Role = Role.User, Content = userPrompt }],495  });496497  // When Claude calls a tool, the response has stop_reason "tool_use"498  // and the content array contains a tool_use block alongside any text.499  Console.WriteLine($"stop_reason: {response.StopReason?.Raw()}");500501  // Find the tool_use block. A response may contain text blocks before the502  // tool_use block, so scan the content array rather than assuming position.503  ToolUseBlock? toolUse = null;504  foreach (var block in response.Content)505  {506      if (block.TryPickToolUse(out var picked))507      {508          toolUse = picked;509          break;510      }511  }512  Console.WriteLine($"Tool: {toolUse!.Name}");513  Console.WriteLine($"Input: {JsonSerializer.Serialize(toolUse.Input)}");514515  // Execute the tool. In a real system this would call your calendar API.516  // Here the result is hardcoded to keep the example self-contained.517  var result = """{"event_id": "evt_123", "status": "created"}""";518519  // Send the result back. The tool_result block goes in a user message and520  // its tool_use_id must match the id from the tool_use block above. The521  // assistant's previous response is included so Claude has the full history.522  List<ContentBlockParam> toolResults =523  [524      new ContentBlockParam(new ToolResultBlockParam()525      {526          ToolUseID = toolUse.ID,527          Content = result,528      }),529  ];530531  var followup = await client.Messages.Create(new MessageCreateParams532  {533      Model = Model.ClaudeOpus5,534      MaxTokens = 1024,535      Tools = tools,536      ToolChoice = toolChoice,537      Messages =538      [539          new() { Role = Role.User, Content = userPrompt },540          new() { Role = Role.Assistant, Content = response.Content.Select(block => new ContentBlockParam(block.Json)).ToList() },541          new() { Role = Role.User, Content = new MessageParamContent(toolResults) },542      ],543  });544545  // With the tool result in hand, Claude produces a final natural-language546  // answer and stop_reason becomes "end_turn".547  Console.WriteLine($"stop_reason: {followup.StopReason?.Raw()}");548  foreach (var block in followup.Content)549  {550      if (block.TryPickText(out var text))551      {552          Console.WriteLine(text.Text);553      }554  }555  ```556557  ```go Go558  // Ring 1: Single tool, single turn.559560  package main561562  import (563  	"context"564  	"fmt"565  	"log"566567  	"github.com/anthropics/anthropic-sdk-go"568  )569570  func main() {571  	// Create a client. It reads ANTHROPIC_API_KEY from the environment.572  	client := anthropic.NewClient()573  	ctx := context.Background()574575  	// Define one tool. The input schema is a JSON Schema object describing576  	// the arguments Claude should pass when it calls this tool. This schema577  	// includes nested objects (recurrence), arrays (attendees), and optional578  	// fields, which is closer to real-world tools than a flat string argument.579  	tools := []anthropic.ToolUnionParam{580  		{OfTool: &anthropic.ToolParam{581  			Name:        "create_calendar_event",582  			Description: anthropic.String("Create a calendar event with attendees and optional recurrence."),583  			InputSchema: anthropic.ToolInputSchemaParam{584  				Properties: map[string]any{585  					"title": map[string]any{"type": "string"},586  					"start": map[string]any{"type": "string", "format": "date-time"},587  					"end":   map[string]any{"type": "string", "format": "date-time"},588  					"attendees": map[string]any{589  						"type":  "array",590  						"items": map[string]any{"type": "string", "format": "email"},591  					},592  					"recurrence": map[string]any{593  						"type": "object",594  						"properties": map[string]any{595  							"frequency": map[string]any{"enum": []string{"daily", "weekly", "monthly"}},596  							"count":     map[string]any{"type": "integer", "minimum": 1},597  						},598  					},599  				},600  				Required: []string{"title", "start", "end"},601  			},602  		}},603  	}604605  	// Ask for at most one tool call per turn so the single-turn flow below606  	// stays predictable.607  	toolChoice := anthropic.ToolChoiceUnionParam{608  		OfAuto: &anthropic.ToolChoiceAutoParam{DisableParallelToolUse: anthropic.Bool(true)},609  	}610611  	userMessage := anthropic.NewUserMessage(anthropic.NewTextBlock(612  		"Schedule a 30-minute sync with alice@example.com and bob@example.com on Monday, March 30, 2026 at 10am.",613  	))614615  	// Send the user's request along with the tool definition. Claude decides616  	// whether to call the tool based on the request and the tool description.617  	response, err := client.Messages.New(ctx, anthropic.MessageNewParams{618  		Model:      anthropic.ModelClaudeOpus5,619  		MaxTokens:  1024,620  		Tools:      tools,621  		ToolChoice: toolChoice,622  		Messages:   []anthropic.MessageParam{userMessage},623  	})624  	if err != nil {625  		log.Fatal(err)626  	}627628  	// When Claude calls a tool, the response has stop_reason "tool_use"629  	// and the content array contains a tool_use block alongside any text.630  	fmt.Printf("stop_reason: %s\n", response.StopReason)631632  	// Find the tool_use block. A response may contain text blocks before the633  	// tool_use block, so scan the content array rather than assuming position.634  	var toolUse anthropic.ContentBlockUnion635  	for _, block := range response.Content {636  		if block.Type == "tool_use" {637  			toolUse = block638  			break639  		}640  	}641  	fmt.Printf("Tool: %s\n", toolUse.Name)642  	fmt.Printf("Input: %s\n", string(toolUse.Input))643644  	// Execute the tool. In a real system this would call your calendar API.645  	// Here the result is hardcoded to keep the example self-contained.646  	result := `{"event_id": "evt_123", "status": "created"}`647648  	// Send the result back. The tool_result block goes in a user message and649  	// its tool_use_id must match the id from the tool_use block above. The650  	// assistant's previous response is included so Claude has the full history.651  	var assistantContent []anthropic.ContentBlockParamUnion652  	for _, block := range response.Content {653  		assistantContent = append(assistantContent, block.ToParam())654  	}655656  	followup, err := client.Messages.New(ctx, anthropic.MessageNewParams{657  		Model:      anthropic.ModelClaudeOpus5,658  		MaxTokens:  1024,659  		Tools:      tools,660  		ToolChoice: toolChoice,661  		Messages: []anthropic.MessageParam{662  			userMessage,663  			anthropic.NewAssistantMessage(assistantContent...),664  			anthropic.NewUserMessage(anthropic.NewToolResultBlock(toolUse.ID, result, false)),665  		},666  	})667  	if err != nil {668  		log.Fatal(err)669  	}670671  	// With the tool result in hand, Claude produces a final natural-language672  	// answer and stop_reason becomes "end_turn".673  	fmt.Printf("stop_reason: %s\n", followup.StopReason)674  	for _, block := range followup.Content {675  		if block.Type == "text" {676  			fmt.Println(block.Text)677  		}678  	}679  }680  ```681682  ```java Java683  // Ring 1: Single tool, single turn.684685  import com.anthropic.client.AnthropicClient;686  import com.anthropic.client.okhttp.AnthropicOkHttpClient;687  import com.anthropic.core.JsonValue;688  import com.anthropic.models.messages.ContentBlockParam;689  import com.anthropic.models.messages.Message;690  import com.anthropic.models.messages.MessageCreateParams;691  import com.anthropic.models.messages.Model;692  import com.anthropic.models.messages.Tool;693  import com.anthropic.models.messages.Tool.InputSchema;694  import com.anthropic.models.messages.ToolChoiceAuto;695  import com.anthropic.models.messages.ToolResultBlockParam;696  import com.anthropic.models.messages.ToolUseBlock;697  import java.util.List;698  import java.util.Map;699700  void main() {701      // Create a client. It reads ANTHROPIC_API_KEY from the environment.702      AnthropicClient client = AnthropicOkHttpClient.fromEnv();703704      // Define one tool. The input schema is a JSON Schema object describing705      // the arguments Claude should pass when it calls this tool. This schema706      // includes nested objects (recurrence), arrays (attendees), and optional707      // fields, which is closer to real-world tools than a flat string argument.708      Tool calendarTool = Tool.builder()709          .name("create_calendar_event")710          .description("Create a calendar event with attendees and optional recurrence.")711          .inputSchema(InputSchema.builder()712              .properties(JsonValue.from(Map.of(713                  "title", Map.of("type", "string"),714                  "start", Map.of("type", "string", "format", "date-time"),715                  "end", Map.of("type", "string", "format", "date-time"),716                  "attendees", Map.of(717                      "type", "array",718                      "items", Map.of("type", "string", "format", "email")719                  ),720                  "recurrence", Map.of(721                      "type", "object",722                      "properties", Map.of(723                          "frequency", Map.of("enum", List.of("daily", "weekly", "monthly")),724                          "count", Map.of("type", "integer", "minimum", 1)725                      )726                  )727              )))728              .required(List.of("title", "start", "end"))729              .build())730          .build();731732      // Ask for at most one tool call per turn so the single-turn flow below733      // stays predictable.734      ToolChoiceAuto toolChoice = ToolChoiceAuto.builder()735          .disableParallelToolUse(true)736          .build();737738      String userPrompt =739          "Schedule a 30-minute sync with alice@example.com and bob@example.com on Monday, March 30, 2026 at 10am.";740741      // Send the user's request along with the tool definition. Claude decides742      // whether to call the tool based on the request and the tool description.743      Message response = client.messages().create(MessageCreateParams.builder()744          .model(Model.CLAUDE_OPUS_5)745          .maxTokens(1024L)746          .addTool(calendarTool)747          .toolChoice(toolChoice)748          .addUserMessage(userPrompt)749          .build());750751      // When Claude calls a tool, the response has stop_reason "tool_use"752      // and the content array contains a tool_use block alongside any text.753      IO.println("stop_reason: " + response.stopReason().orElse(null));754755      // Find the tool_use block. A response may contain text blocks before the756      // tool_use block, so scan the content array rather than assuming position.757      ToolUseBlock toolUse = response.content().stream()758          .flatMap(block -> block.toolUse().stream())759          .findFirst()760          .orElseThrow();761      IO.println("Tool: " + toolUse.name());762      IO.println("Input: " + toolUse._input());763764      // Execute the tool. In a real system this would call your calendar API.765      // Here the result is hardcoded to keep the example self-contained.766      String result = "{\"event_id\": \"evt_123\", \"status\": \"created\"}";767768      // Send the result back. The tool_result block goes in a user message and769      // its tool_use_id must match the id from the tool_use block above. The770      // assistant's previous response is included so Claude has the full history.771      Message followup = client.messages().create(MessageCreateParams.builder()772          .model(Model.CLAUDE_OPUS_5)773          .maxTokens(1024L)774          .addTool(calendarTool)775          .toolChoice(toolChoice)776          .addUserMessage(userPrompt)777          .addMessage(response)778          .addUserMessageOfBlockParams(List.of(ContentBlockParam.ofToolResult(779              ToolResultBlockParam.builder()780                  .toolUseId(toolUse.id())781                  .content(result)782                  .build())))783          .build());784785      // With the tool result in hand, Claude produces a final natural-language786      // answer and stop_reason becomes "end_turn".787      IO.println("stop_reason: " + followup.stopReason().orElse(null));788      followup.content().stream()789          .flatMap(block -> block.text().stream())790          .forEach(textBlock -> IO.println(textBlock.text()));791  }792  ```793794  ```php PHP795  <?php796797  // Ring 1: Single tool, single turn.798799  use Anthropic\Client;800  use Anthropic\Messages\ToolChoiceAuto;801802  // Create a client. It reads ANTHROPIC_API_KEY from the environment.803  $client = new Client();804805  // Define one tool. The input_schema is a JSON Schema object describing806  // the arguments Claude should pass when it calls this tool. This schema807  // includes nested objects (recurrence), arrays (attendees), and optional808  // fields, which is closer to real-world tools than a flat string argument.809  $tools = [810      [811          'name' => 'create_calendar_event',812          'description' => 'Create a calendar event with attendees and optional recurrence.',813          'input_schema' => [814              'type' => 'object',815              'properties' => [816                  'title' => ['type' => 'string'],817                  'start' => ['type' => 'string', 'format' => 'date-time'],818                  'end' => ['type' => 'string', 'format' => 'date-time'],819                  'attendees' => [820                      'type' => 'array',821                      'items' => ['type' => 'string', 'format' => 'email'],822                  ],823                  'recurrence' => [824                      'type' => 'object',825                      'properties' => [826                          'frequency' => ['enum' => ['daily', 'weekly', 'monthly']],827                          'count' => ['type' => 'integer', 'minimum' => 1],828                      ],829                  ],830              ],831              'required' => ['title', 'start', 'end'],832          ],833      ],834  ];835836  $userMessage = [837      'role' => 'user',838      'content' => 'Schedule a 30-minute sync with alice@example.com and bob@example.com on Monday, March 30, 2026 at 10am.',839  ];840841  // Ask for at most one tool call per turn so the single-turn flow below842  // stays predictable.843  $toolChoice = ToolChoiceAuto::with(disableParallelToolUse: true);844845  // Send the user's request along with the tool definition. Claude decides846  // whether to call the tool based on the request and the tool description.847  $response = $client->messages->create(848      model: 'claude-opus-5',849      maxTokens: 1024,850      tools: $tools,851      toolChoice: $toolChoice,852      messages: [$userMessage],853  );854855  // When Claude calls a tool, the response has stop_reason "tool_use"856  // and the content array contains a tool_use block alongside any text.857  printf("stop_reason: %s\n", $response->stopReason);858859  // Find the tool_use block. A response may contain text blocks before the860  // tool_use block, so scan the content array rather than assuming position.861  $toolUse = null;862  foreach ($response->content as $block) {863      if ($block->type === 'tool_use') {864          $toolUse = $block;865          break;866      }867  }868  printf("Tool: %s\n", $toolUse->name);869  printf("Input: %s\n", json_encode($toolUse->input));870871  // Execute the tool. In a real system this would call your calendar API.872  // Here the result is hardcoded to keep the example self-contained.873  $result = ['event_id' => 'evt_123', 'status' => 'created'];874875  // Send the result back. The tool_result block goes in a user message and876  // its tool_use_id must match the id from the tool_use block above. The877  // assistant's previous response is included so Claude has the full history.878  $followup = $client->messages->create(879      model: 'claude-opus-5',880      maxTokens: 1024,881      tools: $tools,882      toolChoice: $toolChoice,883      messages: [884          $userMessage,885          ['role' => 'assistant', 'content' => $response->content],886          [887              'role' => 'user',888              'content' => [889                  [890                      'type' => 'tool_result',891                      'tool_use_id' => $toolUse->id,892                      'content' => json_encode($result),893                  ],894              ],895          ],896      ],897  );898899  // With the tool result in hand, Claude produces a final natural-language900  // answer and stop_reason becomes "end_turn".901  printf("stop_reason: %s\n", $followup->stopReason);902  foreach ($followup->content as $block) {903      if ($block->type === 'text') {904          echo $block->text, "\n";905      }906  }907  ```908909  ```ruby Ruby910  # Ring 1: Single tool, single turn.911912  require "anthropic"913914  # Create a client. It reads ANTHROPIC_API_KEY from the environment.915  client = Anthropic::Client.new916917  # Define one tool. The input_schema is a JSON Schema object describing918  # the arguments Claude should pass when it calls this tool. This schema919  # includes nested objects (recurrence), arrays (attendees), and optional920  # fields, which is closer to real-world tools than a flat string argument.921  tools = [922    {923      name: "create_calendar_event",924      description: "Create a calendar event with attendees and optional recurrence.",925      input_schema: {926        type: "object",927        properties: {928          title: {type: "string"},929          start: {type: "string", format: "date-time"},930          end: {type: "string", format: "date-time"},931          attendees: {932            type: "array",933            items: {type: "string", format: "email"}934          },935          recurrence: {936            type: "object",937            properties: {938              frequency: {enum: ["daily", "weekly", "monthly"]},939              count: {type: "integer", minimum: 1}940            }941          }942        },943        required: ["title", "start", "end"]944      }945    }946  ]947948  user_message = {949    role: "user",950    content: "Schedule a 30-minute sync with alice@example.com and bob@example.com on Monday, March 30, 2026 at 10am."951  }952953  # Ask for at most one tool call per turn so the single-turn flow below954  # stays predictable.955  tool_choice = {type: "auto", disable_parallel_tool_use: true}956957  # Send the user's request along with the tool definition. Claude decides958  # whether to call the tool based on the request and the tool description.959  response = client.messages.create(960    model: "claude-opus-5",961    max_tokens: 1024,962    tools: tools,963    tool_choice: tool_choice,964    messages: [user_message]965  )966967  # When Claude calls a tool, the response has stop_reason "tool_use"968  # and the content array contains a tool_use block alongside any text.969  puts "stop_reason: #{response.stop_reason}"970971  # Find the tool_use block. A response may contain text blocks before the972  # tool_use block, so scan the content array rather than assuming position.973  tool_use = response.content.find { |block| block.type == :tool_use }974  puts "Tool: #{tool_use.name}"975  puts "Input: #{tool_use.input}"976977  # Execute the tool. In a real system this would call your calendar API.978  # Here the result is hardcoded to keep the example self-contained.979  result = {event_id: "evt_123", status: "created"}980981  # Send the result back. The tool_result block goes in a user message and982  # its tool_use_id must match the id from the tool_use block above. The983  # assistant's previous response is included so Claude has the full history.984  followup = client.messages.create(985    model: "claude-opus-5",986    max_tokens: 1024,987    tools: tools,988    tool_choice: tool_choice,989    messages: [990      user_message,991      {role: "assistant", content: response.content},992      {993        role: "user",994        content: [995          {996            type: "tool_result",997            tool_use_id: tool_use.id,998            content: JSON.generate(result)999          }1000        ]1001      }1002    ]1003  )10041005  # With the tool result in hand, Claude produces a final natural-language1006  # answer and stop_reason becomes "end_turn".1007  puts "stop_reason: #{followup.stop_reason}"1008  followup.content.each do |block|1009    puts block.text if block.type == :text1010  end1011  ```1012</CodeGroup>10131014**What to expect**10151016```text Output wrap1017stop_reason: tool_use1018Tool: create_calendar_event1019Input: {'title': 'Sync', 'start': '2026-03-30T10:00:00', 'end': '2026-03-30T10:30:00', 'attendees': ['alice@example.com', 'bob@example.com']}1020stop_reason: end_turn1021I've scheduled your 30-minute sync with Alice and Bob for Monday, March 30 at 10am.1022```10231024The first `stop_reason` is `tool_use` because Claude is waiting for the calendar result. After you send the result, the second `stop_reason` is `end_turn` and the content is natural language for the user.10251026## Ring 2: The agentic loop10271028Ring 1 assumed Claude would call the tool exactly once. Real tasks often need several calls: Claude might create an event, read the confirmation, then create another. The fix is a `while` loop that keeps running tools and feeding results back until `stop_reason` is no longer `"tool_use"`.10291030The other change is conversation history. Instead of rebuilding the `messages` array from scratch on each request, keep a running list and append to it. Every turn sees the complete prior context.10311032<CodeGroup>1033  ```bash cURL1034  #!/bin/bash1035  # Ring 2: The agentic loop.10361037  TOOLS='[1038    {1039      "name": "create_calendar_event",1040      "description": "Create a calendar event with attendees and optional recurrence.",1041      "input_schema": {1042        "type": "object",1043        "properties": {1044          "title": {"type": "string"},1045          "start": {"type": "string", "format": "date-time"},1046          "end": {"type": "string", "format": "date-time"},1047          "attendees": {"type": "array", "items": {"type": "string", "format": "email"}},1048          "recurrence": {1049            "type": "object",1050            "properties": {1051              "frequency": {"enum": ["daily", "weekly", "monthly"]},1052              "count": {"type": "integer", "minimum": 1}1053            }1054          }1055        },1056        "required": ["title", "start", "end"]1057      }1058    }1059  ]'10601061  run_tool() {1062    local name="$1"1063    local input="$2"1064    if [ "$name" = "create_calendar_event" ]; then1065      local title=$(echo "$input" | jq -r '.title')1066      jq -n --arg title "$title" '{event_id: "evt_123", status: "created", title: $title}'1067    else1068      echo "{\"error\": \"Unknown tool: $name\"}"1069    fi1070  }10711072  # Keep the full conversation history in a JSON array so each turn sees prior context.1073  MESSAGES='[{"role": "user", "content": "Schedule a weekly team standup every Monday at 9am for the next 4 weeks. Invite the whole team: alice@example.com, bob@example.com, carol@example.com."}]'10741075  call_api() {1076    curl -s https://api.anthropic.com/v1/messages \1077      -H "x-api-key: $ANTHROPIC_API_KEY" \1078      -H "anthropic-version: 2023-06-01" \1079      -H "content-type: application/json" \1080      -d "$(jq -n --argjson tools "$TOOLS" --argjson messages "$MESSAGES" \1081        '{model: "claude-opus-5", max_tokens: 1024, tools: $tools, tool_choice: {type: "auto", disable_parallel_tool_use: true}, messages: $messages}')"1082  }10831084  RESPONSE=$(call_api)10851086  # Loop until Claude stops asking for tools. Each iteration runs the requested1087  # tool, appends the result to history, and asks Claude to continue.1088  while [ "$(echo "$RESPONSE" | jq -r '.stop_reason')" = "tool_use" ]; do1089    TOOL_USE=$(echo "$RESPONSE" | jq '.content[] | select(.type == "tool_use")')1090    TOOL_NAME=$(echo "$TOOL_USE" | jq -r '.name')1091    TOOL_INPUT=$(echo "$TOOL_USE" | jq -c '.input')1092    TOOL_USE_ID=$(echo "$TOOL_USE" | jq -r '.id')1093    RESULT=$(run_tool "$TOOL_NAME" "$TOOL_INPUT")10941095    ASSISTANT_CONTENT=$(echo "$RESPONSE" | jq '.content')1096    MESSAGES=$(echo "$MESSAGES" | jq \1097      --argjson assistant "$ASSISTANT_CONTENT" \1098      --arg tool_use_id "$TOOL_USE_ID" \1099      --arg result "$RESULT" \1100      '. + [1101        {role: "assistant", content: $assistant},1102        {role: "user", content: [{type: "tool_result", tool_use_id: $tool_use_id, content: $result}]}1103      ]')11041105    RESPONSE=$(call_api)1106  done11071108  echo "$RESPONSE" | jq -r '.content[] | select(.type == "text") | .text'1109  ```11101111  ```bash CLI1112  #!/usr/bin/env bash1113  # Ring 2: The agentic loop.1114  # Uses jq for cross-turn message-array state — building an agentic loop in shell1115  # requires JSON manipulation beyond ant's single-call --transform scope.1116  set -euo pipefail11171118  run_tool() {1119    local name="$1" input="$2"1120    if [ "$name" = "create_calendar_event" ]; then1121      jq -n --arg title "$(jq -r '.title' <<<"$input")" \1122        '{event_id: "evt_123", status: "created", title: $title}'1123    else1124      printf '{"error": "Unknown tool: %s"}' "$name"1125    fi1126  }11271128  # Keep the full conversation history in a JSON array so each turn sees1129  # prior context.1130  MESSAGES='[{"role": "user", "content": "Schedule a weekly team standup every Monday at 9am for the next 4 weeks. Invite the whole team: alice@example.com, bob@example.com, carol@example.com."}]'11311132  call_api() {1133    # ant reads the request body as YAML on stdin: no auth headers, no1134    # hand-built JSON envelope. The static keys (model, tools, tool_choice)1135    # live in a quoted heredoc; the growing messages array is appended as1136    # JSON, which YAML accepts as flow syntax.1137    {1138      cat <<'YAML'1139  model: claude-opus-51140  max_tokens: 10241141  tool_choice: {type: auto, disable_parallel_tool_use: true}1142  tools:1143    - name: create_calendar_event1144      description: Create a calendar event with attendees and optional recurrence.1145      input_schema:1146        type: object1147        properties:1148          title: {type: string}1149          start: {type: string, format: date-time}1150          end: {type: string, format: date-time}1151          attendees:1152            type: array1153            items: {type: string, format: email}1154          recurrence:1155            type: object1156            properties:1157              frequency: {enum: [daily, weekly, monthly]}1158              count: {type: integer, minimum: 1}1159        required: [title, start, end]1160  YAML1161      printf 'messages: %s\n' "$MESSAGES"1162    } | ant messages create --format json1163  }11641165  RESPONSE=$(call_api)11661167  # Loop until Claude stops asking for tools. Each iteration runs the1168  # requested tool, appends the result to history, and asks Claude to1169  # continue.1170  while [ "$(jq -r '.stop_reason' <<<"$RESPONSE")" = "tool_use" ]; do1171    TOOL_USE=$(jq '.content[] | select(.type == "tool_use")' <<<"$RESPONSE")1172    TOOL_NAME=$(jq -r '.name' <<<"$TOOL_USE")1173    TOOL_INPUT=$(jq -c '.input' <<<"$TOOL_USE")1174    TOOL_USE_ID=$(jq -r '.id' <<<"$TOOL_USE")1175    RESULT=$(run_tool "$TOOL_NAME" "$TOOL_INPUT")11761177    MESSAGES=$(jq \1178      --argjson assistant "$(jq '.content' <<<"$RESPONSE")" \1179      --arg tool_use_id "$TOOL_USE_ID" \1180      --arg result "$RESULT" \1181      '. + [1182        {role: "assistant", content: $assistant},1183        {role: "user", content: [1184          {type: "tool_result", tool_use_id: $tool_use_id, content: $result}1185        ]}1186      ]' <<<"$MESSAGES")11871188    RESPONSE=$(call_api)1189  done11901191  jq -r '.content[] | select(.type == "text") | .text' <<<"$RESPONSE"1192  ```11931194  ```python Python1195  # Ring 2: The agentic loop.11961197  import json11981199  import anthropic12001201  client = anthropic.Anthropic()12021203  tools = [1204      {1205          "name": "create_calendar_event",1206          "description": "Create a calendar event with attendees and optional recurrence.",1207          "input_schema": {1208              "type": "object",1209              "properties": {1210                  "title": {"type": "string"},1211                  "start": {"type": "string", "format": "date-time"},1212                  "end": {"type": "string", "format": "date-time"},1213                  "attendees": {1214                      "type": "array",1215                      "items": {"type": "string", "format": "email"},1216                  },1217                  "recurrence": {1218                      "type": "object",1219                      "properties": {1220                          "frequency": {"enum": ["daily", "weekly", "monthly"]},1221                          "count": {"type": "integer", "minimum": 1},1222                      },1223                  },1224              },1225              "required": ["title", "start", "end"],1226          },1227      }1228  ]122912301231  def run_tool(name, tool_input):1232      if name == "create_calendar_event":1233          return {"event_id": "evt_123", "status": "created", "title": tool_input["title"]}1234      return {"error": f"Unknown tool: {name}"}123512361237  # Keep the full conversation history in a list so each turn sees prior context.1238  messages = [1239      {1240          "role": "user",1241          "content": "Schedule a weekly team standup every Monday at 9am for the next 4 weeks. Invite the whole team: alice@example.com, bob@example.com, carol@example.com.",1242      }1243  ]12441245  response = client.messages.create(1246      model="claude-opus-5",1247      max_tokens=1024,1248      tools=tools,1249      tool_choice={"type": "auto", "disable_parallel_tool_use": True},1250      messages=messages,1251  )12521253  # Loop until Claude stops asking for tools. Each iteration runs the requested1254  # tool, appends the result to history, and asks Claude to continue.1255  while response.stop_reason == "tool_use":1256      tool_use = next(block for block in response.content if block.type == "tool_use")1257      result = run_tool(tool_use.name, tool_use.input)12581259      messages.append({"role": "assistant", "content": response.content})1260      messages.append(1261          {1262              "role": "user",1263              "content": [1264                  {1265                      "type": "tool_result",1266                      "tool_use_id": tool_use.id,1267                      "content": json.dumps(result),1268                  }1269              ],1270          }1271      )12721273      response = client.messages.create(1274          model="claude-opus-5",1275          max_tokens=1024,1276          tools=tools,1277          tool_choice={"type": "auto", "disable_parallel_tool_use": True},1278          messages=messages,1279      )12801281  final_text = next(block for block in response.content if block.type == "text")1282  print(final_text.text)1283  ```12841285  ```typescript TypeScript1286  // Ring 2: The agentic loop.12871288  import Anthropic from "@anthropic-ai/sdk";12891290  const client = new Anthropic();12911292  const tools: Anthropic.Tool[] = [1293    {1294      name: "create_calendar_event",1295      description:1296        "Create a calendar event with attendees and optional recurrence.",1297      input_schema: {1298        type: "object",1299        properties: {1300          title: { type: "string" },1301          start: { type: "string", format: "date-time" },1302          end: { type: "string", format: "date-time" },1303          attendees: {1304            type: "array",1305            items: { type: "string", format: "email" },1306          },1307          recurrence: {1308            type: "object",1309            properties: {1310              frequency: { enum: ["daily", "weekly", "monthly"] },1311              count: { type: "integer", minimum: 1 },1312            },1313          },1314        },1315        required: ["title", "start", "end"],1316      },1317    },1318  ];13191320  function runTool(name: string, input: Record<string, unknown>) {1321    if (name === "create_calendar_event") {1322      return { event_id: "evt_123", status: "created", title: input.title };1323    }1324    return { error: `Unknown tool: ${name}` };1325  }13261327  // Keep the full conversation history so each turn sees prior context.1328  const messages: Anthropic.MessageParam[] = [1329    {1330      role: "user",1331      content:1332        "Schedule a weekly team standup every Monday at 9am for the next 4 weeks. Invite the whole team: alice@example.com, bob@example.com, carol@example.com.",1333    },1334  ];13351336  let response = await client.messages.create({1337    model: "claude-opus-5",1338    max_tokens: 1024,1339    tools,1340    tool_choice: { type: "auto", disable_parallel_tool_use: true },1341    messages,1342  });13431344  // Loop until Claude stops asking for tools. Each iteration runs the requested1345  // tool, appends the result to history, and asks Claude to continue.1346  while (response.stop_reason === "tool_use") {1347    const toolUse = response.content.find(1348      (block): block is Anthropic.ToolUseBlock => block.type === "tool_use",1349    )!;1350    const result = runTool(toolUse.name, toolUse.input as Record<string, unknown>);13511352    messages.push({ role: "assistant", content: response.content });1353    messages.push({1354      role: "user",1355      content: [1356        {1357          type: "tool_result",1358          tool_use_id: toolUse.id,1359          content: JSON.stringify(result),1360        },1361      ],1362    });13631364    response = await client.messages.create({1365      model: "claude-opus-5",1366      max_tokens: 1024,1367      tools,1368      tool_choice: { type: "auto", disable_parallel_tool_use: true },1369      messages,1370    });1371  }13721373  for (const block of response.content) {1374    if (block.type === "text") {1375      console.log(block.text);1376    }1377  }1378  ```13791380  ```csharp C#1381  // Ring 2: The agentic loop.13821383  using System;1384  using System.Collections.Generic;1385  using System.Linq;1386  using System.Text.Json;1387  using System.Threading.Tasks;1388  using Anthropic;1389  using Anthropic.Models.Messages;13901391  AnthropicClient client = new();13921393  List<ToolUnion> tools =1394  [1395      new ToolUnion(new Tool()1396      {1397          Name = "create_calendar_event",1398          Description = "Create a calendar event with attendees and optional recurrence.",1399          InputSchema = new InputSchema()1400          {1401              Properties = new Dictionary<string, JsonElement>1402              {1403                  ["title"] = JsonSerializer.SerializeToElement(new { type = "string" }),1404                  ["start"] = JsonSerializer.SerializeToElement(new { type = "string", format = "date-time" }),1405                  ["end"] = JsonSerializer.SerializeToElement(new { type = "string", format = "date-time" }),1406                  ["attendees"] = JsonSerializer.SerializeToElement(new1407                  {1408                      type = "array",1409                      items = new { type = "string", format = "email" },1410                  }),1411                  ["recurrence"] = JsonSerializer.SerializeToElement(new1412                  {1413                      type = "object",1414                      properties = new1415                      {1416                          frequency = new { @enum = new[] { "daily", "weekly", "monthly" } },1417                          count = new { type = "integer", minimum = 1 },1418                      },1419                  }),1420              },1421              Required = ["title", "start", "end"],1422          },1423      }),1424  ];14251426  // Run the requested tool and return its result as a string.1427  string RunTool(ToolUseBlock toolUse)1428  {1429      if (toolUse.Name == "create_calendar_event")1430      {1431          var title = toolUse.Input.TryGetValue("title", out var t) ? t.GetString() : "";1432          return JsonSerializer.Serialize(new { event_id = "evt_123", status = "created", title });1433      }1434      return JsonSerializer.Serialize(new { error = $"Unknown tool: {toolUse.Name}" });1435  }14361437  var toolChoice = new ToolChoice(new ToolChoiceAuto { DisableParallelToolUse = true });14381439  // Keep the full conversation history in a list so each turn sees prior context.1440  List<MessageParam> messages =1441  [1442      new()1443      {1444          Role = Role.User,1445          Content = "Schedule a weekly team standup every Monday at 9am for the next 4 weeks. Invite the whole team: alice@example.com, bob@example.com, carol@example.com.",1446      },1447  ];14481449  var response = await client.Messages.Create(new MessageCreateParams1450  {1451      Model = Model.ClaudeOpus5,1452      MaxTokens = 1024,1453      Tools = tools,1454      ToolChoice = toolChoice,1455      Messages = messages,1456  });14571458  // Loop until Claude stops asking for tools. Each iteration runs the requested1459  // tool, appends the result to history, and asks Claude to continue.1460  while (response.StopReason == StopReason.ToolUse)1461  {1462      ToolUseBlock? toolUse = null;1463      foreach (var block in response.Content)1464      {1465          if (block.TryPickToolUse(out var picked))1466          {1467              toolUse = picked;1468              break;1469          }1470      }1471      var result = RunTool(toolUse!);14721473      messages.Add(new()1474      {1475          Role = Role.Assistant,1476          Content = response.Content.Select(block => new ContentBlockParam(block.Json)).ToList(),1477      });1478      messages.Add(new()1479      {1480          Role = Role.User,1481          Content = new MessageParamContent(1482          [1483              new ContentBlockParam(new ToolResultBlockParam() { ToolUseID = toolUse!.ID, Content = result }),1484          ]),1485      });14861487      response = await client.Messages.Create(new MessageCreateParams1488      {1489          Model = Model.ClaudeOpus5,1490          MaxTokens = 1024,1491          Tools = tools,1492          ToolChoice = toolChoice,1493          Messages = messages,1494      });1495  }14961497  foreach (var block in response.Content)1498  {1499      if (block.TryPickText(out var text))1500      {1501          Console.WriteLine(text.Text);1502      }1503  }1504  ```15051506  ```go Go1507  // Ring 2: The agentic loop.15081509  package main15101511  import (1512  	"context"1513  	"encoding/json"1514  	"fmt"1515  	"log"15161517  	"github.com/anthropics/anthropic-sdk-go"1518  )15191520  func runTool(name string, input map[string]any) string {1521  	if name == "create_calendar_event" {1522  		title, _ := input["title"].(string)1523  		return fmt.Sprintf(`{"event_id": "evt_123", "status": "created", "title": %q}`, title)1524  	}1525  	return fmt.Sprintf(`{"error": "Unknown tool: %s"}`, name)1526  }15271528  func main() {1529  	client := anthropic.NewClient()1530  	ctx := context.Background()15311532  	tools := []anthropic.ToolUnionParam{1533  		{OfTool: &anthropic.ToolParam{1534  			Name:        "create_calendar_event",1535  			Description: anthropic.String("Create a calendar event with attendees and optional recurrence."),1536  			InputSchema: anthropic.ToolInputSchemaParam{1537  				Properties: map[string]any{1538  					"title": map[string]any{"type": "string"},1539  					"start": map[string]any{"type": "string", "format": "date-time"},1540  					"end":   map[string]any{"type": "string", "format": "date-time"},1541  					"attendees": map[string]any{1542  						"type":  "array",1543  						"items": map[string]any{"type": "string", "format": "email"},1544  					},1545  					"recurrence": map[string]any{1546  						"type": "object",1547  						"properties": map[string]any{1548  							"frequency": map[string]any{"enum": []string{"daily", "weekly", "monthly"}},1549  							"count":     map[string]any{"type": "integer", "minimum": 1},1550  						},1551  					},1552  				},1553  				Required: []string{"title", "start", "end"},1554  			},1555  		}},1556  	}15571558  	toolChoice := anthropic.ToolChoiceUnionParam{1559  		OfAuto: &anthropic.ToolChoiceAutoParam{DisableParallelToolUse: anthropic.Bool(true)},1560  	}15611562  	// Keep the full conversation history in a slice so each turn sees prior context.1563  	messages := []anthropic.MessageParam{1564  		anthropic.NewUserMessage(anthropic.NewTextBlock(1565  			"Schedule a weekly team standup every Monday at 9am for the next 4 weeks. Invite the whole team: alice@example.com, bob@example.com, carol@example.com.",1566  		)),1567  	}15681569  	response, err := client.Messages.New(ctx, anthropic.MessageNewParams{1570  		Model:      anthropic.ModelClaudeOpus5,1571  		MaxTokens:  1024,1572  		Tools:      tools,1573  		ToolChoice: toolChoice,1574  		Messages:   messages,1575  	})1576  	if err != nil {1577  		log.Fatal(err)1578  	}15791580  	// Loop until Claude stops asking for tools. Each iteration runs the requested1581  	// tool, appends the result to history, and asks Claude to continue.1582  	for response.StopReason == "tool_use" {1583  		var toolUse anthropic.ContentBlockUnion1584  		for _, block := range response.Content {1585  			if block.Type == "tool_use" {1586  				toolUse = block1587  				break1588  			}1589  		}15901591  		var input map[string]any1592  		if err := json.Unmarshal(toolUse.Input, &input); err != nil {1593  			log.Fatal(err)1594  		}1595  		result := runTool(toolUse.Name, input)15961597  		var assistantContent []anthropic.ContentBlockParamUnion1598  		for _, block := range response.Content {1599  			assistantContent = append(assistantContent, block.ToParam())1600  		}1601  		messages = append(messages, anthropic.NewAssistantMessage(assistantContent...))1602  		messages = append(messages, anthropic.NewUserMessage(1603  			anthropic.NewToolResultBlock(toolUse.ID, result, false),1604  		))16051606  		response, err = client.Messages.New(ctx, anthropic.MessageNewParams{1607  			Model:      anthropic.ModelClaudeOpus5,1608  			MaxTokens:  1024,1609  			Tools:      tools,1610  			ToolChoice: toolChoice,1611  			Messages:   messages,1612  		})1613  		if err != nil {1614  			log.Fatal(err)1615  		}1616  	}16171618  	for _, block := range response.Content {1619  		if block.Type == "text" {1620  			fmt.Println(block.Text)1621  		}1622  	}1623  }1624  ```16251626  ```java Java1627  // Ring 2: The agentic loop.16281629  import com.anthropic.client.AnthropicClient;1630  import com.anthropic.client.okhttp.AnthropicOkHttpClient;1631  import com.anthropic.core.JsonValue;1632  import com.anthropic.models.messages.ContentBlockParam;1633  import com.anthropic.models.messages.Message;1634  import com.anthropic.models.messages.MessageCreateParams;1635  import com.anthropic.models.messages.MessageParam;1636  import com.anthropic.models.messages.Model;1637  import com.anthropic.models.messages.StopReason;1638  import com.anthropic.models.messages.Tool;1639  import com.anthropic.models.messages.Tool.InputSchema;1640  import com.anthropic.models.messages.ToolChoiceAuto;1641  import com.anthropic.models.messages.ToolResultBlockParam;1642  import com.anthropic.models.messages.ToolUseBlock;1643  import java.util.ArrayList;1644  import java.util.List;1645  import java.util.Map;16461647  String runTool(ToolUseBlock toolUse) {1648      // The raw tool input is a JSON object; read fields out of it as a map.1649      Map<String, JsonValue> input = (Map<String, JsonValue>) toolUse._input().asObject().get();1650      if (toolUse.name().equals("create_calendar_event")) {1651          String title = input.containsKey("title") ? input.get("title").asStringOrThrow() : "";1652          return "{\"event_id\": \"evt_123\", \"status\": \"created\", \"title\": \"" + title + "\"}";1653      }1654      return "{\"error\": \"Unknown tool: " + toolUse.name() + "\"}";1655  }16561657  void main() {1658      AnthropicClient client = AnthropicOkHttpClient.fromEnv();16591660      Tool calendarTool = Tool.builder()1661          .name("create_calendar_event")1662          .description("Create a calendar event with attendees and optional recurrence.")1663          .inputSchema(InputSchema.builder()1664              .properties(JsonValue.from(Map.of(1665                  "title", Map.of("type", "string"),1666                  "start", Map.of("type", "string", "format", "date-time"),1667                  "end", Map.of("type", "string", "format", "date-time"),1668                  "attendees", Map.of(1669                      "type", "array",1670                      "items", Map.of("type", "string", "format", "email")1671                  ),1672                  "recurrence", Map.of(1673                      "type", "object",1674                      "properties", Map.of(1675                          "frequency", Map.of("enum", List.of("daily", "weekly", "monthly")),1676                          "count", Map.of("type", "integer", "minimum", 1)1677                      )1678                  )1679              )))1680              .required(List.of("title", "start", "end"))1681              .build())1682          .build();16831684      ToolChoiceAuto toolChoice = ToolChoiceAuto.builder()1685          .disableParallelToolUse(true)1686          .build();16871688      // Keep the full conversation history in a list so each turn sees prior context.1689      List<MessageParam> messages = new ArrayList<>();1690      messages.add(MessageParam.builder()1691          .role(MessageParam.Role.USER)1692          .content("Schedule a weekly team standup every Monday at 9am for the next 4 weeks. Invite the whole team: alice@example.com, bob@example.com, carol@example.com.")1693          .build());16941695      Message response = client.messages().create(MessageCreateParams.builder()1696          .model(Model.CLAUDE_OPUS_5)1697          .maxTokens(1024L)1698          .addTool(calendarTool)1699          .toolChoice(toolChoice)1700          .messages(messages)1701          .build());17021703      // Loop until Claude stops asking for tools. Each iteration runs the requested1704      // tool, appends the result to history, and asks Claude to continue.1705      while (response.stopReason().isPresent()1706              && response.stopReason().get().equals(StopReason.TOOL_USE)) {1707          ToolUseBlock toolUse = response.content().stream()1708              .flatMap(block -> block.toolUse().stream())1709              .findFirst()1710              .orElseThrow();1711          String result = runTool(toolUse);17121713          messages.add(response.toParam());1714          messages.add(MessageParam.builder()1715              .role(MessageParam.Role.USER)1716              .contentOfBlockParams(List.of(ContentBlockParam.ofToolResult(1717                  ToolResultBlockParam.builder()1718                      .toolUseId(toolUse.id())1719                      .content(result)1720                      .build())))1721              .build());17221723          response = client.messages().create(MessageCreateParams.builder()1724              .model(Model.CLAUDE_OPUS_5)1725              .maxTokens(1024L)1726              .addTool(calendarTool)1727              .toolChoice(toolChoice)1728              .messages(messages)1729              .build());1730      }17311732      response.content().stream()1733          .flatMap(block -> block.text().stream())1734          .forEach(textBlock -> IO.println(textBlock.text()));1735  }1736  ```17371738  ```php PHP1739  <?php17401741  // Ring 2: The agentic loop.17421743  use Anthropic\Client;1744  use Anthropic\Messages\ToolChoiceAuto;17451746  $client = new Client();17471748  $tools = [1749      [1750          'name' => 'create_calendar_event',1751          'description' => 'Create a calendar event with attendees and optional recurrence.',1752          'input_schema' => [1753              'type' => 'object',1754              'properties' => [1755                  'title' => ['type' => 'string'],1756                  'start' => ['type' => 'string', 'format' => 'date-time'],1757                  'end' => ['type' => 'string', 'format' => 'date-time'],1758                  'attendees' => [1759                      'type' => 'array',1760                      'items' => ['type' => 'string', 'format' => 'email'],1761                  ],1762                  'recurrence' => [1763                      'type' => 'object',1764                      'properties' => [1765                          'frequency' => ['enum' => ['daily', 'weekly', 'monthly']],1766                          'count' => ['type' => 'integer', 'minimum' => 1],1767                      ],1768                  ],1769              ],1770              'required' => ['title', 'start', 'end'],1771          ],1772      ],1773  ];17741775  function runTool(string $name, array $input): string1776  {1777      if ($name === 'create_calendar_event') {1778          return json_encode([1779              'event_id' => 'evt_123',1780              'status' => 'created',1781              'title' => $input['title'],1782          ]);1783      }17841785      return json_encode(['error' => "Unknown tool: {$name}"]);1786  }17871788  $toolChoice = ToolChoiceAuto::with(disableParallelToolUse: true);17891790  // Keep the full conversation history in an array so each turn sees prior context.1791  $messages = [1792      [1793          'role' => 'user',1794          'content' => 'Schedule a weekly team standup every Monday at 9am for the next 4 weeks. Invite the whole team: alice@example.com, bob@example.com, carol@example.com.',1795      ],1796  ];17971798  $response = $client->messages->create(1799      model: 'claude-opus-5',1800      maxTokens: 1024,1801      tools: $tools,1802      toolChoice: $toolChoice,1803      messages: $messages,1804  );18051806  // Loop until Claude stops asking for tools. Each iteration runs the requested1807  // tool, appends the result to history, and asks Claude to continue.1808  while ($response->stopReason === 'tool_use') {1809      $toolUse = null;1810      foreach ($response->content as $block) {1811          if ($block->type === 'tool_use') {1812              $toolUse = $block;1813              break;1814          }1815      }18161817      $result = runTool($toolUse->name, $toolUse->input);18181819      $messages[] = ['role' => 'assistant', 'content' => $response->content];1820      $messages[] = [1821          'role' => 'user',1822          'content' => [1823              [1824                  'type' => 'tool_result',1825                  'tool_use_id' => $toolUse->id,1826                  'content' => $result,1827              ],1828          ],1829      ];18301831      $response = $client->messages->create(1832          model: 'claude-opus-5',1833          maxTokens: 1024,1834          tools: $tools,1835          toolChoice: $toolChoice,1836          messages: $messages,1837      );1838  }18391840  foreach ($response->content as $block) {1841      if ($block->type === 'text') {1842          echo $block->text, "\n";1843      }1844  }1845  ```18461847  ```ruby Ruby1848  # Ring 2: The agentic loop.18491850  require "anthropic"18511852  client = Anthropic::Client.new18531854  tools = [1855    {1856      name: "create_calendar_event",1857      description: "Create a calendar event with attendees and optional recurrence.",1858      input_schema: {1859        type: "object",1860        properties: {1861          title: {type: "string"},1862          start: {type: "string", format: "date-time"},1863          end: {type: "string", format: "date-time"},1864          attendees: {1865            type: "array",1866            items: {type: "string", format: "email"}1867          },1868          recurrence: {1869            type: "object",1870            properties: {1871              frequency: {enum: ["daily", "weekly", "monthly"]},1872              count: {type: "integer", minimum: 1}1873            }1874          }1875        },1876        required: ["title", "start", "end"]1877      }1878    }1879  ]18801881  def run_tool(name, input)1882    case name1883    when "create_calendar_event"1884      JSON.generate({event_id: "evt_123", status: "created", title: input[:title]})1885    else1886      JSON.generate({error: "Unknown tool: #{name}"})1887    end1888  end18891890  tool_choice = {type: "auto", disable_parallel_tool_use: true}18911892  # Keep the full conversation history in an array so each turn sees prior context.1893  messages = [1894    {1895      role: "user",1896      content: "Schedule a weekly team standup every Monday at 9am for the next 4 weeks. Invite the whole team: alice@example.com, bob@example.com, carol@example.com."1897    }1898  ]18991900  response = client.messages.create(1901    model: "claude-opus-5",1902    max_tokens: 1024,1903    tools: tools,1904    tool_choice: tool_choice,1905    messages: messages1906  )19071908  # Loop until Claude stops asking for tools. Each iteration runs the requested1909  # tool, appends the result to history, and asks Claude to continue.1910  while response.stop_reason == :tool_use1911    tool_use = response.content.find { |block| block.type == :tool_use }1912    result = run_tool(tool_use.name, tool_use.input)19131914    messages << {role: "assistant", content: response.content}1915    messages << {1916      role: "user",1917      content: [1918        {1919          type: "tool_result",1920          tool_use_id: tool_use.id,1921          content: result1922        }1923      ]1924    }19251926    response = client.messages.create(1927      model: "claude-opus-5",1928      max_tokens: 1024,1929      tools: tools,1930      tool_choice: tool_choice,1931      messages: messages1932    )1933  end19341935  response.content.each do |block|1936    puts block.text if block.type == :text1937  end1938  ```1939</CodeGroup>19401941**What to expect**19421943```text Output wrap1944I've set up your weekly team standup for the next 4 Mondays at 9am with Alice, Bob, and Carol invited.1945```19461947The loop might run once or several times depending on how Claude breaks down the task. Your code no longer needs to know in advance.19481949## Ring 3: Multiple tools, parallel calls19501951Agents rarely have just one capability. Add a second tool, `list_calendar_events`, so Claude can check the existing schedule before creating something new.19521953When Claude has multiple independent tool calls to make, it might return several `tool_use` blocks in a single response. Your loop needs to process all of them and send back all results together in one user message. Iterate over every `tool_use` block in `response.content`, not just the first.19541955<CodeGroup>1956  ```bash cURL1957  #!/bin/bash1958  # Ring 3: Multiple tools, parallel calls.19591960  TOOLS='[1961    {1962      "name": "create_calendar_event",1963      "description": "Create a calendar event with attendees and optional recurrence.",1964      "input_schema": {1965        "type": "object",1966        "properties": {1967          "title": {"type": "string"},1968          "start": {"type": "string", "format": "date-time"},1969          "end": {"type": "string", "format": "date-time"},1970          "attendees": {"type": "array", "items": {"type": "string", "format": "email"}},1971          "recurrence": {1972            "type": "object",1973            "properties": {1974              "frequency": {"enum": ["daily", "weekly", "monthly"]},1975              "count": {"type": "integer", "minimum": 1}1976            }1977          }1978        },1979        "required": ["title", "start", "end"]1980      }1981    },1982    {1983      "name": "list_calendar_events",1984      "description": "List all calendar events on a given date.",1985      "input_schema": {1986        "type": "object",1987        "properties": {"date": {"type": "string", "format": "date"}},1988        "required": ["date"]1989      }1990    }1991  ]'19921993  run_tool() {1994    case "$1" in1995      create_calendar_event)1996        jq -n --arg title "$(echo "$2" | jq -r '.title')" '{event_id: "evt_123", status: "created", title: $title}' ;;1997      list_calendar_events)1998        echo '{"events": [{"title": "Existing meeting", "start": "14:00", "end": "15:00"}]}' ;;1999      *)2000        echo "{\"error\": \"Unknown tool: $1\"}" ;;2001    esac2002  }20032004  MESSAGES='[{"role": "user", "content": "Check what I have next Monday, then schedule a planning session that avoids any conflicts."}]'20052006  call_api() {2007    curl -s https://api.anthropic.com/v1/messages \2008      -H "x-api-key: $ANTHROPIC_API_KEY" \2009      -H "anthropic-version: 2023-06-01" \2010      -H "content-type: application/json" \2011      -d "$(jq -n --argjson tools "$TOOLS" --argjson messages "$MESSAGES" \2012        '{model: "claude-opus-5", max_tokens: 1024, tools: $tools, messages: $messages}')"2013  }20142015  RESPONSE=$(call_api)20162017  while [ "$(echo "$RESPONSE" | jq -r '.stop_reason')" = "tool_use" ]; do2018    # A single response can contain multiple tool_use blocks. Process all of2019    # them and return all results together in one user message.2020    TOOL_RESULTS='[]'2021    while read -r block; do2022      NAME=$(echo "$block" | jq -r '.name')2023      INPUT=$(echo "$block" | jq -c '.input')2024      ID=$(echo "$block" | jq -r '.id')2025      RESULT=$(run_tool "$NAME" "$INPUT")2026      TOOL_RESULTS=$(echo "$TOOL_RESULTS" | jq --arg id "$ID" --arg result "$RESULT" \2027        '. + [{type: "tool_result", tool_use_id: $id, content: $result}]')2028    done < <(echo "$RESPONSE" | jq -c '.content[] | select(.type == "tool_use")')20292030    MESSAGES=$(echo "$MESSAGES" | jq \2031      --argjson assistant "$(echo "$RESPONSE" | jq '.content')" \2032      --argjson results "$TOOL_RESULTS" \2033      '. + [{role: "assistant", content: $assistant}, {role: "user", content: $results}]')20342035    RESPONSE=$(call_api)2036  done20372038  echo "$RESPONSE" | jq -r '.content[] | select(.type == "text") | .text'2039  ```20402041  ```bash CLI2042  #!/usr/bin/env bash2043  # Ring 3: Multiple tools, parallel calls.2044  # Uses jq for cross-turn message-array state — building an agentic loop in shell2045  # requires JSON manipulation beyond ant's single-call --transform scope.2046  set -euo pipefail20472048  run_tool() {2049    case "$1" in2050      create_calendar_event)2051        jq -n --arg title "$(jq -r '.title' <<<"$2")" \2052          '{event_id: "evt_123", status: "created", title: $title}' ;;2053      list_calendar_events)2054        echo '{"events": [{"title": "Existing meeting", "start": "14:00", "end": "15:00"}]}' ;;2055      *)2056        printf '{"error": "Unknown tool: %s"}' "$1" ;;2057    esac2058  }20592060  MESSAGES='[{"role": "user", "content": "Check what I have next Monday, then schedule a planning session that avoids any conflicts."}]'20612062  call_api() {2063    # ant reads the request body as YAML on stdin: no auth headers, no2064    # hand-built JSON envelope. The static keys (model, tools) live in a2065    # quoted heredoc; the growing messages array is appended as JSON,2066    # which YAML accepts as flow syntax.2067    {2068      cat <<'YAML'2069  model: claude-opus-52070  max_tokens: 10242071  tools:2072    - name: create_calendar_event2073      description: Create a calendar event with attendees and optional recurrence.2074      input_schema:2075        type: object2076        properties:2077          title: {type: string}2078          start: {type: string, format: date-time}2079          end: {type: string, format: date-time}2080          attendees:2081            type: array2082            items: {type: string, format: email}2083          recurrence:2084            type: object2085            properties:2086              frequency: {enum: [daily, weekly, monthly]}2087              count: {type: integer, minimum: 1}2088        required: [title, start, end]2089    - name: list_calendar_events2090      description: List all calendar events on a given date.2091      input_schema:2092        type: object2093        properties:2094          date: {type: string, format: date}2095        required: [date]2096  YAML2097      printf 'messages: %s\n' "$MESSAGES"2098    } | ant messages create --format json2099  }21002101  RESPONSE=$(call_api)21022103  while [ "$(jq -r '.stop_reason' <<<"$RESPONSE")" = "tool_use" ]; do2104    # A single response can contain multiple tool_use blocks. Process all2105    # of them and return all results together in one user message.2106    TOOL_RESULTS='[]'2107    while read -r block; do2108      NAME=$(jq -r '.name' <<<"$block")2109      INPUT=$(jq -c '.input' <<<"$block")2110      ID=$(jq -r '.id' <<<"$block")2111      RESULT=$(run_tool "$NAME" "$INPUT")2112      TOOL_RESULTS=$(jq --arg id "$ID" --arg result "$RESULT" \2113        '. + [{type: "tool_result", tool_use_id: $id, content: $result}]' \2114        <<<"$TOOL_RESULTS")2115    done < <(jq -c '.content[] | select(.type == "tool_use")' <<<"$RESPONSE")21162117    MESSAGES=$(jq \2118      --argjson assistant "$(jq '.content' <<<"$RESPONSE")" \2119      --argjson results "$TOOL_RESULTS" \2120      '. + [2121        {role: "assistant", content: $assistant},2122        {role: "user", content: $results}2123      ]' <<<"$MESSAGES")21242125    RESPONSE=$(call_api)2126  done21272128  jq -r '.content[] | select(.type == "text") | .text' <<<"$RESPONSE"2129  ```21302131  ```python Python2132  # Ring 3: Multiple tools, parallel calls.21332134  import json21352136  import anthropic21372138  client = anthropic.Anthropic()21392140  tools = [2141      {2142          "name": "create_calendar_event",2143          "description": "Create a calendar event with attendees and optional recurrence.",2144          "input_schema": {2145              "type": "object",2146              "properties": {2147                  "title": {"type": "string"},2148                  "start": {"type": "string", "format": "date-time"},2149                  "end": {"type": "string", "format": "date-time"},2150                  "attendees": {2151                      "type": "array",2152                      "items": {"type": "string", "format": "email"},2153                  },2154                  "recurrence": {2155                      "type": "object",2156                      "properties": {2157                          "frequency": {"enum": ["daily", "weekly", "monthly"]},2158                          "count": {"type": "integer", "minimum": 1},2159                      },2160                  },2161              },2162              "required": ["title", "start", "end"],2163          },2164      },2165      {2166          "name": "list_calendar_events",2167          "description": "List all calendar events on a given date.",2168          "input_schema": {2169              "type": "object",2170              "properties": {2171                  "date": {"type": "string", "format": "date"},2172              },2173              "required": ["date"],2174          },2175      },2176  ]217721782179  def run_tool(name, tool_input):2180      if name == "create_calendar_event":2181          return {"event_id": "evt_123", "status": "created", "title": tool_input["title"]}2182      if name == "list_calendar_events":2183          return {"events": [{"title": "Existing meeting", "start": "14:00", "end": "15:00"}]}2184      return {"error": f"Unknown tool: {name}"}218521862187  messages = [2188      {2189          "role": "user",2190          "content": "Check what I have next Monday, then schedule a planning session that avoids any conflicts.",2191      }2192  ]21932194  response = client.messages.create(2195      model="claude-opus-5",2196      max_tokens=1024,2197      tools=tools,2198      messages=messages,2199  )22002201  while response.stop_reason == "tool_use":2202      # A single response can contain multiple tool_use blocks. Process all of2203      # them and return all results together in one user message.2204      tool_results = []2205      for block in response.content:2206          if block.type == "tool_use":2207              result = run_tool(block.name, block.input)2208              tool_results.append(2209                  {2210                      "type": "tool_result",2211                      "tool_use_id": block.id,2212                      "content": json.dumps(result),2213                  }2214              )22152216      messages.append({"role": "assistant", "content": response.content})2217      messages.append({"role": "user", "content": tool_results})22182219      response = client.messages.create(2220          model="claude-opus-5",2221          max_tokens=1024,2222          tools=tools,2223          messages=messages,2224      )22252226  final_text = next(block for block in response.content if block.type == "text")2227  print(final_text.text)2228  ```22292230  ```typescript TypeScript2231  // Ring 3: Multiple tools, parallel calls.22322233  import Anthropic from "@anthropic-ai/sdk";22342235  const client = new Anthropic();22362237  const tools: Anthropic.Tool[] = [2238    {2239      name: "create_calendar_event",2240      description:2241        "Create a calendar event with attendees and optional recurrence.",2242      input_schema: {2243        type: "object",2244        properties: {2245          title: { type: "string" },2246          start: { type: "string", format: "date-time" },2247          end: { type: "string", format: "date-time" },2248          attendees: {2249            type: "array",2250            items: { type: "string", format: "email" },2251          },2252          recurrence: {2253            type: "object",2254            properties: {2255              frequency: { enum: ["daily", "weekly", "monthly"] },2256              count: { type: "integer", minimum: 1 },2257            },2258          },2259        },2260        required: ["title", "start", "end"],2261      },2262    },2263    {2264      name: "list_calendar_events",2265      description: "List all calendar events on a given date.",2266      input_schema: {2267        type: "object",2268        properties: {2269          date: { type: "string", format: "date" },2270        },2271        required: ["date"],2272      },2273    },2274  ];22752276  function runTool(name: string, input: Record<string, unknown>) {2277    if (name === "create_calendar_event") {2278      return { event_id: "evt_123", status: "created", title: input.title };2279    }2280    if (name === "list_calendar_events") {2281      return {2282        events: [{ title: "Existing meeting", start: "14:00", end: "15:00" }],2283      };2284    }2285    return { error: `Unknown tool: ${name}` };2286  }22872288  const messages: Anthropic.MessageParam[] = [2289    {2290      role: "user",2291      content:2292        "Check what I have next Monday, then schedule a planning session that avoids any conflicts.",2293    },2294  ];22952296  let response = await client.messages.create({2297    model: "claude-opus-5",2298    max_tokens: 1024,2299    tools,2300    messages,2301  });23022303  while (response.stop_reason === "tool_use") {2304    // A single response can contain multiple tool_use blocks. Process all of2305    // them and return all results together in one user message.2306    const toolResults: Anthropic.ToolResultBlockParam[] = [];2307    for (const block of response.content) {2308      if (block.type === "tool_use") {2309        const result = runTool(block.name, block.input as Record<string, unknown>);2310        toolResults.push({2311          type: "tool_result",2312          tool_use_id: block.id,2313          content: JSON.stringify(result),2314        });2315      }2316    }23172318    messages.push({ role: "assistant", content: response.content });2319    messages.push({ role: "user", content: toolResults });23202321    response = await client.messages.create({2322      model: "claude-opus-5",2323      max_tokens: 1024,2324      tools,2325      messages,2326    });2327  }23282329  for (const block of response.content) {2330    if (block.type === "text") {2331      console.log(block.text);2332    }2333  }2334  ```23352336  ```csharp C#2337  // Ring 3: Multiple tools, parallel calls.23382339  using System;2340  using System.Collections.Generic;2341  using System.Linq;2342  using System.Text.Json;2343  using System.Threading.Tasks;2344  using Anthropic;2345  using Anthropic.Models.Messages;23462347  AnthropicClient client = new();23482349  List<ToolUnion> tools =2350  [2351      new ToolUnion(new Tool()2352      {2353          Name = "create_calendar_event",2354          Description = "Create a calendar event with attendees and optional recurrence.",2355          InputSchema = new InputSchema()2356          {2357              Properties = new Dictionary<string, JsonElement>2358              {2359                  ["title"] = JsonSerializer.SerializeToElement(new { type = "string" }),2360                  ["start"] = JsonSerializer.SerializeToElement(new { type = "string", format = "date-time" }),2361                  ["end"] = JsonSerializer.SerializeToElement(new { type = "string", format = "date-time" }),2362                  ["attendees"] = JsonSerializer.SerializeToElement(new2363                  {2364                      type = "array",2365                      items = new { type = "string", format = "email" },2366                  }),2367                  ["recurrence"] = JsonSerializer.SerializeToElement(new2368                  {2369                      type = "object",2370                      properties = new2371                      {2372                          frequency = new { @enum = new[] { "daily", "weekly", "monthly" } },2373                          count = new { type = "integer", minimum = 1 },2374                      },2375                  }),2376              },2377              Required = ["title", "start", "end"],2378          },2379      }),2380      new ToolUnion(new Tool()2381      {2382          Name = "list_calendar_events",2383          Description = "List all calendar events on a given date.",2384          InputSchema = new InputSchema()2385          {2386              Properties = new Dictionary<string, JsonElement>2387              {2388                  ["date"] = JsonSerializer.SerializeToElement(new { type = "string", format = "date" }),2389              },2390              Required = ["date"],2391          },2392      }),2393  ];23942395  string RunTool(ToolUseBlock toolUse)2396  {2397      if (toolUse.Name == "create_calendar_event")2398      {2399          var title = toolUse.Input.TryGetValue("title", out var t) ? t.GetString() : "";2400          return JsonSerializer.Serialize(new { event_id = "evt_123", status = "created", title });2401      }2402      if (toolUse.Name == "list_calendar_events")2403      {2404          return """{"events": [{"title": "Existing meeting", "start": "14:00", "end": "15:00"}]}""";2405      }2406      return JsonSerializer.Serialize(new { error = $"Unknown tool: {toolUse.Name}" });2407  }24082409  List<MessageParam> messages =2410  [2411      new()2412      {2413          Role = Role.User,2414          Content = "Check what I have next Monday, then schedule a planning session that avoids any conflicts.",2415      },2416  ];24172418  var response = await client.Messages.Create(new MessageCreateParams2419  {2420      Model = Model.ClaudeOpus5,2421      MaxTokens = 1024,2422      Tools = tools,2423      Messages = messages,2424  });24252426  while (response.StopReason == StopReason.ToolUse)2427  {2428      // A single response can contain multiple tool_use blocks. Process all of2429      // them and return all results together in one user message.2430      List<ContentBlockParam> toolResults = [];2431      foreach (var block in response.Content)2432      {2433          if (block.TryPickToolUse(out var toolUse))2434          {2435              toolResults.Add(new ContentBlockParam(new ToolResultBlockParam()2436              {2437                  ToolUseID = toolUse.ID,2438                  Content = RunTool(toolUse),2439              }));2440          }2441      }24422443      messages.Add(new()2444      {2445          Role = Role.Assistant,2446          Content = response.Content.Select(block => new ContentBlockParam(block.Json)).ToList(),2447      });2448      messages.Add(new() { Role = Role.User, Content = new MessageParamContent(toolResults) });24492450      response = await client.Messages.Create(new MessageCreateParams2451      {2452          Model = Model.ClaudeOpus5,2453          MaxTokens = 1024,2454          Tools = tools,2455          Messages = messages,2456      });2457  }24582459  foreach (var block in response.Content)2460  {2461      if (block.TryPickText(out var text))2462      {2463          Console.WriteLine(text.Text);2464      }2465  }2466  ```24672468  ```go Go2469  // Ring 3: Multiple tools, parallel calls.24702471  package main24722473  import (2474  	"context"2475  	"encoding/json"2476  	"fmt"2477  	"log"24782479  	"github.com/anthropics/anthropic-sdk-go"2480  )24812482  func runTool(name string, input map[string]any) string {2483  	if name == "create_calendar_event" {2484  		title, _ := input["title"].(string)2485  		return fmt.Sprintf(`{"event_id": "evt_123", "status": "created", "title": %q}`, title)2486  	}2487  	if name == "list_calendar_events" {2488  		return `{"events": [{"title": "Existing meeting", "start": "14:00", "end": "15:00"}]}`2489  	}2490  	return fmt.Sprintf(`{"error": "Unknown tool: %s"}`, name)2491  }24922493  func main() {2494  	client := anthropic.NewClient()2495  	ctx := context.Background()24962497  	tools := []anthropic.ToolUnionParam{2498  		{OfTool: &anthropic.ToolParam{2499  			Name:        "create_calendar_event",2500  			Description: anthropic.String("Create a calendar event with attendees and optional recurrence."),2501  			InputSchema: anthropic.ToolInputSchemaParam{2502  				Properties: map[string]any{2503  					"title": map[string]any{"type": "string"},2504  					"start": map[string]any{"type": "string", "format": "date-time"},2505  					"end":   map[string]any{"type": "string", "format": "date-time"},2506  					"attendees": map[string]any{2507  						"type":  "array",2508  						"items": map[string]any{"type": "string", "format": "email"},2509  					},2510  					"recurrence": map[string]any{2511  						"type": "object",2512  						"properties": map[string]any{2513  							"frequency": map[string]any{"enum": []string{"daily", "weekly", "monthly"}},2514  							"count":     map[string]any{"type": "integer", "minimum": 1},2515  						},2516  					},2517  				},2518  				Required: []string{"title", "start", "end"},2519  			},2520  		}},2521  		{OfTool: &anthropic.ToolParam{2522  			Name:        "list_calendar_events",2523  			Description: anthropic.String("List all calendar events on a given date."),2524  			InputSchema: anthropic.ToolInputSchemaParam{2525  				Properties: map[string]any{2526  					"date": map[string]any{"type": "string", "format": "date"},2527  				},2528  				Required: []string{"date"},2529  			},2530  		}},2531  	}25322533  	messages := []anthropic.MessageParam{2534  		anthropic.NewUserMessage(anthropic.NewTextBlock(2535  			"Check what I have next Monday, then schedule a planning session that avoids any conflicts.",2536  		)),2537  	}25382539  	response, err := client.Messages.New(ctx, anthropic.MessageNewParams{2540  		Model:     anthropic.ModelClaudeOpus5,2541  		MaxTokens: 1024,2542  		Tools:     tools,2543  		Messages:  messages,2544  	})2545  	if err != nil {2546  		log.Fatal(err)2547  	}25482549  	for response.StopReason == "tool_use" {2550  		// A single response can contain multiple tool_use blocks. Process all of2551  		// them and return all results together in one user message.2552  		var toolResults []anthropic.ContentBlockParamUnion2553  		for _, block := range response.Content {2554  			if block.Type == "tool_use" {2555  				var input map[string]any2556  				if err := json.Unmarshal(block.Input, &input); err != nil {2557  					log.Fatal(err)2558  				}2559  				result := runTool(block.Name, input)2560  				toolResults = append(toolResults, anthropic.NewToolResultBlock(block.ID, result, false))2561  			}2562  		}25632564  		var assistantContent []anthropic.ContentBlockParamUnion2565  		for _, block := range response.Content {2566  			assistantContent = append(assistantContent, block.ToParam())2567  		}2568  		messages = append(messages, anthropic.NewAssistantMessage(assistantContent...))2569  		messages = append(messages, anthropic.NewUserMessage(toolResults...))25702571  		response, err = client.Messages.New(ctx, anthropic.MessageNewParams{2572  			Model:     anthropic.ModelClaudeOpus5,2573  			MaxTokens: 1024,2574  			Tools:     tools,2575  			Messages:  messages,2576  		})2577  		if err != nil {2578  			log.Fatal(err)2579  		}2580  	}25812582  	for _, block := range response.Content {2583  		if block.Type == "text" {2584  			fmt.Println(block.Text)2585  		}2586  	}2587  }2588  ```25892590  ```java Java2591  // Ring 3: Multiple tools, parallel calls.25922593  import com.anthropic.client.AnthropicClient;2594  import com.anthropic.client.okhttp.AnthropicOkHttpClient;2595  import com.anthropic.core.JsonValue;2596  import com.anthropic.models.messages.ContentBlock;2597  import com.anthropic.models.messages.ContentBlockParam;2598  import com.anthropic.models.messages.Message;2599  import com.anthropic.models.messages.MessageCreateParams;2600  import com.anthropic.models.messages.MessageParam;2601  import com.anthropic.models.messages.Model;2602  import com.anthropic.models.messages.StopReason;2603  import com.anthropic.models.messages.Tool;2604  import com.anthropic.models.messages.Tool.InputSchema;2605  import com.anthropic.models.messages.ToolResultBlockParam;2606  import com.anthropic.models.messages.ToolUseBlock;2607  import java.util.ArrayList;2608  import java.util.List;2609  import java.util.Map;26102611  String runTool(ToolUseBlock toolUse) {2612      // The raw tool input is a JSON object; read fields out of it as a map.2613      Map<String, JsonValue> input = (Map<String, JsonValue>) toolUse._input().asObject().get();2614      if (toolUse.name().equals("create_calendar_event")) {2615          String title = input.containsKey("title") ? input.get("title").asStringOrThrow() : "";2616          return "{\"event_id\": \"evt_123\", \"status\": \"created\", \"title\": \"" + title + "\"}";2617      }2618      if (toolUse.name().equals("list_calendar_events")) {2619          return "{\"events\": [{\"title\": \"Existing meeting\", \"start\": \"14:00\", \"end\": \"15:00\"}]}";2620      }2621      return "{\"error\": \"Unknown tool: " + toolUse.name() + "\"}";2622  }26232624  void main() {2625      AnthropicClient client = AnthropicOkHttpClient.fromEnv();26262627      Tool calendarTool = Tool.builder()2628          .name("create_calendar_event")2629          .description("Create a calendar event with attendees and optional recurrence.")2630          .inputSchema(InputSchema.builder()2631              .properties(JsonValue.from(Map.of(2632                  "title", Map.of("type", "string"),2633                  "start", Map.of("type", "string", "format", "date-time"),2634                  "end", Map.of("type", "string", "format", "date-time"),2635                  "attendees", Map.of(2636                      "type", "array",2637                      "items", Map.of("type", "string", "format", "email")2638                  ),2639                  "recurrence", Map.of(2640                      "type", "object",2641                      "properties", Map.of(2642                          "frequency", Map.of("enum", List.of("daily", "weekly", "monthly")),2643                          "count", Map.of("type", "integer", "minimum", 1)2644                      )2645                  )2646              )))2647              .required(List.of("title", "start", "end"))2648              .build())2649          .build();26502651      Tool listTool = Tool.builder()2652          .name("list_calendar_events")2653          .description("List all calendar events on a given date.")2654          .inputSchema(InputSchema.builder()2655              .properties(JsonValue.from(Map.of(2656                  "date", Map.of("type", "string", "format", "date")2657              )))2658              .required(List.of("date"))2659              .build())2660          .build();26612662      List<MessageParam> messages = new ArrayList<>();2663      messages.add(MessageParam.builder()2664          .role(MessageParam.Role.USER)2665          .content("Check what I have next Monday, then schedule a planning session that avoids any conflicts.")2666          .build());26672668      Message response = client.messages().create(MessageCreateParams.builder()2669          .model(Model.CLAUDE_OPUS_5)2670          .maxTokens(1024L)2671          .addTool(calendarTool)2672          .addTool(listTool)2673          .messages(messages)2674          .build());26752676      while (response.stopReason().isPresent()2677              && response.stopReason().get().equals(StopReason.TOOL_USE)) {2678          // A single response can contain multiple tool_use blocks. Process all of2679          // them and return all results together in one user message.2680          List<ContentBlockParam> toolResults = new ArrayList<>();2681          for (ContentBlock block : response.content()) {2682              if (block.toolUse().isPresent()) {2683                  ToolUseBlock toolUse = block.toolUse().get();2684                  toolResults.add(ContentBlockParam.ofToolResult(2685                      ToolResultBlockParam.builder()2686                          .toolUseId(toolUse.id())2687                          .content(runTool(toolUse))2688                          .build()));2689              }2690          }26912692          messages.add(response.toParam());2693          messages.add(MessageParam.builder()2694              .role(MessageParam.Role.USER)2695              .contentOfBlockParams(toolResults)2696              .build());26972698          response = client.messages().create(MessageCreateParams.builder()2699              .model(Model.CLAUDE_OPUS_5)2700              .maxTokens(1024L)2701              .addTool(calendarTool)2702              .addTool(listTool)2703              .messages(messages)2704              .build());2705      }27062707      response.content().stream()2708          .flatMap(block -> block.text().stream())2709          .forEach(textBlock -> IO.println(textBlock.text()));2710  }2711  ```27122713  ```php PHP2714  <?php27152716  // Ring 3: Multiple tools, parallel calls.27172718  use Anthropic\Client;27192720  $client = new Client();27212722  $tools = [2723      [2724          'name' => 'create_calendar_event',2725          'description' => 'Create a calendar event with attendees and optional recurrence.',2726          'input_schema' => [2727              'type' => 'object',2728              'properties' => [2729                  'title' => ['type' => 'string'],2730                  'start' => ['type' => 'string', 'format' => 'date-time'],2731                  'end' => ['type' => 'string', 'format' => 'date-time'],2732                  'attendees' => [2733                      'type' => 'array',2734                      'items' => ['type' => 'string', 'format' => 'email'],2735                  ],2736                  'recurrence' => [2737                      'type' => 'object',2738                      'properties' => [2739                          'frequency' => ['enum' => ['daily', 'weekly', 'monthly']],2740                          'count' => ['type' => 'integer', 'minimum' => 1],2741                      ],2742                  ],2743              ],2744              'required' => ['title', 'start', 'end'],2745          ],2746      ],2747      [2748          'name' => 'list_calendar_events',2749          'description' => 'List all calendar events on a given date.',2750          'input_schema' => [2751              'type' => 'object',2752              'properties' => [2753                  'date' => ['type' => 'string', 'format' => 'date'],2754              ],2755              'required' => ['date'],2756          ],2757      ],2758  ];27592760  function runTool(string $name, array $input): string2761  {2762      if ($name === 'create_calendar_event') {2763          return json_encode([2764              'event_id' => 'evt_123',2765              'status' => 'created',2766              'title' => $input['title'],2767          ]);2768      }2769      if ($name === 'list_calendar_events') {2770          return json_encode([2771              'events' => [['title' => 'Existing meeting', 'start' => '14:00', 'end' => '15:00']],2772          ]);2773      }27742775      return json_encode(['error' => "Unknown tool: {$name}"]);2776  }27772778  $messages = [2779      [2780          'role' => 'user',2781          'content' => 'Check what I have next Monday, then schedule a planning session that avoids any conflicts.',2782      ],2783  ];27842785  $response = $client->messages->create(2786      model: 'claude-opus-5',2787      maxTokens: 1024,2788      tools: $tools,2789      messages: $messages,2790  );27912792  while ($response->stopReason === 'tool_use') {2793      // A single response can contain multiple tool_use blocks. Process all of2794      // them and return all results together in one user message.2795      $toolResults = [];2796      foreach ($response->content as $block) {2797          if ($block->type === 'tool_use') {2798              $toolResults[] = [2799                  'type' => 'tool_result',2800                  'tool_use_id' => $block->id,2801                  'content' => runTool($block->name, $block->input),2802              ];2803          }2804      }28052806      $messages[] = ['role' => 'assistant', 'content' => $response->content];2807      $messages[] = ['role' => 'user', 'content' => $toolResults];28082809      $response = $client->messages->create(2810          model: 'claude-opus-5',2811          maxTokens: 1024,2812          tools: $tools,2813          messages: $messages,2814      );2815  }28162817  foreach ($response->content as $block) {2818      if ($block->type === 'text') {2819          echo $block->text, "\n";2820      }2821  }2822  ```28232824  ```ruby Ruby2825  # Ring 3: Multiple tools, parallel calls.28262827  require "anthropic"28282829  client = Anthropic::Client.new28302831  tools = [2832    {2833      name: "create_calendar_event",2834      description: "Create a calendar event with attendees and optional recurrence.",2835      input_schema: {2836        type: "object",2837        properties: {2838          title: {type: "string"},2839          start: {type: "string", format: "date-time"},2840          end: {type: "string", format: "date-time"},2841          attendees: {2842            type: "array",2843            items: {type: "string", format: "email"}2844          },2845          recurrence: {2846            type: "object",2847            properties: {2848              frequency: {enum: ["daily", "weekly", "monthly"]},2849              count: {type: "integer", minimum: 1}2850            }2851          }2852        },2853        required: ["title", "start", "end"]2854      }2855    },2856    {2857      name: "list_calendar_events",2858      description: "List all calendar events on a given date.",2859      input_schema: {2860        type: "object",2861        properties: {2862          date: {type: "string", format: "date"}2863        },2864        required: ["date"]2865      }2866    }2867  ]28682869  def run_tool(name, input)2870    case name2871    when "create_calendar_event"2872      JSON.generate({event_id: "evt_123", status: "created", title: input[:title]})2873    when "list_calendar_events"2874      JSON.generate({events: [{title: "Existing meeting", start: "14:00", end: "15:00"}]})2875    else2876      JSON.generate({error: "Unknown tool: #{name}"})2877    end2878  end28792880  messages = [2881    {2882      role: "user",2883      content: "Check what I have next Monday, then schedule a planning session that avoids any conflicts."2884    }2885  ]28862887  response = client.messages.create(2888    model: "claude-opus-5",2889    max_tokens: 1024,2890    tools: tools,2891    messages: messages2892  )28932894  while response.stop_reason == :tool_use2895    # A single response can contain multiple tool_use blocks. Process all of2896    # them and return all results together in one user message.2897    tool_results = response.content.select { |block| block.type == :tool_use }.map do |tool_use|2898      {2899        type: "tool_result",2900        tool_use_id: tool_use.id,2901        content: run_tool(tool_use.name, tool_use.input)2902      }2903    end29042905    messages << {role: "assistant", content: response.content}2906    messages << {role: "user", content: tool_results}29072908    response = client.messages.create(2909      model: "claude-opus-5",2910      max_tokens: 1024,2911      tools: tools,2912      messages: messages2913    )2914  end29152916  response.content.each do |block|2917    puts block.text if block.type == :text2918  end2919  ```2920</CodeGroup>29212922**What to expect**29232924```text Output wrap2925I checked your calendar for next Monday and found an existing meeting from 2pm to 3pm. I've scheduled the planning session for 10am to 11am to avoid the conflict.2926```29272928For more on concurrent execution and ordering guarantees, see [Parallel tool use](https://platform.claude.com/docs/en/agents-and-tools/tool-use/parallel-tool-use).29292930## Ring 4: Error handling29312932Tools fail. A calendar API might reject an event with too many attendees, or a date might be malformed. When a tool raises an error, send the error message back with `is_error: true` instead of crashing. Claude reads the error and can retry with corrected input, ask the user for clarification, or explain the limitation.29332934<CodeGroup>2935  ```bash cURL2936  #!/bin/bash2937  # Ring 4: Error handling.29382939  TOOLS='[2940    {2941      "name": "create_calendar_event",2942      "description": "Create a calendar event with attendees and optional recurrence.",2943      "input_schema": {2944        "type": "object",2945        "properties": {2946          "title": {"type": "string"},2947          "start": {"type": "string", "format": "date-time"},2948          "end": {"type": "string", "format": "date-time"},2949          "attendees": {"type": "array", "items": {"type": "string", "format": "email"}},2950          "recurrence": {2951            "type": "object",2952            "properties": {2953              "frequency": {"enum": ["daily", "weekly", "monthly"]},2954              "count": {"type": "integer", "minimum": 1}2955            }2956          }2957        },2958        "required": ["title", "start", "end"]2959      }2960    },2961    {2962      "name": "list_calendar_events",2963      "description": "List all calendar events on a given date.",2964      "input_schema": {2965        "type": "object",2966        "properties": {"date": {"type": "string", "format": "date"}},2967        "required": ["date"]2968      }2969    }2970  ]'29712972  run_tool() {2973    case "$1" in2974      create_calendar_event)2975        local count=$(echo "$2" | jq '.attendees | length // 0')2976        if [ "$count" -gt 10 ]; then2977          echo "ERROR: Too many attendees (max 10)"2978          return 12979        fi2980        jq -n --arg title "$(echo "$2" | jq -r '.title')" '{event_id: "evt_123", status: "created", title: $title}' ;;2981      list_calendar_events)2982        echo '{"events": [{"title": "Existing meeting", "start": "14:00", "end": "15:00"}]}' ;;2983      *)2984        echo "ERROR: Unknown tool: $1"2985        return 1 ;;2986    esac2987  }29882989  EMAILS=$(seq 0 14 | sed 's/.*/user&@example.com/' | paste -sd, -)2990  MESSAGES="[{\"role\": \"user\", \"content\": \"Schedule an all-hands with everyone: $EMAILS\"}]"29912992  call_api() {2993    curl -s https://api.anthropic.com/v1/messages \2994      -H "x-api-key: $ANTHROPIC_API_KEY" \2995      -H "anthropic-version: 2023-06-01" \2996      -H "content-type: application/json" \2997      -d "$(jq -n --argjson tools "$TOOLS" --argjson messages "$MESSAGES" \2998        '{model: "claude-opus-5", max_tokens: 1024, tools: $tools, messages: $messages}')"2999  }30003001  RESPONSE=$(call_api)30023003  while [ "$(echo "$RESPONSE" | jq -r '.stop_reason')" = "tool_use" ]; do3004    TOOL_RESULTS='[]'3005    while read -r block; do3006      NAME=$(echo "$block" | jq -r '.name')3007      INPUT=$(echo "$block" | jq -c '.input')3008      ID=$(echo "$block" | jq -r '.id')3009      if OUTPUT=$(run_tool "$NAME" "$INPUT"); then3010        TOOL_RESULTS=$(echo "$TOOL_RESULTS" | jq --arg id "$ID" --arg result "$OUTPUT" \3011          '. + [{type: "tool_result", tool_use_id: $id, content: $result}]')3012      else3013        # Signal failure so Claude can retry or ask for clarification.3014        TOOL_RESULTS=$(echo "$TOOL_RESULTS" | jq --arg id "$ID" --arg result "$OUTPUT" \3015          '. + [{type: "tool_result", tool_use_id: $id, content: $result, is_error: true}]')3016      fi3017    done < <(echo "$RESPONSE" | jq -c '.content[] | select(.type == "tool_use")')30183019    MESSAGES=$(echo "$MESSAGES" | jq \3020      --argjson assistant "$(echo "$RESPONSE" | jq '.content')" \3021      --argjson results "$TOOL_RESULTS" \3022      '. + [{role: "assistant", content: $assistant}, {role: "user", content: $results}]')30233024    RESPONSE=$(call_api)3025  done30263027  echo "$RESPONSE" | jq -r '.content[] | select(.type == "text") | .text'3028  ```30293030  ```bash CLI3031  #!/usr/bin/env bash3032  # Ring 4: Error handling.3033  # Uses jq for cross-turn message-array state — building an agentic loop in shell3034  # requires JSON manipulation beyond ant's single-call --transform scope.3035  set -euo pipefail30363037  run_tool() {3038    case "$1" in3039      create_calendar_event)3040        local count3041        count=$(jq '.attendees | length // 0' <<<"$2")3042        if [ "$count" -gt 10 ]; then3043          echo "ERROR: Too many attendees (max 10)"3044          return 13045        fi3046        jq -n --arg title "$(jq -r '.title' <<<"$2")" \3047          '{event_id: "evt_123", status: "created", title: $title}' ;;3048      list_calendar_events)3049        echo '{"events": [{"title": "Existing meeting", "start": "14:00", "end": "15:00"}]}' ;;3050      *)3051        echo "ERROR: Unknown tool: $1"3052        return 1 ;;3053    esac3054  }30553056  EMAILS=$(seq 0 14 | sed 's/.*/user&@example.com/' | paste -sd, -)3057  MESSAGES=$(jq -n --arg msg "Schedule an all-hands with everyone: $EMAILS" \3058    '[{role: "user", content: $msg}]')30593060  call_api() {3061    # ant reads the request body as YAML on stdin: no auth headers, no3062    # hand-built JSON envelope. The static keys (model, tools) live in a3063    # quoted heredoc; the growing messages array is appended as JSON,3064    # which YAML accepts as flow syntax.3065    {3066      cat <<'YAML'3067  model: claude-opus-53068  max_tokens: 10243069  tools:3070    - name: create_calendar_event3071      description: Create a calendar event with attendees and optional recurrence.3072      input_schema:3073        type: object3074        properties:3075          title: {type: string}3076          start: {type: string, format: date-time}3077          end: {type: string, format: date-time}3078          attendees:3079            type: array3080            items: {type: string, format: email}3081          recurrence:3082            type: object3083            properties:3084              frequency: {enum: [daily, weekly, monthly]}3085              count: {type: integer, minimum: 1}3086        required: [title, start, end]3087    - name: list_calendar_events3088      description: List all calendar events on a given date.3089      input_schema:3090        type: object3091        properties:3092          date: {type: string, format: date}3093        required: [date]3094  YAML3095      printf 'messages: %s\n' "$MESSAGES"3096    } | ant messages create --format json3097  }30983099  RESPONSE=$(call_api)31003101  while [ "$(jq -r '.stop_reason' <<<"$RESPONSE")" = "tool_use" ]; do3102    TOOL_RESULTS='[]'3103    while read -r block; do3104      NAME=$(jq -r '.name' <<<"$block")3105      INPUT=$(jq -c '.input' <<<"$block")3106      ID=$(jq -r '.id' <<<"$block")3107      if OUTPUT=$(run_tool "$NAME" "$INPUT"); then3108        TOOL_RESULTS=$(jq --arg id "$ID" --arg result "$OUTPUT" \3109          '. + [{type: "tool_result", tool_use_id: $id, content: $result}]' \3110          <<<"$TOOL_RESULTS")3111      else3112        # Signal failure so Claude can retry or ask for clarification.3113        TOOL_RESULTS=$(jq --arg id "$ID" --arg result "$OUTPUT" \3114          '. + [{type: "tool_result", tool_use_id: $id, content: $result, is_error: true}]' \3115          <<<"$TOOL_RESULTS")3116      fi3117    done < <(jq -c '.content[] | select(.type == "tool_use")' <<<"$RESPONSE")31183119    MESSAGES=$(jq \3120      --argjson assistant "$(jq '.content' <<<"$RESPONSE")" \3121      --argjson results "$TOOL_RESULTS" \3122      '. + [3123        {role: "assistant", content: $assistant},3124        {role: "user", content: $results}3125      ]' <<<"$MESSAGES")31263127    RESPONSE=$(call_api)3128  done31293130  jq -r '.content[] | select(.type == "text") | .text' <<<"$RESPONSE"3131  ```31323133  ```python Python3134  # Ring 4: Error handling.31353136  import json31373138  import anthropic31393140  client = anthropic.Anthropic()31413142  tools = [3143      {3144          "name": "create_calendar_event",3145          "description": "Create a calendar event with attendees and optional recurrence.",3146          "input_schema": {3147              "type": "object",3148              "properties": {3149                  "title": {"type": "string"},3150                  "start": {"type": "string", "format": "date-time"},3151                  "end": {"type": "string", "format": "date-time"},3152                  "attendees": {3153                      "type": "array",3154                      "items": {"type": "string", "format": "email"},3155                  },3156                  "recurrence": {3157                      "type": "object",3158                      "properties": {3159                          "frequency": {"enum": ["daily", "weekly", "monthly"]},3160                          "count": {"type": "integer", "minimum": 1},3161                      },3162                  },3163              },3164              "required": ["title", "start", "end"],3165          },3166      },3167      {3168          "name": "list_calendar_events",3169          "description": "List all calendar events on a given date.",3170          "input_schema": {3171              "type": "object",3172              "properties": {3173                  "date": {"type": "string", "format": "date"},3174              },3175              "required": ["date"],3176          },3177      },3178  ]317931803181  def run_tool(name, tool_input):3182      if name == "create_calendar_event":3183          if "attendees" in tool_input and len(tool_input["attendees"]) > 10:3184              raise ValueError("Too many attendees (max 10)")3185          return {"event_id": "evt_123", "status": "created", "title": tool_input["title"]}3186      if name == "list_calendar_events":3187          return {"events": [{"title": "Existing meeting", "start": "14:00", "end": "15:00"}]}3188      raise ValueError(f"Unknown tool: {name}")318931903191  messages = [3192      {3193          "role": "user",3194          "content": "Schedule an all-hands with everyone: " + ", ".join(f"user{i}@example.com" for i in range(15)),3195      }3196  ]31973198  response = client.messages.create(3199      model="claude-opus-5",3200      max_tokens=1024,3201      tools=tools,3202      messages=messages,3203  )32043205  while response.stop_reason == "tool_use":3206      tool_results = []3207      for block in response.content:3208          if block.type == "tool_use":3209              try:3210                  result = run_tool(block.name, block.input)3211                  tool_results.append(3212                      {"type": "tool_result", "tool_use_id": block.id, "content": json.dumps(result)}3213                  )3214              except Exception as exc:3215                  # Signal failure so Claude can retry or ask for clarification.3216                  tool_results.append(3217                      {3218                          "type": "tool_result",3219                          "tool_use_id": block.id,3220                          "content": str(exc),3221                          "is_error": True,3222                      }3223                  )32243225      messages.append({"role": "assistant", "content": response.content})3226      messages.append({"role": "user", "content": tool_results})32273228      response = client.messages.create(3229          model="claude-opus-5",3230          max_tokens=1024,3231          tools=tools,3232          messages=messages,3233      )32343235  final_text = next(block for block in response.content if block.type == "text")3236  print(final_text.text)3237  ```32383239  ```typescript TypeScript3240  // Ring 4: Error handling.32413242  import Anthropic from "@anthropic-ai/sdk";32433244  const client = new Anthropic();32453246  const tools: Anthropic.Tool[] = [3247    {3248      name: "create_calendar_event",3249      description:3250        "Create a calendar event with attendees and optional recurrence.",3251      input_schema: {3252        type: "object",3253        properties: {3254          title: { type: "string" },3255          start: { type: "string", format: "date-time" },3256          end: { type: "string", format: "date-time" },3257          attendees: {3258            type: "array",3259            items: { type: "string", format: "email" },3260          },3261          recurrence: {3262            type: "object",3263            properties: {3264              frequency: { enum: ["daily", "weekly", "monthly"] },3265              count: { type: "integer", minimum: 1 },3266            },3267          },3268        },3269        required: ["title", "start", "end"],3270      },3271    },3272    {3273      name: "list_calendar_events",3274      description: "List all calendar events on a given date.",3275      input_schema: {3276        type: "object",3277        properties: {3278          date: { type: "string", format: "date" },3279        },3280        required: ["date"],3281      },3282    },3283  ];32843285  function runTool(name: string, input: Record<string, unknown>) {3286    if (name === "create_calendar_event") {3287      const attendees = input.attendees as string[] | undefined;3288      if (attendees && attendees.length > 10) {3289        throw new Error("Too many attendees (max 10)");3290      }3291      return { event_id: "evt_123", status: "created", title: input.title };3292    }3293    if (name === "list_calendar_events") {3294      return {3295        events: [{ title: "Existing meeting", start: "14:00", end: "15:00" }],3296      };3297    }3298    throw new Error(`Unknown tool: ${name}`);3299  }33003301  const emails = Array.from({ length: 15 }, (_, i) => `user${i}@example.com`);3302  const messages: Anthropic.MessageParam[] = [3303    {3304      role: "user",3305      content: `Schedule an all-hands with everyone: ${emails.join(", ")}`,3306    },3307  ];33083309  let response = await client.messages.create({3310    model: "claude-opus-5",3311    max_tokens: 1024,3312    tools,3313    messages,3314  });33153316  while (response.stop_reason === "tool_use") {3317    const toolResults: Anthropic.ToolResultBlockParam[] = [];3318    for (const block of response.content) {3319      if (block.type === "tool_use") {3320        try {3321          const result = runTool(block.name, block.input as Record<string, unknown>);3322          toolResults.push({3323            type: "tool_result",3324            tool_use_id: block.id,3325            content: JSON.stringify(result),3326          });3327        } catch (err) {3328          // Signal failure so Claude can retry or ask for clarification.3329          toolResults.push({3330            type: "tool_result",3331            tool_use_id: block.id,3332            content: String(err),3333            is_error: true,3334          });3335        }3336      }3337    }33383339    messages.push({ role: "assistant", content: response.content });3340    messages.push({ role: "user", content: toolResults });33413342    response = await client.messages.create({3343      model: "claude-opus-5",3344      max_tokens: 1024,3345      tools,3346      messages,3347    });3348  }33493350  for (const block of response.content) {3351    if (block.type === "text") {3352      console.log(block.text);3353    }3354  }3355  ```33563357  ```csharp C#3358  // Ring 4: Error handling.33593360  using System;3361  using System.Collections.Generic;3362  using System.Linq;3363  using System.Text.Json;3364  using System.Threading.Tasks;3365  using Anthropic;3366  using Anthropic.Models.Messages;33673368  AnthropicClient client = new();33693370  List<ToolUnion> tools =3371  [3372      new ToolUnion(new Tool()3373      {3374          Name = "create_calendar_event",3375          Description = "Create a calendar event with attendees and optional recurrence.",3376          InputSchema = new InputSchema()3377          {3378              Properties = new Dictionary<string, JsonElement>3379              {3380                  ["title"] = JsonSerializer.SerializeToElement(new { type = "string" }),3381                  ["start"] = JsonSerializer.SerializeToElement(new { type = "string", format = "date-time" }),3382                  ["end"] = JsonSerializer.SerializeToElement(new { type = "string", format = "date-time" }),3383                  ["attendees"] = JsonSerializer.SerializeToElement(new3384                  {3385                      type = "array",3386                      items = new { type = "string", format = "email" },3387                  }),3388                  ["recurrence"] = JsonSerializer.SerializeToElement(new3389                  {3390                      type = "object",3391                      properties = new3392                      {3393                          frequency = new { @enum = new[] { "daily", "weekly", "monthly" } },3394                          count = new { type = "integer", minimum = 1 },3395                      },3396                  }),3397              },3398              Required = ["title", "start", "end"],3399          },3400      }),3401      new ToolUnion(new Tool()3402      {3403          Name = "list_calendar_events",3404          Description = "List all calendar events on a given date.",3405          InputSchema = new InputSchema()3406          {3407              Properties = new Dictionary<string, JsonElement>3408              {3409                  ["date"] = JsonSerializer.SerializeToElement(new { type = "string", format = "date" }),3410              },3411              Required = ["date"],3412          },3413      }),3414  ];34153416  string RunTool(ToolUseBlock toolUse)3417  {3418      if (toolUse.Name == "create_calendar_event")3419      {3420          if (toolUse.Input.TryGetValue("attendees", out var attendees) && attendees.GetArrayLength() > 10)3421          {3422              throw new InvalidOperationException("Too many attendees (max 10)");3423          }3424          var title = toolUse.Input.TryGetValue("title", out var t) ? t.GetString() : "";3425          return JsonSerializer.Serialize(new { event_id = "evt_123", status = "created", title });3426      }3427      if (toolUse.Name == "list_calendar_events")3428      {3429          return """{"events": [{"title": "Existing meeting", "start": "14:00", "end": "15:00"}]}""";3430      }3431      throw new InvalidOperationException($"Unknown tool: {toolUse.Name}");3432  }34333434  // Build a request that exceeds the tool's attendee limit so the error path runs.3435  var emails = string.Join(", ", Enumerable.Range(0, 15).Select(i => $"user{i}@example.com"));34363437  List<MessageParam> messages =3438  [3439      new() { Role = Role.User, Content = $"Schedule an all-hands with everyone: {emails}" },3440  ];34413442  var response = await client.Messages.Create(new MessageCreateParams3443  {3444      Model = Model.ClaudeOpus5,3445      MaxTokens = 1024,3446      Tools = tools,3447      Messages = messages,3448  });34493450  while (response.StopReason == StopReason.ToolUse)3451  {3452      List<ContentBlockParam> toolResults = [];3453      foreach (var block in response.Content)3454      {3455          if (block.TryPickToolUse(out var toolUse))3456          {3457              ToolResultBlockParam toolResult;3458              try3459              {3460                  toolResult = new ToolResultBlockParam() { ToolUseID = toolUse.ID, Content = RunTool(toolUse) };3461              }3462              catch (Exception e)3463              {3464                  // Signal failure so Claude can retry or ask for clarification.3465                  toolResult = new ToolResultBlockParam()3466                  {3467                      ToolUseID = toolUse.ID,3468                      Content = e.Message,3469                      IsError = true,3470                  };3471              }3472              toolResults.Add(new ContentBlockParam(toolResult));3473          }3474      }34753476      messages.Add(new()3477      {3478          Role = Role.Assistant,3479          Content = response.Content.Select(block => new ContentBlockParam(block.Json)).ToList(),3480      });3481      messages.Add(new() { Role = Role.User, Content = new MessageParamContent(toolResults) });34823483      response = await client.Messages.Create(new MessageCreateParams3484      {3485          Model = Model.ClaudeOpus5,3486          MaxTokens = 1024,3487          Tools = tools,3488          Messages = messages,3489      });3490  }34913492  foreach (var block in response.Content)3493  {3494      if (block.TryPickText(out var text))3495      {3496          Console.WriteLine(text.Text);3497      }3498  }3499  ```35003501  ```go Go3502  // Ring 4: Error handling.35033504  package main35053506  import (3507  	"context"3508  	"encoding/json"3509  	"fmt"3510  	"log"3511  	"strings"35123513  	"github.com/anthropics/anthropic-sdk-go"3514  )35153516  func runTool(name string, input map[string]any) (string, error) {3517  	if name == "create_calendar_event" {3518  		if attendees, ok := input["attendees"].([]any); ok && len(attendees) > 10 {3519  			return "", fmt.Errorf("too many attendees (max 10)")3520  		}3521  		title, _ := input["title"].(string)3522  		return fmt.Sprintf(`{"event_id": "evt_123", "status": "created", "title": %q}`, title), nil3523  	}3524  	if name == "list_calendar_events" {3525  		return `{"events": [{"title": "Existing meeting", "start": "14:00", "end": "15:00"}]}`, nil3526  	}3527  	return "", fmt.Errorf("unknown tool: %s", name)3528  }35293530  func main() {3531  	client := anthropic.NewClient()3532  	ctx := context.Background()35333534  	tools := []anthropic.ToolUnionParam{3535  		{OfTool: &anthropic.ToolParam{3536  			Name:        "create_calendar_event",3537  			Description: anthropic.String("Create a calendar event with attendees and optional recurrence."),3538  			InputSchema: anthropic.ToolInputSchemaParam{3539  				Properties: map[string]any{3540  					"title": map[string]any{"type": "string"},3541  					"start": map[string]any{"type": "string", "format": "date-time"},3542  					"end":   map[string]any{"type": "string", "format": "date-time"},3543  					"attendees": map[string]any{3544  						"type":  "array",3545  						"items": map[string]any{"type": "string", "format": "email"},3546  					},3547  					"recurrence": map[string]any{3548  						"type": "object",3549  						"properties": map[string]any{3550  							"frequency": map[string]any{"enum": []string{"daily", "weekly", "monthly"}},3551  							"count":     map[string]any{"type": "integer", "minimum": 1},3552  						},3553  					},3554  				},3555  				Required: []string{"title", "start", "end"},3556  			},3557  		}},3558  		{OfTool: &anthropic.ToolParam{3559  			Name:        "list_calendar_events",3560  			Description: anthropic.String("List all calendar events on a given date."),3561  			InputSchema: anthropic.ToolInputSchemaParam{3562  				Properties: map[string]any{3563  					"date": map[string]any{"type": "string", "format": "date"},3564  				},3565  				Required: []string{"date"},3566  			},3567  		}},3568  	}35693570  	// Build a request that exceeds the tool's attendee limit so the error path runs.3571  	emails := make([]string, 15)3572  	for i := range emails {3573  		emails[i] = fmt.Sprintf("user%d@example.com", i)3574  	}3575  	messages := []anthropic.MessageParam{3576  		anthropic.NewUserMessage(anthropic.NewTextBlock(3577  			"Schedule an all-hands with everyone: " + strings.Join(emails, ", "),3578  		)),3579  	}35803581  	response, err := client.Messages.New(ctx, anthropic.MessageNewParams{3582  		Model:     anthropic.ModelClaudeOpus5,3583  		MaxTokens: 1024,3584  		Tools:     tools,3585  		Messages:  messages,3586  	})3587  	if err != nil {3588  		log.Fatal(err)3589  	}35903591  	for response.StopReason == "tool_use" {3592  		var toolResults []anthropic.ContentBlockParamUnion3593  		for _, block := range response.Content {3594  			if block.Type == "tool_use" {3595  				var input map[string]any3596  				if err := json.Unmarshal(block.Input, &input); err != nil {3597  					log.Fatal(err)3598  				}3599  				result, toolErr := runTool(block.Name, input)3600  				if toolErr != nil {3601  					// Signal failure so Claude can retry or ask for clarification.3602  					toolResults = append(toolResults, anthropic.NewToolResultBlock(block.ID, toolErr.Error(), true))3603  				} else {3604  					toolResults = append(toolResults, anthropic.NewToolResultBlock(block.ID, result, false))3605  				}3606  			}3607  		}36083609  		var assistantContent []anthropic.ContentBlockParamUnion3610  		for _, block := range response.Content {3611  			assistantContent = append(assistantContent, block.ToParam())3612  		}3613  		messages = append(messages, anthropic.NewAssistantMessage(assistantContent...))3614  		messages = append(messages, anthropic.NewUserMessage(toolResults...))36153616  		response, err = client.Messages.New(ctx, anthropic.MessageNewParams{3617  			Model:     anthropic.ModelClaudeOpus5,3618  			MaxTokens: 1024,3619  			Tools:     tools,3620  			Messages:  messages,3621  		})3622  		if err != nil {3623  			log.Fatal(err)3624  		}3625  	}36263627  	for _, block := range response.Content {3628  		if block.Type == "text" {3629  			fmt.Println(block.Text)3630  		}3631  	}3632  }3633  ```36343635  ```java Java3636  // Ring 4: Error handling.36373638  import com.anthropic.client.AnthropicClient;3639  import com.anthropic.client.okhttp.AnthropicOkHttpClient;3640  import com.anthropic.core.JsonValue;3641  import com.anthropic.models.messages.ContentBlock;3642  import com.anthropic.models.messages.ContentBlockParam;3643  import com.anthropic.models.messages.Message;3644  import com.anthropic.models.messages.MessageCreateParams;3645  import com.anthropic.models.messages.MessageParam;3646  import com.anthropic.models.messages.Model;3647  import com.anthropic.models.messages.StopReason;3648  import com.anthropic.models.messages.Tool;3649  import com.anthropic.models.messages.Tool.InputSchema;3650  import com.anthropic.models.messages.ToolResultBlockParam;3651  import com.anthropic.models.messages.ToolUseBlock;3652  import java.util.ArrayList;3653  import java.util.List;3654  import java.util.Map;3655  import java.util.stream.Collectors;3656  import java.util.stream.IntStream;36573658  String runTool(ToolUseBlock toolUse) {3659      // The raw tool input is a JSON object; read fields out of it as a map.3660      Map<String, JsonValue> input = (Map<String, JsonValue>) toolUse._input().asObject().get();3661      if (toolUse.name().equals("create_calendar_event")) {3662          int attendeeCount = input.containsKey("attendees")3663              ? ((List<?>) input.get("attendees").asArray().get()).size()3664              : 0;3665          if (attendeeCount > 10) {3666              throw new IllegalArgumentException("Too many attendees (max 10)");3667          }3668          String title = input.containsKey("title") ? input.get("title").asStringOrThrow() : "";3669          return "{\"event_id\": \"evt_123\", \"status\": \"created\", \"title\": \"" + title + "\"}";3670      }3671      if (toolUse.name().equals("list_calendar_events")) {3672          return "{\"events\": [{\"title\": \"Existing meeting\", \"start\": \"14:00\", \"end\": \"15:00\"}]}";3673      }3674      throw new IllegalArgumentException("Unknown tool: " + toolUse.name());3675  }36763677  void main() {3678      AnthropicClient client = AnthropicOkHttpClient.fromEnv();36793680      Tool calendarTool = Tool.builder()3681          .name("create_calendar_event")3682          .description("Create a calendar event with attendees and optional recurrence.")3683          .inputSchema(InputSchema.builder()3684              .properties(JsonValue.from(Map.of(3685                  "title", Map.of("type", "string"),3686                  "start", Map.of("type", "string", "format", "date-time"),3687                  "end", Map.of("type", "string", "format", "date-time"),3688                  "attendees", Map.of(3689                      "type", "array",3690                      "items", Map.of("type", "string", "format", "email")3691                  ),3692                  "recurrence", Map.of(3693                      "type", "object",3694                      "properties", Map.of(3695                          "frequency", Map.of("enum", List.of("daily", "weekly", "monthly")),3696                          "count", Map.of("type", "integer", "minimum", 1)3697                      )3698                  )3699              )))3700              .required(List.of("title", "start", "end"))3701              .build())3702          .build();37033704      Tool listTool = Tool.builder()3705          .name("list_calendar_events")3706          .description("List all calendar events on a given date.")3707          .inputSchema(InputSchema.builder()3708              .properties(JsonValue.from(Map.of(3709                  "date", Map.of("type", "string", "format", "date")3710              )))3711              .required(List.of("date"))3712              .build())3713          .build();37143715      // Build a request that exceeds the tool's attendee limit so the error path runs.3716      String emails = IntStream.range(0, 15)3717          .mapToObj(i -> "user" + i + "@example.com")3718          .collect(Collectors.joining(", "));37193720      List<MessageParam> messages = new ArrayList<>();3721      messages.add(MessageParam.builder()3722          .role(MessageParam.Role.USER)3723          .content("Schedule an all-hands with everyone: " + emails)3724          .build());37253726      Message response = client.messages().create(MessageCreateParams.builder()3727          .model(Model.CLAUDE_OPUS_5)3728          .maxTokens(1024L)3729          .addTool(calendarTool)3730          .addTool(listTool)3731          .messages(messages)3732          .build());37333734      while (response.stopReason().isPresent()3735              && response.stopReason().get().equals(StopReason.TOOL_USE)) {3736          List<ContentBlockParam> toolResults = new ArrayList<>();3737          for (ContentBlock block : response.content()) {3738              if (block.toolUse().isPresent()) {3739                  ToolUseBlock toolUse = block.toolUse().get();3740                  ToolResultBlockParam.Builder resultBuilder = ToolResultBlockParam.builder()3741                      .toolUseId(toolUse.id());3742                  try {3743                      resultBuilder.content(runTool(toolUse));3744                  } catch (Exception e) {3745                      // Signal failure so Claude can retry or ask for clarification.3746                      resultBuilder.content(e.getMessage()).isError(true);3747                  }3748                  toolResults.add(ContentBlockParam.ofToolResult(resultBuilder.build()));3749              }3750          }37513752          messages.add(response.toParam());3753          messages.add(MessageParam.builder()3754              .role(MessageParam.Role.USER)3755              .contentOfBlockParams(toolResults)3756              .build());37573758          response = client.messages().create(MessageCreateParams.builder()3759              .model(Model.CLAUDE_OPUS_5)3760              .maxTokens(1024L)3761              .addTool(calendarTool)3762              .addTool(listTool)3763              .messages(messages)3764              .build());3765      }37663767      response.content().stream()3768          .flatMap(block -> block.text().stream())3769          .forEach(textBlock -> IO.println(textBlock.text()));3770  }3771  ```37723773  ```php PHP3774  <?php37753776  // Ring 4: Error handling.37773778  use Anthropic\Client;37793780  $client = new Client();37813782  $tools = [3783      [3784          'name' => 'create_calendar_event',3785          'description' => 'Create a calendar event with attendees and optional recurrence.',3786          'input_schema' => [3787              'type' => 'object',3788              'properties' => [3789                  'title' => ['type' => 'string'],3790                  'start' => ['type' => 'string', 'format' => 'date-time'],3791                  'end' => ['type' => 'string', 'format' => 'date-time'],3792                  'attendees' => [3793                      'type' => 'array',3794                      'items' => ['type' => 'string', 'format' => 'email'],3795                  ],3796                  'recurrence' => [3797                      'type' => 'object',3798                      'properties' => [3799                          'frequency' => ['enum' => ['daily', 'weekly', 'monthly']],3800                          'count' => ['type' => 'integer', 'minimum' => 1],3801                      ],3802                  ],3803              ],3804              'required' => ['title', 'start', 'end'],3805          ],3806      ],3807      [3808          'name' => 'list_calendar_events',3809          'description' => 'List all calendar events on a given date.',3810          'input_schema' => [3811              'type' => 'object',3812              'properties' => [3813                  'date' => ['type' => 'string', 'format' => 'date'],3814              ],3815              'required' => ['date'],3816          ],3817      ],3818  ];38193820  function runTool(string $name, array $input): string3821  {3822      if ($name === 'create_calendar_event') {3823          if (count($input['attendees'] ?? []) > 10) {3824              throw new InvalidArgumentException('Too many attendees (max 10)');3825          }38263827          return json_encode([3828              'event_id' => 'evt_123',3829              'status' => 'created',3830              'title' => $input['title'],3831          ]);3832      }3833      if ($name === 'list_calendar_events') {3834          return json_encode([3835              'events' => [['title' => 'Existing meeting', 'start' => '14:00', 'end' => '15:00']],3836          ]);3837      }38383839      throw new InvalidArgumentException("Unknown tool: {$name}");3840  }38413842  // Build a request that exceeds the tool's attendee limit so the error path runs.3843  $emails = array_map(fn (int $i): string => "user{$i}@example.com", range(0, 14));3844  $messages = [3845      [3846          'role' => 'user',3847          'content' => 'Schedule an all-hands with everyone: ' . implode(', ', $emails),3848      ],3849  ];38503851  $response = $client->messages->create(3852      model: 'claude-opus-5',3853      maxTokens: 1024,3854      tools: $tools,3855      messages: $messages,3856  );38573858  while ($response->stopReason === 'tool_use') {3859      $toolResults = [];3860      foreach ($response->content as $block) {3861          if ($block->type === 'tool_use') {3862              try {3863                  $toolResults[] = [3864                      'type' => 'tool_result',3865                      'tool_use_id' => $block->id,3866                      'content' => runTool($block->name, $block->input),3867                  ];3868              } catch (Exception $e) {3869                  // Signal failure so Claude can retry or ask for clarification.3870                  $toolResults[] = [3871                      'type' => 'tool_result',3872                      'tool_use_id' => $block->id,3873                      'content' => $e->getMessage(),3874                      'is_error' => true,3875                  ];3876              }3877          }3878      }38793880      $messages[] = ['role' => 'assistant', 'content' => $response->content];3881      $messages[] = ['role' => 'user', 'content' => $toolResults];38823883      $response = $client->messages->create(3884          model: 'claude-opus-5',3885          maxTokens: 1024,3886          tools: $tools,3887          messages: $messages,3888      );3889  }38903891  foreach ($response->content as $block) {3892      if ($block->type === 'text') {3893          echo $block->text, "\n";3894      }3895  }3896  ```38973898  ```ruby Ruby3899  # Ring 4: Error handling.39003901  require "anthropic"39023903  client = Anthropic::Client.new39043905  tools = [3906    {3907      name: "create_calendar_event",3908      description: "Create a calendar event with attendees and optional recurrence.",3909      input_schema: {3910        type: "object",3911        properties: {3912          title: {type: "string"},3913          start: {type: "string", format: "date-time"},3914          end: {type: "string", format: "date-time"},3915          attendees: {3916            type: "array",3917            items: {type: "string", format: "email"}3918          },3919          recurrence: {3920            type: "object",3921            properties: {3922              frequency: {enum: ["daily", "weekly", "monthly"]},3923              count: {type: "integer", minimum: 1}3924            }3925          }3926        },3927        required: ["title", "start", "end"]3928      }3929    },3930    {3931      name: "list_calendar_events",3932      description: "List all calendar events on a given date.",3933      input_schema: {3934        type: "object",3935        properties: {3936          date: {type: "string", format: "date"}3937        },3938        required: ["date"]3939      }3940    }3941  ]39423943  def run_tool(name, input)3944    case name3945    when "create_calendar_event"3946      attendees = input[:attendees]3947      raise ArgumentError, "Too many attendees (max 10)" if attendees && attendees.length > 103948      JSON.generate({event_id: "evt_123", status: "created", title: input[:title]})3949    when "list_calendar_events"3950      JSON.generate({events: [{title: "Existing meeting", start: "14:00", end: "15:00"}]})3951    else3952      raise ArgumentError, "Unknown tool: #{name}"3953    end3954  end39553956  # Build a request that exceeds the tool's attendee limit so the error path runs.3957  emails = (0...15).map { |i| "user#{i}@example.com" }3958  messages = [3959    {3960      role: "user",3961      content: "Schedule an all-hands with everyone: #{emails.join(", ")}"3962    }3963  ]39643965  response = client.messages.create(3966    model: "claude-opus-5",3967    max_tokens: 1024,3968    tools: tools,3969    messages: messages3970  )39713972  while response.stop_reason == :tool_use3973    tool_results = response.content.select { |block| block.type == :tool_use }.map do |tool_use|3974      begin3975        {3976          type: "tool_result",3977          tool_use_id: tool_use.id,3978          content: run_tool(tool_use.name, tool_use.input)3979        }3980      rescue => e3981        # Signal failure so Claude can retry or ask for clarification.3982        {3983          type: "tool_result",3984          tool_use_id: tool_use.id,3985          content: e.message,3986          is_error: true3987        }3988      end3989    end39903991    messages << {role: "assistant", content: response.content}3992    messages << {role: "user", content: tool_results}39933994    response = client.messages.create(3995      model: "claude-opus-5",3996      max_tokens: 1024,3997      tools: tools,3998      messages: messages3999    )4000  end40014002  response.content.each do |block|4003    puts block.text if block.type == :text4004  end4005  ```4006</CodeGroup>40074008**What to expect**40094010```text Output wrap4011I tried to schedule the all-hands but the calendar only allows 10 attendees per event. I can split this into two sessions, or you can let me know which 10 people to prioritize.4012```40134014The `is_error` flag is the only difference from a successful result. Claude sees the flag and the error text, and responds accordingly. See [Handle tool calls](https://platform.claude.com/docs/en/agents-and-tools/tool-use/handle-tool-calls) for the full error-handling reference.40154016## Ring 5: The Tool Runner SDK abstraction40174018Rings 2 through 4 wrote the same loop by hand: call the API, check `stop_reason`, run tools, append results, repeat. The Tool Runner does this for you. Define each tool as a function, pass the list to `tool_runner`, and retrieve the final message once the loop completes. Error wrapping, result formatting, and conversation management are handled internally.40194020Each SDK provides a helper that turns an ordinary function into a runnable tool and derives the input schema from its signature; the tabs below show the idiomatic form for each language.40214022<Note>4023  Tool Runner is available in all seven SDKs: Python, TypeScript, C#, Go, Java, PHP, and Ruby. See [Tool Runner](https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-runner) for the full reference. The cURL and CLI tabs show a note instead of code; keep the Ring 4 loop for curl- or CLI-based scripts.4024</Note>40254026<CodeGroup>4027  ```bash cURL4028  #!/bin/bash4029  # Ring 5: The Tool Runner SDK abstraction.40304031  # The Tool Runner SDK abstraction is available in all seven SDKs: Python,4032  # TypeScript, C#, Go, Java, PHP, and Ruby. There is no equivalent for raw4033  # curl requests. Switch to any SDK tab to see Ring 5, or keep the Ring 44034  # loop as your shell implementation.4035  ```40364037  ```bash CLI4038  #!/usr/bin/env bash4039  # Ring 5: The Tool Runner SDK abstraction.4040  set -euo pipefail40414042  # The Tool Runner SDK abstraction is available in all seven SDKs: Python,4043  # TypeScript, C#, Go, Java, PHP, and Ruby. The ant CLI exposes the Messages4044  # API directly and has no equivalent helper. Switch to any SDK tab to see4045  # Ring 5, or keep the Ring 4 loop as your CLI implementation.4046  ```40474048  ```python Python4049  # Ring 5: The Tool Runner SDK abstraction.40504051  import json40524053  import anthropic4054  from anthropic import beta_tool40554056  client = anthropic.Anthropic()405740584059  @beta_tool4060  def create_calendar_event(4061      title: str,4062      start: str,4063      end: str,4064      attendees: list[str] | None = None,4065      recurrence: dict | None = None,4066  ) -> str:4067      """Create a calendar event with attendees and optional recurrence.40684069      Args:4070          title: Event title.4071          start: Start time in ISO 8601 format.4072          end: End time in ISO 8601 format.4073          attendees: Email addresses to invite.4074          recurrence: Dict with 'frequency' (daily, weekly, monthly) and 'count'.4075      """4076      if attendees and len(attendees) > 10:4077          raise ValueError("Too many attendees (max 10)")4078      return json.dumps({"event_id": "evt_123", "status": "created", "title": title})407940804081  @beta_tool4082  def list_calendar_events(date: str) -> str:4083      """List all calendar events on a given date.40844085      Args:4086          date: Date in YYYY-MM-DD format.4087      """4088      return json.dumps({"events": [{"title": "Existing meeting", "start": "14:00", "end": "15:00"}]})408940904091  final_message = client.beta.messages.tool_runner(4092      model="claude-opus-5",4093      max_tokens=1024,4094      tools=[create_calendar_event, list_calendar_events],4095      messages=[4096          {4097              "role": "user",4098              "content": "Check what I have next Monday, then schedule a planning session that avoids any conflicts.",4099          }4100      ],4101  ).until_done()41024103  for block in final_message.content:4104      if block.type == "text":4105          print(block.text)4106  ```41074108  ```typescript TypeScript4109  // Ring 5: The Tool Runner SDK abstraction.41104111  import Anthropic from "@anthropic-ai/sdk";4112  import { betaZodTool } from "@anthropic-ai/sdk/helpers/beta/zod";4113  import { z } from "zod";41144115  const client = new Anthropic();41164117  const createCalendarEvent = betaZodTool({4118    name: "create_calendar_event",4119    description:4120      "Create a calendar event with attendees and optional recurrence.",4121    inputSchema: z.object({4122      title: z.string(),4123      start: z.string().datetime(),4124      end: z.string().datetime(),4125      attendees: z.array(z.string().email()).optional(),4126      recurrence: z4127        .object({4128          frequency: z.enum(["daily", "weekly", "monthly"]),4129          count: z.number().int().min(1),4130        })4131        .optional(),4132    }),4133    run: async (input) => {4134      if (input.attendees && input.attendees.length > 10) {4135        throw new Error("Too many attendees (max 10)");4136      }4137      return JSON.stringify({4138        event_id: "evt_123",4139        status: "created",4140        title: input.title,4141      });4142    },4143  });41444145  const listCalendarEvents = betaZodTool({4146    name: "list_calendar_events",4147    description: "List all calendar events on a given date.",4148    inputSchema: z.object({4149      date: z.string().date(),4150    }),4151    run: async () => {4152      return JSON.stringify({4153        events: [{ title: "Existing meeting", start: "14:00", end: "15:00" }],4154      });4155    },4156  });41574158  const finalMessage = await client.beta.messages.toolRunner({4159    model: "claude-opus-5",4160    max_tokens: 1024,4161    tools: [createCalendarEvent, listCalendarEvents],4162    messages: [4163      {4164        role: "user",4165        content:4166          "Check what I have next Monday, then schedule a planning session that avoids any conflicts.",4167      },4168    ],4169  });41704171  for (const block of finalMessage.content) {4172    if (block.type === "text") {4173      console.log(block.text);4174    }4175  }4176  ```41774178  ```csharp C#4179  // Ring 5: The Tool Runner SDK abstraction.41804181  using System;4182  using System.Collections.Generic;4183  using System.Text.Json;4184  using System.Threading.Tasks;4185  using Anthropic;4186  using Anthropic.Helpers.Beta;4187  using Anthropic.Models.Beta.Messages;4188  using MessageCreateParams = Anthropic.Models.Beta.Messages.MessageCreateParams;4189  using InputSchema = Anthropic.Models.Beta.Messages.InputSchema;4190  using Role = Anthropic.Models.Beta.Messages.Role;4191  using Model = Anthropic.Models.Messages.Model;41924193  AnthropicClient client = new();41944195  // Define each tool as a runnable tool: the definition carries the JSON Schema4196  // and the Run callback holds the implementation. Throwing an exception sends4197  // the message back to Claude as a tool result with is_error set.4198  var createCalendarEvent = new BetaRunnableTool4199  {4200      Name = "create_calendar_event",4201      Definition = new BetaTool4202      {4203          Name = "create_calendar_event",4204          Description = "Create a calendar event with attendees and optional recurrence.",4205          InputSchema = new InputSchema4206          {4207              Properties = new Dictionary<string, JsonElement>4208              {4209                  ["title"] = JsonSerializer.SerializeToElement(new { type = "string", description = "Event title" }),4210                  ["start"] = JsonSerializer.SerializeToElement(new { type = "string", description = "Start time in ISO 8601 format" }),4211                  ["end"] = JsonSerializer.SerializeToElement(new { type = "string", description = "End time in ISO 8601 format" }),4212                  ["attendees"] = JsonSerializer.SerializeToElement(new4213                  {4214                      type = "array",4215                      items = new { type = "string" },4216                      description = "Email addresses to invite",4217                  }),4218                  ["recurrence"] = JsonSerializer.SerializeToElement(new4219                  {4220                      type = "object",4221                      properties = new4222                      {4223                          frequency = new { @enum = new[] { "daily", "weekly", "monthly" } },4224                          count = new { type = "integer", minimum = 1 },4225                      },4226                  }),4227              },4228              Required = ["title", "start", "end"],4229          },4230      },4231      Run = (toolUse, _) =>4232      {4233          if (toolUse.Input.TryGetValue("attendees", out var attendees) && attendees.GetArrayLength() > 10)4234          {4235              throw new InvalidOperationException("Too many attendees (max 10)");4236          }4237          var title = toolUse.Input.TryGetValue("title", out var t) ? t.GetString() : "";4238          return Task.FromResult<BetaToolResultBlockParamContent>(4239              JsonSerializer.Serialize(new { event_id = "evt_123", status = "created", title })4240          );4241      },4242  };42434244  var listCalendarEvents = new BetaRunnableTool4245  {4246      Name = "list_calendar_events",4247      Definition = new BetaTool4248      {4249          Name = "list_calendar_events",4250          Description = "List all calendar events on a given date.",4251          InputSchema = new InputSchema4252          {4253              Properties = new Dictionary<string, JsonElement>4254              {4255                  ["date"] = JsonSerializer.SerializeToElement(new { type = "string", description = "Date in YYYY-MM-DD format" }),4256              },4257              Required = ["date"],4258          },4259      },4260      Run = (toolUse, _) => Task.FromResult<BetaToolResultBlockParamContent>(4261          """{"events": [{"title": "Existing meeting", "start": "14:00", "end": "15:00"}]}"""4262      ),4263  };42644265  // The runner calls the API, runs requested tools, and feeds results back4266  // until Claude produces a final answer.4267  var runner = client.Beta.Messages.ToolRunner(4268      new MessageCreateParams4269      {4270          Model = Model.ClaudeOpus5,4271          MaxTokens = 1024,4272          Messages =4273          [4274              new()4275              {4276                  Role = Role.User,4277                  Content = "Check what I have next Monday, then schedule a planning session that avoids any conflicts.",4278              },4279          ],4280      },4281      [createCalendarEvent, listCalendarEvents]4282  );42834284  BetaMessage? finalMessage = null;4285  await foreach (var message in runner)4286  {4287      finalMessage = message;4288  }42894290  foreach (var block in finalMessage!.Content)4291  {4292      if (block.TryPickText(out var text))4293      {4294          Console.WriteLine(text.Text);4295      }4296  }4297  ```42984299  ```go Go4300  // Ring 5: The Tool Runner SDK abstraction.43014302  package main43034304  import (4305  	"context"4306  	"fmt"4307  	"log"43084309  	"github.com/anthropics/anthropic-sdk-go"4310  	"github.com/anthropics/anthropic-sdk-go/toolrunner"4311  )43124313  // The input structs define each tool's schema. The tool runner generates the4314  // JSON Schema from the struct fields and their jsonschema tags.4315  type RecurrenceInput struct {4316  	Frequency string `json:"frequency,omitempty" jsonschema:"enum=daily,enum=weekly,enum=monthly,description=How often the event repeats"`4317  	Count     int    `json:"count,omitempty" jsonschema:"description=Number of occurrences"`4318  }43194320  type CreateCalendarEventInput struct {4321  	Title      string           `json:"title" jsonschema:"required,description=Event title"`4322  	Start      string           `json:"start" jsonschema:"required,description=Start time in ISO 8601 format"`4323  	End        string           `json:"end" jsonschema:"required,description=End time in ISO 8601 format"`4324  	Attendees  []string         `json:"attendees,omitempty" jsonschema:"description=Email addresses to invite"`4325  	Recurrence *RecurrenceInput `json:"recurrence,omitempty"`4326  }43274328  type ListCalendarEventsInput struct {4329  	Date string `json:"date" jsonschema:"required,description=Date in YYYY-MM-DD format"`4330  }43314332  func main() {4333  	client := anthropic.NewClient()4334  	ctx := context.Background()43354336  	// Define each tool as a handler function. Returning an error sends the4337  	// message back to Claude as a tool result with is_error set.4338  	createCalendarEvent, err := toolrunner.NewBetaToolFromJSONSchema(4339  		"create_calendar_event",4340  		"Create a calendar event with attendees and optional recurrence.",4341  		func(ctx context.Context, input CreateCalendarEventInput) (anthropic.BetaToolResultBlockParamContentUnion, error) {4342  			if len(input.Attendees) > 10 {4343  				return anthropic.BetaToolResultBlockParamContentUnion{}, fmt.Errorf("too many attendees (max 10)")4344  			}4345  			return anthropic.BetaToolResultBlockParamContentUnion{4346  				OfText: &anthropic.BetaTextBlockParam{4347  					Text: fmt.Sprintf(`{"event_id": "evt_123", "status": "created", "title": %q}`, input.Title),4348  				},4349  			}, nil4350  		},4351  	)4352  	if err != nil {4353  		log.Fatal(err)4354  	}43554356  	listCalendarEvents, err := toolrunner.NewBetaToolFromJSONSchema(4357  		"list_calendar_events",4358  		"List all calendar events on a given date.",4359  		func(ctx context.Context, input ListCalendarEventsInput) (anthropic.BetaToolResultBlockParamContentUnion, error) {4360  			return anthropic.BetaToolResultBlockParamContentUnion{4361  				OfText: &anthropic.BetaTextBlockParam{4362  					Text: `{"events": [{"title": "Existing meeting", "start": "14:00", "end": "15:00"}]}`,4363  				},4364  			}, nil4365  		},4366  	)4367  	if err != nil {4368  		log.Fatal(err)4369  	}43704371  	// The runner calls the API, runs requested tools, and feeds results back4372  	// until Claude produces a final answer.4373  	runner := client.Beta.Messages.NewToolRunner(4374  		[]anthropic.BetaTool{createCalendarEvent, listCalendarEvents},4375  		anthropic.BetaToolRunnerParams{4376  			BetaMessageNewParams: anthropic.BetaMessageNewParams{4377  				Model:     anthropic.ModelClaudeOpus5,4378  				MaxTokens: 1024,4379  				Messages: []anthropic.BetaMessageParam{4380  					anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock(4381  						"Check what I have next Monday, then schedule a planning session that avoids any conflicts.",4382  					)),4383  				},4384  			},4385  		},4386  	)43874388  	var finalMessage *anthropic.BetaMessage4389  	for message, err := range runner.All(ctx) {4390  		if err != nil {4391  			log.Fatal(err)4392  		}4393  		finalMessage = message4394  	}43954396  	for _, block := range finalMessage.Content {4397  		if block.Type == "text" {4398  			fmt.Println(block.Text)4399  		}4400  	}4401  }4402  ```44034404  ```java Java4405  // Ring 5: The Tool Runner SDK abstraction.44064407  import com.anthropic.client.AnthropicClient;4408  import com.anthropic.client.okhttp.AnthropicOkHttpClient;4409  import com.anthropic.helpers.BetaToolRunner;4410  import com.anthropic.models.beta.messages.BetaMessage;4411  import com.anthropic.models.beta.messages.MessageCreateParams;4412  import com.anthropic.models.messages.Model;4413  import com.fasterxml.jackson.annotation.JsonClassDescription;4414  import com.fasterxml.jackson.annotation.JsonPropertyDescription;4415  import java.util.List;4416  import java.util.function.Supplier;44174418  // Define each tool as a class: the fields describe the input schema, and the4419  // get() method holds the implementation. Throwing an exception sends the4420  // message back to Claude as a tool result with is_error set.4421  @JsonClassDescription("Create a calendar event with attendees.")4422  static class CreateCalendarEvent implements Supplier<String> {4423      @JsonPropertyDescription("Event title")4424      public String title;44254426      @JsonPropertyDescription("Start time in ISO 8601 format")4427      public String start;44284429      @JsonPropertyDescription("End time in ISO 8601 format")4430      public String end;44314432      @JsonPropertyDescription("Email addresses to invite")4433      public List<String> attendees;44344435      @Override4436      public String get() {4437          if (attendees != null && attendees.size() > 10) {4438              throw new IllegalArgumentException("Too many attendees (max 10)");4439          }4440          return "{\"event_id\": \"evt_123\", \"status\": \"created\", \"title\": \"" + title + "\"}";4441      }4442  }44434444  @JsonClassDescription("List all calendar events on a given date.")4445  static class ListCalendarEvents implements Supplier<String> {4446      @JsonPropertyDescription("Date in YYYY-MM-DD format")4447      public String date;44484449      @Override4450      public String get() {4451          return "{\"events\": [{\"title\": \"Existing meeting\", \"start\": \"14:00\", \"end\": \"15:00\"}]}";4452      }4453  }44544455  void main() {4456      AnthropicClient client = AnthropicOkHttpClient.fromEnv();44574458      // The runner calls the API, runs requested tools, and feeds results back4459      // until Claude produces a final answer.4460      BetaToolRunner runner = client.beta()4461              .messages()4462              .toolRunner(MessageCreateParams.builder()4463                      .model(Model.CLAUDE_OPUS_5)4464                      .maxTokens(1024)4465                      .addBeta("structured-outputs-2025-11-13")4466                      .addUserMessage("Check what I have next Monday, then schedule a planning session that avoids any conflicts.")4467                      .addTool(CreateCalendarEvent.class)4468                      .addTool(ListCalendarEvents.class)4469                      .build());44704471      BetaMessage finalMessage = null;4472      for (BetaMessage message : runner) {4473          finalMessage = message;4474      }44754476      finalMessage.content().stream()4477          .flatMap(block -> block.text().stream())4478          .forEach(textBlock -> IO.println(textBlock.text()));4479  }4480  ```44814482  ```php PHP4483  <?php44844485  // Ring 5: The Tool Runner SDK abstraction.44864487  use Anthropic\Client;4488  use Anthropic\Lib\Tools\BetaRunnableTool;4489  use Anthropic\Messages\Model;44904491  $client = new Client();44924493  // Define each tool as a runnable tool: the definition carries the JSON Schema4494  // and the run closure holds the implementation. Throwing an exception sends the4495  // message back to Claude as a tool result with is_error set.4496  $createCalendarEvent = new BetaRunnableTool(4497      definition: [4498          'name' => 'create_calendar_event',4499          'description' => 'Create a calendar event with attendees and optional recurrence.',4500          'input_schema' => [4501              'type' => 'object',4502              'properties' => [4503                  'title' => ['type' => 'string', 'description' => 'Event title'],4504                  'start' => ['type' => 'string', 'description' => 'Start time in ISO 8601 format'],4505                  'end' => ['type' => 'string', 'description' => 'End time in ISO 8601 format'],4506                  'attendees' => [4507                      'type' => 'array',4508                      'items' => ['type' => 'string'],4509                      'description' => 'Email addresses to invite',4510                  ],4511                  'recurrence' => [4512                      'type' => 'object',4513                      'properties' => [4514                          'frequency' => ['enum' => ['daily', 'weekly', 'monthly']],4515                          'count' => ['type' => 'integer', 'minimum' => 1],4516                      ],4517                  ],4518              ],4519              'required' => ['title', 'start', 'end'],4520          ],4521      ],4522      run: function (array $input): string {4523          if (count($input['attendees'] ?? []) > 10) {4524              throw new InvalidArgumentException('Too many attendees (max 10)');4525          }45264527          return json_encode([4528              'event_id' => 'evt_123',4529              'status' => 'created',4530              'title' => $input['title'],4531          ]);4532      },4533  );45344535  $listCalendarEvents = new BetaRunnableTool(4536      definition: [4537          'name' => 'list_calendar_events',4538          'description' => 'List all calendar events on a given date.',4539          'input_schema' => [4540              'type' => 'object',4541              'properties' => [4542                  'date' => ['type' => 'string', 'description' => 'Date in YYYY-MM-DD format'],4543              ],4544              'required' => ['date'],4545          ],4546      ],4547      run: fn (array $input): string => json_encode([4548          'events' => [['title' => 'Existing meeting', 'start' => '14:00', 'end' => '15:00']],4549      ]),4550  );45514552  // The runner calls the API, runs requested tools, and feeds results back4553  // until Claude produces a final answer.4554  $runner = $client->beta->messages->toolRunner(4555      maxTokens: 1024,4556      messages: [4557          [4558              'role' => 'user',4559              'content' => 'Check what I have next Monday, then schedule a planning session that avoids any conflicts.',4560          ],4561      ],4562      model: Model::CLAUDE_OPUS_5,4563      tools: [$createCalendarEvent, $listCalendarEvents],4564  );45654566  $finalMessage = null;4567  foreach ($runner as $message) {4568      $finalMessage = $message;4569  }45704571  foreach ($finalMessage->content as $block) {4572      if ($block->type === 'text') {4573          echo $block->text, "\n";4574      }4575  }4576  ```45774578  ```ruby Ruby4579  # Ring 5: The Tool Runner SDK abstraction.45804581  require "anthropic"45824583  client = Anthropic::Client.new45844585  # Define each tool as a class: a typed input model describes the schema, and4586  # the call method holds the implementation. Raising an error sends the message4587  # back to Claude as a tool result with is_error set.4588  class RecurrenceInput < Anthropic::BaseModel4589    optional :frequency, Anthropic::InputSchema::EnumOf["daily", "weekly", "monthly"],4590             doc: "How often the event repeats"4591    optional :count, Integer, doc: "Number of occurrences"4592  end45934594  class CreateCalendarEventInput < Anthropic::BaseModel4595    required :title, String, doc: "Event title"4596    required :start, String, doc: "Start time in ISO 8601 format"4597    required :end, String, doc: "End time in ISO 8601 format"4598    optional :attendees, Anthropic::InputSchema::ArrayOf[String], doc: "Email addresses to invite"4599    optional :recurrence, RecurrenceInput, doc: "Optional recurrence rule"4600  end46014602  class CreateCalendarEvent < Anthropic::BaseTool4603    doc "Create a calendar event with attendees and optional recurrence."4604    input_schema CreateCalendarEventInput46054606    def call(input)4607      raise ArgumentError, "Too many attendees (max 10)" if input.attendees && input.attendees.length > 104608      JSON.generate({event_id: "evt_123", status: "created", title: input.title})4609    end4610  end46114612  class ListCalendarEventsInput < Anthropic::BaseModel4613    required :date, String, doc: "Date in YYYY-MM-DD format"4614  end46154616  class ListCalendarEvents < Anthropic::BaseTool4617    doc "List all calendar events on a given date."4618    input_schema ListCalendarEventsInput46194620    def call(input)4621      JSON.generate({events: [{title: "Existing meeting", start: "14:00", end: "15:00"}]})4622    end4623  end46244625  # The runner calls the API, runs requested tools, and feeds results back4626  # until Claude produces a final answer.4627  runner = client.beta.messages.tool_runner(4628    model: "claude-opus-5",4629    max_tokens: 1024,4630    tools: [CreateCalendarEvent.new, ListCalendarEvents.new],4631    messages: [4632      {4633        role: "user",4634        content: "Check what I have next Monday, then schedule a planning session that avoids any conflicts."4635      }4636    ]4637  )46384639  final_message = nil4640  runner.each_message { |message| final_message = message }46414642  final_message.content.each do |block|4643    puts block.text if block.type == :text4644  end4645  ```4646</CodeGroup>46474648**What to expect**46494650```text Output wrap4651I checked your calendar for next Monday and found an existing meeting from 2pm to 3pm. I've scheduled the planning session for 10am to 11am to avoid the conflict.4652```46534654The output is identical to Ring 3. The difference is in the code: roughly half the lines, no manual loop, and the schema lives next to the implementation.46554656## What you built46574658You started with a single hardcoded tool call and ended with a production-shaped agent that handles multiple tools, parallel calls, and errors, then collapsed all of that into the Tool Runner. Along the way you saw every piece of the tool-use protocol: `tool_use` blocks, `tool_result` blocks, `tool_use_id` matching, `stop_reason` checking, and `is_error` signaling.46594660## Next steps46614662<CardGroup>4663  <Card href="https://platform.claude.com/docs/en/agents-and-tools/tool-use/define-tools" title="Define tools">4664    Schema specification and best practices.4665  </Card>46664667  <Card href="https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-runner" title="Tool Runner">4668    The full SDK abstraction reference.4669  </Card>46704671  <Card href="https://platform.claude.com/docs/en/agents-and-tools/tool-use/troubleshooting-tool-use" title="Troubleshooting">4672    Fix common tool-use errors.4673  </Card>4674</CardGroup>4675
Files main
Menu

Theme