How to execute code in a Foundry Agent?

I would like to let my agent execute arbitrary code within a containerized/safe environment, depending on the user permissions. The goal could be to make specific queries to the ontology, or to read/write files stored in the ontology, etc.

How can I do this ?

1 Like

One easy approach is to use Functions as a safe place to execute arbitrary code.
The access will be limited to the librairies and the OSDK endpoint available, and execute with the user permission.

Example implementation:

from functions.api import function, OntologyEdit, String
from typing import Optional
import json
import sys
import traceback
from io import StringIO

import ontology_sdk
from ontology_sdk import FoundryClient
from ontology_sdk.ontology.objects import ExampleObjectWithMedia
from ontology_sdk.ontology.object_sets import ExampleObjectWithMediaObjectSet


# ---------------------------------------------------------------------------
# Documentation constant
# ---------------------------------------------------------------------------

CODE_EXECUTOR_DOCS = """# Code Executor — LLM Reference Guide
 ... [See other snippet]
"""


# ---------------------------------------------------------------------------
# Shared execution engine
# ---------------------------------------------------------------------------


def _build_namespace(input_data: Optional[str] = None) -> dict:
    """Return the execution namespace pre-loaded with useful libraries."""
    return {
        # caller-provided data
        "input_data": input_data,
        # standard library
        "sys": sys,
        "os": __import__("os"),
        "json": __import__("json"),
        "re": __import__("re"),
        "math": __import__("math"),
        "random": __import__("random"),
        "collections": __import__("collections"),
        "datetime": __import__("datetime"),
        "timedelta": __import__("datetime").timedelta,
        "date": __import__("datetime").date,
        # data processing
        "pandas": __import__("pandas"),
        "pd": __import__("pandas"),
        "numpy": __import__("numpy"),
        "np": __import__("numpy"),
        # document / image manipulation
        "pptx": __import__("pptx"),
        "Presentation": __import__("pptx").Presentation,
        "openpyxl": __import__("openpyxl"),
        "docx": __import__("docx"),
        "Document": __import__("docx").Document,
        "PIL": __import__("PIL"),
        "Image": __import__("PIL.Image").Image,
        "jinja2": __import__("jinja2"),
        "BytesIO": __import__("io").BytesIO,
        # Ontology SDK
        "ontology_sdk": ontology_sdk,
        "FoundryClient": FoundryClient,
        "client": FoundryClient(),
        "ExampleObjectWithMedia": ExampleObjectWithMedia,
        "ExampleObjectWithMediaObjectSet": ExampleObjectWithMediaObjectSet,
    }


def _run_code(
    code: str,
    input_data: Optional[str] = None,
) -> dict:
    """Execute *code* and return the resulting namespace and captured stdout.

    Returns a dict with keys:
        namespace  – the post-execution namespace (contains all variables defined by the code)
        stdout     – everything the code printed
        error      – None on success, or a formatted traceback string on failure
    """
    namespace = _build_namespace(input_data)

    old_stdout = sys.stdout
    sys.stdout = captured = StringIO()

    try:
        exec(code, namespace)  # noqa: S102
        error = None
    except Exception:
        error = traceback.format_exc()
    finally:
        sys.stdout = old_stdout

    return {
        "namespace": namespace,
        "stdout": captured.getvalue(),
        "error": error,
    }


# ---------------------------------------------------------------------------
# Public functions
# ---------------------------------------------------------------------------


@function
def get_code_executor_docs() -> String:
    """
    Return the complete code executor reference documentation.

    This includes:
    - Available functions (execute_code, execute_code_with_edits)
    - Pre-loaded variables and libraries
    - Ontology SDK syntax and patterns
    - Document manipulation examples (PowerPoint, Excel, Word, Images)
    - Common usage patterns
    - Important notes and best practices

    Returns:
        String: The complete markdown documentation for the code executor.
    """
    return CODE_EXECUTOR_DOCS


