Tool use: the request-run-return loop

Claude API · Practical · 15 min

Tools let Claude look things up and take actions through your code; it is the basis of every agent.

With tool use you describe functions your program can run, and Claude decides when to call them. The API never runs your code; it only asks you to.

Each tool has a name, a description (Claude reads this to decide when to use it, so be specific) and an input_schema: a JSON Schema, a standard way to describe the shape of data, saying which arguments exist and their types.

The loop has four steps:

  • Send the user message with tools=[...].
  • If stop_reason is tool_use, the content holds one or more tool_use blocks, each with an id, the tool name and an input dict.
  • Run the matching Python function, then append Claude's whole reply as the assistant turn and a user turn containing a tool_result block per call, each with the matching tool_use_id.
  • Call the API again. Repeat until stop_reason is end_turn.

Claude may request several tools in one reply (parallel tool use), so handle every tool_use block, and return all results in one user message. If your function fails, return a tool_result with is_error: True and a short message; Claude can then try again or explain.

tool_choice controls the decision: auto (default, Claude chooses), any (must use some tool), tool (must use the named one) or none. Adding "strict": True to a tool guarantees its inputs match the schema exactly.

Example

import anthropic

client = anthropic.Anthropic()
tools = [{
    "name": "get_weather",
    "description": "Current weather for a city. Use for any weather question.",
    "input_schema": {"type": "object",
                     "properties": {"city": {"type": "string"}},
                     "required": ["city"]},
}]
def get_weather(city): return f"{city}: 18C, light rain"   # fake lookup

messages = [{"role": "user", "content": "Do I need an umbrella in Oslo?"}]
while True:
    resp = client.messages.create(model="claude-opus-5-5", max_tokens=1024,
                                  tools=tools, messages=messages)
    if resp.stop_reason != "tool_use":
        break
    messages.append({"role": "assistant", "content": resp.content})
    results = [{"type": "tool_result", "tool_use_id": b.id,
                "content": get_weather(**b.input)}
               for b in resp.content if b.type == "tool_use"]
    messages.append({"role": "user", "content": results})

print(next(b.text for b in resp.content if b.type == "text"))

Remember

  • Tools = name + description + input_schema; your code runs them
  • Loop while stop_reason is tool_use; reply with tool_result blocks by id
  • Handle every tool_use block; report failures with is_error

Watch out for

  • Dropping Claude's assistant turn before the tool results; the API needs it to match tool_use ids.

Check yourself

  1. Claude's reply contains two tool_use blocks. What do you send back?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: platform.claude.com/docs/en/agents-and-tools/tool-use/overview