minipy

A minimal Python ‘code interpreter’ for LLM tool use, with timeout and error capture

In language model clients it’s often useful to have a ‘code interpreter’ – this is something that runs code, and generally outputs the result of the last expression (i.e like IPython or Jupyter).

In this section we’ll create the minipy function, which executes a string as Python code, with an optional timeout. If the last line is an expression, we’ll return that – just like in IPython or Jupyter, but without needing them installed.

This is an internal function that’s needed for _run to ensure that location information is available in the abstract syntax tree (AST), since otherwise python complains.

This is the internal function used to actually run the code – we pull off the last AST to see if it’s an expression (i.e something that returns a value), and if so, we store it to a special _result variable so we can return it.

_run('import math;math.factorial(12)')
479001600
_run('print(1+1)')
'2'

We now have the machinery needed to create our minipy function.


source

minipy

def minipy(
    code:str, # Code to execute
    glb:Optional=None, # Globals namespace
    loc:Optional=None, # Locals namespace
    timeout:int=3600, # Maximum run time in seconds
):

Executes python code with timeout and returning final expression (similar to IPython).

There’s no builtin security here – you should generally use this in a sandbox, or alternatively prompt before running code. It can handle multiline function definitions, and pretty much any other normal Python syntax.

minipy("""def factorial(n):
    if n == 0 or n == 1: return 1
    else: return n * factorial(n-1)
factorial(5)""")
120

If the code takes longer than timeout then it returns an error string.

print(minipy('import time; time.sleep(10)', timeout=1))
Traceback (most recent call last):
  File "<ipython-input-1-15361b58d9a8>", line 14, in minipy
    try: return _run(code, glb, loc)
                ~~~~^^^^^^^^^^^^^^^^
  File "<ipython-input-1-c68b1c7bc9d7>", line 18, in _run
    try: exec(compiled_code, glb, loc)
         ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<ast>", line 1, in <module>
  File "<ipython-input-1-15361b58d9a8>", line 9, in handler
    def handler(*args): raise TimeoutError()
                        ^^^^^^^^^^^^^^^^^^^^
TimeoutError

By default the caller’s global namespace is used.

minipy("a=1")
a
1

Pass a different glb if needed; this requires using python_ns.

glb = {}
minipy("a=3", glb=glb)
a, glb['a']
(1, 3)

minipy is designed to be offered as a tool, e.g. via fastcore.funccall.get_schema:

from fastcore.funccall import get_schema
get_schema(minipy)
{'name': 'minipy',
 'description': 'Executes python `code` with `timeout` and returning final expression (similar to IPython).',
 'input_schema': {'type': 'object',
  'properties': {'code': {'description': 'Code to execute', 'type': 'string'},
   'glb': {'description': 'Globals namespace',
    'default': None,
    'anyOf': [{'type': 'object'}, {'type': 'null'}]},
   'loc': {'description': 'Locals namespace',
    'default': None,
    'anyOf': [{'type': 'object'}, {'type': 'null'}]},
   'timeout': {'description': 'Maximum run time in seconds',
    'default': 3600,
    'type': 'integer'}},
  'required': ['code']}}