forked from HAL9000/cleveragents-core
255 lines
8.8 KiB
Python
255 lines
8.8 KiB
Python
"""Behave steps for actor registry persistence with YAML text retention."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from behave import given, then, when
|
|
from behave.runner import Context
|
|
|
|
from cleveragents.actor.registry import ActorRegistry
|
|
from cleveragents.config.settings import ProviderDefaults
|
|
from cleveragents.core.exceptions import ValidationError
|
|
from cleveragents.domain.models.core.actor import Actor
|
|
from cleveragents.providers.registry import ProviderInfo
|
|
|
|
|
|
class _StubSettings:
|
|
def __init__(self) -> None:
|
|
self._defaults = ProviderDefaults(
|
|
provider=None, provider_source="test", model=None, model_source="test"
|
|
)
|
|
|
|
def resolve_provider_defaults(self) -> ProviderDefaults:
|
|
return self._defaults
|
|
|
|
|
|
class _StubProviderRegistry:
|
|
def get_configured_providers(self) -> list[ProviderInfo]:
|
|
return []
|
|
|
|
|
|
class _StubActorService:
|
|
"""In-memory actor service stub for persistence tests."""
|
|
|
|
def __init__(self) -> None:
|
|
self.actors: dict[str, Actor] = {}
|
|
self.default_actor_name: str | None = None
|
|
|
|
def upsert_actor(
|
|
self,
|
|
*,
|
|
name: str,
|
|
provider: str,
|
|
model: str,
|
|
config_blob: dict[str, Any] | None = None,
|
|
graph_descriptor: dict[str, Any] | None = None,
|
|
unsafe: bool = False,
|
|
set_default: bool = False,
|
|
is_built_in: bool = False,
|
|
yaml_text: str | None = None,
|
|
schema_version: str | None = None,
|
|
compiled_metadata: dict[str, Any] | None = None,
|
|
) -> Actor:
|
|
blob = config_blob or {}
|
|
actor = Actor(
|
|
id=len(self.actors) + 1,
|
|
name=name,
|
|
provider=provider,
|
|
model=model,
|
|
config_blob=blob,
|
|
config_hash=Actor.compute_hash(blob),
|
|
graph_descriptor=graph_descriptor,
|
|
yaml_text=yaml_text,
|
|
schema_version=schema_version or "1.0",
|
|
compiled_metadata=compiled_metadata,
|
|
unsafe=unsafe,
|
|
is_built_in=is_built_in,
|
|
is_default=False,
|
|
)
|
|
self.actors[name] = actor
|
|
if set_default:
|
|
self.default_actor_name = name
|
|
return actor
|
|
|
|
def get_actor(self, name: str) -> Actor:
|
|
if name not in self.actors:
|
|
raise ValueError(f"Actor '{name}' not found")
|
|
return self.actors[name]
|
|
|
|
def list_actors(self) -> list[Actor]:
|
|
return list(self.actors.values())
|
|
|
|
def remove_actor(self, name: str) -> None:
|
|
if name not in self.actors:
|
|
raise ValueError(f"Actor '{name}' not found")
|
|
del self.actors[name]
|
|
if self.default_actor_name == name:
|
|
self.default_actor_name = None
|
|
|
|
def get_default_actor(self) -> Actor | None:
|
|
if self.default_actor_name and self.default_actor_name in self.actors:
|
|
return self.actors[self.default_actor_name]
|
|
return None
|
|
|
|
def set_default_actor(self, name: str) -> Actor:
|
|
actor = self.actors.get(name)
|
|
if actor is None:
|
|
raise ValueError("Actor does not exist")
|
|
self.default_actor_name = name
|
|
return actor
|
|
|
|
|
|
def _build_persistence_registry(context: Context) -> None:
|
|
context.actor_service = _StubActorService()
|
|
context.provider_registry = _StubProviderRegistry()
|
|
context.settings = _StubSettings()
|
|
context.registry = ActorRegistry(
|
|
actor_service=context.actor_service,
|
|
provider_registry=context.provider_registry,
|
|
settings=context.settings,
|
|
)
|
|
|
|
|
|
@given("an actor registry with no configured providers for persistence")
|
|
def step_registry_no_providers_persistence(context: Context) -> None:
|
|
_build_persistence_registry(context)
|
|
|
|
|
|
@when("I add actor from YAML text:")
|
|
def step_add_actor_yaml(context: Context) -> None:
|
|
yaml_text = context.text
|
|
context.added_actor = context.registry.add(yaml_text)
|
|
|
|
|
|
@when('I add actor from YAML text with schema_version "{version}":')
|
|
def step_add_actor_yaml_version(context: Context, version: str) -> None:
|
|
yaml_text = context.text
|
|
context.added_actor = context.registry.add(yaml_text, schema_version=version)
|
|
|
|
|
|
@when("I add actor from YAML text with compiled_metadata:")
|
|
def step_add_actor_yaml_compiled(context: Context) -> None:
|
|
yaml_text = context.text
|
|
metadata = {"graph_nodes": ["node1", "node2"], "tool_count": 3}
|
|
context.added_actor = context.registry.add(yaml_text, compiled_metadata=metadata)
|
|
|
|
|
|
@when("I update actor from YAML text:")
|
|
def step_update_actor_yaml(context: Context) -> None:
|
|
yaml_text = context.text
|
|
context.updated_actor = context.registry.add(yaml_text, update=True)
|
|
|
|
|
|
@when("I add actor from YAML text with update:")
|
|
def step_add_actor_yaml_with_update(context: Context) -> None:
|
|
yaml_text = context.text
|
|
context.added_actor = context.registry.add(yaml_text, update=True)
|
|
|
|
|
|
@when('I remove actor "{name}" via registry')
|
|
def step_remove_actor_via_registry(context: Context, name: str) -> None:
|
|
context.registry.remove(name)
|
|
|
|
|
|
@when("I attempt to add actor from YAML text without name:")
|
|
def step_attempt_add_no_name(context: Context) -> None:
|
|
yaml_text = context.text
|
|
try:
|
|
context.registry.add(yaml_text)
|
|
context.error = None
|
|
except (ValidationError, ValueError) as exc:
|
|
context.error = exc
|
|
|
|
|
|
@when("I attempt to add duplicate actor from YAML text:")
|
|
def step_attempt_add_duplicate(context: Context) -> None:
|
|
yaml_text = context.text
|
|
try:
|
|
context.registry.add(yaml_text, update=False)
|
|
context.error = None
|
|
except (ValidationError, ValueError) as exc:
|
|
context.error = exc
|
|
|
|
|
|
@when('I upsert actor "{name}" with yaml_text and schema_version "{version}"')
|
|
def step_upsert_legacy_yaml(context: Context, name: str, version: str) -> None:
|
|
context.saved_actor = context.registry.upsert_actor(
|
|
name=name,
|
|
config_blob={"provider": "openai", "model": "gpt-4"},
|
|
provider="openai",
|
|
model="gpt-4",
|
|
yaml_text="legacy yaml content",
|
|
schema_version=version,
|
|
)
|
|
|
|
|
|
@then('the actor "{name}" should have yaml_text containing "{substring}"')
|
|
def step_assert_yaml_text(context: Context, name: str, substring: str) -> None:
|
|
actor = context.actor_service.actors[name]
|
|
assert actor.yaml_text is not None, f"Actor {name} has no yaml_text"
|
|
assert substring in actor.yaml_text, (
|
|
f"Expected '{substring}' in yaml_text, got: {actor.yaml_text}"
|
|
)
|
|
|
|
|
|
@then('the actor "{name}" should have schema_version "{version}"')
|
|
def step_assert_schema_version(context: Context, name: str, version: str) -> None:
|
|
actor = context.actor_service.actors[name]
|
|
assert actor.schema_version == version, (
|
|
f"Expected schema_version '{version}', got: {actor.schema_version}"
|
|
)
|
|
|
|
|
|
@then('the actor "{name}" should have compiled_metadata key "{key}"')
|
|
def step_assert_compiled_metadata(context: Context, name: str, key: str) -> None:
|
|
actor = context.actor_service.actors[name]
|
|
assert actor.compiled_metadata is not None, f"Actor {name} has no compiled_metadata"
|
|
assert key in actor.compiled_metadata, (
|
|
f"Expected key '{key}' in compiled_metadata, "
|
|
f"got keys: {list(actor.compiled_metadata.keys())}"
|
|
)
|
|
|
|
|
|
@then('the actor "{name}" should not exist')
|
|
def step_assert_actor_not_exist(context: Context, name: str) -> None:
|
|
assert name not in context.actor_service.actors, f"Actor {name} still exists"
|
|
|
|
|
|
@then('the actor "{name}" should exist')
|
|
def step_assert_actor_exists(context: Context, name: str) -> None:
|
|
assert name in context.actor_service.actors, f"Actor {name} does not exist"
|
|
|
|
|
|
@then('listing actors with namespace "{namespace}" should return {count:d} actors')
|
|
def step_assert_namespace_filter(context: Context, namespace: str, count: int) -> None:
|
|
actors = context.registry.list(namespace=namespace)
|
|
assert len(actors) == count, (
|
|
f"Expected {count} actors in namespace '{namespace}', "
|
|
f"got {len(actors)}: {[a.name for a in actors]}"
|
|
)
|
|
|
|
|
|
@then('getting actor "{name}" via registry should return provider "{provider}"')
|
|
def step_assert_get_actor(context: Context, name: str, provider: str) -> None:
|
|
actor = context.registry.get(name)
|
|
assert actor.provider == provider, (
|
|
f"Expected provider '{provider}', got: {actor.provider}"
|
|
)
|
|
|
|
|
|
@then("a validation error should be raised for missing name")
|
|
def step_assert_missing_name_error(context: Context) -> None:
|
|
assert context.error is not None, "Expected a validation error"
|
|
assert "name" in str(context.error).lower(), (
|
|
f"Expected error about name, got: {context.error}"
|
|
)
|
|
|
|
|
|
@then("a validation error should be raised for duplicate actor")
|
|
def step_assert_duplicate_error(context: Context) -> None:
|
|
assert context.error is not None, "Expected a validation error"
|
|
assert "already exists" in str(context.error).lower(), (
|
|
f"Expected error about duplicate, got: {context.error}"
|
|
)
|