Cookbook

Programmatic tool calling (PTC)

Reduce latency and token consumption by letting Claude write code that calls tools programmatically in the code execution environment.

Pedram Navid
Pedram Navid
@PedramNavid
Published on November 24, 2025

Programatic Tool Calling (PTC) with the Claude API

Programmatic Tool Calling (PTC) allows Claude to write code that calls tools programmatically within the Code Execution environment, rather than requiring round-trips through the model for each tool invocation. This substantially reduces end-to-end latency for multiple tool calls, and can dramatically reduce token consumption by allowing the model to write code that removes irrelevant context before it hits the model’s context window (for example, by grepping for key information within large and noisy files).

When faced with third-party APIs and tools that you may not be able to modify directly, PTC can help reduce usage of context by allowing Claude to write code that can be invoked in the Code Execution environment.

In this cookbook, we will work with a mock API for team expense management. The API is designed to require multiple invocations and will return large results which help illustrate the benefits of Programmatic Tool Calling.

By the end of this cookbook, you'll be able to:

  • Understand the difference between regular tool calling and programatic tool calling (PTC)
  • Write agents that leverage PTC

Prerequisites

Before following this guide, ensure you have:

Required Knowledge

  • Python fundamentals - comfortable with async/await, functions, and basic data structures
  • Basic understanding of agentic patterns and tool calling

Required Tools

  • Python 3.11 or higher
  • Anthropic API key
  • Anthropic Python SDK >= 0.72

Setup

First, install the required dependencies:

python
# %pip install -r requirements.txt

Note: Ensure your .env file contains:

ANTHROPIC_API_KEY=your_key_here

Load your environment variables and configure the client. We also load a helper utility to visualize Claude message responses.

python
from dotenv import load_dotenv
from utils.visualize import visualize
 
load_dotenv()
 
MODEL = "claude-sonnet-4-5"
 
viz = visualize(auto_show=True)

Understanding the Third-Party API

In utils/team_expense_api.py, there are three functions defined: get_team_members, get_expenses, and get_custom_budget. The get_team_members function allows us to retrieve all employees in a given department with their role, level, and contact information. The get_expenses function returns all expense line items for an employee in a specific quarter—this can be several hundred records per employee, with each record containing extensive metadata including receipt URLs, approval chains, merchant details, and more. The get_custom_budget function checks if a specific employee has a custom travel budget exception (otherwise they use the standard $5,000 quarterly limit).

In this scenario, we need to analyze team expenses and identify which employees have exceeded their budgets. Traditionally, we might manually pull expense reports for each person, sum up their expenses by category, compare against budget limits (checking for custom budget exceptions), and compile a report. Instead, we will ask Claude to perform this analysis for us, using the available tools to retrieve team data, fetch potentially hundreds of expense line items with rich metadata, and determine who has gone over budget.

The key challenge here is that each employee may have 100+ expense line items that need to be fetched, parsed, and aggregated—and the get_custom_budget tool can only be called after analyzing expenses to see if someone exceeded the standard budget. This creates a sequential dependency chain that makes this an ideal use case for demonstrating the benefits of Programmatic Tool Calling.

We'll pass our tool definitions to the messages API and ask Claude to perform the analysis. Read the docs on implementing tool use if you are not familiar with how tool use works with Claude's API.

python
import json
 
import anthropic
from utils.team_expense_api import get_custom_budget, get_expenses, get_team_members
 
client = anthropic.Anthropic()
 
# Tool definitions for the team expense API
tools = [
    {
        "name": "get_team_members",
        "description": 'Returns a list of team members for a given department. Each team member includes their ID, name, role, level (junior, mid, senior, staff, principal), and contact information. Use this to get a list of people whose expenses you want to analyze. Available departments are: engineering, sales, and marketing.\n\nRETURN FORMAT: Returns a JSON string containing an ARRAY of team member objects (not wrapped in an outer object). Parse with json.loads() to get a list. Example: [{"id": "ENG001", "name": "Alice", ...}, {"id": "ENG002", ...}]',
        "input_schema": {
            "type": "object",
            "properties": {
                "department": {
                    "type": "string",
                    "description": "The department name. Case-insensitive.",
                }
            },
            "required": ["department"],
        },
        "input_examples": [
            {"department": "engineering"},
            {"department": "sales"},
            {"department": "marketing"},
        ],
    },
    {
        "name": "get_expenses",
        "description": "Returns all expense line items for a given employee in a specific quarter. Each expense includes extensive metadata: date, category, description, amount (in USD), currency, status (approved, pending, rejected), receipt URL, approval chain, merchant name and location, payment method, and project codes. An employee may have 20-50+ expense line items per quarter, and each line item contains substantial metadata for audit and compliance purposes. Categories include: 'travel' (flights, trains, rental cars, taxis, parking), 'lodging' (hotels, airbnb), 'meals', 'software', 'equipment', 'conference', 'office', and 'internet'. IMPORTANT: Only expenses with status='approved' should be counted toward budget limits.\n\nRETURN FORMAT: Returns a JSON string containing an ARRAY of expense objects (not wrapped in an outer object with an 'expenses' key). Parse with json.loads() to get a list directly. Example: [{\"expense_id\": \"ENG001_Q3_001\", \"amount\": 1250.50, \"category\": \"travel\", ...}, {...}]",
        "input_schema": {
            "type": "object",
            "properties": {
                "employee_id": {
                    "type": "string",
                    "description": "The unique employee identifier",
                },
                "quarter": {
                    "type": "string",
                    "description": "Quarter identifier: 'Q1', 'Q2', 'Q3', or 'Q4'",
                },
            },
            "required": ["employee_id", "quarter"],
        },
        "input_examples": [
            {"employee_id": "ENG001", "quarter": "Q3"},
            {"employee_id": "SAL002", "quarter": "Q1"},
            {"employee_id": "MKT001", "quarter": "Q4"},
        ],
    },
    {
        "name": "get_custom_budget",
        "description": 'Get the custom quarterly travel budget for a specific employee. Most employees have a standard $5,000 quarterly travel budget. However, some employees have custom budget exceptions based on their role requirements. This function checks if a specific employee has a custom budget assigned.\n\nRETURN FORMAT: Returns a JSON string containing a SINGLE OBJECT (not an array). Parse with json.loads() to get a dict. Example: {"user_id": "ENG001", "has_custom_budget": false, "travel_budget": 5000, "reason": "Standard", "currency": "USD"}',
        "input_schema": {
            "type": "object",
            "properties": {
                "user_id": {
                    "type": "string",
                    "description": "The unique employee identifier",
                }
            },
            "required": ["user_id"],
        },
        "input_examples": [
            {"user_id": "ENG001"},
            {"user_id": "SAL002"},
            {"user_id": "MKT001"},
        ],
    },
]
 
