a074b4846f
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 15s
CI / build (pull_request) Successful in 19s
CI / quality (pull_request) Successful in 28s
CI / security (pull_request) Successful in 50s
CI / typecheck (pull_request) Successful in 56s
CI / integration_tests (pull_request) Successful in 4m51s
CI / unit_tests (pull_request) Successful in 19m29s
CI / docker (pull_request) Successful in 39s
CI / benchmark-regression (pull_request) Successful in 26m10s
CI / coverage (pull_request) Successful in 47m42s
CI / lint (push) Successful in 13s
CI / quality (push) Successful in 17s
CI / build (push) Successful in 23s
CI / typecheck (push) Successful in 30s
CI / security (push) Successful in 30s
CI / benchmark-regression (push) Has been skipped
CI / integration_tests (push) Successful in 2m52s
CI / unit_tests (push) Successful in 10m8s
CI / docker (push) Successful in 1m19s
CI / benchmark-publish (push) Successful in 11m54s
CI / coverage (push) Failing after 39m51s
Remove FakeListLLM as a silent fallback in agent graph constructors (plan_generation.py, context_analysis.py, auto_debug.py). All three now raise ValueError when llm=None, making missing-provider errors explicit. Add Settings.mock_providers flag and validate_provider_availability() method. Update container.get_ai_provider() to check Settings.mock_providers first, with env-var fallback for backward compatibility. Add resolve_provider_by_name() helper to the provider registry and export it from cleveragents.providers. Add structlog trace logging to ProviderRegistry.get_default_provider_type() to record selection reasoning. Update all existing behave step files, robot tests, and benchmarks that relied on the implicit FakeListLLM default to pass an explicit LLM instance instead. Add new BDD tests (features/provider_fixes.feature with 17 scenarios), Robot Framework integration tests (robot/provider_detection_smoke.robot), and ASV benchmarks (benchmarks/provider_selection_bench.py). ISSUES CLOSED: #323
205 lines
6.3 KiB
Python
205 lines
6.3 KiB
Python
"""ASV benchmarks for DecisionService DI resolution and operations.
|
|
|
|
Measures:
|
|
- Container resolution of DecisionService
|
|
- Decision recording through the service layer
|
|
- Decision tree retrieval through the service layer
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from datetime import UTC, datetime
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock
|
|
|
|
try:
|
|
from cleveragents.application.services.decision_service import DecisionService
|
|
from cleveragents.domain.models.core.action import Action, ActionState
|
|
from cleveragents.domain.models.core.decision import Decision, DecisionType
|
|
from cleveragents.domain.models.core.plan import (
|
|
NamespacedName,
|
|
Plan,
|
|
PlanIdentity,
|
|
PlanPhase,
|
|
PlanTimestamps,
|
|
ProcessingState,
|
|
)
|
|
from cleveragents.infrastructure.database.models import Base
|
|
from cleveragents.infrastructure.database.unit_of_work import UnitOfWork
|
|
except ModuleNotFoundError:
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
|
from cleveragents.application.services.decision_service import DecisionService
|
|
from cleveragents.domain.models.core.action import Action, ActionState
|
|
from cleveragents.domain.models.core.decision import Decision, DecisionType
|
|
from cleveragents.domain.models.core.plan import (
|
|
NamespacedName,
|
|
Plan,
|
|
PlanIdentity,
|
|
PlanPhase,
|
|
PlanTimestamps,
|
|
ProcessingState,
|
|
)
|
|
from cleveragents.infrastructure.database.models import Base
|
|
from cleveragents.infrastructure.database.unit_of_work import UnitOfWork
|
|
|
|
from sqlalchemy import create_engine
|
|
from sqlalchemy.orm import sessionmaker
|
|
|
|
_PLAN_ID = "01HV00000000000000DIBENCH1"
|
|
|
|
|
|
def _make_uow() -> UnitOfWork:
|
|
"""Create a UoW backed by an in-memory SQLite database."""
|
|
engine = create_engine("sqlite:///:memory:", echo=False)
|
|
Base.metadata.create_all(engine)
|
|
|
|
uow = UnitOfWork.__new__(UnitOfWork)
|
|
uow.database_url = "sqlite:///:memory:"
|
|
uow._engine = engine
|
|
uow._session_factory = sessionmaker(
|
|
bind=engine,
|
|
expire_on_commit=False,
|
|
autoflush=False,
|
|
autocommit=False,
|
|
)
|
|
uow._database_initialized = True
|
|
uow._prompt_for_migration = None
|
|
return uow
|
|
|
|
|
|
def _seed_prerequisites(uow: UnitOfWork) -> None:
|
|
"""Create the action + plan needed to satisfy FK constraints."""
|
|
with uow.transaction() as ctx:
|
|
action_repo = ctx.actions
|
|
action = Action(
|
|
namespaced_name=NamespacedName.parse("local/bench-di-action"),
|
|
description="Benchmark action",
|
|
definition_of_done="Done",
|
|
strategy_actor="local/s",
|
|
execution_actor="local/e",
|
|
state=ActionState.AVAILABLE,
|
|
created_at=datetime(2026, 1, 1, tzinfo=UTC),
|
|
updated_at=datetime(2026, 1, 1, tzinfo=UTC),
|
|
)
|
|
action_repo.create(action)
|
|
|
|
with uow.transaction() as ctx:
|
|
plan_repo = ctx.lifecycle_plans
|
|
now = datetime(2026, 3, 1, tzinfo=UTC)
|
|
plan = Plan(
|
|
identity=PlanIdentity(plan_id=_PLAN_ID, attempt=1),
|
|
namespaced_name=NamespacedName(namespace="local", name="bench-di-plan"),
|
|
action_name="local/bench-di-action",
|
|
description="Benchmark DI plan",
|
|
definition_of_done="Done",
|
|
phase=PlanPhase.STRATEGIZE,
|
|
processing_state=ProcessingState.PROCESSING,
|
|
strategy_actor="local/s",
|
|
execution_actor="local/e",
|
|
timestamps=PlanTimestamps(created_at=now, updated_at=now),
|
|
created_by="bench",
|
|
tags=[],
|
|
reusable=True,
|
|
read_only=False,
|
|
)
|
|
plan_repo.create(plan)
|
|
|
|
|
|
def _make_decision(
|
|
seq: int = 0,
|
|
parent_id: str | None = None,
|
|
dtype: DecisionType = DecisionType.PROMPT_DEFINITION,
|
|
) -> Decision:
|
|
return Decision(
|
|
plan_id=_PLAN_ID,
|
|
parent_decision_id=parent_id,
|
|
sequence_number=seq,
|
|
decision_type=dtype,
|
|
question="Benchmark question?",
|
|
chosen_option="Benchmark option",
|
|
)
|
|
|
|
|
|
class TimeDecisionServiceResolution:
|
|
"""Benchmark DecisionService resolution from the DI container."""
|
|
|
|
timeout = 60
|
|
|
|
def setup(self) -> None:
|
|
import os
|
|
|
|
os.environ["CLEVERAGENTS_DATABASE_URL"] = "sqlite:///:memory:"
|
|
from cleveragents.application.container import get_container, reset_container
|
|
|
|
reset_container()
|
|
self.container = get_container()
|
|
|
|
def time_decision_service_resolution(self) -> None:
|
|
self.container.decision_service()
|
|
|
|
def teardown(self) -> None:
|
|
import os
|
|
|
|
from cleveragents.application.container import reset_container
|
|
|
|
reset_container()
|
|
os.environ.pop("CLEVERAGENTS_DATABASE_URL", None)
|
|
|
|
|
|
class TimeDecisionRecord:
|
|
"""Benchmark decision recording through DecisionService."""
|
|
|
|
timeout = 60
|
|
|
|
def setup(self) -> None:
|
|
self.uow = _make_uow()
|
|
_seed_prerequisites(self.uow)
|
|
self.svc = DecisionService(settings=MagicMock(), unit_of_work=self.uow)
|
|
self._seq = 100
|
|
|
|
def time_decision_record(self) -> None:
|
|
self._seq += 1
|
|
d = _make_decision(
|
|
seq=self._seq,
|
|
dtype=DecisionType.STRATEGY_CHOICE,
|
|
)
|
|
self.svc.record_decision(d)
|
|
|
|
|
|
class TimeDecisionTreeRetrieval:
|
|
"""Benchmark decision tree retrieval through DecisionService."""
|
|
|
|
timeout = 60
|
|
|
|
def setup(self) -> None:
|
|
self.uow = _make_uow()
|
|
_seed_prerequisites(self.uow)
|
|
self.svc = DecisionService(settings=MagicMock(), unit_of_work=self.uow)
|
|
|
|
# Build a small tree
|
|
root = _make_decision(seq=0)
|
|
self.svc.record_decision(root)
|
|
self.root_id = root.decision_id
|
|
|
|
seq = 1
|
|
for _ in range(3):
|
|
child = _make_decision(
|
|
seq=seq,
|
|
parent_id=root.decision_id,
|
|
dtype=DecisionType.STRATEGY_CHOICE,
|
|
)
|
|
self.svc.record_decision(child)
|
|
for _ in range(2):
|
|
seq += 1
|
|
gc = _make_decision(
|
|
seq=seq,
|
|
parent_id=child.decision_id,
|
|
dtype=DecisionType.IMPLEMENTATION_CHOICE,
|
|
)
|
|
self.svc.record_decision(gc)
|
|
seq += 1
|
|
|
|
def time_decision_tree_retrieval(self) -> None:
|
|
self.svc.get_decision_tree(self.root_id)
|