diff --git a/features/provider_registry_coverage.feature b/features/provider_registry_coverage.feature index 5d6012255..393b4c982 100644 --- a/features/provider_registry_coverage.feature +++ b/features/provider_registry_coverage.feature @@ -373,10 +373,33 @@ Feature: Provider Registry Coverage Given I have a ProviderRegistry with OpenAI API key set And CLEVERAGENTS_DEFAULT_MODEL is not set When I create an AI provider for provider "openai" - Then the provider registry AI provider should be a LangChainChatProvider + Then the provider registry AI provider should be an OpenAIChatProvider And the provider registry AI provider name should be "openai" And the provider registry AI provider model_id should be "gpt-4o" + @unit @providers @registry + Scenario: Create AI provider returns Anthropic adapter + Given I have a ProviderRegistry with Anthropic API key set + And CLEVERAGENTS_DEFAULT_MODEL is not set + When I create an AI provider for provider "anthropic" + Then the provider registry AI provider should be an AnthropicChatProvider + And the provider registry AI provider name should be "anthropic" + And the provider registry AI provider model_id should be "claude-sonnet-4-20250514" + + @unit @providers @registry + Scenario: Create AI provider raises error when OpenAI API key is missing + Given I have a ProviderRegistry with no API keys + When I try to create an AI provider for provider "openai" + Then a provider registry ValueError should be raised + And the provider registry error should mention missing "OPENAI_API_KEY" environment variable + + @unit @providers @registry + Scenario: Create AI provider raises error when Anthropic API key is missing + Given I have a ProviderRegistry with no API keys + When I try to create an AI provider for provider "anthropic" + Then a provider registry ValueError should be raised + And the provider registry error should mention missing "ANTHROPIC_API_KEY" environment variable + @unit @providers @registry Scenario: Create AI provider returns Google adapter Given I have a ProviderRegistry with "google" API key set @@ -401,7 +424,7 @@ Feature: Provider Registry Coverage And CLEVERAGENTS_DEFAULT_PROVIDER is not set And CLEVERAGENTS_DEFAULT_MODEL is not set When I create an AI provider without specifying a provider - Then the provider registry AI provider should be a LangChainChatProvider + Then the provider registry AI provider should be an OpenAIChatProvider And the provider registry AI provider name should be "openai" @@ -414,11 +437,11 @@ Feature: Provider Registry Coverage @unit @providers @registry Scenario: AI provider exposes working llm factory - Given I have a ProviderRegistry with OpenAI API key set + Given I have a ProviderRegistry with "groq" API key set And the provider registry LLM factory is stubbed - When I create an AI provider for provider "openai" - And I call the AI provider llm factory with model "gpt-mini-cover" - Then the stubbed LLM factory should be called for ProviderType.OPENAI with model "gpt-mini-cover" + When I create an AI provider for provider "groq" + And I call the AI provider llm factory with model "llama-mini-cover" + Then the stubbed LLM factory should be called for ProviderType.GROQ with model "llama-mini-cover" @unit @providers @registry Scenario: ProviderCapabilities is immutable diff --git a/features/steps/provider_registry_steps.py b/features/steps/provider_registry_steps.py index 5b4cbaa3d..4f97c32d7 100644 --- a/features/steps/provider_registry_steps.py +++ b/features/steps/provider_registry_steps.py @@ -9,8 +9,10 @@ from unittest.mock import MagicMock from behave import given, then, when # type: ignore[import-untyped] +from cleveragents.providers.llm.anthropic_provider import AnthropicChatProvider from cleveragents.providers.llm.google_provider import GoogleChatProvider from cleveragents.providers.llm.langchain_chat_provider import LangChainChatProvider +from cleveragents.providers.llm.openai_provider import OpenAIChatProvider from cleveragents.providers.llm.openrouter_provider import OpenRouterChatProvider from cleveragents.providers.registry import ( ProviderCapabilities, @@ -781,6 +783,16 @@ def step_impl_ai_provider_is_langchain(context: Any) -> None: assert isinstance(context.ai_provider, LangChainChatProvider) +@then("the provider registry AI provider should be an OpenAIChatProvider") +def step_impl_ai_provider_is_openai(context: Any) -> None: + assert isinstance(context.ai_provider, OpenAIChatProvider) + + +@then("the provider registry AI provider should be an AnthropicChatProvider") +def step_impl_ai_provider_is_anthropic(context: Any) -> None: + assert isinstance(context.ai_provider, AnthropicChatProvider) + + @then("the provider registry AI provider should be a GoogleChatProvider") def step_impl_ai_provider_is_google(context: Any) -> None: assert isinstance(context.ai_provider, GoogleChatProvider) diff --git a/src/cleveragents/providers/llm/__init__.py b/src/cleveragents/providers/llm/__init__.py index e69de29bb..9a07b2bed 100644 --- a/src/cleveragents/providers/llm/__init__.py +++ b/src/cleveragents/providers/llm/__init__.py @@ -0,0 +1,17 @@ +"""LLM provider adapters for CleverAgents. + +Exports all concrete LangChain-backed provider classes so that consumers can +import them directly from the ``cleveragents.providers.llm`` subpackage. +""" + +from cleveragents.providers.llm.anthropic_provider import AnthropicChatProvider +from cleveragents.providers.llm.google_provider import GoogleChatProvider +from cleveragents.providers.llm.openai_provider import OpenAIChatProvider +from cleveragents.providers.llm.openrouter_provider import OpenRouterChatProvider + +__all__ = [ + "AnthropicChatProvider", + "GoogleChatProvider", + "OpenAIChatProvider", + "OpenRouterChatProvider", +] diff --git a/src/cleveragents/providers/registry.py b/src/cleveragents/providers/registry.py index 3723e9061..a7b04c0f1 100644 --- a/src/cleveragents/providers/registry.py +++ b/src/cleveragents/providers/registry.py @@ -600,6 +600,49 @@ class ProviderRegistry: provider_info.capabilities if provider_info else ProviderCapabilities() ) + if provider_type == ProviderType.OPENAI: + from cleveragents.providers.llm.openai_provider import OpenAIChatProvider + + key_attr = self.PROVIDER_KEY_ATTRS.get(provider_type) + api_key = getattr(self._settings, key_attr, None) if key_attr else None + if not api_key: + missing_env = ( + key_attr.upper() if key_attr else provider_type.value.upper() + ) + raise ValueError( + f"Provider {provider_type.value} is not configured. " + f"Please set the {missing_env} environment variable." + ) + + return OpenAIChatProvider( + api_key=api_key, + model=model_id or self.DEFAULT_MODELS.get(provider_type, "gpt-4o"), + max_retries=max_retries, + ) + + if provider_type == ProviderType.ANTHROPIC: + from cleveragents.providers.llm.anthropic_provider import ( + AnthropicChatProvider, + ) + + key_attr = self.PROVIDER_KEY_ATTRS.get(provider_type) + api_key = getattr(self._settings, key_attr, None) if key_attr else None + if not api_key: + missing_env = ( + key_attr.upper() if key_attr else provider_type.value.upper() + ) + raise ValueError( + f"Provider {provider_type.value} is not configured. " + f"Please set the {missing_env} environment variable." + ) + + return AnthropicChatProvider( + api_key=api_key, + model=model_id + or self.DEFAULT_MODELS.get(provider_type, "claude-sonnet-4-20250514"), + max_retries=max_retries, + ) + if provider_type == ProviderType.GOOGLE: from cleveragents.providers.llm.google_provider import GoogleChatProvider