tool_functions = {
    "get_team_members": get_team_members,
    "get_expenses": get_expenses,
    "get_custom_budget": get_custom_budget,
}

Traditional Tool Calling (Baseline)

In this first example, we'll use traditional tool calling to establish our baseline.

We'll call the messages.create API with our initial query. When the model stops with a tool_use reason, we will execute the tool as requested, and then add the output from the tool to the messages and call the model again.

python
import time
 
from anthropic.types import TextBlock, ToolUseBlock
from anthropic.types.beta import (
    BetaMessageParam as MessageParam,
)
from anthropic.types.beta import (
    BetaTextBlock,
    BetaToolUseBlock,
)
 
messages: list[MessageParam] = []
 
 
def run_agent_without_ptc(user_message):
    """Run agent using traditional tool calling"""
    messages.append({"role": "user", "content": user_message})
    total_tokens = 0
    start_time = time.time()
    api_counter = 0
 
    while True:
        response = client.beta.messages.create(
            model=MODEL,
            max_tokens=4000,
            tools=tools,
            messages=messages,
            betas=["advanced-tool-use-2025-11-20"],
        )
 
        api_counter += 1
 
        # Track token usage
        total_tokens += response.usage.input_tokens + response.usage.output_tokens
        viz.capture(response)
        if response.stop_reason == "end_turn":
            # Extract the first text block from the response
            final_response = next(
                (
                    block.text
                    for block in response.content
                    if isinstance(block, (BetaTextBlock, TextBlock))
                ),
                None,
            )
            elapsed_time = time.time() - start_time
            return final_response, messages, total_tokens, elapsed_time, api_counter
 
        # Process tool calls
        if response.stop_reason == "tool_use":
            # First, add the assistant's response to messages
            messages.append({"role": "assistant", "content": response.content})
 
            # Collect all tool results
            tool_results = []
 
            for block in response.content:
                if isinstance(block, (BetaToolUseBlock, ToolUseBlock)):
                    tool_name = block.name
                    tool_input = block.input
                    tool_use_id = block.id
 
                    result = tool_functions[tool_name](**tool_input)
 
                    content = str(result)
 
                    tool_result = {
                        "type": "tool_result",
                        "tool_use_id": tool_use_id,
                        "content": content,
                    }
                    tool_results.append(tool_result)
 
            # Append all tool results at once after collecting them
            messages.append({"role": "user", "content": tool_results})
 
        else:
            print(f"\nUnexpected stop reason: {response.stop_reason}")
            elapsed_time = time.time() - start_time
 
            final_response = next(
                (
                    block.text
                    for block in response.content
                    if isinstance(block, (BetaTextBlock, TextBlock))
                ),
                f"Stopped with reason: {response.stop_reason}",
            )
            return final_response, messages, total_tokens, elapsed_time, api_counter

Our initial query to the model provides some instructions to help guide the model. For brevity, we've asked the model to only call each tool once. For deeper investigations, the model may wish to look into multiple systems or time spans.

python
query = "Which engineering team members exceeded their Q3 travel budget? Standard quarterly travel budget is $5,000. However, some employees have custom budget limits. For anyone who exceeded the $5,000 standard budget, check if they have a custom budget exception. If they do, use that custom limit instead to determine if they truly exceeded their budget."
python
# Run the agent
result, conversation, total_tokens, elapsed_time, api_count_without_ptc = run_agent_without_ptc(
    query
)
 
print(f"Result: {result}")
print(f"API calls made: {api_count_without_ptc}")
print(f"Total tokens used: {total_tokens:,}")
print(f"Total time taken: {elapsed_time:.2f}s")
╭────────────────────────────────────────────── Claude API Response ──────────────────────────────────────────────╮
 Claude Message (assistant)  tokens: 1,859 in • 85 out • 1,944 total                                            
 ├── Model: claude-sonnet-4-5-20250929                                                                           
 ├── Stop Reason: tool_use                                                                                       
 └── Content (2 blocks)                                                                                          
     ├── Block 1                                                                                                 
     │   └── Text                                                                                                
     │       └── I'll help you identify which engineering team members exceeded their Q3 travel budget. Let me   
start by getting the list of engineering team members.                                          
     └── Block 2                                                                                                 
         └── Tool Use: get_team_members                                                                          
             ├── ID: toolu_01LuouuJYp1sSvBe2Du7EG7v                                                              
             ├── Caller: model (direct)                                                                          
             └── Input:                                                                                          
                 └── {                                                                                           
                       "department": "engineering"                                                               
                     }                                                                                           
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯

