# A calculator as a tool

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

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

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

You sent Claude a math question and it answered from memory. Now you want it to answer from code — code you control, code that is always correct. The gap between "Claude answers" and "Claude runs your function" is exactly one JSON schema and one extra API call.

**What a tool actually is**

A tool is a JSON schema you attach to a Messages API request. The schema tells Claude what functions exist, what arguments they take, and what they return. Claude does not execute the function. Claude decides whether to call it and, when it does, emits a `tool_use` content block containing the function name and a validated input dict. Your code reads that block, runs the function, and hands the result back. The model never touches your runtime.

Here is the smallest possible tool definition:

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

client = anthropic.Anthropic()

tools = [{
    "name": "add",
    "description": "Add two integers and return their sum.",
    "input_schema": {
        "type": "object",
        "properties": {
            "a": {"type": "integer", "description": "First addend"},
            "b": {"type": "integer", "description": "Second addend"}
        },
        "required": ["a", "b"]
    }
}]

def add(a: int, b: int) -> int:
    return a + b

response = client.messages.create(
    model="claude-opus-4-5",
    max_tokens=256,
    tools=tools,
    messages=[{"role": "user", "content": "What is 41 plus 1?"}]
)

print(response.stop_reason)          # → tool_use
print(response.content)              # → [ToolUseBlock(id='...', name='add', input={'a': 41, 'b': 1})]
```

The response has `stop_reason: "tool_use"` and its `content` array contains a `ToolUseBlock`. That block has three fields you will use every time: `id` (a string like `"toolu_01..."`), `name` (the function name), and `input` (a dict matching your schema). Nothing about this block changes across any tool you will ever build.

**Completing the round-trip**

Once you have the tool call, you run the function and send the result back as a second request. That second request must include both the original assistant turn and a new `user` turn carrying the result:

```python
# scripts/calculator.py (continued)
tool_block = next(b for b in response.content if b.type == "tool_use")
result = add(**tool_block.input)          # → 42

final = client.messages.create(
    model="claude-opus-4-5",
    max_tokens=256,
    tools=tools,
    messages=[
        {"role": "user", "content": "What is 41 plus 1?"},
        {"role": "assistant", "content": response.content},   # full assistant turn
        {
            "role": "user",
            "content": [{
                "type": "tool_result",
                "tool_use_id": tool_block.id,
                "content": str(result)
            }]
        }
    ]
)

print(final.content[0].text)   # → "41 plus 1 is 42."
```

The assistant turn in the second request is `response.content` verbatim — not just the text, the full list of content blocks. Discarding it and sending only the `tool_result` is the single most common beginner error. The API will accept the malformed request but Claude will not know what it asked, and the answer will be incoherent.

**Why start with addition**

Addition has no side effects, no authentication, no error states, and no ambiguity. The result is always correct or obviously wrong. This means every bug you encounter is in the envelope — the tool definition, the round-trip structure, the result format — not in the tool logic. Learn the envelope here, with no noise, before adding any real complexity.

**If you get stuck**

*Schema validation error on the first request:* The `input_schema` must have `"type": "object"` at the top level. A bare `{"type": "integer"}` for a single-argument tool will fail. Always wrap in an object even if you have one parameter.

*`stop_reason` is `"end_turn"` instead of `"tool_use"`:* The model decided not to call the tool. Usually the prompt does not require computation — try phrasing the question so the answer is a specific number, not something the model can guess from training data. "What is 41 plus 1?" works because the model recognizes the tool is the right channel. "How much is forty-one and one?" sometimes does not.

*`tool_result` gets a validation error:* The `content` field of a `tool_result` block must be a string (or a list of content blocks for richer returns). Passing an integer directly raises a validation error. Cast to `str()`.

**Cross-reference**

If the two-turn structure feels unfamiliar, the `api-fundamentals` course, lesson l2, covers multi-turn message shape in detail. This lesson extends that directly — the only new element is `tool_use` blocks in the assistant turn and `tool_result` blocks in the user turn.

**Deliverable**

`scripts/calculator.py` — a self-contained script that defines the `add` tool, sends one prompt, executes the function, completes the round-trip, and prints the final text response. Thirty lines. No imports beyond `anthropic`. When you run it, the last line of output is "41 plus 1 is 42." or equivalent.

**Further reading**

- ["The Widget That Knows What a Gunshot Sounds Like"](https://sethshoultes.com/blog/widget-gunshot.html) — a worked case for when a substring-match function is the right tool and an LLM is the wrong one, and where the line falls between them.

## Try these prompts

- Define a JSON schema for a multiply tool that takes two integers and returns their product. Include the description field, required array, and one field description example.
- Write the Python code to extract the tool_use block from a Messages API response and call the corresponding function.
- Explain why the content field of a tool_result block must be a string, not an integer, and what error results from passing the integer directly.

Next: [How tool calls round-trip](https://academy.memberpress.ai/courses/tool-use/l2/)
