Skip to content

Programmatic Tool Calling (PTC)

PTC lets LLMs write Python code that orchestrates multiple tool calls in a single code block, reducing round-trips and token consumption.

Quick start

from toolregistry import ToolRegistry

registry = ToolRegistry()
registry.register(search)
registry.register(summarize)
registry.enable_code_execution()  # registers "code_execution" tool

# Now the LLM can generate tool_use("code_execution", {code: "..."})

How it works

LLM: tool_use("code_execution", {code: "..."})
  → CodeExecutionTool.execute(code)
    → Subprocess: exec(code, {search: stub, summarize: stub})
      → search(query="weather")
        → IPC → main process → registry.invoke("search", {...})
        → result back via IPC
      → summarize(data)
        → IPC → main process → registry.invoke("summarize", {...})
        → result back via IPC
      → print(final_output)
    → return stdout to LLM

Key points:

  • Code runs in an isolated subprocess — crashes don't affect the main process
  • Tool calls go through registry.invoke()permissions and logging are enforced
  • Only print() output is returned to the LLM — intermediate results stay in variables
  • AST validation blocks dangerous code (file I/O, network, unsafe imports)

Example: multi-tool orchestration

Without PTC (3 round-trips):

Turn 1: LLM → tool_use("search", {query: "..."})     → result
Turn 2: LLM → tool_use("filter", {data: result, ...}) → filtered
Turn 3: LLM → tool_use("summarize", {data: filtered}) → summary

With PTC (1 round-trip):

# LLM generates this code:
data = search(query="climate change")
filtered = [item for item in data if item["year"] >= 2024]
summary = summarize(data=filtered)
print(f"Found {len(filtered)} recent articles.\n{summary}")

Safety model

Layer Protection
AST validation Blocks import os, open(), eval(), subprocess, network access, etc.
Subprocess isolation Code runs in a fresh process — segfaults, OOM, infinite loops are contained
Permission enforcement Tool calls go through registry.invoke() with full permission checks
Namespace restriction Only registered tools are available — no access to registry internals

Invocation tracking

Each PTC execution generates a tr_ptc_ invocation ID shared by all tool calls within that execution:

registry.enable_logging()
registry.enable_code_execution()

tool = registry.get_tool("code_execution")
tool.run({"code": "print(add(a=1, b=2))"})

# Get the invocation ID
executor = registry._code_execution
inv_id = executor.last_invocation_id  # "tr_ptc_a1b2c3d4"

# Query all tool calls from this execution
log = registry.get_execution_log()
entries = log.get_entries(invocation_id=inv_id)

Configuration

# Custom timeout (default: 30 seconds)
registry.enable_code_execution(timeout=60)

# Disable when not needed
registry.disable_code_execution()

Requirements

PTC requires the codecell package:

pip install toolregistry[ptc]

What PTC cannot do

  • Call tools not registered in the registry — only namespace-injected tools are available
  • Persist state between executions — each execute() runs in a fresh subprocess
  • Access files or network directly — all I/O must go through registered tools
  • Import arbitrary Python packages — only safe computation modules are allowed