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_reasonistool_use, the content holds one or moretool_useblocks, each with anid, the toolnameand aninputdict. - Run the matching Python function, then append Claude's whole reply as the
assistantturn and auserturn containing atool_resultblock per call, each with the matchingtool_use_id. - Call the API again. Repeat until
stop_reasonisend_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.