@function(beta=True)
def execute_code(
    code: String,
    input_data: Optional[String] = None,
    return_variable: Optional[String] = None,
) -> String:
    """
    Execute arbitrary Python code (read-only) and return a string result.

    Pre-loaded variables:
        Standard — json, os, re, math, random, datetime, collections
        Data     — pandas (pd), numpy (np)
        Ontology — client (FoundryClient), ExampleObjectWithMedia, ExampleObjectWithMediaObjectSet

    Args:
        code:            Python source code to execute.
        input_data:      Optional string (e.g. JSON) exposed as ``input_data``.
        return_variable: Variable name to return. If omitted, captured stdout is returned.

    Returns:
        String representation of the result, stdout, or an error message.
    """
    result = _run_code(code, input_data)

    if result["error"]:
        return f"Execution error:\n{result['error']}"

    if return_variable is not None:
        ns = result["namespace"]
        if return_variable not in ns:
            return f"Error: variable '{return_variable}' was not defined by the executed code."
        value = ns[return_variable]
        if isinstance(value, (dict, list)):
            return json.dumps(value, indent=2, default=str)
        return str(value)

    return result["stdout"] or "Code executed successfully (no output)."


@function(edits=[ExampleObjectWithMedia], beta=True)
def execute_code_with_edits(
    code: String,
    input_data: Optional[String] = None,
) -> list[OntologyEdit]:
    """
    Execute arbitrary Python code that can edit ExampleObjectWithMedia objects.

    The code MUST create an edits container and assign it to ``edits``, e.g.:

        edits = client.ontology.edits()
        editable = edits.objects.ExampleObjectWithMedia.edit("some-pk")
        editable.title = "New title"

    Pre-loaded variables: same as execute_code.

    Args:
        code:       Python source code to execute. Must define an ``edits`` variable.
        input_data: Optional string (e.g. JSON) exposed as ``input_data``.

    Returns:
        list[OntologyEdit] — the edits to be applied to the Ontology.
    """
    result = _run_code(code, input_data)

    if result["error"]:
        raise RuntimeError(f"Execution error:\n{result['error']}")

    ns = result["namespace"]
    if "edits" not in ns:
        raise RuntimeError("The executed code must define an 'edits' variable. Use: edits = client.ontology.edits()")

    return ns["edits"].get_edits()

With the docs being similar to the below:

