The Tool-Call Round Trip
Tool calling is a two-turn dance. You send the user's message along with your tool schemas; the model answers with a tool_use block saying "call get_weather with {city: 'Paris'}"; your code runs that function and sends the answer back as a tool_result; then the model uses it to write the final reply. This lesson builds the middle step -> the part where the model's request becomes a real Python call and the result is packaged for the trip back.
Here is the real loop (illustrative only -> we do not call the network):
resp = client.messages.create(model="claude-sonnet-4-6", max_tokens=1024,
tools=TOOLS, messages=history)
# resp.stop_reason == "tool_use"; find the tool_use block:
for block in resp.content:
if block.type == "tool_use":
result = TOOLS_BY_NAME[block.name](**block.input)
history.append({"role": "assistant", "content": resp.content})
history.append({"role": "user", "content": [
{"type": "tool_result", "tool_use_id": block.id, "content": str(result)}
]})
final = client.messages.create(model="claude-sonnet-4-6", max_tokens=1024,
tools=TOOLS, messages=history)The two non-negotiable details: you call the function the model named with the arguments it gave, and you echo its tool_use_id back in the tool_result so the model can match the answer to the request. We model the response as a plain dict so it runs offline:
{"content": [{"type": "tool_use", "id": "toolu_1",
"name": "get_weather", "input": {"city": "Paris"}}],
"stop_reason": "tool_use"}You are building run_tool_call(resp, tools), where tools maps a tool name to a Python function. It must:
- Find the first block in
resp["content"]whosetypeis"tool_use". - Look up
tools[name]and call it with the block'sinputas keyword arguments:tools[name](**block["input"]). If the name is not intools, raise (aKeyErrororValueError) -> never run a tool the model hallucinated. - Return the follow-up user message that carries the result back:
{"role": "user",
"content": [{"type": "tool_result",
"tool_use_id": <the block's id>,
"content": str(result)}]}Stringify the result with str(result) -> the tool_result content travels as text. Build it so two different tool calls (different name and input) produce two different tool_result messages, the original tool_use_id is echoed back, and an unknown tool name raises.
Write run_tool_call(resp, tools). Find the first {"type": "tool_use", "id", "name", "input"} block in resp["content"], call tools[name](**input), and return the follow-up message {"role": "user", "content": [{"type": "tool_result", "tool_use_id": <id>, "content": str(result)}]}. Raise on a tool name that is not in tools.
This lesson is locked
Lessons open one at a time. Finish the previous lesson to unlock this one.