forked from cleveragents/cleveragents-core
393 lines
13 KiB
Python
393 lines
13 KiB
Python
"""Step definitions for provider_fixes.feature."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import os
|
|
from typing import Any
|
|
from unittest.mock import MagicMock
|
|
|
|
from behave import given, then, when # type: ignore[import-untyped]
|
|
|
|
|
|
def _make_pf_mock_settings(
|
|
*,
|
|
openai: str | None = None,
|
|
mock_providers: bool = False,
|
|
env: str = "test",
|
|
) -> MagicMock:
|
|
"""Create a lightweight Settings-like mock."""
|
|
settings = MagicMock()
|
|
settings.openai_api_key = openai
|
|
settings.anthropic_api_key = None
|
|
settings.google_api_key = None
|
|
settings.gemini_api_key = None
|
|
settings.azure_api_key = None
|
|
settings.openrouter_api_key = None
|
|
settings.cohere_api_key = None
|
|
settings.groq_api_key = None
|
|
settings.together_api_key = None
|
|
settings.default_provider = None
|
|
settings.default_model = None
|
|
settings.azure_openai_endpoint = None
|
|
settings.azure_openai_api_version = None
|
|
settings.azure_openai_deployment = None
|
|
settings.openrouter_organization = None
|
|
settings.mock_providers = mock_providers
|
|
settings.env = env
|
|
return settings
|
|
|
|
|
|
# ── Given steps ──────────────────────────────────────────────────────
|
|
|
|
|
|
@given("a provider-fix settings instance with openai configured")
|
|
def step_pf_settings_with_openai(context: Any) -> None:
|
|
context.pf_settings = _make_pf_mock_settings(openai="sk-test-key")
|
|
|
|
|
|
@given("a provider-fix settings instance with no providers configured")
|
|
def step_pf_settings_no_providers(context: Any) -> None:
|
|
context.pf_settings = _make_pf_mock_settings()
|
|
|
|
|
|
@given("provider-fix mock_providers is disabled")
|
|
def step_pf_mock_disabled(context: Any) -> None:
|
|
context.pf_settings.mock_providers = False
|
|
|
|
|
|
@given("provider-fix mock_providers is disabled on that instance")
|
|
def step_pf_mock_disabled_on_instance(context: Any) -> None:
|
|
context.pf_settings.mock_providers = False
|
|
|
|
|
|
@given("a provider-fix settings instance with mock_providers enabled")
|
|
def step_pf_settings_mock(context: Any) -> None:
|
|
context.pf_settings = _make_pf_mock_settings(mock_providers=True)
|
|
|
|
|
|
@given('the provider-fix environment is set to "{env_name}"')
|
|
def step_pf_set_env(context: Any, env_name: str) -> None:
|
|
context.pf_settings.env = env_name
|
|
|
|
|
|
@given("I create a provider-fix fresh settings instance")
|
|
def step_pf_fresh_settings(context: Any) -> None:
|
|
from cleveragents.config.settings import Settings
|
|
|
|
saved: dict[str, str | None] = {}
|
|
for key in ("CLEVERAGENTS_MOCK_PROVIDERS",):
|
|
saved[key] = os.environ.pop(key, None)
|
|
try:
|
|
Settings._instance = None # type: ignore[attr-defined]
|
|
context.pf_fresh_settings = Settings()
|
|
finally:
|
|
for key, val in saved.items():
|
|
if val is not None:
|
|
os.environ[key] = val
|
|
|
|
|
|
@given("I have a provider-fix FakeListLLM instance for testing")
|
|
def step_pf_create_fake_llm(context: Any) -> None:
|
|
from langchain_community.llms import FakeListLLM
|
|
|
|
context.pf_test_llm = FakeListLLM(
|
|
responses=[
|
|
"Dependencies: ['os', 'sys']",
|
|
"Relevance: High",
|
|
"Summary: test summary",
|
|
]
|
|
)
|
|
|
|
|
|
# ── When steps ───────────────────────────────────────────────────────
|
|
|
|
|
|
@when("I create a provider-fix registry from those settings")
|
|
def step_pf_create_registry(context: Any) -> None:
|
|
from cleveragents.providers.registry import ProviderRegistry
|
|
|
|
context.pf_registry = ProviderRegistry(settings=context.pf_settings)
|
|
|
|
|
|
@when("I call provider-fix validate_provider_availability")
|
|
def step_pf_validate(context: Any) -> None:
|
|
from cleveragents.config.settings import Settings
|
|
|
|
# Clear all provider env vars so Settings sees no providers
|
|
provider_env_keys = [
|
|
"OPENAI_API_KEY",
|
|
"ANTHROPIC_API_KEY",
|
|
"GOOGLE_API_KEY",
|
|
"GOOGLE_GENAI_API_KEY",
|
|
"AZURE_OPENAI_API_KEY",
|
|
"AZURE_API_KEY",
|
|
"OPENROUTER_API_KEY",
|
|
"GEMINI_API_KEY",
|
|
"GOOGLE_GEMINI_API_KEY",
|
|
"HF_TOKEN",
|
|
"HUGGINGFACEHUB_API_TOKEN",
|
|
"HUGGING_FACE_HUB_TOKEN",
|
|
"COHERE_API_KEY",
|
|
"PERPLEXITY_API_KEY",
|
|
"GROQ_API_KEY",
|
|
"TOGETHER_API_KEY",
|
|
]
|
|
saved_env: dict[str, str | None] = {}
|
|
if not context.pf_settings.openai_api_key:
|
|
for key in provider_env_keys:
|
|
saved_env[key] = os.environ.pop(key, None)
|
|
|
|
# Set env vars for Settings to pick up
|
|
env_overrides: dict[str, str] = {}
|
|
if context.pf_settings.mock_providers:
|
|
env_overrides["CLEVERAGENTS_MOCK_PROVIDERS"] = "true"
|
|
if hasattr(context.pf_settings, "env"):
|
|
env_overrides["CLEVERAGENTS_ENV"] = context.pf_settings.env
|
|
for k, v in env_overrides.items():
|
|
os.environ[k] = v
|
|
|
|
try:
|
|
Settings._instance = None # type: ignore[attr-defined]
|
|
real = Settings()
|
|
if context.pf_settings.openai_api_key:
|
|
real.openai_api_key = context.pf_settings.openai_api_key
|
|
|
|
context.pf_error = None
|
|
context.pf_warning_logged = False
|
|
|
|
class _Catcher(logging.Handler):
|
|
def __init__(self) -> None:
|
|
super().__init__()
|
|
self.warnings: list[str] = []
|
|
|
|
def emit(self, record: logging.LogRecord) -> None:
|
|
if record.levelno >= logging.WARNING:
|
|
self.warnings.append(record.getMessage())
|
|
|
|
handler = _Catcher()
|
|
log = logging.getLogger("cleveragents.config.settings")
|
|
log.addHandler(handler)
|
|
try:
|
|
real.validate_provider_availability()
|
|
except ValueError as exc:
|
|
context.pf_error = exc
|
|
finally:
|
|
log.removeHandler(handler)
|
|
context.pf_warning_logged = len(handler.warnings) > 0
|
|
finally:
|
|
for key, val in saved_env.items():
|
|
if val is not None:
|
|
os.environ[key] = val
|
|
else:
|
|
os.environ.pop(key, None)
|
|
for k in env_overrides:
|
|
os.environ.pop(k, None)
|
|
|
|
|
|
@when("I build the provider-fix AI provider from the container helper")
|
|
def step_pf_build_provider(context: Any) -> None:
|
|
from cleveragents.application.container import get_ai_provider
|
|
from cleveragents.providers.registry import (
|
|
ProviderRegistry,
|
|
reset_provider_registry,
|
|
)
|
|
|
|
reset_provider_registry()
|
|
registry = ProviderRegistry(settings=context.pf_settings)
|
|
context.pf_provider_result = get_ai_provider(
|
|
settings=context.pf_settings,
|
|
provider_registry=registry,
|
|
)
|
|
|
|
|
|
@when("I attempt to create a provider-fix PlanGenerationGraph without an LLM")
|
|
def step_pf_plan_gen_no_llm(context: Any) -> None:
|
|
from cleveragents.agents.graphs.plan_generation import (
|
|
PlanGenerationGraph,
|
|
)
|
|
|
|
context.pf_error = None
|
|
try:
|
|
PlanGenerationGraph(llm=None)
|
|
except ValueError as exc:
|
|
context.pf_error = exc
|
|
|
|
|
|
@when("I attempt to create a provider-fix ContextAnalysisAgent without an LLM")
|
|
def step_pf_ctx_no_llm(context: Any) -> None:
|
|
from cleveragents.agents.graphs.context_analysis import (
|
|
ContextAnalysisAgent,
|
|
)
|
|
|
|
context.pf_error = None
|
|
try:
|
|
ContextAnalysisAgent(llm=None)
|
|
except ValueError as exc:
|
|
context.pf_error = exc
|
|
|
|
|
|
@when("I attempt to create a provider-fix AutoDebugAgent without an LLM")
|
|
def step_pf_debug_no_llm(context: Any) -> None:
|
|
from cleveragents.agents.graphs.auto_debug import AutoDebugAgent
|
|
|
|
context.pf_error = None
|
|
try:
|
|
AutoDebugAgent(llm=None)
|
|
except ValueError as exc:
|
|
context.pf_error = exc
|
|
|
|
|
|
@when("I create a provider-fix registry and request the default provider")
|
|
def step_pf_registry_default(context: Any) -> None:
|
|
from cleveragents.providers.registry import ProviderRegistry
|
|
|
|
context.pf_registry = ProviderRegistry(settings=context.pf_settings)
|
|
context.pf_default = context.pf_registry.get_default_provider_type()
|
|
|
|
|
|
@when('I provider-fix resolve provider by name "{name}"')
|
|
def step_pf_resolve_by_name(context: Any, name: str) -> None:
|
|
from cleveragents.providers.registry import (
|
|
reset_provider_registry,
|
|
resolve_provider_by_name,
|
|
)
|
|
|
|
reset_provider_registry()
|
|
context.pf_error = None
|
|
context.pf_resolved = None
|
|
try:
|
|
context.pf_resolved = resolve_provider_by_name(
|
|
name, settings=context.pf_settings
|
|
)
|
|
except ValueError as exc:
|
|
context.pf_error = exc
|
|
|
|
|
|
@when("I call provider-fix get_ai_provider with those settings")
|
|
def step_pf_get_ai_provider(context: Any) -> None:
|
|
from cleveragents.application.container import get_ai_provider
|
|
from cleveragents.providers.registry import (
|
|
ProviderRegistry,
|
|
reset_provider_registry,
|
|
)
|
|
|
|
reset_provider_registry()
|
|
registry = ProviderRegistry(settings=context.pf_settings)
|
|
context.pf_provider_result = get_ai_provider(
|
|
settings=context.pf_settings,
|
|
provider_registry=registry,
|
|
)
|
|
|
|
|
|
@when("I create a provider-fix PlanGenerationGraph with that LLM")
|
|
def step_pf_create_graph(context: Any) -> None:
|
|
from cleveragents.agents.graphs.plan_generation import (
|
|
PlanGenerationGraph,
|
|
)
|
|
|
|
context.pf_error = None
|
|
try:
|
|
context.pf_graph = PlanGenerationGraph(llm=context.pf_test_llm)
|
|
except Exception as exc:
|
|
context.pf_error = exc
|
|
|
|
|
|
@when("I create a provider-fix ContextAnalysisAgent with that LLM")
|
|
def step_pf_create_ctx_agent(context: Any) -> None:
|
|
from cleveragents.agents.graphs.context_analysis import (
|
|
ContextAnalysisAgent,
|
|
)
|
|
|
|
context.pf_error = None
|
|
try:
|
|
context.pf_agent = ContextAnalysisAgent(llm=context.pf_test_llm)
|
|
except Exception as exc:
|
|
context.pf_error = exc
|
|
|
|
|
|
@when("I create a provider-fix AutoDebugAgent with that LLM")
|
|
def step_pf_create_debug_agent(context: Any) -> None:
|
|
from cleveragents.agents.graphs.auto_debug import AutoDebugAgent
|
|
|
|
context.pf_error = None
|
|
try:
|
|
context.pf_agent = AutoDebugAgent(llm=context.pf_test_llm)
|
|
except Exception as exc:
|
|
context.pf_error = exc
|
|
|
|
|
|
# ── Then steps ───────────────────────────────────────────────────────
|
|
|
|
|
|
@then('the provider-fix default provider type should be "{expected}"')
|
|
def step_pf_check_default(context: Any, expected: str) -> None:
|
|
from cleveragents.providers.registry import ProviderType
|
|
|
|
default = context.pf_registry.get_default_provider_type()
|
|
assert default is not None, "Expected a default provider"
|
|
assert default == ProviderType(expected), f"Expected {expected}, got {default}"
|
|
|
|
|
|
@then('a provider-fix ValueError should be raised containing "{fragment}"')
|
|
def step_pf_check_error(context: Any, fragment: str) -> None:
|
|
assert context.pf_error is not None, (
|
|
f"Expected ValueError with '{fragment}' but no error"
|
|
)
|
|
assert fragment in str(context.pf_error), (
|
|
f"Expected '{fragment}' in: {context.pf_error}"
|
|
)
|
|
|
|
|
|
@then("a provider-fix mock AI provider should be returned")
|
|
def step_pf_check_mock(context: Any) -> None:
|
|
if context.pf_provider_result is not None:
|
|
name = getattr(context.pf_provider_result, "_name", "")
|
|
cls = type(context.pf_provider_result).__name__.lower()
|
|
assert "mock" in name.lower() or "mock" in cls
|
|
|
|
|
|
@then("the provider-fix selection should be logged")
|
|
def step_pf_check_logging(context: Any) -> None:
|
|
assert context.pf_default is not None
|
|
|
|
|
|
@then("I should receive a provider-fix AI provider instance")
|
|
def step_pf_check_instance(context: Any) -> None:
|
|
assert context.pf_resolved is not None
|
|
|
|
|
|
@then("no provider-fix error should be raised")
|
|
def step_pf_no_error(context: Any) -> None:
|
|
assert context.pf_error is None, f"Expected no error but got: {context.pf_error}"
|
|
|
|
|
|
@then("a provider-fix warning should be logged about mock_providers")
|
|
def step_pf_check_warning(context: Any) -> None:
|
|
assert context.pf_warning_logged, "Expected a warning"
|
|
|
|
|
|
@then("provider-fix mock_providers should be False by default")
|
|
def step_pf_check_default_mock(context: Any) -> None:
|
|
assert context.pf_fresh_settings.mock_providers is False
|
|
|
|
|
|
@then("a provider-fix mock provider or None should be returned")
|
|
def step_pf_mock_or_none(context: Any) -> None:
|
|
result = context.pf_provider_result
|
|
if result is not None:
|
|
cls = type(result).__name__.lower()
|
|
assert "mock" in cls or hasattr(result, "_name")
|
|
|
|
|
|
@then("the provider-fix graph should be created successfully")
|
|
def step_pf_graph_ok(context: Any) -> None:
|
|
assert context.pf_error is None, f"Expected no error but got: {context.pf_error}"
|
|
assert context.pf_graph is not None
|
|
|
|
|
|
@then("the provider-fix agent should be created successfully")
|
|
def step_pf_agent_ok(context: Any) -> None:
|
|
assert context.pf_error is None, f"Expected no error but got: {context.pf_error}"
|
|
assert context.pf_agent is not None
|