Skip to main content

Handling tool_use Blocks and Returning Results

14/28
Chapter 4 Tool Use and Function Calling

Handling tool_use Blocks and Returning Results

22 min read Lesson 14 / 28

Handling tool_use Blocks and Returning Results

When Claude decides to use a tool, it returns a tool_use content block instead of text. Your code must execute the tool and return the result to continue the conversation.

Parsing tool_use Blocks

import anthropic
import json

client = anthropic.Anthropic()

def get_weather(city: str, units: str = "celsius") -> str:
    """Mock weather function — replace with real API."""
    return json.dumps({
        "city": city,
        "temperature": 18,
        "units": units,
        "conditions": "Partly cloudy",
        "humidity": 72,
    })

tools = [
    {
        "name": "get_weather",
        "description": "Get current weather for a city.",
        "input_schema": {
            "type": "object",
            "properties": {
                "city": {"type": "string"},
                "units": {"type": "string", "enum": ["celsius", "fahrenheit"]},
            },
            "required": ["city"],
        },
    }
]

# Step 1: Send initial message
messages = [{"role": "user", "content": "What is the weather in Paris?"}]

response = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=1024,
    tools=tools,
    messages=messages,
)

# Step 2: Handle tool_use
if response.stop_reason == "tool_use":
    tool_results = []

    for block in response.content:
        if block.type == "tool_use":
            tool_name = block.name
            tool_input = block.input
            tool_use_id = block.id

            # Execute the actual tool
            if tool_name == "get_weather":
                result = get_weather(**tool_input)
            else:
                result = json.dumps({"error": f"Unknown tool: {tool_name}"})

            tool_results.append({
                "type": "tool_result",
                "tool_use_id": tool_use_id,
                "content": result,
            })

    # Step 3: Append assistant response and tool results
    messages.append({"role": "assistant", "content": response.content})
    messages.append({"role": "user", "content": tool_results})

    # Step 4: Continue the conversation
    final_response = client.messages.create(
        model="claude-sonnet-4-5",
        max_tokens=1024,
        tools=tools,
        messages=messages,
    )

    print(final_response.content[0].text)
    # "The current weather in Paris is 18°C (partly cloudy) with 72% humidity."

Handling Tool Errors Gracefully

def safe_tool_call(tool_name: str, tool_input: dict) -> str:
    try:
        if tool_name == "get_weather":
            return get_weather(**tool_input)
        elif tool_name == "search_web":
            return search_web(**tool_input)
        else:
            return json.dumps({"error": f"Tool '{tool_name}' not implemented"})
    except Exception as e:
        # Return error as string — Claude will handle it gracefully
        return json.dumps({"error": str(e), "tool": tool_name})

JavaScript Implementation

async function handleToolUse(response, tools) {
  if (response.stop_reason !== "tool_use") return response;

  const toolResults = [];

  for (const block of response.content) {
    if (block.type === "tool_use") {
      let result;
      try {
        result = await executeToolByName(block.name, block.input);
      } catch (err) {
        result = JSON.stringify({ error: err.message });
      }

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

  return toolResults;
}

Always return a tool_result for every tool_use block. Claude expects a one-to-one correspondence.