Skip to main content

schema2code

Convert LLM tool schemas into a compact, sandboxed Python code interface.

PyPI Python License Tests Dependencies

English · 한국어 · 日本語


Instead of resending JSON Schemas on every turn, hand the model short function signatures and let it write one Python program that calls your tools inside a restricted sandbox.

Traditional:  [JSON schemas, every turn] → tool-call JSON → execute → repeat
schema2code:  [compact signatures, once] → Python code → sandbox → result

Measured, not estimated

Live A/B against the OpenAI API — 15 tasks × 2 arms, gpt-4o-mini ×3 repeats + gpt-4o ×1, temperature 0, token counts taken from the API usage field:

JSON tool-calling schema2code delta
Tokens per task 3,319 995 −70%
API round trips 3.35 1.15 −66%
Answer accuracy 61.7% 78.3% +16.7pt
… gpt-4o only 80% 100%
Cost $0.138 $0.054 −61%
Sandbox-guard false positives — 0 / 60 runs

The gap widens with task complexity: loop-style tasks drop from 4.2 round trips to 1.0, and the hardest task saved 89% of tokens. Full method, per-task tables, and raw transcripts live in benchmarks/results/ — every run writes report.md, transcript.jsonl, and runs.json.

Install

pip install schema2code
# optional extras
pip install "schema2code[tiktoken]"   # accurate token counts
pip install "schema2code[openai]"     # agent-loop example / integration tests

Zero runtime dependencies. Python ≥ 3.10, Windows/macOS/Linux.

Sixty seconds

from schema2code import ToolRegistry, Sandbox

registry = ToolRegistry()

@registry.tool
def get_weather(city: str, unit: str = "celsius") -> dict:
    """Return current weather for a city."""
    return {"city": city, "temp": 20, "unit": unit}

@registry.tool
def add(a: int, b: int) -> int:
    """Add two numbers."""
    return a + b

sandbox = Sandbox(registry, timeout=8.0)

outcome = sandbox.run("""
w = get_weather("Berlin")
result = add(w["temp"], 2)
print("temp+2 =", result)
""")

outcome is a plain dataclass:

success       True
result        22
stdout        'temp+2 = 22\n'
tools_called  ['get_weather', 'add']
duration      0.0002

And this is the entire prompt block the model needs — the output of registry.to_prompt():

# Available tools
get_weather(city: str, unit: str = 'celsius') -> dict
    # Return current weather for a city.
add(a: int, b: int) -> int
    # Add two numbers.

## How to call tools

Write Python code that calls the functions below.
- Use only the listed tools and allowed standard modules.
- Prefer clear intermediate variables.
- Print values you want to inspect; assign the final value to `result`
  or leave it as the last expression.
- Do not import disallowed modules, open files, or access the network.

Failures come back classified, ready to feed to the model for a retry:

outcome = sandbox.run('import json\nresult = json.__builtins__')
outcome.error_type    # "security"
outcome.short_error   # "... Blocked by sandbox guard: attribute access to '__builtins__' is blocked (line 2) ..."

error_type is one of ok · syntax · timeout · tool · security · import · runtime · policy · validation.

Already have schemas?

OpenAI and Anthropic tool definitions import directly, constraints included. Names that aren't valid Python identifiers (MCP-style set-temp) are sanitized so the generated code can actually call them:

registry.load_openai_tools(openai_tools, handlers={"set-temp": set_temp})
registry.load_anthropic_tools(anthropic_tools, handlers=...)
### set_temp
Parameters:
  - value: float (required) [minimum=0, maximum=100]
  - unit: str (required) [enum=['celsius', 'fahrenheit']]

With Sandbox(registry, validate_calls=True) those constraints are enforced at call time — set_temp(value=150, ...) fails with Invalid argument for set_temp.value: 150 > maximum 100 before your handler runs.

Sandbox levels

Level Class Isolation Use it for
0 RestrictedSandbox restricted builtins, same process notebooks, tests
1 Sandbox (default) child process + hard timeout + tool RPC local agent loops
2 DockerSandbox container (stub — no host tools) isolated pure-Python eval

The default Sandbox runs your tools in the parent process over an RPC channel, so closures, bound methods, and lambdas all work — nothing gets pickled. Timeouts kill the child; a hung host tool can't stall run() past its deadline either.