CODE_EXECUTOR_DOCS = """# Code Executor — LLM Reference Guide

You have access to two Python functions that execute arbitrary code in a Foundry environment. Use them to read data, process documents, query the Ontology, and apply edits to Ontology objects.

---

## Available Functions

### `execute_code(code, input_data?, return_variable?) → String`

Read-only execution. Returns a string.

| Parameter | Type | Description |
|---|---|---|
| `code` | `String` (required) | Python source code to execute |
| `input_data` | `String` (optional) | Arbitrary string (e.g. JSON) available as the variable `input_data` |
| `return_variable` | `String` (optional) | Name of a variable to return. If omitted, captured `stdout` is returned |

### `execute_code_with_edits(code, input_data?) → list[OntologyEdit]`

Execution with Ontology write access. The code **must** define an `edits` variable.

| Parameter | Type | Description |
|---|---|---|
| `code` | `String` (required) | Python source code to execute. Must assign `edits = client.ontology.edits()` |
| `input_data` | `String` (optional) | Arbitrary string available as `input_data` |

---

## Pre-loaded Variables

All variables below are available in the execution namespace — **no imports needed**.

### Standard Library

| Variable | Module |
|---|---|
| `sys` | `sys` |
| `os` | `os` |
| `json` | `json` |
| `re` | `re` |
| `math` | `math` |
| `random` | `random` |
| `collections` | `collections` |
| `datetime` | `datetime` (module) |
| `timedelta` | `datetime.timedelta` |
| `date` | `datetime.date` |
| `BytesIO` | `io.BytesIO` |

### Data Processing

| Variable | Module |
|---|---|
| `pandas` / `pd` | `pandas` |
| `numpy` / `np` | `numpy` |

### Document & Image Manipulation

| Variable | Module | Purpose |
|---|---|---|
| `pptx` | `python-pptx` | PowerPoint reading/writing |
| `Presentation` | `pptx.Presentation` | Shortcut constructor for .pptx files |
| `openpyxl` | `openpyxl` | Excel (.xlsx) reading/writing |
| `docx` | `python-docx` | Word (.docx) reading/writing |
| `Document` | `docx.Document` | Shortcut constructor for .docx files |
| `PIL` | `Pillow` | Image processing |
| `Image` | `PIL.Image.Image` | Image class |
| `jinja2` | `jinja2` | Templating engine |

### Ontology SDK

| Variable | Description |
|---|---|
| `client` | A live `FoundryClient()` instance — use to query and edit the Ontology |
| `FoundryClient` | The class itself, if a fresh instance is needed |
| `ontology_sdk` | The full SDK module |
| `ExampleObjectWithMedia` | Object type class for filtering & type references |
| `ExampleObjectWithMediaObjectSet` | Object set class |

---

## Ontology SDK Syntax

### Querying Objects

```python
# Get all objects (lazy iterator)
for obj in client.ontology.objects.ExampleObjectWithMedia.iterate():
    print(obj.primary_key_, obj.title)

# Get a limited list
objs = client.ontology.objects.ExampleObjectWithMedia.take(num_items=5)

# Get one by primary key
obj = client.ontology.objects.ExampleObjectWithMedia.get("some-primary-key")

# Filter with .where()
filtered = client.ontology.objects.ExampleObjectWithMedia.where(
    ExampleObjectWithMedia.object_type.title == "Some Title"
)
results = filtered.take(num_items=10)

# Set operations
set_a = client.ontology.objects.ExampleObjectWithMedia.where(...)
set_b = client.ontology.objects.ExampleObjectWithMedia.where(...)
combined = set_a.union(set_b)
common   = set_a.intersect(set_b)
diff     = set_a.subtract(set_b)
```

### Object Properties — `ExampleObjectWithMedia`

| Property | Type | Description |
|---|---|---|
| `primary_key_` | `str` | Primary key (read-only on edit) |
| `title` | `str \| None` | Title |
| `old_file` | `Media \| None` | Media attachment (beta) |
| `edited_file` | `Media \| None` | Media attachment (beta) |

### Reading Media Content

```python
obj = client.ontology.objects.ExampleObjectWithMedia.get("pk")

# Read binary content as BytesIO
content: BytesIO = obj.old_file.get_media_content()
raw_bytes = content.read()

# Read metadata
metadata = obj.old_file.get_media_metadata()

# Get a MediaReference (needed to copy/assign media to another property)
ref = obj.old_file.get_media_reference()
```

### Editing Objects (use `execute_code_with_edits` only)

The code **must** define an `edits` variable. The function returns `edits.get_edits()` automatically.

```python
# Create the edits container
edits = client.ontology.edits()

# Edit an existing object (by object or primary key string)
editable = edits.objects.ExampleObjectWithMedia.edit("some-pk")
editable.title = "New title"
editable.edited_file = obj.old_file.get_media_reference()  # copy media

# Create a new object
new_obj = edits.objects.ExampleObjectWithMedia.create("new-pk")
new_obj.title = "Brand new object"

# Delete an object
edits.objects.ExampleObjectWithMedia.delete("some-pk")
```

---

## Document Manipulation Patterns

### PowerPoint (.pptx)

```python
# Read a .pptx from an Ontology media property
obj = client.ontology.objects.ExampleObjectWithMedia.get("pk")
pptx_bytes = obj.old_file.get_media_content()
prs = Presentation(pptx_bytes)

# Iterate slides
for slide in prs.slides:
    for shape in slide.shapes:
        if shape.has_text_frame:
            print(shape.text_frame.text)

# Modify text
slide = prs.slides[0]
slide.shapes[0].text_frame.text = "Updated title"

# Save to bytes
output = BytesIO()
prs.save(output)
output.seek(0)
result = f"Generated pptx of {len(output.read())} bytes"
```

### Excel (.xlsx)

```python
# Read with openpyxl
obj = client.ontology.objects.ExampleObjectWithMedia.get("pk")
xlsx_bytes = obj.old_file.get_media_content()
wb = openpyxl.load_workbook(xlsx_bytes)
ws = wb.active
for row in ws.iter_rows(values_only=True):
    print(row)

# Read with pandas
df = pd.read_excel(xlsx_bytes)
print(df.describe())

# Write with openpyxl
wb = openpyxl.Workbook()
ws = wb.active
ws.append(["Name", "Value"])
ws.append(["test", 42])
output = BytesIO()
wb.save(output)

# Write with pandas
output = BytesIO()
df.to_excel(output, index=False)
```

### Word (.docx)

```python
# Read
obj = client.ontology.objects.ExampleObjectWithMedia.get("pk")
docx_bytes = obj.old_file.get_media_content()
doc = Document(docx_bytes)
for para in doc.paragraphs:
    print(para.text)

# Modify
doc.paragraphs[0].text = "Updated heading"
output = BytesIO()
doc.save(output)
```

### Images (Pillow)

```python
from PIL import Image as PILImage

obj = client.ontology.objects.ExampleObjectWithMedia.get("pk")
img_bytes = obj.old_file.get_media_content()
img = PILImage.open(img_bytes)
print(f"Size: {img.size}, Mode: {img.mode}")

# Resize
resized = img.resize((800, 600))
output = BytesIO()
resized.save(output, format="PNG")
```

---

## Common Patterns

### Pass structured data via `input_data`

```python
# input_data = '{"keys": ["pk1", "pk2"], "new_title": "Hello"}'
data = json.loads(input_data)
for key in data["keys"]:
    obj = client.ontology.objects.ExampleObjectWithMedia.get(key)
    print(f"{key}: {obj.title}")
```

### Return structured results

```python
# Use return_variable="result" to get this back
result = {
    "count": len(objs),
    "titles": [o.title for o in objs],
}
# Dicts and lists are automatically JSON-serialized
```

### Error handling in code

```python
obj = client.ontology.objects.ExampleObjectWithMedia.get("maybe-missing")
if obj is None:
    result = "Object not found"
else:
    result = f"Found: {obj.title}"
```

---

## Important Notes

- **`execute_code`** is read-only. Any edits container created inside it will NOT be applied.
- **`execute_code_with_edits`** is write-capable. It must define `edits = client.ontology.edits()` and the function returns `edits.get_edits()` automatically.
- **`iterate()`** returns a lazy iterator with no `limit` parameter. Use `take(num_items=N)` to get a bounded list.
- **Media properties** (`old_file`, `edited_file`) are beta. Use `.get_media_content()` to read, `.get_media_reference()` to copy between properties.
- **`input_data`** is always a string. Parse it with `json.loads()` if you pass JSON.
- **`return_variable`** extracts a named variable from the executed namespace. Dicts and lists are auto-serialized to JSON.
- All code runs in a **single flat namespace** — no need for imports of pre-loaded libraries.
"""