╭────────────────────────────── Claude API Response ───────────────────────────────╮
 Claude Message (assistant)  tokens: 2,473 in • 497 out • 2,970 total            
 ├── Model: claude-sonnet-4-5-20250929                                            
 ├── Stop Reason: tool_use                                                        
 └── Content (9 blocks)                                                           
     ├── Block 1                                                                  
     │   └── Text                                                                 
     │       └── Now let me get the Q3 expenses for all engineering team members: 
     ├── Block 2                                                                  
     │   └── Tool Use: get_expenses                                               
     │       ├── ID: toolu_01Wu8LLTT2sKTTqpVwGT65Lj                               
     │       ├── Caller: model (direct)                                           
     │       └── Input:                                                           
     │           └── {                                                            
     │                 "employee_id": "ENG001",                                   
     │                 "quarter": "Q3"                                            
     │               }                                                            
     ├── Block 3                                                                  
     │   └── Tool Use: get_expenses                                               
     │       ├── ID: toolu_01KzjQ5mQJa9ocWjCGzYkD9F                               
     │       ├── Caller: model (direct)                                           
     │       └── Input:                                                           
     │           └── {                                                            
     │                 "employee_id": "ENG002",                                   
     │                 "quarter": "Q3"                                            
     │               }                                                            
     ├── Block 4                                                                  
     │   └── Tool Use: get_expenses                                               
     │       ├── ID: toolu_01RjjhZTg9JsKXE5E9S6Foho                               
     │       ├── Caller: model (direct)                                           
     │       └── Input:                                                           
     │           └── {                                                            
     │                 "employee_id": "ENG003",                                   
     │                 "quarter": "Q3"                                            
     │               }                                                            
     ├── Block 5                                                                  
     │   └── Tool Use: get_expenses                                               
     │       ├── ID: toolu_013xqpxpfc2N9rP5W5uMLAo9                               
     │       ├── Caller: model (direct)                                           
     │       └── Input:                                                           
     │           └── {                                                            
     │                 "employee_id": "ENG004",                                   
     │                 "quarter": "Q3"                                            
     │               }                                                            
     ├── Block 6                                                                  
     │   └── Tool Use: get_expenses                                               
     │       ├── ID: toolu_019zfzG6Wox8iDqy1dUXiH3t                               
     │       ├── Caller: model (direct)                                           
     │       └── Input:                                                           
     │           └── {                                                            
     │                 "employee_id": "ENG005",                                   
     │                 "quarter": "Q3"                                            
     │               }                                                            
     ├── Block 7                                                                  
     │   └── Tool Use: get_expenses                                               
     │       ├── ID: toolu_01RxfTz11tzvbVE7oEtqHaVB                               
     │       ├── Caller: model (direct)                                           
     │       └── Input:                                                           
     │           └── {                                                            
     │                 "employee_id": "ENG006",                                   
     │                 "quarter": "Q3"                                            
     │               }                                                            
     ├── Block 8                                                                  
     │   └── Tool Use: get_expenses                                               
     │       ├── ID: toolu_01FsFEtK1gTEPxg56eVrhhf6                               
     │       ├── Caller: model (direct)                                           
     │       └── Input:                                                           
     │           └── {                                                            
     │                 "employee_id": "ENG007",                                   
     │                 "quarter": "Q3"                                            
     │               }                                                            
     └── Block 9                                                                  
         └── Tool Use: get_expenses                                               
             ├── ID: toolu_01Ctq9dZbvzaVSLSZe86MTzb                               
             ├── Caller: model (direct)                                           
             └── Input:                                                           
                 └── {                                                            
                       "employee_id": "ENG008",                                   
                       "quarter": "Q3"                                            
                     }                                                            
╰──────────────────────────────────────────────────────────────────────────────────╯

╭────────────────────────────────────────────── Claude API Response ──────────────────────────────────────────────╮
 Claude Message (assistant)  tokens: 51,744 in • 290 out • 52,034 total                                         
 ├── Model: claude-sonnet-4-5-20250929                                                                           
 ├── Stop Reason: tool_use                                                                                       
 └── Content (7 blocks)                                                                                          
     ├── Block 1                                                                                                 
     │   └── Text                                                                                                
     │       └── Now let me calculate the approved travel expenses for each engineer and identify who exceeded   
$5,000:                                                                                         
     ├── Block 2                                                                                                 
     │   └── Tool Use: get_custom_budget                                                                         
     │       ├── ID: toolu_013oegKwjvToLwEW1daDD8av                                                              
     │       ├── Caller: model (direct)                                                                          
     │       └── Input:                                                                                          
     │           └── {                                                                                           
     │                 "user_id": "ENG001"                                                                       
     │               }                                                                                           
     ├── Block 3                                                                                                 
     │   └── Tool Use: get_custom_budget                                                                         
     │       ├── ID: toolu_0162W4Ycr9FcVVED65exjAj4                                                              
     │       ├── Caller: model (direct)                                                                          
     │       └── Input:                                                                                          
     │           └── {                                                                                           
     │                 "user_id": "ENG003"                                                                       
     │               }                                                                                           
     ├── Block 4                                                                                                 
     │   └── Tool Use: get_custom_budget                                                                         
     │       ├── ID: toolu_01JcTX5rnwFxA99Am33gXmh6                                                              
     │       ├── Caller: model (direct)                                                                          
     │       └── Input:                                                                                          
     │           └── {                                                                                           
     │                 "user_id": "ENG005"                                                                       
     │               }                                                                                           
     ├── Block 5                                                                                                 
     │   └── Tool Use: get_custom_budget                                                                         
     │       ├── ID: toolu_01QwNJz1wGeV5VeZoCd4ByER                                                              
     │       ├── Caller: model (direct)                                                                          
     │       └── Input:                                                                                          
     │           └── {                                                                                           
     │                 "user_id": "ENG006"                                                                       
     │               }                                                                                           
     ├── Block 6                                                                                                 
     │   └── Tool Use: get_custom_budget                                                                         
     │       ├── ID: toolu_01KoJ4gzfiu1TPccLJB86Wiq                                                              
     │       ├── Caller: model (direct)                                                                          
     │       └── Input:                                                                                          
     │           └── {                                                                                           
     │                 "user_id": "ENG007"                                                                       
     │               }                                                                                           
     └── Block 7                                                                                                 
         └── Tool Use: get_custom_budget                                                                         
             ├── ID: toolu_01MxeFPzHot9aE5fPuniFkui                                                              
             ├── Caller: model (direct)                                                                          
             └── Input:                                                                                          
                 └── {                                                                                           
                       "user_id": "ENG008"                                                                       
                     }                                                                                           
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯

╭────────────────────────────────────────────── Claude API Response ──────────────────────────────────────────────╮
 Claude Message (assistant)  tokens: 52,533 in • 992 out • 53,525 total                                         
 ├── Model: claude-sonnet-4-5-20250929                                                                           
 ├── Stop Reason: end_turn                                                                                       
 └── Content (1 blocks)                                                                                          
     └── Block 1                                                                                                 
         └── Text                                                                                                
             └── Now let me analyze the data. I'll calculate the approved travel expenses for each engineer:     
                                                                                                                 
                 **Analysis of Q3 Travel Expenses:**                                                             
                                                                                                                 
                 **ENG001 - Alice Chen (Senior Software Engineer)**                                              
                 - Approved travel expenses: $1,161.04 + $18.63 + $13.21 + $36.55 + $1,440.42 + $166.46 + $48.43 
                 + $1,124.56 + $1,245.90 + $1,498.42 = **$6,753.62**                                             
                 - Budget: $5,000 (Standard)                                                                     
                 - **EXCEEDED by $1,753.62** ❌                                                                  
                                                                                                                 
                 **ENG002 - Bob Martinez (Staff Engineer)**                                                      
                 - Approved travel expenses: $180.16 + $10.07 + $20.76 = **$210.99**                             
                 - Budget: $5,000 (Standard)                                                                     
                 - Under budget ✓                                                                                
                                                                                                                 
                 **ENG003 - Carol White (Software Engineer)**                                                    
                 - Approved travel expenses: $24.75 + $424.74 + $1,397.17 + $1,026.12 + $1,288.36 + $1,128.90 +  
                 $1,148.42 + $45.03 = **$6,483.49**                                                              
                 - Budget: $5,000 (Standard)                                                                     
                 - **EXCEEDED by $1,483.49** ❌                                                                  
                                                                                                                 
                 **ENG004 - David Kim (Principal Engineer)**                                                     
                 - Approved travel expenses: $21.68 + $46.12 + $1,008.68 + $46.43 = **$1,122.91**                
                 - Budget: $5,000 (Standard)                                                                     
                 - Under budget ✓                                                                                
                                                                                                                 
                 **ENG005 - Emma Johnson (Junior Software Engineer)                                              
                 ... (truncated)                                                                                 
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯

Result: Now let me analyze the data. I'll calculate the approved travel expenses for each engineer:

**Analysis of Q3 Travel Expenses:**

**ENG001 - Alice Chen (Senior Software Engineer)**
- Approved travel expenses: $1,161.04 + $18.63 + $13.21 + $36.55 + $1,440.42 + $166.46 + $48.43 + $1,124.56 + $1,245.90 + $1,498.42 = **$6,753.62**
- Budget: $5,000 (Standard)
- **EXCEEDED by $1,753.62** ❌

**ENG002 - Bob Martinez (Staff Engineer)**
- Approved travel expenses: $180.16 + $10.07 + $20.76 = **$210.99**
- Budget: $5,000 (Standard)
- Under budget ✓

**ENG003 - Carol White (Software Engineer)**
- Approved travel expenses: $24.75 + $424.74 + $1,397.17 + $1,026.12 + $1,288.36 + $1,128.90 + $1,148.42 + $45.03 = **$6,483.49**
- Budget: $5,000 (Standard)
- **EXCEEDED by $1,483.49** ❌

**ENG004 - David Kim (Principal Engineer)**
- Approved travel expenses: $21.68 + $46.12 + $1,008.68 + $46.43 = **$1,122.91**
- Budget: $5,000 (Standard)
- Under budget ✓

**ENG005 - Emma Johnson (Junior Software Engineer)**
- Approved travel expenses: $450.00 + $1,376.36 + $1,164.49 + $151.55 + $1,253.88 = **$4,396.28**
- Budget: $5,000 (Standard)
- Under budget ✓

**ENG006 - Frank Liu (Senior Software Engineer)**
- Approved travel expenses: $596.48 + $1,018.71 + $1,193.82 + $159.08 + $1,112.11 + $24.97 = **$4,105.17**
- Budget: $5,000 (Standard)
- Under budget ✓

**ENG007 - Grace Taylor (Software Engineer)**
- Approved travel expenses: $1,476.63 + $39.85 + $1,220.19 + $189.16 + $1,032.52 + $1,331.00 = **$5,289.35**
- Budget: $5,000 (Standard)
- **EXCEEDED by $289.35** ❌

**ENG008 - Henry Park (Staff Engineer)**
- Approved travel expenses: $15.63 + $166.05 + $1,018.94 + $1,224.34 + $1,120.32 + $1,345.90 = **$4,891.18**
- Budget: $5,000 (Standard)
- Under budget ✓

---

## Summary: Engineering Team Members Who Exceeded Their Q3 Travel Budget

**3 team members exceeded their quarterly travel budget:**

1. **Alice Chen (ENG001)** - Senior Software Engineer
   - Travel expenses: **$6,753.62**
   - Budget: $5,000
   - **Over budget by $1,753.62 (35% over)**

2. **Carol White (ENG003)** - Software Engineer
   - Travel expenses: **$6,483.49**
   - Budget: $5,000
   - **Over budget by $1,483.49 (30% over)**

3. **Grace Taylor (ENG007)** - Software Engineer
   - Travel expenses: **$5,289.35**
   - Budget: $5,000
   - **Over budget by $289.35 (6% over)**

All three employees have the standard $5,000 quarterly travel budget with no custom exceptions.
API calls made: 4
Total tokens used: 110,473
Total time taken: 35.38s

Great! We can see that Claude was able to use the available tools successfully to identify which team members exceeded their travel budgets. However, we can also see that we used a lot of tokens to accomplish this task. Claude had to ingest all the expense line items through its context window—potentially 100+ records per employee, each with extensive metadata including receipt URLs, approval chains, merchant information, and more—in order to parse them, sum up the totals by category, and compare against budget limits.

