# How tool calls round-trip

Web page: https://academy.memberpress.ai/courses/tool-use/l2/

Lesson 2 of 9 in [Tool Use — From Calculator to Agent](https://academy.memberpress.ai/courses/tool-use/), 18 min.

Video: https://www.youtube.com/watch?v=AzngV85ZEVw

The calculator lesson showed one tool, one call, one result. Most real tasks require more than one tool and sometimes more than one call per turn. When Claude emits two `tool_use` blocks in a single response, both results must come back in a single user turn. Getting this wrong produces a validation error that tells you nothing useful about which rule you broke.

**The full message structure, turn by turn**

A tool-use exchange requires at minimum two API calls. Knowing the exact shape of each turn — every field, not just the conceptual summary — prevents the class of bugs that manifest as mysterious validation errors or incoherent model responses.

Turn one request:

```python
# scripts/round_trip.py
import anthropic, json

client = anthropic.Anthropic()

tools = [
    {
        "name": "get_weather",
        "description": "Return the current temperature in Celsius for a given city.",
        "input_schema": {
            "type": "object",
            "properties": {"city": {"type": "string"}},
            "required": ["city"]
        }
    },
    {
        "name": "get_humidity",
        "description": "Return the current relative humidity percentage for a given city.",
        "input_schema": {
            "type": "object",
            "properties": {"city": {"type": "string"}},
            "required": ["city"]
        }
    }
]

messages = [{"role": "user", "content": "What is the weather and humidity in Oslo right now?"}]

r1 = client.messages.create(
    model="claude-opus-4-5",
    max_tokens=512,
    tools=tools,
    messages=messages
)

print(r1.stop_reason)   # → tool_use
print(json.dumps([b.model_dump() for b in r1.content], indent=2))
```

Turn one response content (two tool calls in one turn):

```json
[
  {
    "type": "tool_use",
    "id": "toolu_01ABCxyz",
    "name": "get_weather",
    "input": {"city": "Oslo"}
  },
  {
    "type": "tool_use",
    "id": "toolu_02DEFabc",
    "name": "get_humidity",
    "input": {"city": "Oslo"}
  }
]
```

Turn two — collect all results, return in one user message:

```python
# scripts/round_trip.py (continued)
def get_weather(city: str) -> str:
    return "4"          # Celsius — real handler would call a weather API

def get_humidity(city: str) -> str:
    return "78"         # percent

HANDLERS = {"get_weather": get_weather, "get_humidity": get_humidity}

tool_results = []
for block in r1.content:
    if block.type == "tool_use":
        result = HANDLERS[block.name](**block.input)
        tool_results.append({
            "type": "tool_result",
            "tool_use_id": block.id,
            "content": result
        })

messages.append({"role": "assistant", "content": r1.content})
messages.append({"role": "user", "content": tool_results})

r2 = client.messages.create(
    model="claude-opus-4-5",
    max_tokens=512,
    tools=tools,
    messages=messages
)

print(r2.stop_reason)          # → end_turn
print(r2.content[0].text)      # → "Oslo is currently 4°C with 78% humidity."
```

**The three mistakes this structure prevents**

First: discarding the assistant turn. The second request's `messages` list must include the full assistant response as it arrived — `{"role": "assistant", "content": r1.content}`. Sending only the `tool_result` without that preceding assistant turn causes a validation error: `messages: roles must alternate between "user" and "assistant"`.

Second: splitting multi-tool results across multiple user turns. When the model emits two `tool_use` blocks in one response, both results go into a single `user` turn with two `tool_result` blocks. Returning them one at a time violates the alternation rule and will fail.

Third: mismatching `tool_use_id`. Each `tool_result` must carry the exact `id` from the corresponding `tool_use` block. A typo here produces a validation error on turn two: `tool_use_id 'toolu_...' does not match any tool_use block`.

**What the message list looks like after turn two**

After a complete two-call exchange, your `messages` list is:

```python
[
  {"role": "user",      "content": "What is the weather and humidity in Oslo?"},
  {"role": "assistant", "content": [ToolUseBlock(...get_weather...), ToolUseBlock(...get_humidity...)]},
  {"role": "user",      "content": [tool_result(get_weather), tool_result(get_humidity)]},
]
```

This is the canonical state after a two-call exchange. Every subsequent turn appends to this list in the same alternating pattern. The model reads the entire history on each call — there is no session or context object that persists server-side. You are responsible for keeping the list intact.

**If you get stuck**

*`400 messages: roles must alternate`:* You forgot the assistant turn, or you returned multi-tool results in separate user messages. Print `messages` before the failing call and check the role sequence.

*`400 tool_use_id does not match`:* Check that the `id` you pass in `tool_use_id` is the exact string from the `tool_use` block, character for character. These are case-sensitive. Copy the id directly from `block.id` — do not reconstruct it.

*Model makes a second tool call instead of answering:* The model decided it needs more information. Handle it the same way — look for `stop_reason: "tool_use"`, run the handlers, append results, call again. This is the beginning of an agent loop, covered in l7.

**Cross-reference**

The alternating-roles shape is introduced in the `api-fundamentals` course, lesson l2. Tool-use turns are an extension of that same pattern, not a separate protocol. The agent loop in l7 of this course wraps this same structure in a `while` loop with a termination condition.

**Deliverable**

`scripts/round_trip.py` — a script with two stub handlers and the full two-turn exchange. The final `print` outputs the model's text answer. Add a third tool (e.g. `get_wind_speed`), confirm that three results in one user turn still produces a coherent response, and note the output.

## Try these prompts

- Write the Python code to collect tool results from a response that contains two tool_use blocks — get_weather and get_humidity — and return both results in a single user turn.
- Explain the three mistakes the two-turn message structure prevents: discarding the assistant turn, splitting multi-tool results, and mismatching tool_use_id.
- Write what the messages list looks like after a complete two-call exchange with one user question and two tool results. Show the role and content shape for each entry.

Next: [When the model picks the wrong tool](https://academy.memberpress.ai/courses/tool-use/l3/)
