Skip to content

Commit 15469b8

Browse files
committed
linting
1 parent 95bff82 commit 15469b8

8 files changed

Lines changed: 2183 additions & 8 deletions

File tree

‎examples/ipython_repl_example.py‎

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
"""IPython REPL environment example.
2+
3+
Runs user code inside a real IPython session. Two kernel modes:
4+
5+
- ``in_process``: IPython InteractiveShell in the same Python process as RLM.
6+
Fast, zero-overhead subcalls, full Python object interop.
7+
- ``subprocess``: ipykernel subprocess managed via jupyter_client. Gives you
8+
real per-cell timeouts via kernel interrupt, and full isolation from the
9+
host namespace.
10+
11+
Setup:
12+
pip install 'rlms[ipython]'
13+
python -m examples.ipython_repl_example
14+
"""
15+
16+
from __future__ import annotations
17+
18+
from rlm.clients.base_lm import BaseLM
19+
from rlm.core.lm_handler import LMHandler
20+
from rlm.core.types import ModelUsageSummary, UsageSummary
21+
from rlm.environments.ipython_repl import IPythonREPL
22+
23+
24+
class MockLM(BaseLM):
25+
def __init__(self):
26+
super().__init__(model_name="mock")
27+
28+
def completion(self, prompt):
29+
return f"Mock: {str(prompt)[:50]}"
30+
31+
async def acompletion(self, prompt):
32+
return self.completion(prompt)
33+
34+
def get_usage_summary(self):
35+
return UsageSummary({"mock": ModelUsageSummary(1, 10, 10)})
36+
37+
def get_last_usage(self):
38+
return self.get_usage_summary()
39+
40+
41+
def section(title: str) -> None:
42+
print("\n" + "=" * 60)
43+
print(title)
44+
print("=" * 60)
45+
46+
47+
def demo_in_process() -> None:
48+
section("[1] In-process IPython kernel")
49+
with IPythonREPL(kernel_mode="in_process") as repl:
50+
r = repl.execute_code("x = 40\ny = 2\nprint(x + y)")
51+
print(f" stdout: {r.stdout.strip()}")
52+
print(f" locals: x={repl.locals['x']}, y={repl.locals['y']}")
53+
54+
# FINAL_VAR captures the final answer out-of-band.
55+
repl.execute_code('answer = "forty-two"')
56+
r = repl.execute_code('FINAL_VAR("answer")')
57+
print(f" FINAL_VAR → {r.final_answer}")
58+
59+
60+
def demo_subprocess() -> None:
61+
section("[2] Subprocess ipykernel — real timeouts")
62+
with IPythonREPL(kernel_mode="subprocess", cell_timeout=1.0) as repl:
63+
r = repl.execute_code("import sys; print(sys.executable)")
64+
print(f" kernel python: {r.stdout.strip()}")
65+
66+
r = repl.execute_code("xs = [i*i for i in range(5)]; print(xs)")
67+
print(f" stdout: {r.stdout.strip()}")
68+
69+
print(" -- running a 5s sleep with cell_timeout=1.0s --")
70+
r = repl.execute_code("import time; time.sleep(5); print('never')")
71+
print(f" elapsed: {r.execution_time:.2f}s")
72+
print(f" stderr: {r.stderr.strip()}")
73+
74+
# Kernel survives the interrupt and still responds.
75+
r = repl.execute_code("print('kernel still alive')")
76+
print(f" follow-up: {r.stdout.strip()}")
77+
78+
79+
def demo_llm_query() -> None:
80+
section("[3] llm_query plumbing via LMHandler")
81+
with LMHandler(client=MockLM()) as handler:
82+
for mode in ("in_process", "subprocess"):
83+
print(f"\n -- kernel_mode={mode} --")
84+
with IPythonREPL(
85+
kernel_mode=mode,
86+
lm_handler_address=handler.address,
87+
) as repl:
88+
r = repl.execute_code('r = llm_query("ping"); print(r)')
89+
print(f" response: {r.stdout.strip()}")
90+
91+
r = repl.execute_code('print(llm_query_batched(["a", "b"]))')
92+
print(f" batched: {r.stdout.strip()}")
93+
94+
95+
def demo_recursion() -> None:
96+
section("[4] rlm_query — recursive RLM subcalls")
97+
# Stand-in for RLM._subcall: returns a canned reply for the demo.
98+
from rlm.core.types import RLMChatCompletion
99+
100+
class _FakeSubcall:
101+
def __call__(self, prompt: str, model=None) -> RLMChatCompletion:
102+
return RLMChatCompletion(
103+
root_model="fake",
104+
prompt=prompt,
105+
response=f"child-answer[{prompt}]",
106+
usage_summary=UsageSummary({"fake": ModelUsageSummary(1, 0, 0)}),
107+
execution_time=0.0,
108+
)
109+
110+
subcall = _FakeSubcall()
111+
for mode in ("in_process", "subprocess"):
112+
print(f"\n -- kernel_mode={mode} --")
113+
with IPythonREPL(kernel_mode=mode, subcall_fn=subcall) as repl:
114+
r = repl.execute_code('print(rlm_query("deep-think-topic-A"))')
115+
print(f" single: {r.stdout.strip()}")
116+
r = repl.execute_code('print(rlm_query_batched(["t1","t2","t3"]))')
117+
print(f" batched: {r.stdout.strip()}")
118+
119+
120+
def main() -> None:
121+
demo_in_process()
122+
demo_subprocess()
123+
demo_llm_query()
124+
demo_recursion()
125+
print("\nDone.")
126+
127+
128+
if __name__ == "__main__":
129+
main()