Additionally, the traditional tool calling approach requires multiple sequential round trips: first fetching team members, then expenses for each person, then checking custom budgets for those who exceeded the standard limit. Each round trip adds latency, and all the rich metadata from expense records flows through the model's context.

Let's see if we can use PTC to improve performance by allowing Claude to write code that processes these large datasets in the code execution environment instead.

To enable PTC on tools, we must first add the allowed_callers field to any tool that should be callable via code execution.

Key points to consider

  • Tools without allowed_callers default to model-only invocation
  • Tools can be invoked by both the model AND code execution by including multiple callers: ["direct", "code_execution_20250825"]
  • Only opt in tools that are safe for programmatic/repeated execution.
python
import copy
 
ptc_tools = copy.deepcopy(tools)
for tool in ptc_tools:
    tool["allowed_callers"] = ["code_execution_20250825"]  # type: ignore
 
 
# Add the code execution tool
ptc_tools.append(
    {
        "type": "code_execution_20250825",  # type: ignore
        "name": "code_execution",
    }
)

Now that we've updated our tool definitions to allow programmatic tool calling, we can run our agent with PTC. In order to do so, we've had to make a few changes to our function. We must use the beta messages API.

  1. We've added "advanced-tool-use-2025-11-20" to betas.
  2. We pass in the container_id if it is defined with our request. This is only necessary for stateful workflows like ours. In single-turn workflows this is not required.
  3. We can check the caller field in the tool_use block to determine if this tool call is from a direct model invocation or from programmatic invocation.

Note that in either case, we send our tool results via the Claude API, however only direct invocations will be "seen" by the model. code_execution_20250825 types will only be seen my the code execution container.

python
messages = []
 
 
def run_agent_with_ptc(user_message):
    """Run agent using PTC"""
    messages.append({"role": "user", "content": user_message})
    total_tokens = 0
    start_time = time.time()
    container_id = None
    api_counter = 0
 
    while True:
        # Build request with PTC beta headers
        request_params = {
            "model": MODEL,
            "max_tokens": 4000,
            "tools": ptc_tools,
            "messages": messages,
        }
 
        response = client.beta.messages.create(
            **request_params,
            betas=[
                "advanced-tool-use-2025-11-20",
            ],
            extra_body={"container": container_id} if container_id else None,
        )
        viz.capture(response)
        api_counter += 1
 
        # Track container for stateful execution
        if hasattr(response, "container") and response.container:
            container_id = response.container.id
            print(f"\n[Container] ID: {container_id}")
            if hasattr(response.container, "expires_at"):
                # If the container has expired, we would need to restart our workflow. In our case, it completes before expiration.
                print(f"[Container] Expires at: {response.container.expires_at}")
 
        # Track token usage
        total_tokens += response.usage.input_tokens + response.usage.output_tokens
 
        if response.stop_reason == "end_turn":
            # Extract the first text block from the response
            final_response = next(
                (block.text for block in response.content if isinstance(block, BetaTextBlock)),
                None,
            )
            elapsed_time = time.time() - start_time
            return final_response, messages, total_tokens, elapsed_time, api_counter
 
        # As before, we process tool calls
        if response.stop_reason == "tool_use":
            # First, add the assistant's response to messages
            messages.append({"role": "assistant", "content": response.content})
 
            # Collect all tool results
            tool_results = []
 
            for block in response.content:
                if isinstance(block, BetaToolUseBlock):
                    tool_name = block.name
                    tool_input = block.input
                    tool_use_id = block.id
 
                    # We can use caller type to understand how the tool was invoked
                    caller_type = block.caller["type"]  # type: ignore
 
                    if caller_type == "code_execution_20250825":
                        print(f"[PTC] Tool called from code execution environment: {tool_name}")
 
                    elif caller_type == "direct":
                        print(f"[Direct] Tool called by model: {tool_name}")
 
                    result = tool_functions[tool_name](**tool_input)
 
                    # Format result as proper content for the API
                    if isinstance(result, list) and result and isinstance(result[0], str):
                        content = "\n".join(result)
                    elif isinstance(result, (dict, list)):
                        content = json.dumps(result)
                    else:
                        content = str(result)
 
                    tool_results.append(
                        {
                            "type": "tool_result",
                            "tool_use_id": tool_use_id,
                            "content": content,
                        }
                    )
 
            messages.append({"role": "user", "content": tool_results})
 
        else:
            print(f"\nUnexpected stop reason: {response.stop_reason}")
            elapsed_time = time.time() - start_time
 
            final_response = next(
                (block.text for block in response.content if isinstance(block, BetaTextBlock)),
                f"Stopped with reason: {response.stop_reason}",
            )
            return final_response, messages, total_tokens, elapsed_time, api_counter
python
# Run the PTC agent
result_ptc, conversation_ptc, total_tokens_ptc, elapsed_time_ptc, api_count_with_ptc = (
    run_agent_with_ptc(query)
)
╭────────────────────────────────────────────── Claude API Response ──────────────────────────────────────────────╮
 Claude Message (assistant)  tokens: 4,134 in • 539 out • 4,673 total                                           
 ├── Model: claude-sonnet-4-5-20250929                                                                           
 ├── Stop Reason: tool_use                                                                                       
 └── Content (3 blocks)                                                                                          
     ├── Block 1                                                                                                 
     │   └── Text                                                                                                
     │       └── I'll help you identify which engineering team members exceeded their Q3 travel budget. Let me   
