skip to content

Tool use and function calling

Claude's function calling: tools declared with JSON Schema, tool_use blocks coming back in the response, tool_result blocks handed in on the next user turn. The interview angle is the multi-turn loop and using tool_choice to force, forbid, or leave a call to the model.

on this pageshow

questions

5

How do you declare a tool for Claude in Anthropic's Messages API?

level: juniorimportance: must knowfreq 72%

answer

  1. Top-level array on the request
  2. Three fields per tool
  3. Not OpenAI's parameters field
  4. Description drives when, not just what
  5. name, description, input_schema

basics

~10 s

Send a top-level tools array on the Messages request. Each entry needs a name, a description that tells Claude when to call it, and an input_schema: a JSON Schema object describing the parameters.

solid answer

~40 s

Tools live in a top-level `tools` array on the Messages request, alongside `model` and `messages`. Each tool object has three fields: `name` (the identifier Claude echoes back when it calls it), `description` (natural-language prose Claude reads to decide *when* to call it), and `input_schema` — a JSON Schema object with `type: "object"`, a `properties` map, and a `required` list. The description is doing real work: it is the only thing that tells the model the trigger conditions, so write "Call this when the user asks about current weather" rather than just "gets weather". Property-level `description` strings and `enum` constraints materially improve argument quality. The tool definitions are serialised into the prompt, so they count as input tokens on every request.

code

python · 36 lines
python
import anthropic

client = anthropic.Anthropic()

tools = [
    {
        "name": "get_current_weather",
        "description": (
            "Get the current weather for a city. Call this whenever the user "
            "asks about weather, temperature, or conditions right now."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "City and country, e.g. Berlin, Germany",
                },
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "Temperature unit to report",
                },
            },
            "required": ["location"],
        },
    }
]

response = client.messages.create(
    model="claude-opus-5",
    max_tokens=1024,
    tools=tools,
    messages=[{"role": "user", "content": "What is the weather in Berlin?"}],
)
print(response.stop_reason)

go deeper

for a junior

Be able to name the three fields — name, description, input_schema — and say that input_schema is JSON Schema. Know that the array is top-level on the request, not inside a message.

for a middle

Explain why the description drives call behaviour, and use enums plus per-property descriptions to constrain arguments. Be ready to contrast the flat Anthropic shape with OpenAI's nested function/parameters wrapper.

for a senior

Show judgement on tool-surface size and token cost, and know when strict schemas are worth the constraints they impose. Mention that unstable tool ordering silently breaks prefix caching.

for a principal

Own the tool surface as an interface: naming conventions, versioning a schema without breaking in-flight agents, and the tradeoff between many narrow tools and a few broad ones with rich enums.

