93e3893d69
CI / lint (pull_request) Successful in 14s
CI / typecheck (pull_request) Successful in 34s
CI / security (pull_request) Successful in 29s
CI / quality (pull_request) Successful in 15s
CI / benchmark-publish (pull_request) Has been skipped
CI / build (pull_request) Successful in 14s
CI / unit_tests (pull_request) Successful in 2m4s
CI / integration_tests (pull_request) Successful in 2m50s
CI / coverage (pull_request) Successful in 4m15s
CI / docker (pull_request) Successful in 42s
CI / lint (push) Successful in 12s
CI / typecheck (push) Successful in 31s
CI / quality (push) Successful in 16s
CI / security (push) Successful in 31s
CI / build (push) Successful in 16s
CI / unit_tests (push) Successful in 3m23s
CI / benchmark-regression (push) Has been skipped
CI / docker (push) Successful in 42s
CI / integration_tests (push) Successful in 4m11s
CI / coverage (push) Successful in 4m8s
CI / benchmark-publish (push) Successful in 16m5s
CI / benchmark-regression (pull_request) Successful in 29m59s
Implement the runtime execution engine for validation tool wrapping, as specified in docs/specification.md § Tool Wrapping. WrappedToolExecutor resolves wraps references and delegates execution to wrapped tools, supporting composable wrapping chains with cycle detection and depth limiting (max 10 levels). ArgumentMapper translates arguments between wrapper and wrapped tool schemas using the argument_mapping configuration. Supports both forwarded parameter names and literal fixed values. TransformExecutor runs user-supplied transform functions in a sandboxed Python environment with restricted builtins (no imports, no filesystem, no network access). Validates that transforms return proper validation-format dicts with a passed boolean. Wired into the tool package public API via tool/__init__.py exports. All new error types (WrappedToolNotFoundError, WrappingCycleError, WrappingDepthExceededError, TransformExecutionError) provide clear diagnostic messages. Tests: 20 Behave scenarios covering argument mapping, transform execution, simple/chained delegation, error handling, and sandbox restrictions. 8 Robot Framework integration smoke tests. ASV benchmarks for delegation overhead measurement. ISSUES CLOSED: #543
186 lines
5.5 KiB
Python
186 lines
5.5 KiB
Python
"""ASV benchmarks for tool wrapping delegation overhead.
|
|
|
|
Measures the performance of:
|
|
- ArgumentMapper.apply() with identity and configured mappings
|
|
- TransformExecutor.execute() with simple transform functions
|
|
- WrappedToolExecutor.execute() for single and chained delegation
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
# Ensure the local *source* tree is importable even when ASV has an
|
|
# older build of the package installed.
|
|
_SRC = str(Path(__file__).resolve().parents[1] / "src")
|
|
if _SRC not in sys.path:
|
|
sys.path.insert(0, _SRC)
|
|
|
|
import cleveragents # noqa: E402
|
|
|
|
importlib.reload(cleveragents)
|
|
|
|
from cleveragents.domain.models.core.tool import ( # noqa: E402
|
|
Tool,
|
|
ToolCapability,
|
|
ToolSource,
|
|
ToolType,
|
|
Validation,
|
|
ValidationMode,
|
|
)
|
|
from cleveragents.tool.wrapping import ( # noqa: E402
|
|
ArgumentMapper,
|
|
TransformExecutor,
|
|
WrappedToolExecutor,
|
|
)
|
|
|
|
_SIMPLE_TRANSFORM = """\
|
|
def transform(tool_output):
|
|
passed = tool_output.get("returncode") == 0
|
|
return {"passed": passed, "message": "done", "data": tool_output}
|
|
"""
|
|
|
|
_PASSTHROUGH_TRANSFORM = """\
|
|
def transform(tool_output):
|
|
return {"passed": True, "message": "ok", "data": tool_output}
|
|
"""
|
|
|
|
|
|
def _make_tool(name: str) -> Tool:
|
|
return Tool(
|
|
name=name,
|
|
description=f"Benchmark tool {name}",
|
|
source=ToolSource.BUILTIN,
|
|
capability=ToolCapability(read_only=True),
|
|
)
|
|
|
|
|
|
def _make_validation(
|
|
name: str,
|
|
wraps: str,
|
|
transform_code: str,
|
|
argument_mapping: dict[str, Any] | None = None,
|
|
) -> Validation:
|
|
return Validation(
|
|
name=name,
|
|
description=f"Benchmark validation wrapping {wraps}",
|
|
source=ToolSource.WRAPPED,
|
|
tool_type=ToolType.VALIDATION,
|
|
mode=ValidationMode.REQUIRED,
|
|
wraps=wraps,
|
|
transform=transform_code,
|
|
argument_mapping=argument_mapping,
|
|
)
|
|
|
|
|
|
class ArgumentMapperSuite:
|
|
"""Benchmarks for ArgumentMapper."""
|
|
|
|
def time_identity_mapping(self) -> None:
|
|
"""Measure identity (passthrough) mapping overhead."""
|
|
mapper = ArgumentMapper(None)
|
|
for _ in range(1000):
|
|
mapper.apply({"path": "/src", "verbose": True, "count": 42})
|
|
|
|
def time_configured_mapping(self) -> None:
|
|
"""Measure mapping with configured argument translation."""
|
|
mapper = ArgumentMapper(
|
|
{
|
|
"test_directory": "source_dir",
|
|
"coverage_enabled": True,
|
|
"verbose": False,
|
|
}
|
|
)
|
|
for _ in range(1000):
|
|
mapper.apply({"source_dir": "/tests", "extra": "ignored"})
|
|
|
|
|
|
class TransformExecutorSuite:
|
|
"""Benchmarks for TransformExecutor."""
|
|
|
|
def setup(self) -> None:
|
|
self._executor = TransformExecutor(_SIMPLE_TRANSFORM, "bench/tool")
|
|
self._output = {"returncode": 0, "tests_run": 100}
|
|
|
|
def time_execute_transform(self) -> None:
|
|
"""Measure transform function execution."""
|
|
for _ in range(1000):
|
|
self._executor.execute(self._output)
|
|
|
|
def time_create_and_execute(self) -> None:
|
|
"""Measure creation + execution (cold path)."""
|
|
for _ in range(100):
|
|
executor = TransformExecutor(_SIMPLE_TRANSFORM, "bench/tool")
|
|
executor.execute(self._output)
|
|
|
|
|
|
class WrappedToolExecutorSuite:
|
|
"""Benchmarks for WrappedToolExecutor delegation."""
|
|
|
|
def setup(self) -> None:
|
|
self._tool = _make_tool("local/base-tool")
|
|
self._output: dict[str, Any] = {"returncode": 0, "data": "ok"}
|
|
self._validation = _make_validation(
|
|
"local/bench-wrap",
|
|
"local/base-tool",
|
|
_SIMPLE_TRANSFORM,
|
|
)
|
|
|
|
tools: dict[str, tuple[Tool, dict[str, Any]]] = {
|
|
"local/base-tool": (self._tool, self._output),
|
|
}
|
|
|
|
def lookup(name: str) -> Tool | Validation | None:
|
|
if name in tools:
|
|
return tools[name][0]
|
|
return None
|
|
|
|
def executor(name: str, args: dict[str, Any]) -> dict[str, Any]:
|
|
if name in tools:
|
|
return tools[name][1]
|
|
raise RuntimeError(f"Not found: {name}")
|
|
|
|
self._executor = WrappedToolExecutor(lookup, executor)
|
|
|
|
def time_single_delegation(self) -> None:
|
|
"""Measure single-level wrapping delegation."""
|
|
for _ in range(1000):
|
|
self._executor.execute(self._validation, {"path": "/src"})
|
|
|
|
def time_chained_delegation(self) -> None:
|
|
"""Measure two-level wrapping delegation chain."""
|
|
inner_val = _make_validation(
|
|
"local/inner-wrap",
|
|
"local/base-tool",
|
|
_PASSTHROUGH_TRANSFORM,
|
|
)
|
|
outer_val = _make_validation(
|
|
"local/outer-wrap",
|
|
"local/inner-wrap",
|
|
_SIMPLE_TRANSFORM,
|
|
)
|
|
|
|
tools: dict[str, tuple[Tool, dict[str, Any]]] = {
|
|
"local/base-tool": (self._tool, self._output),
|
|
}
|
|
|
|
all_items: dict[str, Tool | Validation] = {
|
|
"local/inner-wrap": inner_val,
|
|
"local/base-tool": self._tool,
|
|
}
|
|
|
|
def lookup(name: str) -> Tool | Validation | None:
|
|
return all_items.get(name)
|
|
|
|
def executor(name: str, args: dict[str, Any]) -> dict[str, Any]:
|
|
if name in tools:
|
|
return tools[name][1]
|
|
raise RuntimeError(f"Not found: {name}")
|
|
|
|
chained_executor = WrappedToolExecutor(lookup, executor)
|
|
for _ in range(500):
|
|
chained_executor.execute(outer_val, {"path": "/src"})
|