# A Complete Tool Loop With the Anthropic SDK — Claude API / OpenAI API Basics

Source: https://www.geekswithgeeks.com/en/llm-apis/t-anthropic

> Define a tool, detect tool_use, return tool_result.

## tool_use blocks and tool_result blocks

With Anthropic, tools are listed with `name`, `description` and `input_schema`. A reply with `stop_reason == "tool_use"` contains one or more **`tool_use` content blocks** (each with an `id`, the tool `name` and its `input` dict). You append the whole assistant reply to `messages`, then add a **user message** whose content is a list of **`tool_result` blocks**, each referring to the matching `tool_use_id`. Return errors as text in the result (optionally flagged as an error) rather than raising, so the model can recover. Always **cap the number of loop steps**.

## Anthropic tool loop, run

I ran this in a Python virtual environment with the anthropic 1.11.0 and openai 3.22.1 SDKs against a small local stand-in server (shown in the testing topic, saved as `mock.py`). The server returns canned replies, so no key, network or real model is involved: it proves how the SDK builds requests and handles replies, not what a real model would say. The stand-in server asks for `get_order_status` on the first call and returns text on the second. The loop prints the requested tool and arguments, runs the local function, and ends with the final answer after 2 API calls.

```python
import json
import mock, anthropic, openai
srv = mock.start(); base = f"http://127.0.0.1:{srv.server_address[1]}"      # local stand-in server, not a real API

client = anthropic.Anthropic(api_key="k", base_url=base)
tools = [{"name": "get_order_status", "description": "Look up an order by 6-digit id.",
          "input_schema": {"type": "object", "properties": {"order_id": {"type": "string"}}, "required": ["order_id"]}}]
def get_order_status(order_id): return {"order_id": order_id, "status": "shipped"}

messages = [{"role": "user", "content": "Where is order 481516?"}]
for step in range(5):                                           # always cap the loop
    reply = client.messages.create(model="demo-model", max_tokens=200, tools=tools, messages=messages)
    if reply.stop_reason != "tool_use":
        print("final:", reply.content[0].text); break
    messages.append({"role": "assistant", "content": reply.content})
    results = []
    for block in reply.content:
        if block.type == "tool_use":
            print("model asked for:", block.name, block.input)
            results.append({"type": "tool_result", "tool_use_id": block.id,
                            "content": json.dumps(get_order_status(**block.input))})
    messages.append({"role": "user", "content": results})
print("calls to the API:", len(mock.STATE["log"]))

```

Output:

```
model asked for: get_order_status {'order_id': '481516'}
final: Your order 481516 has shipped.
calls to the API: 2
```

## Return errors as results

If a tool fails, send the error text as the tool result so the model can explain or retry instead of the loop crashing.

**Quiz:** How does a tool_result block link to the request?

- [ ] Through the API key
- [x] Through tool_use_id matching the tool_use block id
- [ ] Through the model name
- [ ] It does not link

*Answer:* Through tool_use_id matching the tool_use block id. Each result must reference the id of the call it answers.