## What a tool declaration is A tool declaration is a contract you hand Claude at request time. It does not give Claude the ability to run anything — Anthropic's API never executes your code. It gives Claude a vocabulary: a set of function names it may *ask* you to run, and a schema describing the arguments it must fill in. Execution stays entirely on your side. Declarations go in a top-level `tools` array on the `POST /v1/messages` request, a sibling of `model`, `messages` and `max_tokens` — not nested inside a message. ## The three fields **`name`** — a short identifier, conventionally snake_case. This is the string Claude echoes back in the `tool_use` block, so your dispatch table keys off it. Prefer specific names: `get_current_weather` beats `weather`. **`description`** — free prose, and the highest-leverage field in the whole declaration. Claude reads it to decide whether this turn warrants a call. A description that only says *what* the tool does under-triggers; one that says *when* to reach for it triggers reliably. Recent Claude models are comparatively conservative about calling tools, so explicit trigger conditions ("Call this whenever the user asks about prices, stock levels, or anything that could have changed today") give measurable lift. It is also the place to state preconditions and side effects ("This sends a real email; only call it after the user confirms the recipient"). **`input_schema`** — a JSON Schema object. In practice it is always `{"type": "object", "properties": {...}, "required": [...]}`. Each property should carry its own `description`; use `enum` wherever the value comes from a fixed set, because an enum is far more effective than prose at stopping the model inventing a unit or a status string. Mark only genuinely mandatory parameters `required` — everything else is optional and the model will omit it when it does not apply. ## Anthropic's shape is not OpenAI's This is the single most common mistake when porting code. Anthropic's tool object is flat — `name`, `description`, `input_schema` — with no `{"type": "function", "function": {...}}` wrapper and no field named `parameters`. Writing `parameters` instead of `input_schema` is a validation error, not a silently ignored field. ## Strict schemas Setting `strict: true` as a top-level field on the tool definition (beside `name`, not on `tool_choice`) makes the API guarantee that the `input` Claude returns validates against your schema exactly. It requires `additionalProperties: false` on every object and an explicit `required` list. Without it, schema adherence is very good but best-effort, so defensive code still validates. Strict schemas support `enum`, `const`, `anyOf`, `allOf` and `$ref`, but not recursive schemas or numeric/string range constraints such as `minimum` or `maxLength` — the Python and TypeScript SDKs strip those and validate them client-side. A new schema pays a one-time compilation cost on its first request and is then cached. ## Cost and sizing Tool definitions are rendered into the prompt as text, so a large tool surface is a per-request token cost that recurs on every turn of a long agent loop. Keeping the set focused helps accuracy too: dozens of overlapping tools make selection worse, not better. If a tool needs a paragraph of usage rules, that paragraph belongs in the description, not in the system prompt, because it travels with the tool. ## Stability matters for caching The `tools` block is rendered before the system prompt and messages, so it sits at the very front of the cacheable prefix. Generating the array in a non-deterministic order — iterating an unordered map, say — changes bytes at the front of every request. Sort it and build it once. ## What good looks like A well-formed declaration reads like documentation written for a competent colleague who has never seen your codebase: a precise name, a description that states both purpose and trigger, per-property descriptions with units and formats spelled out, enums for closed sets, and a `required` list that is honest about what is optional.

  • What does setting strict: true on a Claude tool definition buy you, and what does it require?
    It makes the API guarantee the `input` Claude returns validates against your schema, so you can skip defensive coercion. It goes at the top level of the tool object, next to `name` — not on `tool_choice`. It requires `additionalProperties: false` on every object and an explicit `required` list, and it does not support recursive schemas or numeric range keywords like `minimum`. A new schema pays a one-time compile cost, then is cached.
  • Why does a vague tool description hurt more on Anthropic's API than a vague function name?
    The name is a bare identifier; the description is the entire behavioural spec Claude sees. Selection between tools, and the decision to call one at all, come almost wholly from the description. Recent Claude models call tools conservatively, so a description that omits trigger conditions produces silent under-calling: the model answers from its own knowledge instead, and nothing in the response signals that a tool was skipped.
  • Where do tool definitions sit in the request, and why does that placement matter for cost?
    They are a top-level `tools` array, rendered into the prompt ahead of the system prompt and messages. That means every definition is billed as input tokens on every turn of an agent loop, and it means the tools block is at the front of the cacheable prefix — so a non-deterministically ordered array invalidates caching for the whole request. Build the array once, in a fixed order.

saying these in an interview costs you the question

  • Says the field is called parameters, as in OpenAI's shape
  • Wraps the tool in a {type: function, function: {...}} object
  • Thinks Anthropic executes the tool server-side
  • Writes a description of what the tool does but never when to call it
  • Believes tool definitions are free and cost no input tokens

context

open as a page

What are the tool_choice modes in Anthropic's Messages API?

level: middleimportance: must knowfreq 62%

basics

~20 s

Four values: auto lets Claude decide (the default when tools are present), any forces at least one tool call, tool with a name forces that specific tool, and none forbids calling any tool. Each can also carry disable_parallel_tool_use.

open as a page

How do you return a tool_result to Claude after a tool_use block?

level: middleimportance: must knowfreq 78%

basics

~20 s

Append the assistant's entire content array to messages, then add a new user message whose content holds a tool_result block. That block carries tool_use_id matching the call's id, plus the result content. Then call the API again.

open as a page

Why does a Claude agent loop with tool_choice any never terminate?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Because tool_choice applies to every request you send, and any forbids a plain-text answer. Each turn is therefore obliged to return a tool_use block, so a loop that exits on stop_reason leaving tool_use never exits. Force only the first turn.

open as a page

How do you reply when Claude emits multiple tool_use blocks at once?

level: seniorimportance: should knowfreq 48%

basics

~20 s

Execute them all, then send every tool_result back in a single user message. Splitting them across separate user turns is invalid and teaches Claude to stop batching. A failed call still needs its block, marked is_error.

open as a page