Claude Academy
Sign in

Tool Use (Function Calling)

The loop

Tool use is a multi-turn loop driven by stop_reason:

flowchart LR
    Start([User request]) --> Req[messages.create<br/>with tools schema]
    Req --> SR{stop_reason?}
    SR -- end_turn --> Done([Return to user])
    SR -- tool_use --> Run[Run each tool_use block]
    Run --> Append[Append assistant turn + user turn with tool_result blocks]
    Append --> Req
  1. Send messages + tools schema.
  2. Claude returns content with one or more tool_use blocks; stop_reason = "tool_use".
  3. Execute each tool.
  4. Append the assistant turn (with tool_use blocks) and a user turn containing tool_result blocks.
  5. Loop until stop_reason = "end_turn".

Skeleton:

messages = [{"role": "user", "content": "What's the weather in Berlin?"}]

while True:
    resp = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1024,
        tools=[weather_tool_schema],
        messages=messages,
    )

    messages.append({"role": "assistant", "content": resp.content})

    if resp.stop_reason != "tool_use":
        break

    tool_results = []
    for block in resp.content:
        if block.type == "tool_use":
            output = run_tool(block.name, block.input)
            tool_results.append({
                "type": "tool_result",
                "tool_use_id": block.id,
                "content": output,
                "is_error": False,
            })

    messages.append({"role": "user", "content": tool_results})

Tool schema

{
  "name": "get_weather",
  "description": "Get the current weather in a specific location. Use when the user asks about temperature, precipitation, or conditions.",
  "input_schema": {
    "type": "object",
    "properties": {
      "location": {"type": "string", "description": "City and country, e.g., 'Berlin, Germany'"},
      "unit": {"type": "string", "enum": ["celsius", "fahrenheit"], "description": "Temperature unit"}
    },
    "required": ["location"]
  }
}

The description is the most important field. Claude decides whether to call based on the description — not the name. Be specific about when to use the tool, not just what it does.

tool_choice

  • auto — Claude decides (default).
  • any — Claude must call some tool, no plain-text answer.
  • {type: "tool", name: "X"} — Force a specific tool. Useful for structured extraction.
  • none — Disable tool use for this turn.

Parallel tool use

Claude can return multiple tool_use blocks in one turn. Run them in parallel and return all tool_results in a single user turn. This is a major latency win.

To allow Claude to do this, your tools should be independent — no result depends on another.

Tool result content

tool_result content can be:

  • A string.
  • A list of blocks (text + image), enabling tools that return screenshots.
  • is_error: true for failed tool calls — Claude will see the error and may retry or escalate.

Common pitfalls

PitfallFix
Vague description ("does stuff")Specify when to use AND when NOT to use
Required vs optional confusedList only truly-required params in required
Returning huge blobsSummarize / truncate; consider returning a resource URI
Silent failureAlways return is_error: true with a clear message
Infinite loopCap loop iterations (e.g., 20) and surface the cap

See also