Wire a tool to the model: pass it in the request, watch the model return a tool_use block, run your function, and hand the result back. This is the round trip every agent is built on.
Why: adding tools to the request lets the model choose to call one instead of answering directly. When: it wants a tool, stop_reason becomes "tool_use" and the response carries a tool_use block with the arguments. Where: the model returns the request — it does not run anything itself.
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
tools=[weather_tool],
messages=[{"role": "user", "content": "What's the weather in Paris?"}],
)
print(response.stop_reason) # "tool_use"
for block in response.content:
if block.type == "tool_use":
print(block.name, block.input) # get_weather {'city': 'Paris'}
print(block.id) # toolu_... — you need this idWhy: the model is now waiting for the answer — you execute the function and send the output back as a tool_result. Where: the tool_result must carry the same id (tool_use_id) the model gave you, or the API cannot match it to the request.
# The model asked for get_weather(city="Paris")
tool_block = next(b for b in response.content if b.type == "tool_use")
result = get_weather(**tool_block.input)
followup = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
tools=[weather_tool],
messages=[
{"role": "user", "content": "What's the weather in Paris?"},
{"role": "assistant", "content": response.content}, # the tool_use turn
{"role": "user", "content": [{
"type": "tool_result",
"tool_use_id": tool_block.id,
"content": result,
}]},
],
)
print(text_of(followup)) # "It's 21°C and sunny in Paris."Why: sometimes you must force a tool (or forbid one) rather than let the model decide. When: use tool_choice to require a call for structured extraction, or disable tools for a plain answer. Where: "auto" is the default and right for most agents.
{"type": "auto"} — The model decides whether to use a tool. The default — use it for normal agents.{"type": "any"} — The model must call at least one of the provided tools.{"type": "tool", "name": "..."} — Force this specific tool — useful for guaranteed structured output.{"type": "none"} — The model may not use tools this turn.response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
tools=[weather_tool],
tool_choice={"type": "tool", "name": "get_weather"}, # force the call
messages=[{"role": "user", "content": "Paris weather?"}],
)Why: function calling is an industry-wide pattern, so what you just learned transfers. When: switching providers, the wire format differs but the loop is identical — describe tools, the model requests one, you run it and return the result. Where: this course uses Claude; the concept is portable.
Same idea, different SDK surface:
Anthropic (this course) -> tools=[...] -> tool_use block
OpenAI -> tools=[...] -> tool_calls
Google Gemini -> tools=[...] -> functionCall
Learn the loop once; the provider is a detail.