forked from HAL9000/cleveragents-core
3a2b134f3c
Added Behave BDD feature files and step definitions targeting coverage gaps in six modules: - container.py: exercise get_database_url env-var fallback, AI provider None path, cached container singleton, override_providers edge cases (lines 66-69, 125-130; branches at 51, 57, 82, 87, 256, 284-288) - correction_service.py: exercise exception-handling paths in execute_revert and execute_append via monkeypatched analyze_impact and ULID failures (lines 254-262, 320-328) - plan_lifecycle_service.py: exercise _persisted UoW commit paths, InvalidPhaseTransitionError custom message branch, non-reusable action archive, and error_details merge logic (branches at 100, 216, 237, 327, 461, 570, 576, 607) - plan.py (CLI): exercise spec-dict optional field branches, _print_lifecycle_plan conditional rendering, use_action argument parsing, auto-resolve paths, legacy wrappers, and validation error branches across 66 scenarios - skill.py (CLI): exercise singleton cache, timestamp-absent show, no-tools MCP, add/remove/list/show format and error branches across 26 scenarios - models.py (DB): exercise to_domain/from_domain None-field branches in SkillModel, SessionModel, ToolModel, LifecycleActionModel, LifecyclePlanModel, NamespacedProjectModel, and SessionMessageModel across 41 scenarios All 302 features, 6503 scenarios, 28271 steps pass (nox -e unit_tests). ISSUES CLOSED: #446
209 lines
6.8 KiB
Python
209 lines
6.8 KiB
Python
"""ASV benchmarks for M2 actor + tool source smoke suite.
|
|
|
|
Measures baseline runtime for:
|
|
- Actor YAML loading from fixture
|
|
- Skill registry resolution
|
|
- Tool lifecycle overhead (discover/activate/execute/deactivate)
|
|
- MCP stub server discovery and invocation
|
|
"""
|
|
|
|
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)
|
|
|
|
# Make the MCP stub mock importable (lives in features/mocks/, not installed).
|
|
_MOCKS = str(Path(__file__).resolve().parents[1] / "features" / "mocks")
|
|
if _MOCKS not in sys.path:
|
|
sys.path.insert(0, _MOCKS)
|
|
|
|
# Force-reload the top-level package so Python picks up the source tree
|
|
# version instead of a potentially stale installed copy.
|
|
import cleveragents # noqa: E402
|
|
|
|
importlib.reload(cleveragents)
|
|
|
|
from mcp_stub_server import McpStubServer # noqa: E402
|
|
|
|
from cleveragents.actor.loader import ActorLoader # noqa: E402
|
|
from cleveragents.actor.schema import ActorConfigSchema # noqa: E402
|
|
from cleveragents.domain.models.core.skill import Skill, SkillInlineTool # noqa: E402
|
|
from cleveragents.domain.models.core.tool import ( # noqa: E402
|
|
Tool,
|
|
ToolCapability,
|
|
ToolSource,
|
|
ToolType,
|
|
)
|
|
from cleveragents.skills.protocol import SkillDefinition, SkillMetadata # noqa: E402
|
|
from cleveragents.skills.registry import SkillRegistry # noqa: E402
|
|
from cleveragents.tool.context import ToolExecutionContext # noqa: E402
|
|
from cleveragents.tool.lifecycle import ( # noqa: E402
|
|
ToolDescriptor,
|
|
ToolResult,
|
|
ToolRuntime,
|
|
)
|
|
|
|
_FIXTURES_DIR = Path(__file__).resolve().parents[1] / "features" / "fixtures" / "m2"
|
|
|
|
|
|
# ── Actor loading benchmarks ─────────────────────────────────────
|
|
|
|
|
|
class TimeM2ActorLoading:
|
|
"""Benchmark actor YAML loading from M2 fixtures."""
|
|
|
|
def setup(self) -> None:
|
|
self.fixture_path = _FIXTURES_DIR / "actors" / "m2_hierarchical_actor.yaml"
|
|
|
|
def time_load_hierarchical_actor(self) -> None:
|
|
ActorConfigSchema.from_yaml_file(self.fixture_path)
|
|
|
|
def time_discover_from_fixture_dir(self) -> None:
|
|
loader = ActorLoader(search_roots=[_FIXTURES_DIR / "actors"])
|
|
loader.discover()
|
|
|
|
def time_actor_loader_cache_hit(self) -> None:
|
|
loader = ActorLoader(search_roots=[_FIXTURES_DIR / "actors"])
|
|
loader.discover()
|
|
loader.discover() # Second call should be a cache hit
|
|
|
|
|
|
# ── Skill registry benchmarks ────────────────────────────────────
|
|
|
|
|
|
class TimeM2SkillRegistry:
|
|
"""Benchmark skill registry operations."""
|
|
|
|
def setup(self) -> None:
|
|
self.skill = Skill(
|
|
name="m2bench/file-ops",
|
|
description="Benchmark skill pack",
|
|
tool_refs=["builtin/read_file", "builtin/list_files"],
|
|
anonymous_tools=[
|
|
SkillInlineTool(
|
|
description="Bench echo tool",
|
|
source=ToolSource.CUSTOM,
|
|
code="def echo(t: str) -> str: return t",
|
|
timeout=300,
|
|
),
|
|
],
|
|
)
|
|
self.metadata = SkillMetadata.from_skill(self.skill)
|
|
self.defn = SkillDefinition(skill=self.skill, metadata=self.metadata)
|
|
|
|
def time_register_skill(self) -> None:
|
|
registry = SkillRegistry()
|
|
registry.register(self.defn)
|
|
|
|
def time_resolve_tools(self) -> None:
|
|
registry = SkillRegistry()
|
|
registry.register(self.defn)
|
|
registry.resolve_tools(self.skill.name)
|
|
|
|
def time_list_all_skills(self) -> None:
|
|
registry = SkillRegistry()
|
|
registry.register(self.defn)
|
|
registry.list_all()
|
|
|
|
|
|
# ── Tool lifecycle benchmarks ────────────────────────────────────
|
|
|
|
|
|
class _BenchToolInstance:
|
|
"""Minimal tool instance for benchmark."""
|
|
|
|
def __init__(self, name: str) -> None:
|
|
self._name = name
|
|
|
|
def discover(self) -> ToolDescriptor:
|
|
return ToolDescriptor(
|
|
name=self._name, description="bench tool", source="custom"
|
|
)
|
|
|
|
def activate(self, ctx: ToolExecutionContext) -> None:
|
|
pass
|
|
|
|
def execute(self, params: dict[str, Any], ctx: ToolExecutionContext) -> ToolResult:
|
|
return ToolResult(success=True, data={"echo": params.get("text", "")})
|
|
|
|
def deactivate(self, ctx: ToolExecutionContext) -> None:
|
|
pass
|
|
|
|
|
|
class TimeM2ToolLifecycle:
|
|
"""Benchmark tool lifecycle overhead."""
|
|
|
|
def setup(self) -> None:
|
|
self.name = "m2bench/echo"
|
|
self.tool = Tool(
|
|
name=self.name,
|
|
description="Benchmark tool",
|
|
source=ToolSource.BUILTIN,
|
|
tool_type=ToolType.TOOL,
|
|
capability=ToolCapability(
|
|
writes=False, read_only=True, checkpointable=False
|
|
),
|
|
timeout=300,
|
|
)
|
|
self.instance = _BenchToolInstance(self.name)
|
|
|
|
def time_register_and_discover(self) -> None:
|
|
runtime = ToolRuntime()
|
|
runtime.register_tool(self.tool, self.instance)
|
|
runtime.discover(self.name)
|
|
|
|
def time_full_lifecycle(self) -> None:
|
|
runtime = ToolRuntime()
|
|
runtime.register_tool(self.tool, self.instance)
|
|
ctx = ToolExecutionContext(plan_id="bench-plan")
|
|
runtime.activate(self.name, ctx)
|
|
runtime.execute(self.name, {"text": "bench"}, ctx)
|
|
runtime.deactivate(self.name, ctx)
|
|
|
|
def time_execute_only(self) -> None:
|
|
runtime = ToolRuntime()
|
|
runtime.register_tool(self.tool, self.instance)
|
|
ctx = ToolExecutionContext(plan_id="bench-plan")
|
|
runtime.execute(self.name, {"text": "bench"}, ctx)
|
|
|
|
|
|
# ── MCP stub benchmarks ─────────────────────────────────────────
|
|
|
|
|
|
class TimeM2McpStub:
|
|
"""Benchmark MCP stub server discovery and invocation."""
|
|
|
|
def setup(self) -> None:
|
|
self.server = McpStubServer()
|
|
self.server.start()
|
|
|
|
def teardown(self) -> None:
|
|
self.server.stop()
|
|
|
|
def time_discover_tools(self) -> None:
|
|
self.server.discover()
|
|
|
|
def time_invoke_search(self) -> None:
|
|
self.server.invoke("mcp/search", {"query": "benchmark"})
|
|
|
|
def time_invoke_fetch(self) -> None:
|
|
self.server.invoke("mcp/fetch", {"url": "http://example.com"})
|
|
|
|
def time_invoke_transform(self) -> None:
|
|
self.server.invoke("mcp/transform", {"data": "hello", "format": "upper"})
|
|
|
|
|
|
# Module-level instances for ASV discovery
|
|
time_actor = TimeM2ActorLoading()
|
|
time_skill = TimeM2SkillRegistry()
|
|
time_tool = TimeM2ToolLifecycle()
|
|
time_mcp = TimeM2McpStub()
|