Give Claude the ability to take actions in the world — call APIs, run code, query databases — and close the loop between language and execution.
Claude cannot browse the web or run code on its own. Tool use lets you extend Claude with callable functions — Claude decides when and how to call them based on the conversation.
tool_use block with argumentstool_result block back to ClaudeA tool is a Python dict with three required keys. The description is the most important part — Claude reads it to decide whether to use this tool.
calculator_tool = {
"name": "calculator",
"description": (
"Perform arithmetic operations on two numbers. "
"Use this whenever you need to compute a mathematical result precisely. "
"Supports add, subtract, multiply, and divide."
),
"input_schema": {
"type": "object",
"properties": {
"operation": {
"type": "string",
"enum": ["add", "subtract", "multiply", "divide"],
"description": "The arithmetic operation to perform"
},
"a": {"type": "number", "description": "First operand"},
"b": {"type": "number", "description": "Second operand"}
},
"required": ["operation", "a", "b"]
}
}
Tool use is inherently multi-turn. You must loop until Claude produces a response with no tool calls.
client.messages.create()response.stop_reason == "tool_use"tool_use blocks from response.contenttool_result message and append to messagesstop_reason == "end_turn" — extract and return the textwhile response.stop_reason == "tool_use":
tool_calls = [b for b in response.content if b.type == "tool_use"]
results = []
for tc in tool_calls:
output = execute_tool(tc.name, tc.input)
results.append({"type": "tool_result", "tool_use_id": tc.id,
"content": str(output)})
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": results})
response = client.messages.create(model=MODEL, tools=tools,
messages=messages, max_tokens=1024)
Claude can request multiple tool calls in a single response when they are independent. This is more efficient than sequential calls.
get_weather(Paris) + get_calendar(today) simultaneously
When handling parallel calls, collect all tool_use blocks from response.content, execute each, and return all results in a single tool_result message — one entry per tool call.
Tool execution can fail. A divide-by-zero, file not found, or network timeout should not crash your loop. Return the error in the tool_result — Claude will adapt its response.
def execute_tool(name: str, inputs: dict) -> str:
try:
if name == "calculator":
return str(run_calculator(**inputs))
elif name == "unit_converter":
return str(run_converter(**inputs))
else:
return f"Error: unknown tool '{name}'"
except ZeroDivisionError:
return "Error: division by zero"
except Exception as e:
return f"Error: {type(e).__name__}: {e}"
"Error:". Claude recognises this, tells the user what went wrong, and may try an alternative approach.
Prevent infinite loops by limiting how many tool rounds you allow. 10 is a safe default for most tasks.
Run the standalone demo to see a complete tool loop in action.
cd lesson07-tool-use
python demo/tool_demo.py
execute_tool()run_with_tools() with multi-turn supportpathlib for real file operationsweb_search tool and use it to answer research questions