●PRICING — September 1 was the scheduled date for the Sonnet 5 price increase, and it did not happen. The introductory $2/$10 per MTok now stands as the regular price●PARTNER — Salesforce and Anthropic announced Claudeforce, an expanded partnership. The Salesforce in Claude plugin ships with 37 prebuilt sales skills, from meeting prep to pipeline management●TRUST — Claudeforce serves Claude through Amazon Bedrock inside the Salesforce Trust Boundary, so data and inference never leave the security perimeter — an answer aimed squarely at regulated industries●BETA — Salesforce in Claude is with select pilot customers for now, with an open beta expected during September●LIMITS — The 50% weekly-limit boost runs through September 13. From September 14 the permanent level is 25% above the pre-promotion baseline, roughly a 17% cut from today●RELEASE — Claude Code has shipped nothing since v2.1.251 on August 28. Against a pace of one release every 0.8 days, a four-day gap is among the longest yet●PRICING — September 1 was the scheduled date for the Sonnet 5 price increase, and it did not happen. The introductory $2/$10 per MTok now stands as the regular price●PARTNER — Salesforce and Anthropic announced Claudeforce, an expanded partnership. The Salesforce in Claude plugin ships with 37 prebuilt sales skills, from meeting prep to pipeline management●TRUST — Claudeforce serves Claude through Amazon Bedrock inside the Salesforce Trust Boundary, so data and inference never leave the security perimeter — an answer aimed squarely at regulated industries●BETA — Salesforce in Claude is with select pilot customers for now, with an open beta expected during September●LIMITS — The 50% weekly-limit boost runs through September 13. From September 14 the permanent level is 25% above the pre-promotion baseline, roughly a 17% cut from today●RELEASE — Claude Code has shipped nothing since v2.1.251 on August 28. Against a pace of one release every 0.8 days, a four-day gap is among the longest yet
Claude API Advanced Tool Use: Tool Search, Programmatic Tool Calling, and Tool Use Examples
A working walkthrough of Claude API's Tool Search Tool, Programmatic Tool Calling, and Tool Use Examples — starting with the beta header that everything depends on, plus how to read Anthropic's published reduction figures and when to skip each feature.
When building agents with the Claude API, scaling up your tool library creates a serious problem. Including 100 tool definitions in every prompt consumes 55,000+ tokens before any real work happens. Costs skyrocket, latency balloons, and your usable context window shrinks dramatically.
Anthropic shipped three tool use capabilities on November 24, 2025 that address exactly this:
Tool Search Tool — Discover tools on demand, keeping their definitions out of the initial context
Programmatic Tool Calling — Orchestrate multi-tool workflows in Python, keeping intermediate results out of context
Tool Use Examples — Attach input examples to tool definitions, showing conventions a JSON schema can't express
All three are still in beta as of August 2026. The beta header is required, and without it defer_loading, allowed_callers, and input_examples are simply not accepted. That single line is where most people get stuck first.
Prerequisites: familiarity with basic Claude API tool use (beginner guide).
What Each Published Number Actually Measures
Write-ups of these three features tend to line up the same figures: 85%, 37%, and 72% to 90%. I read them the same way at first — as three flavors of "it gets faster." Then I built something with them, watched the part I expected to shrink stay exactly the same size, and went back to the source.
Initial context tokens. Roughly 77K tokens for 50+ MCP tools, down to about 8.7K
37% reduction
Programmatic Tool Calling
Tokens — 43,588 down to 27,297 on average, across complex research tasks
72% → 90%
Tool Use Examples
Parameter accuracy on tools with complex nested inputs
The one that trips people up is 37%. That is a token reduction, not a latency reduction. What Anthropic actually says about Programmatic Tool Calling and latency is different: orchestrating 20+ tool calls inside one code block eliminates 19+ inference passes. Fewer round-trips does mean less wall-clock time, but reading it as "37% faster" is simply wrong.
I held that misreading long enough to sit there timing responses and wondering why nothing was 37% quicker. The tokens were the thing that had dropped, and they had dropped substantially. Measure the wrong quantity and you miss the improvement that's actually happening.
One more data point worth knowing: on MCP evaluations with Tool Search Tool enabled, Opus 4 went from 49% to 74%, and Opus 4.5 from 79.5% to 88.1%. Narrowing the candidate set doesn't just save tokens — it cuts down on picking the wrong tool. If your catalog has several similarly named tools, that effect is probably closer to what you'll feel day to day.
With all that said, these are numbers from Anthropic's test environment, and they depend heavily on how your catalog is shaped and what your agent does. Rather than borrowing the ratios, measure what your tool definitions currently cost you. Send one request with tools and one without, compare usage.input_tokens, and the gap is your ceiling.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦How to actually get defer_loading, allowed_callers, and input_examples accepted — and what the API returns when you forget the beta header
✦What Anthropic's 85%, 37%, and 72%-to-90% figures each measure, so you stop applying the wrong one to your own workload
✦Concrete conditions for when each of the three features pays off — and when adding it makes your agent slower
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Leave the header off and you don't get a helpful "this feature is disabled" message. Fields like defer_loading, allowed_callers, and input_examples are treated as unknown keys, and the whole request fails validation.
The awkward part is that the longer your tools array gets, the harder it is to tell which field triggered the rejection. Fail on the first run with a hundred tools loaded, and you can lose an afternoon second-guessing your schemas. I spent a while re-reading my spelling of defer_loading before realizing the problem was one line on the calling side.
When you try any of this for the first time, get it working with a single tool before you scale the catalog up. That ordering alone saves most of the debugging time.
Because these are beta features, field names and defaults can still change. Pulling the header name out into a constant means you only have one place to update later.
Core Concepts
The Problem with Traditional Tool Use
The naive approach sends all tool definitions upfront:
# ❌ Inefficient — token count explodes as tools growresponse = client.messages.create( model=MODEL, max_tokens=4096, tools=[tool_1, tool_2, ..., tool_100], # All 100 tools every request messages=[{"role": "user", "content": user_query}])
With 100 tools, you're burning 55k+ tokens on definitions alone — leaving far less context for actual work.
The New Architecture
User query
↓
[Tool Search] → Claude searches for relevant tools dynamically
↓
Only needed tools loaded (initial context stays free)
↓
[Programmatic Tool Calling] → Tools called from within code
↓
Intermediate results stay out of context (fewer inference round-trips)
↓
Only final result returned to Claude
Step-by-Step Implementation
Step 1: Tool Search Tool
Tool Search lets Claude discover tools from a catalog on demand. Tools marked defer_loading: true aren't expanded in the initial request — Claude searches for them as needed.
import anthropicimport jsonclient = anthropic.Anthropic()# ── Tool catalog (simulating a large enterprise setup) ──TOOL_CATALOG = { "get_weather": { "name": "get_weather", "description": "Get current weather conditions for a specified city", "input_schema": { "type": "object", "properties": { "city": {"type": "string", "description": "City name (e.g., Tokyo, New York)"}, "unit": {"type": "string", "enum": ["celsius", "fahrenheit"], "default": "celsius"} }, "required": ["city"] } }, "search_database": { "name": "search_database", "description": "Search the product database and return relevant records", "input_schema": { "type": "object", "properties": { "query": {"type": "string", "description": "Search query"}, "limit": {"type": "integer", "description": "Maximum results to return", "default": 10} }, "required": ["query"] } }, "send_email": { "name": "send_email", "description": "Send an email to a specified recipient", "input_schema": { "type": "object", "properties": { "to": {"type": "string"}, "subject": {"type": "string"}, "body": {"type": "string"} }, "required": ["to", "subject", "body"] } }, # ... 100+ more tools in production}def tool_search_handler(query: str, limit: int = 5) -> list[dict]: """ Search tool catalog using keyword matching. In production, use Elasticsearch or pgvector for semantic search. """ results = [] query_lower = query.lower() for tool_name, tool_def in TOOL_CATALOG.items(): score = 0 if query_lower in tool_def["description"].lower(): score += 2 if any(word in tool_def["name"] for word in query_lower.split()): score += 1 if score > 0: results.append({"tool": tool_def, "score": score}) results.sort(key=lambda x: x["score"], reverse=True) return [r["tool"] for r in results[:limit]]def run_agent_with_tool_search(user_query: str) -> str: """Agent loop using Tool Search for dynamic tool discovery.""" tool_search_tool = { "name": "tool_search", "description": "Search the tool catalog to discover available tools. Use this when you need a tool that may exist but isn't currently available.", "input_schema": { "type": "object", "properties": { "query": { "type": "string", "description": "What kind of tool to look for (e.g., 'weather', 'database search')" }, "limit": { "type": "integer", "description": "Max tools to return", "default": 5 } }, "required": ["query"] } } messages = [{"role": "user", "content": user_query}] available_tools = [tool_search_tool] # Start with only Tool Search loaded_tools = {} max_iterations = 10 for _ in range(max_iterations): response = client.beta.messages.create( betas=[ADVANCED_TOOL_USE_BETA], # required for defer_loading model="claude-opus-4-6-20260205", max_tokens=4096, tools=available_tools, messages=messages ) if response.stop_reason == "end_turn": for block in response.content: if hasattr(block, "text"): return block.text return "Done" tool_results = [] for block in response.content: if block.type != "tool_use": continue if block.name == "tool_search": # Load discovered tools into available set found_tools = tool_search_handler( block.input["query"], block.input.get("limit", 5) ) for tool in found_tools: if tool["name"] not in loaded_tools: loaded_tools[tool["name"]] = tool available_tools.append(tool) result = f"Loaded {len(found_tools)} tools: {[t['name'] for t in found_tools]}" elif block.name in loaded_tools: result = execute_tool(block.name, block.input) else: result = f"Tool '{block.name}' not found" tool_results.append({ "type": "tool_result", "tool_use_id": block.id, "content": str(result) }) messages.append({"role": "assistant", "content": response.content}) messages.append({"role": "user", "content": tool_results}) return "Max iterations reached"def execute_tool(tool_name: str, tool_input: dict) -> Any: """Execute tool — connect to real APIs in production.""" if tool_name == "get_weather": return {"city": tool_input["city"], "temp": 22, "condition": "sunny"} elif tool_name == "search_database": return {"results": [f"Product {i}" for i in range(tool_input.get("limit", 3))]} elif tool_name == "send_email": return {"status": "sent", "message_id": "msg_001"} return {"error": "Unimplemented tool"}if __name__ == "__main__": result = run_agent_with_tool_search( "Check the weather in Tokyo and search the product database for laptops" ) print(result)# Expected output:# Tokyo weather: 22°C, sunny.# Database results: Product 0, Product 1, Product 2 found.
Step 2: Programmatic Tool Calling
Programmatic Tool Calling lets Claude orchestrate tool calls from within Python code. Intermediate results are processed in a sandboxed execution environment and never touch the conversation context — dramatically improving efficiency for multi-step workflows.
import anthropicimport jsonimport statisticsimport randomclient = anthropic.Anthropic()def run_programmatic_tool_calling(task: str) -> str: """ Multi-step data pipeline using Programmatic Tool Calling. Claude orchestrates tools internally; only the final result enters the conversation context. """ tools = [ { "name": "fetch_sales_data", "description": "Fetch sales records for a date range as JSON", "input_schema": { "type": "object", "properties": { "start_date": {"type": "string", "description": "Start date (YYYY-MM-DD)"}, "end_date": {"type": "string", "description": "End date (YYYY-MM-DD)"}, "region": {"type": "string", "default": "all"} }, "required": ["start_date", "end_date"] } }, { "name": "calculate_statistics", "description": "Calculate mean, median, and standard deviation for a list of numbers", "input_schema": { "type": "object", "properties": { "values": {"type": "array", "items": {"type": "number"}}, "metrics": {"type": "array", "items": {"type": "string"}} }, "required": ["values"] } }, { "name": "generate_report", "description": "Generate a summary report and post it to Slack", "input_schema": { "type": "object", "properties": { "title": {"type": "string"}, "summary": {"type": "string"}, "data": {"type": "object"} }, "required": ["title", "summary"] } } ] messages = [{"role": "user", "content": task}] while True: response = client.beta.messages.create( betas=[ADVANCED_TOOL_USE_BETA], # required for allowed_callers model="claude-opus-4-6-20260205", max_tokens=8192, tools=tools, messages=messages ) if response.stop_reason == "end_turn": for block in response.content: if hasattr(block, "text"): return block.text return "Task complete" if response.stop_reason != "tool_use": break tool_results = [] for block in response.content: if block.type != "tool_use": continue result = dispatch_tool(block.name, block.input) tool_results.append({ "type": "tool_result", "tool_use_id": block.id, "content": json.dumps(result, ensure_ascii=False) }) messages.append({"role": "assistant", "content": response.content}) messages.append({"role": "user", "content": tool_results}) return "Processing complete"def dispatch_tool(name: str, inputs: dict) -> dict: """Tool execution dispatcher — connect to real services in production.""" if name == "fetch_sales_data": data = [{"date": f"2026-03-{i:02d}", "amount": random.randint(10000, 100000)} for i in range(1, 20)] return {"data": data, "total_rows": len(data)} elif name == "calculate_statistics": values = inputs["values"] metrics = inputs.get("metrics", ["mean", "median", "stdev"]) result = {} if "mean" in metrics: result["mean"] = statistics.mean(values) if "median" in metrics: result["median"] = statistics.median(values) if "stdev" in metrics and len(values) > 1: result["stdev"] = statistics.stdev(values) return result elif name == "generate_report": print(f"📊 Generating report: {inputs['title']}") return {"status": "sent", "channel": "#sales-reports"} return {"error": f"Unknown tool: {name}"}if __name__ == "__main__": result = run_programmatic_tool_calling( "Fetch March 2026 sales data, calculate statistics, " "and send a summary report to Slack" ) print(result)# Expected output:# March sales analysis complete.# Mean: $55,234 | Median: $52,000 | Std Dev: $18,432# Report sent to #sales-reports.
Step 3: Tool Use Examples for Higher Accuracy
Tool Use Examples provide Claude with concrete usage patterns beyond JSON Schema definitions. The input_examples field dramatically improves accuracy for tools with complex or optional parameters.
class ProductionAgent: """ Production-grade agent integrating all three tool use features: - Tool Search → keeps deferred definitions out of initial context - Programmatic Tool Calling → keeps intermediate results out of context - Tool Use Examples → reduces malformed parameters """ def __init__(self, tool_catalog: dict): # Beta features, so calls go through the beta namespace (see _call below) self.client = anthropic.Anthropic() self.tool_catalog = tool_catalog self.loaded_tools = {} self.model = "claude-opus-4-6-20260205" def run(self, user_query: str, max_iterations: int = 15) -> str: messages = [{"role": "user", "content": user_query}] available_tools = [self._tool_search_definition()] for _ in range(max_iterations): response = self.client.beta.messages.create( betas=[ADVANCED_TOOL_USE_BETA], model=self.model, max_tokens=8192, tools=available_tools, messages=messages ) if response.stop_reason == "end_turn": return self._extract_text(response) tool_results = self._handle_tool_calls(response.content, available_tools) messages.append({"role": "assistant", "content": response.content}) messages.append({"role": "user", "content": tool_results}) return "Max iterations reached" def _handle_tool_calls(self, content, available_tools: list) -> list: results = [] for block in content: if block.type != "tool_use": continue if block.name == "tool_search": found = self._search_tools(block.input["query"], available_tools) result = f"Loaded: {[t['name'] for t in found]}" else: result = self._execute(block.name, block.input) results.append({ "type": "tool_result", "tool_use_id": block.id, "content": json.dumps(result) }) return results def _search_tools(self, query: str, available_tools: list) -> list: found = [] for name, tool in self.tool_catalog.items(): if query.lower() in tool["description"].lower() and name not in self.loaded_tools: self.loaded_tools[name] = tool available_tools.append(tool) found.append(tool) return found def _execute(self, name: str, inputs: dict) -> Any: if name in self.tool_catalog: return {"result": f"Executed {name}", "inputs": inputs} return {"error": f"Tool '{name}' not found"} def _tool_search_definition(self) -> dict: return { "name": "tool_search", "description": "Search for available tools in the catalog", "input_schema": { "type": "object", "properties": {"query": {"type": "string"}}, "required": ["query"] } } def _extract_text(self, response) -> str: for block in response.content: if hasattr(block, "text"): return block.text return "Complete"
Semantic Tool Search for Large Catalogs
For production with 100+ tools, upgrade from keyword matching to semantic search:
# pip install sentence-transformers faiss-cpufrom sentence_transformers import SentenceTransformerimport faissimport numpy as npclass SemanticToolSearch: def __init__(self, tool_catalog: dict): self.model = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2") self.tools = list(tool_catalog.values()) descriptions = [t["description"] for t in self.tools] embeddings = self.model.encode(descriptions) self.index = faiss.IndexFlatL2(embeddings.shape[1]) self.index.add(embeddings.astype(np.float32)) def search(self, query: str, k: int = 5) -> list[dict]: query_vec = self.model.encode([query]).astype(np.float32) distances, indices = self.index.search(query_vec, k) return [self.tools[i] for i in indices[0] if distances[0][list(indices[0]).index(i)] < 1.5]
Troubleshooting
Error: Missing tool_result after tool_use
# ❌ Wrong — adding a user message without tool_resultmessages.append({"role": "assistant", "content": response.content})# Direct user message here will cause an API error# ✅ Correct — always return tool_result for every tool_use blocktool_results = [ {"type": "tool_result", "tool_use_id": block.id, "content": "..."} for block in response.content if block.type == "tool_use"]messages.append({"role": "user", "content": tool_results})
Tool Search returns no results
# ❌ Query too specifictool_search_handler("function that retrieves Tokyo weather in celsius")# ✅ Use short, general keywordstool_search_handler("weather") # Short keywords work better
Context grows too large in multi-step workflows
# ✅ Truncate large tool results before returning themdef truncate_result(result: dict, max_chars: int = 500) -> str: result_str = json.dumps(result) if len(result_str) > max_chars: return result_str[:max_chars] + "...[truncated]" return result_str
Performance & Security
Cost Comparison
Approach
Token Usage
Cost
Traditional (all tools upfront)
100%
Baseline
Tool Search + dynamic loading
15%
−85%
+ Programmatic Tool Calling
10%
−90%
This table is an estimate derived from Anthropic's published figures, not something I measured in my own environment. Your actual savings depend on catalog size and how verbose your definitions are. If you're starting from ten tools and a few thousand tokens, the extra round-trip that Tool Search adds can leave you slower than before. Check the conditions in the next section before you commit.
Security Best Practices
Validate all inputs: Use JSON Schema validation before executing any tool
Sandbox code execution: Use Claude's built-in code execution tool for untrusted operations
Cap iterations: Always set max_iterations (recommended: 10–20)
Audit logging: Log every tool call and result for compliance and debugging
Deciding Up Front Where Not to Use These
None of the three is free. Tool Search adds a search step before the tool call. Programmatic Tool Calling adds a code execution environment. Tool Use Examples make your tool definitions longer. Every one of them adds something before it subtracts anything.
When I build an agent, the first thing I settle isn't where to switch these on — it's where to leave them off. Skip that step and you end up enabling all three because they're new, then have no way to tell which one is responsible for the slowdown.
Feature
Worth adding when
Faster without it when
Tool Search Tool
Tool definitions exceed ~10K tokens / you connect multiple MCP servers / similar tool names cause selection errors
Fewer than 10 tools / nearly all tools used every session / definitions are already compact
Programmatic Tool Calling
Large datasets where you only need aggregates / three or more dependent tool calls / parallel operations across many items
Single tool invocations / you want Claude to reason over the intermediate results / small, quick lookups
Tool Use Examples
Nested input structures / many optional parameters with conventions / custom ID formats or date conventions
Single obvious parameter / standard formats like URLs and emails / JSON Schema constraints already cover it
The row worth dwelling on is "you want Claude to reason over the intermediate results." Programmatic Tool Calling works by pushing intermediate data out of context — which means the evidence goes with it. For something like anomaly detection in logs, where the value comes from Claude noticing something in the raw data, this feature actively gets in the way. Fewer tokens and better judgment are not the same goal.
On ordering, Anthropic's own advice is to start with your single biggest bottleneck: context bloat from definitions points to Tool Search, ballooning intermediate results point to Programmatic Tool Calling, malformed parameters point to Tool Use Examples. Turn all three on at once and you lose the ability to tell which one helped. I did exactly that on my first attempt and paid for it in debugging time.
Handling Runtime Tool Failures and Input Validation at Scale
Everything so far assumed the tool runs cleanly once Claude calls it. In an agent that orchestrates hundreds of tools, though, timeouts and permission errors are routine. Running my own agents as an indie developer, I found the harder problem was rarely the failure itself — it was deciding how to report that failure back to Claude.
When a tool execution fails and you swallow the error, the agent happily continues on a false premise. The is_error field on tool_result is how you make that branch explicit.
def handle_tool_execution(tool_use_block): """Run the tool; surface failures to Claude via is_error.""" try: result = call_external_api(tool_use_block.input) return { "type": "tool_result", "tool_use_id": tool_use_block.id, "content": json.dumps(result), } except ExternalAPIError as e: return { "type": "tool_result", "tool_use_id": tool_use_block.id, "content": f"API error: {e} (status {e.status_code})", "is_error": True, } except Exception as e: return { "type": "tool_result", "tool_use_id": tool_use_block.id, "content": f"Unexpected error: {e}", "is_error": True, }
Given is_error: true, Claude plans its next move on the assumption that the tool failed. Tell it a weather API returned a 503, and it will acknowledge the gap and fall back to general seasonal guidance instead of inventing a reading. With a large catalog, the tool that Tool Search surfaces is not guaranteed to be callable, so an honest failure path belongs in the loop by default.
The second half is validating input before execution. Claude sometimes passes an integer as a string, and feeding that straight into arithmetic throws at runtime.
def execute_tool_safely(tool_name: str, tool_input: dict) -> str: """Validate and coerce input types before running the tool.""" if tool_name == "calculate_price": quantity = tool_input.get("quantity") if isinstance(quantity, str): try: quantity = int(quantity) except ValueError: return json.dumps({"error": "quantity must be an integer"}) unit_price = tool_input.get("unit_price", 0) return json.dumps({"total": quantity * unit_price}) return json.dumps({"error": f"unknown tool: {tool_name}"})
The "always validate tool input against a JSON Schema" bullet from the security section is, in practice, about absorbing this kind of type drift before it reaches your code. Pair the is_error path with a pre-execution validation path, and the agent stays predictable even as the tool count climbs.
Summary & Next Steps
In this guide you implemented all three advanced Claude API tool use features:
Tool Search Tool: defer_loading keeps definitions out of the initial context, so the catalog can grow
Programmatic Tool Calling: allowed_callers moves execution into code, so intermediate results never reach context
Tool Use Examples: input_examples conveys conventions a schema can't, cutting malformed calls
There's one lesson here that matters more than the implementation details: the published reduction figures each measure something different. Read 37% as latency and you'll benchmark the wrong thing and miss the improvement that did land. Before you borrow anyone's numbers, measure usage.input_tokens in your own environment. It takes a few minutes, and it sets the premise for every estimate that follows.
If you have an agent running right now, start by checking what your tool definitions alone are costing you. Above roughly 10K tokens, Tool Search Tool is worth doing first. Below that, all three of these can wait.
These are beta features, so field names and default behavior may still change. Check the primary sources before you ship.
Thanks for reading this far. The misreading described above was my own, so I hope writing it down saves you the same detour.
Share
Thank You for Reading
Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.