(no commit message)

This commit is contained in:
2026-01-27 22:37:59 -08:00
parent bb191efd1d
commit 6a57bde8f2
10 changed files with 1132 additions and 1 deletions

145
host_interpreter.py Normal file
View File

@@ -0,0 +1,145 @@
from __future__ import annotations
import builtins
import io
import sys
import traceback
from dataclasses import dataclass, field
from types import MappingProxyType
from typing import Any, Callable
from dspy.primitives.code_interpreter import CodeInterpreterError, FinalOutput
@dataclass
class UnsafeHostInterpreter:
"""
A minimal CodeInterpreter implementation that executes code in the host Python process.
Why this exists:
- DSPy's default RLM interpreter (Deno/Pyodide) currently relies on pyodide.ffi.run_sync
to bridge async tool calls, which fails on runtimes without WASM stack switching support.
Tradeoff:
- This is NOT a security sandbox. It will execute arbitrary Python code produced by the LLM.
Use only in trusted/local environments.
"""
tools: dict[str, Callable[..., str]] = field(default_factory=dict)
# If RLM injects this attribute, we can map SUBMIT() to output fields.
output_fields: list[dict] | None = None
_started: bool = False
_globals: dict[str, Any] = field(default_factory=dict)
def start(self) -> None:
if self._started:
return
# Start with a constrained global namespace. This is not a real sandbox.
self._globals = {
"__name__": "__rlm_host__",
"__builtins__": MappingProxyType(
{
# Allow common harmless builtins needed for analysis.
"print": builtins.print,
"len": builtins.len,
"type": builtins.type,
"range": builtins.range,
"reversed": builtins.reversed,
"min": builtins.min,
"max": builtins.max,
"sum": builtins.sum,
"sorted": builtins.sorted,
"enumerate": builtins.enumerate,
"str": builtins.str,
"int": builtins.int,
"float": builtins.float,
"bool": builtins.bool,
"dict": builtins.dict,
"list": builtins.list,
"set": builtins.set,
"tuple": builtins.tuple,
"abs": builtins.abs,
"all": builtins.all,
"any": builtins.any,
"zip": builtins.zip,
}
),
}
# Provide a few commonly-used stdlib modules without enabling arbitrary imports.
# (The host interpreter is already unsafe, but keeping imports closed reduces footguns.)
import json as _json
import math as _math
import re as _re
self._globals.update({"re": _re, "json": _json, "math": _math})
self._started = True
def execute(self, code: str, variables: dict[str, Any] | None = None) -> Any:
if not self._started:
self.start()
# Inject variables and tools into the exec namespace.
if variables:
self._globals.update(variables)
self._globals.update(self.tools)
# Provide SUBMIT for early termination.
class _SubmitSignal(BaseException):
def __init__(self, payload: dict[str, Any]):
super().__init__()
self.payload = payload
def SUBMIT(*args: Any, **kwargs: Any) -> None: # noqa: N802 - matches DSPy contract
# RLM expects interpreter.execute() to RETURN a FinalOutput instance,
# not raise it as an exception. We raise a private control-flow signal
# and convert it into FinalOutput below.
if not kwargs:
# Support SUBMIT("...") for single-output signatures.
if (
len(args) == 1
and self.output_fields
and len(self.output_fields) == 1
):
name = self.output_fields[0]["name"]
kwargs = {name: args[0]}
# Support SUBMIT() if user assigned output variables in globals.
elif len(args) == 0 and self.output_fields:
payload: dict[str, Any] = {}
for f in self.output_fields:
fname = f["name"]
if fname in self._globals:
payload[fname] = self._globals[fname]
if payload:
kwargs = payload
else:
raise _SubmitSignal(
{
"error": "SUBMIT called without outputs; provide kwargs or set output variables."
}
)
raise _SubmitSignal(kwargs)
self._globals["SUBMIT"] = SUBMIT
buf = io.StringIO()
old_stdout, old_stderr = sys.stdout, sys.stderr
sys.stdout, sys.stderr = buf, buf
try:
exec(code, self._globals, self._globals)
except _SubmitSignal as sig:
return FinalOutput(sig.payload)
except SyntaxError:
raise
except Exception as e:
tb = traceback.format_exc()
raise CodeInterpreterError(f"{e}\n\n{tb}")
finally:
sys.stdout, sys.stderr = old_stdout, old_stderr
out = buf.getvalue()
return out.strip() if out.strip() else None
def shutdown(self) -> None:
self._globals.clear()
self._started = False