Full feature list
Area What you get
Registration @registry.tool decorator, register(), module-level @tool
Signatures type hints + docstrings; imported schemas keep their constraints
Prompt styles signatures (cheapest), detailed (shows constraints), minimal
Tool RPC default path — host callables never need pickling
Isolation child process with hard timeout; deadline covers host tool time
Outcome success, result, stdout, error, duration, tools_called, error_type, short_error, truncation flags, cached, debug
Escape guards AST dunder rejection + SafeModule import proxies (guard=True)
Call policy max_tool_calls, max_total_calls, max_calls_per_tool
Validation validate_calls=True enforces enum / min / max / pattern
Schema import load_openai_tools, load_anthropic_tools (+ name sanitizing)
Metrics compare_tokens(), estimate_tokens() (tiktoken when installed)
Cache opt-in ResultCache, keyed on code + tools + security config
Async async def tools are awaited; await sandbox.run_async(code)
Debug debug=True → code hash, call counts, backend, duration
Public API surface
from schema2code import (
    ToolRegistry, tool, get_default_registry,
    Sandbox,            # process + restricted + RPC (default)
    RestrictedSandbox,  # in-process only
    ProcessSandbox, DockerSandbox, docker_available,
    Outcome, ToolSpec, CallPolicy, ResultCache,
    compare_tokens, estimate_tokens,
    load_openai_tools, load_anthropic_tools,
    SecurityError, PolicyError, ValidationError, UnsupportedError,
)

What this is not

  • Not an agent framework. No planner, memory, router, or chain. You own the loop — this library turns tools into a prompt block and runs the code that comes back. It sits underneath whatever framework you already use.
  • Not a security boundary for untrusted input. It defends against model mistakes, not against an adversary. See the warning above.
  • Not a win for every setup. Below roughly 10 tools the interface can cost more than the schemas it replaces.
  • Not a hosted runtime. Everything runs on your machine — no service, no account, no vendor.

The measured numbers come from one workload: 15 tasks over a 14-tool registry, OpenAI models, temperature 0. The direction should transfer — more tools and longer chains favour code — but treat the exact percentages as workload-specific and re-run benchmarks/bench_v2.py against your own tools.

Documentation

Usage guide docs/usage.md registration, prompt styles, retry loop, policies, caching, async
Architecture docs/architecture.md RPC protocol, guard design, cache keys, error taxonomy
Security model SECURITY.md threat model, backend levels, hard requirements
Releasing RELEASING.md build & publish checklist

한국어: docs/usage.ko.md · docs/architecture.ko.md / 日本語: docs/usage.ja.md · docs/architecture.ja.md

Development

git clone https://github.com/DW-dev-UE/schema2code.git && cd schema2code
pip install -e ".[dev]"
python -m pytest -q                  # 97 tests
python -m ruff check src benchmarks
python benchmarks/run_all.py         # local suite (no API key needed)
python benchmarks/bench_v2.py --dry-run   # live A/B cost preview

License

MIT

Metadata

Release files for schema2code 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for schema2code 0.2.0
File Size Uploaded
schema2code-0.2.0.tar.gz 44.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for schema2code 0.2.0
File Interpreter ABI Platform
schema2code-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 87.0 kB

Release files / schema2code-0.2.0.tar.gz

Download URL schema2code-0.2.0.tar.gz
Size 44.3 kB
Tags Source
SHA-256 checksum
How to use checksums
87951b9d927603996164b2b50d073b5d2b46535bf12a8a65e803ef1d09456b8d
BLAKE2b-256 checksum
How to use checksums
412836ca73227764df39abbaccce7a05eb278f27f4cc375c997618d6ef422ccf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 28, 2026.

Transparency log

Release files / schema2code-0.2.0-py3-none-any.whl

Download URL schema2code-0.2.0-py3-none-any.whl
Size 42.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1991c5c7290f5b3865a924ee1606d5a7cc6cc5d83f25a06ae1f098bbbc13822b
BLAKE2b-256 checksum
How to use checksums
65066abf3e6edf6445adcfc85c4f90ac0e633e9229c2da8e74645c5047167f5b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 28, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page