"""Helper script for Robot Framework actor runtime smoke tests.""" from __future__ import annotations import sys from pathlib import Path from typing import Any # Ensure src is importable when run from workspace root sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) from cleveragents.tool.actor_context import ToolActorContext from cleveragents.tool.actor_runtime import ( LLMResponse, LLMToolCall, ToolCallingRuntime, ) from cleveragents.tool.registry import ToolRegistry from cleveragents.tool.runner import ToolRunner from cleveragents.tool.runtime import ToolSpec def _echo(inputs: dict[str, Any]) -> dict[str, Any]: return {"echoed": inputs} def _fail(inputs: dict[str, Any]) -> dict[str, Any]: raise RuntimeError("boom") _captured: dict[str, Any] = {} def _capture(inputs: dict[str, Any]) -> dict[str, Any]: _captured.clear() _captured.update(inputs) return {"ok": True} class SingleToolLLM: """Requests one tool call then returns.""" def __init__(self) -> None: self._n = 0 def invoke( self, prompt: str, tool_schemas: list[dict[str, Any]], tool_results: list[dict[str, Any]] | None = None, actor_config: dict[str, Any] | None = None, ) -> LLMResponse: self._n += 1 if self._n == 1: return LLMResponse( tool_calls=[LLMToolCall(name="test/echo", arguments={"a": 1})] ) return LLMResponse(content="done") class AlwaysToolLLM: """Always requests tool calls.""" def invoke( self, prompt: str, tool_schemas: list[dict[str, Any]], tool_results: list[dict[str, Any]] | None = None, actor_config: dict[str, Any] | None = None, ) -> LLMResponse: return LLMResponse( tool_calls=[LLMToolCall(name="test/echo", arguments={"x": 1})] ) class FailToolLLM: """Requests failing tool then stops.""" def __init__(self) -> None: self._n = 0 def invoke( self, prompt: str, tool_schemas: list[dict[str, Any]], tool_results: list[dict[str, Any]] | None = None, actor_config: dict[str, Any] | None = None, ) -> LLMResponse: self._n += 1 if self._n == 1: return LLMResponse(tool_calls=[LLMToolCall(name="test/fail", arguments={})]) return LLMResponse(content="handled error") class CaptureToolLLM: """Requests capturing tool then stops.""" def __init__(self) -> None: self._n = 0 def invoke( self, prompt: str, tool_schemas: list[dict[str, Any]], tool_results: list[dict[str, Any]] | None = None, actor_config: dict[str, Any] | None = None, ) -> LLMResponse: self._n += 1 if self._n == 1: return LLMResponse( tool_calls=[LLMToolCall(name="test/capture", arguments={"d": 1})] ) return LLMResponse(content="captured") class ImmediateLLM: """Returns immediately without tool calls.""" def invoke( self, prompt: str, tool_schemas: list[dict[str, Any]], tool_results: list[dict[str, Any]] | None = None, actor_config: dict[str, Any] | None = None, ) -> LLMResponse: return LLMResponse(content="immediate") def _make_registry_with_echo() -> ToolRegistry: reg = ToolRegistry() reg.register(ToolSpec(name="test/echo", description="Echo", handler=_echo)) return reg def test_single_tool() -> None: reg = _make_registry_with_echo() runner = ToolRunner(reg) rt = ToolCallingRuntime(registry=reg, runner=runner, llm_caller=SingleToolLLM()) result = rt.run_tool_loop(prompt="go") assert result.content == "done" assert len(result.tool_call_history) == 1 assert not result.terminated_by_limit print("single-tool-ok") def test_max_iterations() -> None: reg = _make_registry_with_echo() runner = ToolRunner(reg) rt = ToolCallingRuntime( registry=reg, runner=runner, llm_caller=AlwaysToolLLM(), max_iterations=3 ) result = rt.run_tool_loop(prompt="loop") assert result.terminated_by_limit assert result.iterations == 3 print("max-iterations-ok") def test_error_handling() -> None: reg = ToolRegistry() reg.register(ToolSpec(name="test/fail", description="Fail", handler=_fail)) runner = ToolRunner(reg) rt = ToolCallingRuntime(registry=reg, runner=runner, llm_caller=FailToolLLM()) result = rt.run_tool_loop(prompt="fail") assert len(result.tool_call_history) == 1 assert not result.tool_call_history[0].success assert result.tool_call_history[0].error is not None print("error-handling-ok") def test_context_props() -> None: ctx = ToolActorContext(plan_id="p1", phase="execute", sandbox_root="/sb") assert ctx.plan_id == "p1" assert ctx.phase == "execute" assert ctx.sandbox_root == "/sb" assert len(ctx.tool_call_history) == 0 summary = ctx.as_summary() assert summary["plan_id"] == "p1" print("context-props-ok") def test_sandbox_thread() -> None: reg = ToolRegistry() reg.register(ToolSpec(name="test/capture", description="Capture", handler=_capture)) runner = ToolRunner(reg) ctx = ToolActorContext(plan_id="p2", phase="execute", sandbox_root="/sandbox/path") rt = ToolCallingRuntime(registry=reg, runner=runner, llm_caller=CaptureToolLLM()) rt.run_tool_loop(prompt="capture", context=ctx) assert _captured.get("sandbox_root") == "/sandbox/path" print("sandbox-thread-ok") def test_empty_registry() -> None: reg = ToolRegistry() runner = ToolRunner(reg) rt = ToolCallingRuntime(registry=reg, runner=runner, llm_caller=ImmediateLLM()) result = rt.run_tool_loop(prompt="nothing") assert result.content == "immediate" assert len(result.tool_call_history) == 0 print("empty-registry-ok") COMMANDS = { "single_tool": test_single_tool, "max_iterations": test_max_iterations, "error_handling": test_error_handling, "context_props": test_context_props, "sandbox_thread": test_sandbox_thread, "empty_registry": test_empty_registry, } if __name__ == "__main__": cmd = sys.argv[1] if len(sys.argv) > 1 else "single_tool" COMMANDS[cmd]()