C.W.K.
Stream
Lesson 04 of 06 · published

Tool Loops in Python: From One Round to Many

~18 min · tool-use, tool-loop, function-calling

Level 0Observer
0 XP0/64 lessons0/13 achievements
0/150 XP to next level150 XP to go0% complete

The two-round shape

A tool call is at minimum two round-trips: (1) you send tools + user message; the model returns stop_reason='tool_use' with a tool_use block; (2) you execute the tool locally and append a tool_result back into the conversation; the model produces the final answer. Real loops continue until the model returns stop_reason='end_turn'.

One handler per tool, dispatched by name

The clean pattern is a registry of {tool_name: callable}. When you see a tool_use block, look up the callable, call it with the arguments, and append the result. Resist the urge to put tool dispatch inline in a single big function — it grows fast as tools accumulate.

Loop budget

Some tasks legitimately want 10+ tool rounds. Some indicate a stuck model and should be cut off. Always carry a max-iteration budget (start with 10) and surface the abort to the user so they understand why the agent stopped.

Principle: A tool loop is just a controlled while-true with a budget and a registry. Keep both explicit.

Code

Tool registry and a complete loop·python
from anthropic import Anthropic
import json

client = Anthropic()

# 1. Tool definitions sent to the model.
TOOLS = [
    {
        "name": "get_weather",
        "description": "Get current weather for a city.",
        "input_schema": {
            "type": "object",
            "properties": {"city": {"type": "string"}},
            "required": ["city"],
        },
    }
]

# 2. Local handlers — one per tool name.
def get_weather(city: str) -> dict:
    return {"city": city, "temp_c": 22, "condition": "clear"}

HANDLERS = {"get_weather": get_weather}

# 3. The loop.
def run_loop(user_text: str, max_iters: int = 10) -> str:
    messages = [{"role": "user", "content": user_text}]
    for _ in range(max_iters):
        resp = client.messages.create(
            model="claude-sonnet-4-6",
            max_tokens=1024,
            tools=TOOLS,
            messages=messages,
        )
        # Append the assistant turn (full content list) before any tool_results.
        messages.append({"role": "assistant", "content": resp.content})

        if resp.stop_reason != "tool_use":
            return next(b.text for b in resp.content if b.type == "text")

        # Resolve every tool_use block, then send all tool_results in one user turn.
        tool_results = []
        for block in resp.content:
            if block.type != "tool_use":
                continue
            handler = HANDLERS[block.name]
            output = handler(**block.input)
            tool_results.append({
                "type": "tool_result",
                "tool_use_id": block.id,
                "content": json.dumps(output),
            })
        messages.append({"role": "user", "content": tool_results})
    raise RuntimeError("tool loop exceeded max_iters")

print(run_loop("What is the weather in Seoul?"))
Parallel tool execution inside one round·python
import asyncio

async def execute_block(block, async_handlers):
    output = await async_handlers[block.name](**block.input)
    return {
        "type": "tool_result",
        "tool_use_id": block.id,
        "content": json.dumps(output),
    }

# When the model calls multiple tools in one turn, run them concurrently.
async def resolve_round(blocks, async_handlers):
    return await asyncio.gather(*(execute_block(b, async_handlers) for b in blocks))

External links

Exercise

Build a tool loop with two tools (one local function, one HTTP fetch) and a max_iters budget. Add a unit test that fails if the loop exceeds 5 iterations on a simple weather query.
Hint
If you find yourself appending tool_use blocks back to the model, check that you appended the full assistant content list, not just a string.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.