إنتقل إلى المحتوى الرئيسي

Programmatic Tool Calling

متقدّم
What you'll learn
  • Understand what actually happens when Claude calls your tool from inside a sandbox — and why your tool still runs on your own machine
  • Enable it correctly with allowed_callers, and know why it is not a security boundary
  • Know the real numbers: what it saves, on which workloads, and where it costs you
  • Avoid the five failure modes that produce 400s and TimeoutErrors in production

The problem it solves

Classic tool use is a conversation. Claude asks for one tool call, you answer, the whole result lands in the context window, Claude reads it and asks for the next one. Twenty lookups means twenty inference passes and twenty raw payloads sitting in context forever.

Most of that payload is waste. If you want to know which of twenty employees blew their expense budget, Claude does not need every line item — it needs the handful of names. But in classic tool use, the line items have to pass through the model to get filtered by it.

Programmatic tool calling inverts that. Claude writes a Python script, the script calls your tools in a loop, filters the results, and only what the script prints comes back to the model. The raw data never enters the context window at all.

What is actually happening

Here is the part almost every summary of this feature gets wrong: your tool does not run inside the sandbox. Anthropic's container has no access to your database.

What really happens is that Claude's Python code pauses mid-execution, the API hands the call back to you, and the interpreter resumes once you answer:

Guided walkthrough1 of 5
  1. It runs inside the code-execution container. Your tools appear to that code as async Python functions — one per tool, each taking a single dict of arguments and returning a string.

Because the functions are async, Claude can fan out with asyncio.gather and hit ten tools concurrently — something classic tool use can only approximate with parallel tool blocks.

What Claude's generated code actually looks like

import json

rows = json.loads(await query_database({"sql": "<sql>"}))
top = sorted(rows, key=lambda r: r["revenue"], reverse=True)[:5]
print(f"Top 5 customers: {top}")

Note the json.loads. The tool function returns a string — the literal text of the tool_result you send back. If your tool description does not say "returns a list of rows as JSON objects", Claude has no way to know it can deserialize the thing, and it will handle your data as an opaque blob. The output-format sentence in your tool description stops being documentation and becomes load-bearing code. That is the single highest-leverage line you will write when adopting this feature.

Turning it on

One field on the tool you want called from code, plus the code-execution tool in the request:

Enabling programmatic calling on a tool

{
"name": "query_database",
"description": "Execute a SQL query against the sales database. Returns a list of rows as JSON objects.",
"input_schema": { "type": "object", "properties": { "sql": { "type": "string" } }, "required": ["sql"] },
"allowed_callers": ["code_execution_20260120"]
}

allowed_callers takes three shapes:

ValueMeaning
["direct"]Classic tool use. This is the default when the field is omitted.
["code_execution_20260120"]Claude is guided to call it only from inside code.
["direct", "code_execution_20260120"]Either. The docs advise against this — pick one, so Claude gets an unambiguous signal.

Every tool_use block in the response now carries a caller: either {"type": "direct"} or a code-execution caller whose tool_id matches the server_tool_use block that ran the script. That is how you attribute a call to the script that made it.

It is not a security boundary

The docs are unusually blunt about this, and it is worth repeating because it is easy to assume the opposite: allowed_callers controls how the tool is presented to Claude. It is not a hard API-level block. Claude is strongly guided to respect it — but your client must still be prepared to receive a direct tool_use for any tool it defines, and you must not use this field as an authorization mechanism. Authorization belongs in your tool handler, where it always did.

The numbers

Anthropic's own reported figures, so you can judge whether the complexity is worth it:

  • On complex research tasks, average usage fell from 43,588 to 27,297 tokens — a 37% reduction.
  • On the GIA benchmarks, accuracy rose from 46.5% to 51.2%; on internal knowledge retrieval, 25.6% to 28.5%. Fewer tokens and better answers, because the model reasons over conclusions instead of drowning in raw payloads.
  • On agentic search benchmarks (BrowseComp, DeepSearchQA), layering programmatic calling on top of basic search tools improved performance by an average of 11% while using 24% fewer input tokens.
  • Latency: orchestrating 20+ tool calls in one code block eliminates 19+ inference passes.

