Skip to content

Commit 10a4ebd

Browse files
kiranandcodeeb8680
andauthored
EvalProvider using RestrictedPython [Depends on #519] (#520)
* Fresh diff * remove instructionhandler * updated internal interface to make all tests pass * fixed caching tests * updated llm.ipynb * removed unnecessarily defensive validation * updated tool call decoding to use concrete type of tool result instead of annotations * updated completions to fix basic type errors * updated call assistant to handle decoding tool calls * dropped stale comments * moved model and param model back to internals of `completions` * added default encodable instance for Callable * fixed type errors * update to use more structured type for synthesis * updated callable encoding tests * s/TypeError/NotImplementedError * simplified smart constructor * bare callables not allowed * droped synthesis and removed encoding_instructions * fixed imports * fixed imports and tests * added restricted python again * added test for custom policies for restricted python * more specific arguments to RestrictedEvalProvider and made exec more customisable * reverted flags for customizing rglobals, using same rglobals for local and globals for exec, fixing bug * fixed failing tests (env was not being mutated) * updated restricted python to be a llm dependency --------- Co-authored-by: Eli <eli@basis.ai>
1 parent 7679e47 commit 10a4ebd

3 files changed

Lines changed: 248 additions & 41 deletions

File tree

effectful/handlers/llm/evaluation.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@
55
from types import CodeType
66
from typing import Any
77

8+
from RestrictedPython import (
9+
Eval,
10+
Guards,
11+
RestrictingNodeTransformer,
12+
compile_restricted,
13+
safe_globals,
14+
)
15+
816
from effectful.ops.syntax import ObjectInterpretation, defop, implements
917

1018

@@ -86,3 +94,80 @@ def exec(
8694

8795
# Execute module-style so top-level defs land in `env`.
8896
builtins.exec(bytecode, env, env)
97+
98+
99+
class RestrictedEvalProvider(ObjectInterpretation):
100+
"""
101+
Safer provider using RestrictedPython.
102+
103+
RestrictedPython is not a complete sandbox, but it enforces a restricted
104+
language subset and expects you to provide a constrained exec environment.
105+
106+
policy : dict[str, Any], optional
107+
RestrictedPython compile_restricted policy for compilation
108+
"""
109+
110+
policy: type[RestrictingNodeTransformer] | None = None
111+
112+
def __init__(
113+
self,
114+
*,
115+
policy: type[RestrictingNodeTransformer] | None = None,
116+
):
117+
self.policy = policy
118+
119+
@implements(parse)
120+
def parse(self, source: str, filename: str) -> ast.Module:
121+
# Keep inspect.getsource() working for dynamically-defined objects.
122+
linecache.cache[filename] = (
123+
len(source),
124+
None,
125+
source.splitlines(True),
126+
filename,
127+
)
128+
return ast.parse(source, filename=filename, mode="exec")
129+
130+
@implements(compile)
131+
def compile(self, module: ast.Module, filename: str) -> CodeType:
132+
# RestrictedPython can compile from an AST directly.
133+
return compile_restricted(
134+
module,
135+
filename=filename,
136+
mode="exec",
137+
policy=self.policy or RestrictingNodeTransformer,
138+
)
139+
140+
@implements(exec)
141+
def exec(
142+
self,
143+
bytecode: CodeType,
144+
env: dict[str, Any],
145+
) -> None:
146+
# Build restricted globals from RestrictedPython's defaults
147+
rglobals: dict[str, Any] = safe_globals.copy()
148+
149+
# Enable class definitions (required for Python 3)
150+
rglobals["__metaclass__"] = type
151+
rglobals["__name__"] = "restricted"
152+
153+
# Layer `env` on top (without letting callers replace the restricted builtins).
154+
rglobals.update({k: v for k, v in env.items() if k != "__builtins__"})
155+
156+
# Enable for loops and comprehensions
157+
rglobals["_getiter_"] = Eval.default_guarded_getiter
158+
# Enable sequence unpacking in comprehensions and for loops
159+
rglobals["_iter_unpack_sequence_"] = Guards.guarded_iter_unpack_sequence
160+
161+
rglobals["getattr"] = Guards.safer_getattr
162+
rglobals["setattr"] = Guards.guarded_setattr
163+
rglobals["_write_"] = lambda x: x
164+
165+
# Track keys before execution to identify new definitions
166+
keys_before = set(rglobals.keys())
167+
168+
builtins.exec(bytecode, rglobals, rglobals)
169+
170+
# Copy newly defined items back to env so caller can access them
171+
for key in rglobals:
172+
if key not in keys_before:
173+
env[key] = rglobals[key]

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ llm = [
4444
"litellm",
4545
"pillow",
4646
"pydantic",
47+
"restrictedpython>=8.1"
4748
]
4849
prettyprinter = ["prettyprinter"]
4950
docs = [

0 commit comments

Comments
 (0)