Skip to main content
This guide will walk you through the basics of using Monty to execute Python code safely in your applications.

Choose Your Language

Basic Expression Evaluation

Start by running a simple Python expression:
import pydantic_monty

# Create interpreter with code
m = pydantic_monty.Monty('1 + 2')

# Execute and get result
result = m.run()
print(result)
# Output: 3

Using Input Variables

Pass variables to your code:
import pydantic_monty

# Define code that uses input variables
m = pydantic_monty.Monty('x * y', inputs=['x', 'y'])

# Run with different inputs
print(m.run(inputs={'x': 2, 'y': 3}))
# Output: 6

print(m.run(inputs={'x': 10, 'y': 5}))
# Output: 50

External Functions

Call host functions from your sandboxed code:
import pydantic_monty

# Code that calls an external function
m = pydantic_monty.Monty('double(x)', inputs=['x'])

# Provide the external function implementation
result = m.run(
    inputs={'x': 5},
    external_functions={'double': lambda x: x * 2}
)
print(result)
# Output: 10

Iterative Execution

For fine-grained control over external function calls:
import pydantic_monty

code = """
data = fetch(url)
len(data)
"""

m = pydantic_monty.Monty(code, inputs=['url'])

# Start execution - pauses at fetch() call
result = m.start(inputs={'url': 'https://example.com'})

print(type(result))
# Output: <class 'pydantic_monty.FunctionSnapshot'>

print(result.function_name)
# Output: fetch

print(result.args)
# Output: ('https://example.com',)

# Perform the actual fetch, then resume
result = result.resume(return_value='hello world')

print(type(result))
# Output: <class 'pydantic_monty.MontyComplete'>

print(result.output)
# Output: 11

Async External Functions

Use async/await with external functions:
import asyncio
from typing import Any
import pydantic_monty

code = """
async def agent(prompt: str, messages: Messages):
    while True:
        print(f'messages so far: {messages}')
        output = await call_llm(prompt, messages)
        if isinstance(output, str):
            return output
        messages.extend(output)

await agent(prompt, [])
"""

type_definitions = """
from typing import Any

Messages = list[dict[str, Any]]

async def call_llm(prompt: str, messages: Messages) -> str | Messages:
    raise NotImplementedError()

prompt: str = ''
"""

m = pydantic_monty.Monty(
    code,
    inputs=['prompt'],
    script_name='agent.py',
    type_check=True,
    type_check_stubs=type_definitions,
)

Messages = list[dict[str, Any]]

async def call_llm(prompt: str, messages: Messages) -> str | Messages:
    if len(messages) < 2:
        return [{'role': 'system', 'content': 'example response'}]
    else:
        return f'example output, message count {len(messages)}'

async def main():
    output = await pydantic_monty.run_monty_async(
        m,
        inputs={'prompt': 'testing'},
        external_functions={'call_llm': call_llm},
    )
    print(output)
    # Output: example output, message count 2

asyncio.run(main())

Resource Limits

Control resource usage to prevent runaway execution:
import pydantic_monty

m = pydantic_monty.Monty('x + y', inputs=['x', 'y'])

# Set resource limits
limits = pydantic_monty.ResourceLimits(
    max_duration_secs=1.0,
    max_allocations=10000,
    max_memory=1024 * 1024  # 1MB
)

result = m.run(
    inputs={'x': 1, 'y': 2},
    limits=limits
)
print(result)
# Output: 3

Key Concepts

Inputs

Variables you want to pass into the sandboxed code. Declare them when creating the interpreter.

External Functions

Host functions that sandboxed code can call. These are the only way for Monty code to interact with the outside world.

Resource Limits

Control execution time, memory usage, and allocations to prevent runaway code.

Snapshots

Pause execution at external function calls and resume later, even across process boundaries.

Common Patterns

Agent Workflow Example

Here’s a real-world example of using Monty for an agent workflow:
import asyncio
from typing import Any
import pydantic_monty

Messages = list[dict[str, Any]]

code = """
async def agent(prompt: str):
    messages = []
    while True:
        output = await call_llm(prompt, messages)
        if isinstance(output, str):
            return output
        messages.extend(output)

await agent(prompt)
"""

m = pydantic_monty.Monty(
    code,
    inputs=['prompt'],
    script_name='agent.py'
)

async def call_llm(prompt: str, messages: Messages) -> str | Messages:
    # Your LLM logic here
    if len(messages) < 2:
        return [{'role': 'system', 'content': 'response'}]
    else:
        return f'Final answer based on {len(messages)} messages'

async def main():
    result = await pydantic_monty.run_monty_async(
        m,
        inputs={'prompt': 'What is the weather?'},
        external_functions={'call_llm': call_llm}
    )
    print(result)

asyncio.run(main())

Next Steps

Core Concepts

Learn more about Monty’s core features

External Functions

Deep dive into external function handling

Resource Limits

Configure resource limits for safety

Type Checking

Use static type checking with your code

Build docs developers (and LLMs) love