Files
temp/benchmarks/actor_list_empty_bench.py
T
Brent E. Edwards 73d5552467 fix(actor): handle empty actor list without validation error
ActorRegistry._actor_name() built names via f"{provider}/{model}", which
produced names with multiple slashes when providers included models
containing "/" (e.g. OpenRouter's "anthropic/claude-sonnet-4-20250514").
The resulting name violated the spec pattern ^[a-z0-9_-]+/[a-z0-9_-]+$
and triggered a ValidationError during actor upsert.

Now sanitises both provider and model components by replacing "/" with "-"
and lowercasing, so multi-slash provider models no longer break actor
listing.

Includes 6 Behave BDD regression scenarios (covering zero-provider,
multi-slash, consecutive-slash, leading-slash, and name-validation
cases), Robot Framework integration smoke tests, and ASV benchmarks.

ISSUES CLOSED: #592
2026-03-10 23:11:22 +00:00

107 lines
3.5 KiB
Python

"""ASV benchmarks for actor list on fresh project.
Measures the cost of listing actors via the ``ActorRegistry`` when
no providers are configured and when a provider with a multi-slash
default model is present.
Targets bug #592 — ``_actor_name()`` now sanitises provider and model
names by replacing ``/`` with ``-`` and lowercasing, so multi-slash
provider models no longer trigger ``ValidationError``.
"""
from __future__ import annotations
import importlib
import sys
from pathlib import Path
from typing import Any
from unittest.mock import MagicMock
# 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)
# Ensure the features directory is importable so we can reuse shared
# mock stubs instead of duplicating them locally.
_FEATURES = str(Path(__file__).resolve().parents[1] / "features")
if _FEATURES not in sys.path:
sys.path.insert(0, _FEATURES)
# Force-reload so ASV picks up the source tree version.
import cleveragents # noqa: E402
importlib.reload(cleveragents)
from cleveragents.actor.registry import ActorRegistry # noqa: E402
from cleveragents.core.exceptions import ValidationError # noqa: E402
from cleveragents.domain.models.core.actor import Actor # noqa: E402
from mocks.fake_provider import FakeProviderInfo, FakeProviderRegistry # noqa: E402
def _make_registry(
providers: list[FakeProviderInfo] | None = None,
) -> ActorRegistry:
"""Build an ``ActorRegistry`` with mocked dependencies."""
prov_reg = FakeProviderRegistry(providers=providers or [])
mock_service = MagicMock()
mock_service.list_actors.return_value = []
mock_service.get_default_actor.return_value = None
mock_service.upsert_actor.side_effect = lambda **kw: Actor(
name=kw.get("name", "mock/actor"),
provider=kw.get("provider", "mock"),
model=kw.get("model", "actor"),
config_blob={},
config_hash=Actor.compute_hash({}),
)
mock_settings = MagicMock()
mock_settings.resolve_provider_defaults.return_value = MagicMock(
provider=None, model=None
)
return ActorRegistry(
actor_service=mock_service,
provider_registry=prov_reg,
settings=mock_settings,
)
class ActorListEmptySuite:
"""Benchmark actor list with zero and multi-slash providers."""
timeout = 60
def setup(self) -> None:
self._empty_registry = _make_registry()
self._slash_registry = _make_registry(
providers=[
FakeProviderInfo(
name="Openrouter",
default_model="anthropic/claude-sonnet-4-20250514",
),
]
)
def time_list_empty(self) -> None:
"""List actors with zero configured providers."""
self._empty_registry.list_actors()
def time_list_multi_slash_provider(self) -> None:
"""List actors when a multi-slash provider is configured."""
self._slash_registry.list_actors()
def track_multi_slash_succeeds(self) -> int:
"""Track whether listing with a multi-slash provider succeeds.
Returns 1 when list_actors() completes without error; 0 if
ValidationError is raised.
"""
try:
self._slash_registry.list_actors()
return 1
except ValidationError:
return 0
ActorListEmptySuite.track_multi_slash_succeeds.unit = "success"