‎pyproject.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ modal = ["modal>=0.73.0", "dill>=0.3.7"]
3939
e2b = ["e2b-code-interpreter>=0.0.11", "dill>=0.3.7"]
4040
daytona = ["daytona>=0.128.1", "dill>=0.3.7"]
4141
prime = ["prime-sandboxes>=0.2.0", "dill>=0.3.7"]
42+
ipython = ["ipython>=8.0.0", "jupyter_client>=8.0.0", "ipykernel>=6.0.0", "dill>=0.3.7"]
4243

4344
[build-system]
4445
requires = ["setuptools>=61.0"]

‎rlm/core/rlm.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,8 @@ def _spawn_completion_context(self, prompt: str | dict[str, Any]):
232232
env_kwargs["lm_handler_address"] = (lm_handler.host, lm_handler.port)
233233
env_kwargs["context_payload"] = prompt
234234
env_kwargs["depth"] = self.depth + 1 # Environment depth is RLM depth + 1
235-
# For local environment with max_depth > 1, pass subcall callback for recursive RLM calls
236-
if self.environment_type == "local" and self.max_depth > 1:
235+
# For local/ipython environments with max_depth > 1, pass subcall callback for recursive RLM calls
236+
if self.environment_type in ("local", "ipython") and self.max_depth > 1:
237237
env_kwargs["subcall_fn"] = self._subcall
238238
# Pass custom tools to the environment
239239
if self.custom_tools is not None:
@@ -828,7 +828,7 @@ def _validate_persistent_environment_support(self) -> None:
828828
ValueError: If the environment type does not support persistent mode.
829829
"""
830830
# Known environments that support persistence
831-
persistent_supported_environments = {"local"}
831+
persistent_supported_environments = {"local", "ipython"}
832832

833833
if self.environment_type not in persistent_supported_environments:
834834
raise ValueError(

‎rlm/core/types.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"azure_openai",
1313
"gemini",
1414
]
15-
EnvironmentType = Literal["local", "docker", "modal", "prime", "daytona", "e2b"]
15+
EnvironmentType = Literal["local", "ipython", "docker", "modal", "prime", "daytona", "e2b"]
1616

1717

1818
def _serialize_value(value: Any) -> Any:

‎rlm/environments/__init__.py‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
__all__ = [
1818
"BaseEnv",
19+
"IPythonREPL",
1920
"LocalREPL",
2021
"RESERVED_TOOL_NAMES",
2122
"SupportsCustomTools",
@@ -31,15 +32,19 @@
3132

3233

3334
def get_environment(
34-
environment: Literal["local", "modal", "docker", "daytona", "prime", "e2b"],
35+
environment: Literal["local", "ipython", "modal", "docker", "daytona", "prime", "e2b"],
3536
environment_kwargs: dict[str, Any],
3637
) -> BaseEnv:
3738
"""
3839
Routes a specific environment and the args (as a dict) to the appropriate environment if supported.
39-
Currently supported environments: ['local', 'modal', 'docker', 'daytona', 'prime', 'e2b']
40+
Currently supported environments: ['local', 'ipython', 'modal', 'docker', 'daytona', 'prime', 'e2b']
4041
"""
4142
if environment == "local":
4243
return LocalREPL(**environment_kwargs)
44+
elif environment == "ipython":
45+
from rlm.environments.ipython_repl import IPythonREPL
46+
47+
return IPythonREPL(**environment_kwargs)
4348
elif environment == "modal":
4449
from rlm.environments.modal_repl import ModalREPL
4550

@@ -62,5 +67,5 @@ def get_environment(
6267
return E2BREPL(**environment_kwargs)
6368
else:
6469
raise ValueError(
65-
f"Unknown environment: {environment}. Supported: ['local', 'modal', 'docker', 'daytona', 'prime', 'e2b']"
70+
f"Unknown environment: {environment}. Supported: ['local', 'ipython', 'modal', 'docker', 'daytona', 'prime', 'e2b']"
6671
)

0 commit comments

Comments
 (0)