From f44060e857cf7f152254c8fed021ee2b437059aa Mon Sep 17 00:00:00 2001 From: Jeffrey Phillips Freeman Date: Sun, 5 Apr 2026 18:00:27 +0000 Subject: [PATCH] fix(providers): wire OpenAIChatProvider and AnthropicChatProvider into ProviderRegistry Previously, ProviderRegistry.create_ai_provider() dispatched to dedicated provider classes only for Google and OpenRouter. For OpenAI and Anthropic, execution fell through to the generic LangChainChatProvider factory, leaving OpenAIChatProvider and AnthropicChatProvider as dead code in production. This commit: - Adds ProviderType.OPENAI dispatch branch in create_ai_provider() to instantiate OpenAIChatProvider with the configured API key and model - Adds ProviderType.ANTHROPIC dispatch branch in create_ai_provider() to instantiate AnthropicChatProvider with the configured API key and model - Both branches raise ValueError with the missing env var name when the API key is not configured, consistent with Google and OpenRouter branches - Updates src/cleveragents/providers/llm/__init__.py to export all four dedicated provider classes (OpenAIChatProvider, AnthropicChatProvider, GoogleChatProvider, OpenRouterChatProvider) - Updates provider_registry_coverage.feature to assert that create_ai_provider returns OpenAIChatProvider for openai and AnthropicChatProvider for anthropic - Adds new scenarios for Anthropic dispatch and missing-key error paths for both OpenAI and Anthropic - Updates the 'AI provider exposes working llm factory' scenario to use groq (which still goes through the generic LangChainChatProvider path) since OpenAI now uses the dedicated class - Adds step definitions for OpenAIChatProvider and AnthropicChatProvider isinstance assertions Closes #3427 --- features/provider_registry_coverage.feature | 35 ++++++++++++++--- features/steps/provider_registry_steps.py | 12 ++++++ src/cleveragents/providers/llm/__init__.py | 17 ++++++++ src/cleveragents/providers/registry.py | 43 +++++++++++++++++++++ 4 files changed, 101 insertions(+), 6 deletions(-) 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 -- 2.52.0