c6d831b5ff
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Failing after 16s
CI / quality (pull_request) Successful in 21s
CI / build (pull_request) Successful in 24s
CI / security (pull_request) Successful in 27s
CI / typecheck (pull_request) Successful in 40s
CI / coverage (pull_request) Has been skipped
CI / benchmark-regression (pull_request) Has been skipped
CI / integration_tests (pull_request) Successful in 2m26s
CI / unit_tests (pull_request) Successful in 5m40s
CI / docker (pull_request) Has been skipped
711 lines
23 KiB
Python
711 lines
23 KiB
Python
"""Step definitions for the Tool-Calling Actor Runtime feature."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import contextlib
|
|
from typing import Any
|
|
|
|
from behave import given, then, when
|
|
|
|
from cleveragents.tool.actor_context import ToolActorContext, ToolCallRecord
|
|
from cleveragents.tool.actor_runtime import (
|
|
LLMResponse,
|
|
LLMToolCall,
|
|
ToolCallingRuntime,
|
|
ToolCallRunResult,
|
|
)
|
|
from cleveragents.tool.registry import ToolRegistry
|
|
from cleveragents.tool.router import ToolCallRouter
|
|
from cleveragents.tool.runner import ToolRunner
|
|
from cleveragents.tool.runtime import ToolSpec
|
|
|
|
__all__: list[str] = []
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Mock handlers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _rt_echo_handler(inputs: dict[str, Any]) -> dict[str, Any]:
|
|
return {"echoed": inputs}
|
|
|
|
|
|
def _rt_adder_handler(inputs: dict[str, Any]) -> dict[str, Any]:
|
|
return {"sum": inputs.get("a", 0) + inputs.get("b", 0)}
|
|
|
|
|
|
def _rt_failing_handler(inputs: dict[str, Any]) -> dict[str, Any]:
|
|
raise RuntimeError("handler exploded")
|
|
|
|
|
|
def _rt_generic_error_handler(inputs: dict[str, Any]) -> dict[str, Any]:
|
|
raise KeyError("missing_key")
|
|
|
|
|
|
_rt_captured_inputs: dict[str, Any] = {}
|
|
|
|
|
|
def _rt_capturing_handler(inputs: dict[str, Any]) -> dict[str, Any]:
|
|
_rt_captured_inputs.clear()
|
|
_rt_captured_inputs.update(inputs)
|
|
return {"captured": True}
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Mock LLM callers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class _SingleToolLLM:
|
|
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(
|
|
content="",
|
|
tool_calls=[LLMToolCall(name="test/echo", arguments={"msg": "hello"})],
|
|
)
|
|
return LLMResponse(content="Final answer after tool call", tool_calls=[])
|
|
|
|
|
|
class _TwoToolsLLM:
|
|
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(
|
|
content="",
|
|
tool_calls=[LLMToolCall(name="test/echo", arguments={"msg": "first"})],
|
|
)
|
|
if self._n == 2:
|
|
return LLMResponse(
|
|
content="",
|
|
tool_calls=[LLMToolCall(name="test/adder", arguments={"a": 1, "b": 2})],
|
|
)
|
|
return LLMResponse(content="Done after two tool calls", tool_calls=[])
|
|
|
|
|
|
class _AlwaysToolsLLM:
|
|
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="",
|
|
tool_calls=[LLMToolCall(name="test/echo", arguments={"x": "loop"})],
|
|
)
|
|
|
|
|
|
class _NonexistentToolLLM:
|
|
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(
|
|
content="",
|
|
tool_calls=[LLMToolCall(name="test/nonexistent", arguments={"x": 1})],
|
|
)
|
|
return LLMResponse(content="Done after error", tool_calls=[])
|
|
|
|
|
|
class _FailingToolLLM:
|
|
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(
|
|
content="",
|
|
tool_calls=[LLMToolCall(name="test/fail", arguments={})],
|
|
)
|
|
return LLMResponse(content="Done after failing tool", tool_calls=[])
|
|
|
|
|
|
class _ImmediateLLM:
|
|
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="No tools needed", tool_calls=[])
|
|
|
|
|
|
class _CapturingToolLLM:
|
|
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(
|
|
content="",
|
|
tool_calls=[
|
|
LLMToolCall(name="test/capture", arguments={"data": "test"})
|
|
],
|
|
)
|
|
return LLMResponse(content="Done after capture", tool_calls=[])
|
|
|
|
|
|
class _GenericErrorToolLLM:
|
|
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(
|
|
content="",
|
|
tool_calls=[LLMToolCall(name="test/generic_error", arguments={"x": 1})],
|
|
)
|
|
return LLMResponse(content="Done after generic error", tool_calls=[])
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Given steps — registry & runner
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given("a tool registry with an echo tool")
|
|
def step_given_registry_echo(context: Any) -> None:
|
|
if not hasattr(context, "registry"):
|
|
context.registry = ToolRegistry()
|
|
with contextlib.suppress(Exception):
|
|
context.registry.register(
|
|
ToolSpec(
|
|
name="test/echo", description="Echo tool", handler=_rt_echo_handler
|
|
)
|
|
)
|
|
|
|
|
|
@given("a tool registry with an adder tool")
|
|
def step_given_registry_adder(context: Any) -> None:
|
|
if not hasattr(context, "registry"):
|
|
context.registry = ToolRegistry()
|
|
with contextlib.suppress(Exception):
|
|
context.registry.register(
|
|
ToolSpec(
|
|
name="test/adder", description="Adder tool", handler=_rt_adder_handler
|
|
)
|
|
)
|
|
|
|
|
|
@given("a tool registry with a failing tool")
|
|
def step_given_registry_failing(context: Any) -> None:
|
|
if not hasattr(context, "registry"):
|
|
context.registry = ToolRegistry()
|
|
with contextlib.suppress(Exception):
|
|
context.registry.register(
|
|
ToolSpec(
|
|
name="test/fail",
|
|
description="Failing tool",
|
|
handler=_rt_failing_handler,
|
|
)
|
|
)
|
|
|
|
|
|
@given("a tool registry with an input-capturing tool")
|
|
def step_given_registry_capturing(context: Any) -> None:
|
|
if not hasattr(context, "registry"):
|
|
context.registry = ToolRegistry()
|
|
with contextlib.suppress(Exception):
|
|
context.registry.register(
|
|
ToolSpec(
|
|
name="test/capture",
|
|
description="Capturing tool",
|
|
handler=_rt_capturing_handler,
|
|
)
|
|
)
|
|
|
|
|
|
@given("a tool registry with a tool that raises a generic exception")
|
|
def step_given_registry_generic_error(context: Any) -> None:
|
|
if not hasattr(context, "registry"):
|
|
context.registry = ToolRegistry()
|
|
with contextlib.suppress(Exception):
|
|
context.registry.register(
|
|
ToolSpec(
|
|
name="test/generic_error",
|
|
description="Generic error tool",
|
|
handler=_rt_generic_error_handler,
|
|
)
|
|
)
|
|
|
|
|
|
@given("an empty actor tool registry")
|
|
def step_given_empty_registry(context: Any) -> None:
|
|
context.registry = ToolRegistry()
|
|
|
|
|
|
@given("a tool runner for the actor registry")
|
|
def step_given_runner(context: Any) -> None:
|
|
context.runner = ToolRunner(context.registry)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Given steps — mock LLM callers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given("a mock LLM caller that requests one tool call then responds")
|
|
def step_given_mock_llm_single(context: Any) -> None:
|
|
context.llm_caller = _SingleToolLLM()
|
|
|
|
|
|
@given("a mock LLM caller that requests two sequential tool calls then responds")
|
|
def step_given_mock_llm_two(context: Any) -> None:
|
|
context.llm_caller = _TwoToolsLLM()
|
|
|
|
|
|
@given("a mock LLM caller that always requests tool calls")
|
|
def step_given_mock_llm_always(context: Any) -> None:
|
|
context.llm_caller = _AlwaysToolsLLM()
|
|
|
|
|
|
@given("a mock LLM caller that requests a nonexistent tool then responds")
|
|
def step_given_mock_llm_nonexistent(context: Any) -> None:
|
|
context.llm_caller = _NonexistentToolLLM()
|
|
|
|
|
|
@given("a mock LLM caller that requests the failing tool then responds")
|
|
def step_given_mock_llm_failing(context: Any) -> None:
|
|
context.llm_caller = _FailingToolLLM()
|
|
|
|
|
|
@given("a mock LLM caller that responds immediately without tool calls")
|
|
def step_given_mock_llm_immediate(context: Any) -> None:
|
|
context.llm_caller = _ImmediateLLM()
|
|
|
|
|
|
@given("a mock LLM caller that requests the capturing tool then responds")
|
|
def step_given_mock_llm_capturing(context: Any) -> None:
|
|
context.llm_caller = _CapturingToolLLM()
|
|
|
|
|
|
@given("a mock LLM caller that requests the generic-error tool then responds")
|
|
def step_given_mock_llm_generic_error(context: Any) -> None:
|
|
context.llm_caller = _GenericErrorToolLLM()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Given steps — runtime construction
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given("a tool-calling runtime with the mock LLM caller")
|
|
def step_given_runtime(context: Any) -> None:
|
|
context.runtime = ToolCallingRuntime(
|
|
registry=context.registry,
|
|
runner=context.runner,
|
|
llm_caller=context.llm_caller,
|
|
)
|
|
|
|
|
|
@given("a tool-calling runtime with max iterations {n:d}")
|
|
def step_given_runtime_max_iter(context: Any, n: int) -> None:
|
|
context.runtime = ToolCallingRuntime(
|
|
registry=context.registry,
|
|
runner=context.runner,
|
|
llm_caller=context.llm_caller,
|
|
max_iterations=n,
|
|
)
|
|
|
|
|
|
@given("a tool-calling runtime with the mock LLM caller and router")
|
|
def step_given_runtime_with_router(context: Any) -> None:
|
|
context.runtime = ToolCallingRuntime(
|
|
registry=context.registry,
|
|
runner=context.runner,
|
|
llm_caller=context.llm_caller,
|
|
router=context.router,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Given steps — actor context
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given('an actor context with plan "{plan_id}" phase "{phase}"')
|
|
def step_given_actor_ctx_plan_phase(context: Any, plan_id: str, phase: str) -> None:
|
|
context.actor_context = ToolActorContext(plan_id=plan_id, phase=phase)
|
|
|
|
|
|
@given('an actor context with plan "{plan_id}" sandbox "{root}"')
|
|
def step_given_actor_ctx_plan_sandbox(context: Any, plan_id: str, root: str) -> None:
|
|
context.actor_context = ToolActorContext(plan_id=plan_id, sandbox_root=root)
|
|
|
|
|
|
@given('an actor context with plan "{plan_id}"')
|
|
def step_given_actor_ctx_plan(context: Any, plan_id: str) -> None:
|
|
context.actor_context = ToolActorContext(plan_id=plan_id)
|
|
|
|
|
|
@given('an actor context with sandbox root "{root}"')
|
|
def step_given_actor_ctx_sandbox(context: Any, root: str) -> None:
|
|
context.actor_context = ToolActorContext(
|
|
plan_id="plan-test",
|
|
phase="execute",
|
|
sandbox_root=root,
|
|
)
|
|
|
|
|
|
@given("an actor context with resource bindings")
|
|
def step_given_actor_ctx_bindings(context: Any) -> None:
|
|
context.actor_context = ToolActorContext(
|
|
plan_id="plan-test",
|
|
phase="execute",
|
|
resource_bindings={"source": {"type": "git-checkout", "path": "/repo"}},
|
|
)
|
|
|
|
|
|
@given("an actor context with all optional fields")
|
|
def step_given_actor_ctx_all_fields(context: Any) -> None:
|
|
context.actor_context = ToolActorContext(
|
|
plan_id="plan-full",
|
|
phase="execute",
|
|
sandbox_root="/tmp/sand",
|
|
automation_profile="auto-profile",
|
|
resource_bindings={"src": {"path": "/src"}},
|
|
project_resources={"repo": "/home/repo"},
|
|
metadata={"version": "1.0"},
|
|
)
|
|
# Pre-add a record so we can test clear_history
|
|
context.actor_context.record_tool_call(
|
|
ToolCallRecord(
|
|
tool_name="test/dummy",
|
|
success=True,
|
|
duration_ms=1.0,
|
|
iteration=1,
|
|
)
|
|
)
|
|
|
|
|
|
@given('a tool call router with plan id "{plan_id}"')
|
|
def step_given_router(context: Any, plan_id: str) -> None:
|
|
context.router = ToolCallRouter(
|
|
registry=context.registry,
|
|
runner=context.runner,
|
|
plan_id=plan_id,
|
|
)
|
|
|
|
|
|
@given('a tool call record with name "{name}" and duration {duration:g}')
|
|
def step_given_tool_call_record(context: Any, name: str, duration: float) -> None:
|
|
context.tool_call_record = ToolCallRecord(tool_name=name, duration_ms=duration)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# When steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when('I run the tool loop with prompt "{prompt}"')
|
|
def step_when_run_loop(context: Any, prompt: str) -> None:
|
|
context.run_result = context.runtime.run_tool_loop(prompt=prompt)
|
|
|
|
|
|
@when('I run the tool loop with prompt "{prompt}" and the actor context')
|
|
def step_when_run_loop_with_context(context: Any, prompt: str) -> None:
|
|
context.run_result = context.runtime.run_tool_loop(
|
|
prompt=prompt,
|
|
context=context.actor_context,
|
|
)
|
|
|
|
|
|
@when('I run the tool loop with prompt "{prompt}" and no context')
|
|
def step_when_run_loop_no_context(context: Any, prompt: str) -> None:
|
|
context.run_result = context.runtime.run_tool_loop(prompt=prompt, context=None)
|
|
|
|
|
|
@when('I record a tool call with name "{name}" and success {success}')
|
|
def step_when_record_tool_call(context: Any, name: str, success: str) -> None:
|
|
record = ToolCallRecord(tool_name=name, success=success == "True")
|
|
context.actor_context.record_tool_call(record)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Then steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@then('the run result content should be "{expected}"')
|
|
def step_then_content(context: Any, expected: str) -> None:
|
|
result: ToolCallRunResult = context.run_result
|
|
assert result.content == expected, f"Expected '{expected}', got '{result.content}'"
|
|
|
|
|
|
@then("the run result should have {count:d} tool call records")
|
|
def step_then_tool_call_count(context: Any, count: int) -> None:
|
|
result: ToolCallRunResult = context.run_result
|
|
actual = len(result.tool_call_history)
|
|
assert actual == count, f"Expected {count} records, got {actual}"
|
|
|
|
|
|
@then("the run result iterations should be {expected:d}")
|
|
def step_then_iterations(context: Any, expected: int) -> None:
|
|
result: ToolCallRunResult = context.run_result
|
|
assert result.iterations == expected, (
|
|
f"Expected {expected}, got {result.iterations}"
|
|
)
|
|
|
|
|
|
@then("the run result should not be terminated by limit")
|
|
def step_then_not_terminated(context: Any) -> None:
|
|
assert not context.run_result.terminated_by_limit
|
|
|
|
|
|
@then("the run result should be terminated by limit")
|
|
def step_then_terminated(context: Any) -> None:
|
|
assert context.run_result.terminated_by_limit
|
|
|
|
|
|
@then("the first tool call record should have success {expected}")
|
|
def step_then_first_record_success(context: Any, expected: str) -> None:
|
|
record = context.run_result.tool_call_history[0]
|
|
assert record.success == (expected == "True")
|
|
|
|
|
|
@then("the first tool call record should have an error message")
|
|
def step_then_first_record_has_error(context: Any) -> None:
|
|
record = context.run_result.tool_call_history[0]
|
|
assert record.error is not None and len(record.error) > 0
|
|
|
|
|
|
@then('the first tool call record error should contain "{fragment}"')
|
|
def step_then_first_record_error_contains(context: Any, fragment: str) -> None:
|
|
record = context.run_result.tool_call_history[0]
|
|
assert record.error is not None
|
|
assert fragment in record.error, f"Expected '{fragment}' in '{record.error}'"
|
|
|
|
|
|
@then('the first tool call record tool name should be "{name}"')
|
|
def step_then_first_record_name(context: Any, name: str) -> None:
|
|
assert context.run_result.tool_call_history[0].tool_name == name
|
|
|
|
|
|
@then("the first tool call record inputs should not be empty")
|
|
def step_then_first_record_inputs(context: Any) -> None:
|
|
assert len(context.run_result.tool_call_history[0].inputs) > 0
|
|
|
|
|
|
@then("the first tool call record output should not be empty")
|
|
def step_then_first_record_output(context: Any) -> None:
|
|
assert len(context.run_result.tool_call_history[0].output) > 0
|
|
|
|
|
|
@then("the first tool call record duration should be greater than 0")
|
|
def step_then_first_record_duration(context: Any) -> None:
|
|
assert context.run_result.tool_call_history[0].duration_ms >= 0.0
|
|
|
|
|
|
@then("the first tool call record iteration should be {expected:d}")
|
|
def step_then_first_record_iteration(context: Any, expected: int) -> None:
|
|
assert context.run_result.tool_call_history[0].iteration == expected
|
|
|
|
|
|
@then('the captured tool inputs should contain sandbox root "{root}"')
|
|
def step_then_captured_sandbox(context: Any, root: str) -> None:
|
|
assert _rt_captured_inputs.get("sandbox_root") == root
|
|
|
|
|
|
@then("the captured tool inputs should contain resource bindings")
|
|
def step_then_captured_bindings(context: Any) -> None:
|
|
assert isinstance(_rt_captured_inputs.get("resource_bindings"), dict)
|
|
|
|
|
|
@then("creating a runtime with max iterations 0 should raise ValueError")
|
|
def step_then_invalid_max_iter(context: Any) -> None:
|
|
try:
|
|
ToolCallingRuntime(
|
|
registry=context.registry,
|
|
runner=context.runner,
|
|
llm_caller=context.llm_caller,
|
|
max_iterations=0,
|
|
)
|
|
raise AssertionError("Expected ValueError")
|
|
except ValueError:
|
|
pass
|
|
|
|
|
|
@then("running the tool loop with empty prompt should raise ValueError")
|
|
def step_then_empty_prompt(context: Any) -> None:
|
|
try:
|
|
context.runtime.run_tool_loop(prompt="")
|
|
raise AssertionError("Expected ValueError")
|
|
except ValueError:
|
|
pass
|
|
|
|
|
|
@then('the actor context plan id should be "{expected}"')
|
|
def step_then_actor_plan_id(context: Any, expected: str) -> None:
|
|
assert context.actor_context.plan_id == expected
|
|
|
|
|
|
@then('the actor context phase should be "{expected}"')
|
|
def step_then_actor_phase(context: Any, expected: str) -> None:
|
|
assert context.actor_context.phase == expected
|
|
|
|
|
|
@then("the actor context tool call history should be empty")
|
|
def step_then_actor_history_empty(context: Any) -> None:
|
|
assert len(context.actor_context.tool_call_history) == 0
|
|
|
|
|
|
@then("the actor context tool call history should have {count:d} records")
|
|
def step_then_actor_history_count(context: Any, count: int) -> None:
|
|
assert len(context.actor_context.tool_call_history) == count
|
|
|
|
|
|
@then("creating a tool actor context with empty plan id should raise ValueError")
|
|
def step_then_empty_plan_id(context: Any) -> None:
|
|
try:
|
|
ToolActorContext(plan_id="")
|
|
raise AssertionError("Expected ValueError")
|
|
except ValueError:
|
|
pass
|
|
|
|
|
|
@then('the actor context summary should contain plan id "{plan_id}"')
|
|
def step_then_summary_plan_id(context: Any, plan_id: str) -> None:
|
|
assert context.actor_context.as_summary()["plan_id"] == plan_id
|
|
|
|
|
|
@then('the actor context summary should contain sandbox root "{root}"')
|
|
def step_then_summary_sandbox(context: Any, root: str) -> None:
|
|
assert context.actor_context.as_summary()["sandbox_root"] == root
|
|
|
|
|
|
@then('the tool call record tool name should be "{name}"')
|
|
def step_then_record_name(context: Any, name: str) -> None:
|
|
assert context.tool_call_record.tool_name == name
|
|
|
|
|
|
@then("the tool call record duration should be {expected:g}")
|
|
def step_then_record_duration(context: Any, expected: float) -> None:
|
|
assert context.tool_call_record.duration_ms == expected
|
|
|
|
|
|
# -- Actor Context additional property steps --
|
|
|
|
|
|
@then('the actor context automation profile should be "{expected}"')
|
|
def step_then_actor_automation_profile(context: Any, expected: str) -> None:
|
|
assert context.actor_context.automation_profile == expected
|
|
|
|
|
|
@then('the actor context project resources should have key "{key}"')
|
|
def step_then_actor_project_resources_key(context: Any, key: str) -> None:
|
|
assert key in context.actor_context.project_resources
|
|
|
|
|
|
@then('the actor context metadata should have key "{key}"')
|
|
def step_then_actor_metadata_key(context: Any, key: str) -> None:
|
|
assert key in context.actor_context.metadata
|
|
|
|
|
|
@then("the actor context clear history should empty the history")
|
|
def step_then_actor_clear_history(context: Any) -> None:
|
|
assert len(context.actor_context.tool_call_history) > 0
|
|
context.actor_context.clear_history()
|
|
assert len(context.actor_context.tool_call_history) == 0
|
|
|
|
|
|
@then("recording a non-ToolCallRecord should raise TypeError")
|
|
def step_then_invalid_record_type(context: Any) -> None:
|
|
try:
|
|
context.actor_context.record_tool_call("not a record") # type: ignore[arg-type]
|
|
raise AssertionError("Expected TypeError")
|
|
except TypeError:
|
|
pass
|
|
|
|
|
|
# -- Runtime constructor type validation steps --
|
|
|
|
|
|
@then("creating a runtime with a non-ToolRegistry should raise TypeError")
|
|
def step_then_invalid_registry_type(context: Any) -> None:
|
|
try:
|
|
ToolCallingRuntime(
|
|
registry="not a registry", # type: ignore[arg-type]
|
|
runner=ToolRunner(ToolRegistry()),
|
|
llm_caller=_ImmediateLLM(),
|
|
)
|
|
raise AssertionError("Expected TypeError")
|
|
except TypeError:
|
|
pass
|
|
|
|
|
|
@then("creating a runtime with a non-ToolRunner should raise TypeError")
|
|
def step_then_invalid_runner_type(context: Any) -> None:
|
|
try:
|
|
ToolCallingRuntime(
|
|
registry=context.registry,
|
|
runner="not a runner", # type: ignore[arg-type]
|
|
llm_caller=_ImmediateLLM(),
|
|
)
|
|
raise AssertionError("Expected TypeError")
|
|
except TypeError:
|
|
pass
|
|
|
|
|
|
# -- Runtime property accessors steps --
|
|
|
|
|
|
@then("the runtime max iterations should be {expected:d}")
|
|
def step_then_runtime_max_iter(context: Any, expected: int) -> None:
|
|
assert context.runtime.max_iterations == expected
|
|
|
|
|
|
@then("the runtime provider format should not be None")
|
|
def step_then_runtime_provider_format(context: Any) -> None:
|
|
assert context.runtime.provider_format is not None
|