start by getting the engineering team members and their expenses.                               
     ├── Block 2                                                                                                 
     │   └── Server Tool Use                                                                                     
     │       ├── ID: srvtoolu_015mWPqaFni4B313UieCxbny                                                           
     │       ├── Caller: direct                                                                                  
     │       └── Code:                                                                                           
     │           └──    1                                                                                        
   2 import asyncio                                                                         
   3 import json                                                                            
   4                                                                                        
   5 async def main():                                                                      
   6     # First, get all engineering team members                                          
   7     team_members_json = await get_team_members({'department': 'engineering'})          
   8     team_members = json.loads(team_members_json)                                       
   9                                                                                        
  10     print(f"Found {len(team_members)} engineering team members")                       
  11                                                                                        
  12     # Get Q3 expenses for all team members in parallel                                 
  13     expense_tasks = [                                                                  
  14         get_expenses({'employee_id': member['id'], 'quarter': 'Q3'})                   
  15         for member in team_members                                                     
  16     ]                                                                                  
  17                                                                                        
  18     expenses_results = await asyncio.gather(*expense_tasks)                            
  19                                                                                        
  20     # Calculate travel expenses for each member                                        
  21     travel_spending = {}                                                               
  22     for i, member in enumerate(team_members):                                          
  23         expenses = json.loads(expenses_results[i])                                     
  24         # Only count approved expenses in travel category                              
  25         travel_total = sum(                                                            
  26             expense['amount']                                                          
  27             for expense in expenses                                                    
  28             if expense['category'] == 'travel' and expense['status'] == 'approved'     
  29         )                                                                              
  30         travel_spending[member[                                                        
  31 ... (truncated)                                                                        
     └── Block 3                                                                                                 
         └── Tool Use: get_team_members                                                                          
             ├── ID: toolu_016EtCE7G5rH645G3gvjzrP6                                                              
             ├── Caller: code execution environment                                                              
             └── Input:                                                                                          
                 └── {                                                                                           
                       "department": "engineering"                                                               
                     }                                                                                           
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯


[Container] ID: container_011CVSAwq5J4vNPi3A4P2Rwh
[Container] Expires at: 2025-11-24 05:41:17.467494+00:00
[PTC] Tool called from code execution environment: get_team_members

╭──────────────────── Claude API Response ────────────────────╮
 Claude Message (assistant)  tokens: 0 in • 0 out • 0 total 
 ├── Model: claude-sonnet-4-5-20250929                       
 ├── Stop Reason: tool_use                                   
 └── Content (8 blocks)                                      
     ├── Block 1                                             
     │   └── Tool Use: get_expenses                          
     │       ├── ID: toolu_01Nq2au3W69RmDFZaSdqe6u1          
     │       ├── Caller: code execution environment          
     │       └── Input:                                      
     │           └── {                                       
     │                 "employee_id": "ENG007",              
     │                 "quarter": "Q3"                       
     │               }                                       
     ├── Block 2                                             
     │   └── Tool Use: get_expenses                          
     │       ├── ID: toolu_01YYw9cuTSXk7bu7P38qBz6P          
     │       ├── Caller: code execution environment          
     │       └── Input:                                      
     │           └── {                                       
     │                 "employee_id": "ENG005",              
     │                 "quarter": "Q3"                       
     │               }                                       
     ├── Block 3                                             
     │   └── Tool Use: get_expenses                          
     │       ├── ID: toolu_01Fyxxe2KmVpVmw4jJL2CXSz          
     │       ├── Caller: code execution environment          
     │       └── Input:                                      
     │           └── {                                       
     │                 "employee_id": "ENG008",              
     │                 "quarter": "Q3"                       
     │               }                                       
     ├── Block 4                                             
     │   └── Tool Use: get_expenses                          
     │       ├── ID: toolu_01J4ovDu2UJa9Se19vxKTa6y          
     │       ├── Caller: code execution environment          
     │       └── Input:                                      
     │           └── {                                       
     │                 "employee_id": "ENG006",              
     │                 "quarter": "Q3"                       
     │               }                                       
     ├── Block 5                                             
     │   └── Tool Use: get_expenses                          
     │       ├── ID: toolu_01T24CrvQYA3LqGfZftCmueC          
     │       ├── Caller: code execution environment          
     │       └── Input:                                      
     │           └── {                                       
     │                 "employee_id": "ENG003",              
     │                 "quarter": "Q3"                       
     │               }                                       
     ├── Block 6                                             
     │   └── Tool Use: get_expenses                          
     │       ├── ID: toolu_01HotYZN6sk3gMLkpdWXbdz4          
     │       ├── Caller: code execution environment          
     │       └── Input:                                      
     │           └── {                                       
     │                 "employee_id": "ENG004",              
     │                 "quarter": "Q3"                       
     │               }                                       
     ├── Block 7                                             
     │   └── Tool Use: get_expenses                          
     │       ├── ID: toolu_01AxvEqi3AKqdnH44kGH1U6E          
     │       ├── Caller: code execution environment          
     │       └── Input:                                      
     │           └── {                                       
     │                 "employee_id": "ENG002",              
     │                 "quarter": "Q3"                       
     │               }                                       
     └── Block 8                                             
         └── Tool Use: get_expenses                          
             ├── ID: toolu_01A4agznkK1jA4AyJo3H2jpg          
             ├── Caller: code execution environment          
             └── Input:                                      
                 └── {                                       
                       "employee_id": "ENG001",              
                       "quarter": "Q3"                       
                     }                                       
╰─────────────────────────────────────────────────────────────╯


[Container] ID: container_011CVSAwq5J4vNPi3A4P2Rwh
[Container] Expires at: 2025-11-24 05:41:19.266670+00:00
[PTC] Tool called from code execution environment: get_expenses
[PTC] Tool called from code execution environment: get_expenses
[PTC] Tool called from code execution environment: get_expenses
[PTC] Tool called from code execution environment: get_expenses
[PTC] Tool called from code execution environment: get_expenses
[PTC] Tool called from code execution environment: get_expenses
[PTC] Tool called from code execution environment: get_expenses
[PTC] Tool called from code execution environment: get_expenses

╭─────────────────────────────────────── Claude API Response ───────────────────────────────────────╮
 Claude Message (assistant)  tokens: 4,751 in • 679 out • 5,430 total                             
 ├── Model: claude-sonnet-4-5-20250929                                                             
 ├── Stop Reason: tool_use                                                                         
 └── Content (6 blocks)                                                                            
     ├── Block 1                                                                                   
     │   └── Code Execution Result: Success (exit 0)                                               
     │       └── stdout:                                                                           
     │           └── Found 8 engineering team members                                              

Employees who exceeded $5,000 standard budget: 3                              
ENG001: Alice Chen - $9177.88                                                 
ENG003: Carol White - $6483.49                                                
ENG007: Grace Taylor - $5289.35                                               

     ├── Block 2                                                                                   
     │   └── Text                                                                                  
     │       └── Now let me check if any of these three employees have custom budget exceptions:   
     ├── Block 3                                                                                   
     │   └── Server Tool Use                                                                       
     │       ├── ID: srvtoolu_015GTpFmCbd2JPAQLAioB4Qb                                             
     │       ├── Caller: direct                                                                    
     │       └── Code:                                                                             
     │           └──    1                                                                          
   2 import asyncio                                                           
   3 import json                                                              
   4                                                                          
   5 async def main():                                                        
   6     # Check custom budgets for the three employees who exceeded standard 
   7     exceeded_ids = ['ENG001', 'ENG003', 'ENG007']                        
   8     exceeded_amounts = {                                                 
   9         'ENG001': {'name': 'Alice Chen', 'travel_total': 9177.88},       
  10         'ENG003': {'name': 'Carol White', 'travel_total': 6483.49},      
  11         'ENG007': {'name': 'Grace Taylor', 'travel_total': 5289.35}      
  12     }                                                                    
  13                                                                          
  14     # Get custom budgets in parallel                                     
  15     budget_tasks = [                                                     
  16         get_custom_budget({'user_id': emp_id})                           
  17         for emp_id in exceeded_ids                                       
  18     ]                                                                    
  19                                                                          
  20     budget_results = await asyncio.gather(*budget_tasks)                 
  21                                                                          
  22     # Analyze who truly exceeded their budget                            
  23     truly_exceeded = []                                                  
  24                                                                          
  25     for i, emp_id in enumerate(exceeded_ids):                            
  26         budget_info = json.loads(budget_results[i])                      
  27         actual_budget = budget_info['travel_budget']                     
  28         travel_total = exceeded_amounts[emp_id]['travel_total']          
  29         name = exceeded_amounts[emp_id]['name']                          
  30                                                                          
  31         if travel_total > actua                                          
  32 ... (truncated)                                                          
     ├── Block 4                                                                                   
     │   └── Tool Use: get_custom_budget                                                           
     │       ├── ID: toolu_01EaaJ3SikeniibPsEdAqXo8                                                
     │       ├── Caller: code execution environment                                                
     │       └── Input:                                                                            
     │           └── {                                                                             
     │                 "user_id": "ENG003"                                                         
     │               }                                                                             
     ├── Block 5                                                                                   
     │   └── Tool Use: get_custom_budget                                                           
     │       ├── ID: toolu_01E5bqQ4xKX7FTdhh6xLkw4E                                                
     │       ├── Caller: code execution environment                                                
     │       └── Input:                                                                            
     │           └── {                                                                             
     │                 "user_id": "ENG001"                                                         
     │               }                                                                             
     └── Block 6                                                                                   
         └── Tool Use: get_custom_budget                                                           
             ├── ID: toolu_01PLF38q7ndVQB4mqdqaFt7u                                                
             ├── Caller: code execution environment                                                
             └── Input:                                                                            
                 └── {                                                                             
                       "user_id": "ENG007"                                                         
                     }                                                                             
╰───────────────────────────────────────────────────────────────────────────────────────────────────╯


[Container] ID: container_011CVSAwq5J4vNPi3A4P2Rwh
[Container] Expires at: 2025-11-24 05:41:33.430636+00:00
[PTC] Tool called from code execution environment: get_custom_budget
[PTC] Tool called from code execution environment: get_custom_budget
[PTC] Tool called from code execution environment: get_custom_budget

╭────────────────────────────────────────────── Claude API Response ──────────────────────────────────────────────╮
 Claude Message (assistant)  tokens: 5,611 in • 205 out • 5,816 total                                           
 ├── Model: claude-sonnet-4-5-20250929                                                                           
 ├── Stop Reason: end_turn                                                                                       
 └── Content (2 blocks)                                                                                          
     ├── Block 1                                                                                                 
     │   └── Code Execution Result: Success (exit 0)                                                             
     │       └── stdout:                                                                                         
     │           └── ENGINEERING TEAM MEMBERS WHO EXCEEDED THEIR Q3 TRAVEL BUDGET:                               
================================================================================            

Alice Chen (ENG001)                                                                         
  Budget Limit: $5,000.00 (Standard)                                                        
  Travel Spending: $9,177.88                                                                
  Exceeded By: $4,177.88                                                                    

Carol White (ENG003)                                                                        
  Budget Limit: $5,000.00 (Standard)                                                        
  Travel Spending: $6,483.49                                                                
  Exceeded By: $1,483.49                                                                    

Grace Taylor (ENG007)                                                                       
  Budget Limit: $5,000.00 (Standard)                                                        
  Travel Spending: $5,289.35                                                                
  Exceeded By: $289.35                                                                      

     └── Block 2                                                                                                 
         └── Text                                                                                                
             └── ## Summary                                                                                      
                                                                                                                 
                 **Three engineering team members exceeded their Q3 travel budget:**                             
                                                                                                                 
                 1. **Alice Chen (ENG001)**                                                                      
                    - Budget: $5,000 (Standard)                                                                  
                    - Spent: $9,177.88                                                                           
                    - Over budget by: **$4,177.88**                                                              
                                                                                                                 
                 2. **Carol White (ENG003)**                                                                     
                    - Budget: $5,000 (Standard)                                                                  
                    - Spent: $6,483.49                                                                           
                    - Over budget by: **$1,483.49**                                                              
                                                                                                                 
                 3. **Grace Taylor (ENG007)**                                                                    
                    - Budget: $5,000 (Standard)                                                                  
                    - Spent: $5,289.35                                                                           
                    - Over budget by: **$289.35**                                                                
                                                                                                                 
                 All three employees are on the standard $5,000 quarterly travel budget with no custom           
                 exceptions, so they all genuinely exceeded their allocated travel budget for Q3.                
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
python
print(f"\n{'=' * 60}")
print(f"Result: {result_ptc}")
print(f"\n{'=' * 60}")
print("Performance Metrics:")
print(
    f"  Total API calls to Claude: {len([m for m in conversation_ptc if m['role'] == 'assistant'])}"
)
print(f"  Total tokens used: {total_tokens_ptc:,}")
print(f"  Total time taken: {elapsed_time_ptc:.2f}s")
============================================================
Result: ## Summary

**Three engineering team members exceeded their Q3 travel budget:**

1. **Alice Chen (ENG001)**
   - Budget: $5,000 (Standard)
   - Spent: $9,177.88
   - Over budget by: **$4,177.88**

2. **Carol White (ENG003)**
   - Budget: $5,000 (Standard)
   - Spent: $6,483.49
   - Over budget by: **$1,483.49**

3. **Grace Taylor (ENG007)**
   - Budget: $5,000 (Standard)
   - Spent: $5,289.35
   - Over budget by: **$289.35**

All three employees are on the standard $5,000 quarterly travel budget with no custom exceptions, so they all genuinely exceeded their allocated travel budget for Q3.

============================================================
Performance Metrics:
  Total API calls to Claude: 3
  Total tokens used: 15,919
  Total time taken: 34.88s

Performance Comparison

Let's compare the performance between traditional tool calling and PTC:

Note on API Call Count: You may notice that PTC requires more API calls in this example. This is because PTC writes more structured, sequential code that follows best practices—for instance, separating the expense fetching step from the budget checking step. Traditional tool calling can sometimes batch operations together in a single turn, but at the cost of sending all raw data through the model's context. The token efficiency gains from PTC far outweigh the minimal increase in round trips, especially when working with large, metadata-rich datasets.

python
import pandas as pd
 
# Create comparison dataframe
comparison_data = {
    "Metric": [
        "API Calls",
        "Total Tokens",
        "Elapsed Time (s)",
        "Token Reduction",
        "Time Reduction",
    ],
    "Traditional": [
        api_count_without_ptc,
        f"{total_tokens:,}",
        f"{elapsed_time:.2f}",
        "-",
        "-",
    ],
    "PTC": [
        api_count_with_ptc,
        f"{total_tokens_ptc:,}",
        f"{elapsed_time_ptc:.2f}",
        f"{((total_tokens - total_tokens_ptc) / total_tokens * 100):.1f}%",
        f"{((elapsed_time - elapsed_time_ptc) / elapsed_time * 100):.1f}%",
    ],
}
 
df = pd.DataFrame(comparison_data)
print(df.to_string(index=False))
Metric Traditional    PTC
       API Calls           4      4
    Total Tokens     110,473 15,919
Elapsed Time (s)       35.38  34.88
 Token Reduction           -  85.6%
  Time Reduction           -   1.4%

Key Takeaways

In this example, PTC demonstrated significant performance improvements through three core capabilities:

1. Context Preservation Through Large Data Parsing

This was the primary benefit demonstrated in our workflow. Claude wrote code to fetch and process hundreds of expense line items within the code execution environment. By processing this data programmatically, Claude parsed JSON, filtered by status, summed amounts by category, and compared against budget limits—all without sending the raw expense data and metadata through the model's context window. This resulted in a significant reduction in token usage.

2. Sequential Dependency Optimization

The API has a sequential dependency: get_custom_budget(user_id) which can only be called after analyzing expenses to identify who exceeded the standard $5,000 budget. In traditional tool calling, this requires multiple round trips—fetch team members, fetch expenses for each person, identify those over budget, then check their custom budgets one by one. With PTC, Claude writes code that orchestrates this entire workflow in the code execution environment, making programmatic tool calls in a loop and maintaining state across calls. This transforms what would be many sequential API round trips into fewer calls with smarter orchestration.

3. Computational Logic in Code Execution

Rather than requiring the model to mentally track and sum dozens of expenses with complex metadata, Claude delegated the arithmetic and aggregation logic to Python code. This reduced cognitive load on the model, ensured precise calculations, and kept irrelevant metadata (like receipt URLs and merchant locations) out of the model's context entirely.


When to Use PTC

PTC is most beneficial when:

  • Working with large, metadata-rich datasets that need filtering, parsing, or aggregation (like our expense analysis with receipt URLs, approval chains, merchant details, etc.)
  • Sequential dependencies exist where one tool call depends on the results of previous calls (like checking custom budgets only for employees who exceeded standard limits)
  • Multiple tool calls are needed in sequence or in loops across similar entities (checking expenses and budgets for each team member)
  • Computational logic can reduce what needs to flow through the model's context
  • Tools are safe for programmatic/repeated execution without human oversight

Conclusion

Our team expense analysis demonstrated PTC's strengths: dramatically reducing context consumption when working with large, metadata-rich datasets and optimizing workflows with sequential dependencies. By allowing Claude to write code that orchestrates tool calls and processes results programmatically, we achieved substantial token savings while maintaining accuracy and insight quality.

PTC is particularly valuable for workflows involving bulk data processing with rich metadata, repeated tool invocations with dependencies, or scenarios where raw tool outputs would otherwise pollute the model's context.

Next Steps

Try adapting this pattern to your own use cases:

  • Financial data analysis and reporting with sequential lookups
  • Multi-entity health checks that depend on initial scan results
  • Large file processing with metadata (CSV, JSON, XML parsing)
  • Database query result aggregation with follow-up queries
  • Batch API operations with conditional logic based on initial results