The shape of the win is the tell. It pays when you have 3+ dependent calls, a loop, a filter, or a fan-out. It pays nothing — and costs you a container — when Claude needs exactly one tool call and wants to read the whole answer anyway.

Watch out
  • Claude Haiku 4.5 accepts the newer tool types but does NOT support programmatic tool calling or the REPL state persistence that depends on it. The newer versions silently behave like code_execution_20250825 there. If you are routing to Haiku for cost, you are not getting this feature — and you will not get an error telling you so.

What it costs

Programmatic tool calling is billed as code execution, and code execution is billed by container-hour, not by call:

  • 1,550 free hours per month, per organization.
  • Beyond that, $0.05 per hour, per container.
  • Execution time has a minimum of 5 minutes — a two-second script still bills five minutes of container.
  • If you attach files to the request, execution time is billed even if the tool is never invoked, because the files get preloaded onto a container regardless.
  • It is free when the same request also uses web search or web fetch (web_search_20260209 / web_fetch_20260209 or later).

Two consequences worth internalizing. First, the 5-minute floor means many short-lived containers is the expensive pattern; reusing one container across a session is the cheap one. Second, this feature is not eligible for Zero Data Retention — if ZDR is a contractual requirement for you, this is a hard stop, not a tuning knob.

The five ways this breaks

Guided walkthrough1 of 5
  1. When there are pending programmatic tool calls, your response message must contain ONLY tool_result blocks. Not text plus tool results. Not tool results followed by a polite sentence. Only tool_result blocks.

Version strings, decoded

All three code-execution versions are generally available and need no beta header:

VersionWhat it adds
code_execution_20250825The baseline. Bash + Python + file ops. Supported on every current model.
code_execution_20260120Adds REPL state persistence and programmatic tool calling. This is the one you need.
code_execution_20260521Identical runtime to 20260120. The only difference is that the tool description tells Claude about the 90-second wall-clock limit per Python cell, so it can budget long-running cells. A cell that blows the limit returns a non-zero return_code with a detection_timeout status.

That last row is a nice piece of API design to notice: a version bump whose entire payload is a better prompt for the model. Both strings are interchangeable inside allowed_callers, and responses always tag the caller as code_execution_20260120 regardless of which you declared.

The container itself has no internet access — Claude cannot pip install at runtime, so you get the pre-installed library set (pandas, numpy, scipy, scikit-learn, statsmodels, and friends) and nothing more. Containers are checkpointed after about five minutes of inactivity, restorable by ID, and expire 30 days after creation.

When to reach for it

Reach for programmatic tool calling when the model is being used as a loop and a filter rather than as a reasoner: batch lookups across N entities, early termination once a condition is met, conditional tool selection based on an intermediate result, or crushing a 200 KB log dump down to the ten lines that matter.

Reach for the Tool Search Tool instead when your problem is that definitions are eating your context before a single call is made — mark tools defer_loading: true and Claude loads them on demand. The two are complements, not alternatives: tool search finds the right tool, programmatic calling executes it cheaply. If your tool definitions exceed roughly 10K tokens, you probably need both.

And if you are hitting this from the other end — an agent whose context is drowning in MCP tool results — start with MCP token cost and Context Engineering, because the cheapest tokens are still the ones you never send.

Check yourself

0/5
  1. Where does your tool actually execute during programmatic tool calling?
  2. Can you rely on allowed_callers to prevent a tool from being invoked directly?
  3. Your agent routes to Claude Haiku 4.5 to save money and passes code_execution_20260120. What happens?
  4. When there is a pending programmatic tool call, what may your reply message contain?
  5. A two-second script runs in a fresh container. How much code-execution time is billed?
اضغط Enter أو مفتاح المسافة لقلب البطاقة. استخدم مفتاحي السهمين الأيسر والأيمن للتنقل بين البطاقات.تم إظهار المصطلح.
1 / 7

Sources & further reading