You should import the libraries you want to be available, here is the meta.yml I’m using as reference:

package:
  name: python-example
  version: '{{ PACKAGE_VERSION }}'
source:
  path: ../python
requirements:
  build:
  - setuptools
  run:
  - functions-python-api >={{ FUNCTIONS_PYTHON_API_VERSION }}, <1
  - ontology_sdk==0.1.0
  - pandas
  - numpy
  - python-pptx
  - openpyxl
  - python-docx
  - pillow
  - jinja2
  - pytest
build:
  script: python setup.py install --single-version-externally-managed --record=record.txt
  noarch: python

Those should be of course updated relative to the object/actions/functions/etc. and librairies available within the function code repository. You should use the “Continue extension” (or any LLM) to generate and up to date version relative to your code repository configuration.

In this example, I’m having one object available:

To edit the ontology, the Ontology editing function should be wrapped in an Action.

The functions and action need to be provided to the final Agent:

Providing documentation for the 3 tools will help the agent navigate what to use when.

For “execute_code”:

This functions lets you execute code, the same way that the Action will execute it. However, this function won't actually edit the ontology, and is just a dry-run endpoint. 
You may use it for further debugging purposes, as you can get an arbitrary string back; or when a table/some information from the ontology is required to be displayed. Same if you want to just execute arbitrary code and see the result.

For “Execute code and edit ontology”:

This action executes code, the same way that the function executes it. However, this action *actually* edits the ontology.
Be careful with the code you send, as it can actually edit things !
A typical usecase is to edit an object to upload a new media to this object instance (e.g. an edited powerpoint)

For “get_code_executor_docs”:

This provides you with additional docs and examples of how to use the execute_code function and action

The agent can then execute code and edit the ontology autonomously.

Including editing files

3 Likes

@VincentF always with the coolest tutorials!

3 Likes