Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/rlm/tools/ipython.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,19 @@ def schema(self) -> dict[str, Any]:
return schema

def execute(self, args: dict[str, Any], context: ToolContext) -> ToolOutcome:
# A missing "code" key used to fall through to "" and execute nothing
# silently — the model would then hit NameErrors on state it thought it
# created. Laguna in particular emits the arg under "cmd". Turn any such
# call into a loud, learnable error instead of a no-op.
if "code" not in args or args.get("code") in (None, ""):
wrong = ", ".join(repr(k) for k in args if k != "timeout") or "none"
return ToolOutcome(
content=(
"Error: the ipython tool requires a non-empty 'code' argument; "
f"got keys: {wrong}. Pass your Python under the 'code' key."
),
metric_events=[IpythonExecuted(input_chars=0, input_loc=0)],
)
code = args.get("code", "")
if not isinstance(code, str):
code = str(code)
Expand Down
30 changes: 30 additions & 0 deletions tests/test_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,33 @@ def test_ipython_kernel_does_not_inherit_parent_stdio(session, capfd):
assert captured.out == ""
assert captured.err == ""
assert result.strip() == "4 14"


def _ipython_ctx():
from rlm.tools.base import ToolContext
from rlm.types import RLMMetrics, TokenUsage

return ToolContext(
messages=[],
metrics=RLMMetrics(),
total_usage=TokenUsage(),
last_prompt_tokens=0,
exec_timeout=30,
repl=None, # error paths return before touching the REPL
)


def test_ipython_missing_code_key_errors_not_noop():
"""A tool call under the wrong key (e.g. Laguna's 'cmd') must error loudly."""
from rlm.tools.ipython import IpythonTool

out = IpythonTool().execute({"cmd": "print(1)"}, _ipython_ctx())
assert "Error" in out.content and "code" in out.content
assert "'cmd'" in out.content


def test_ipython_empty_code_errors():
from rlm.tools.ipython import IpythonTool

out = IpythonTool().execute({"code": ""}, _ipython_ctx())
assert "Error" in out.content and "code" in out.content
Loading