diff --git a/features/llm_provider_subpackage_exports.feature b/features/llm_provider_subpackage_exports.feature new file mode 100644 index 000000000..49ec564ef --- /dev/null +++ b/features/llm_provider_subpackage_exports.feature @@ -0,0 +1,68 @@ +Feature: LLM provider subpackage exports + As a developer using the CleverAgents providers + I want to import LLM provider classes directly from cleveragents.providers.llm + So that I can use a consistent, package-level import path + + @unit @providers @llm + Scenario: LangChainChatProvider is importable from the llm subpackage + Given I import LangChainChatProvider from cleveragents.providers.llm + Then the imported LangChainChatProvider class should not be None + + @unit @providers @llm + Scenario: OpenAIChatProvider is importable from the llm subpackage + Given I import OpenAIChatProvider from cleveragents.providers.llm + Then the imported OpenAIChatProvider class should not be None + + @unit @providers @llm + Scenario: AnthropicChatProvider is importable from the llm subpackage + Given I import AnthropicChatProvider from cleveragents.providers.llm + Then the imported AnthropicChatProvider class should not be None + + @unit @providers @llm + Scenario: GoogleChatProvider is importable from the llm subpackage + Given I import GoogleChatProvider from cleveragents.providers.llm + Then the imported GoogleChatProvider class should not be None + + @unit @providers @llm + Scenario: OpenRouterChatProvider is importable from the llm subpackage + Given I import OpenRouterChatProvider from cleveragents.providers.llm + Then the imported OpenRouterChatProvider class should not be None + + @unit @providers @llm + Scenario: All provider classes are listed in __all__ + Given I inspect the cleveragents.providers.llm module + Then __all__ should contain "LangChainChatProvider" + And __all__ should contain "OpenAIChatProvider" + And __all__ should contain "AnthropicChatProvider" + And __all__ should contain "GoogleChatProvider" + And __all__ should contain "OpenRouterChatProvider" + + @unit @providers @llm + Scenario: Package-level import matches direct module import for OpenAIChatProvider + Given I import OpenAIChatProvider from cleveragents.providers.llm + And I import OpenAIChatProvider directly from cleveragents.providers.llm.openai_provider + Then both OpenAIChatProvider imports should refer to the same class + + @unit @providers @llm + Scenario: Package-level import matches direct module import for AnthropicChatProvider + Given I import AnthropicChatProvider from cleveragents.providers.llm + And I import AnthropicChatProvider directly from cleveragents.providers.llm.anthropic_provider + Then both AnthropicChatProvider imports should refer to the same class + + @unit @providers @llm + Scenario: Package-level import matches direct module import for GoogleChatProvider + Given I import GoogleChatProvider from cleveragents.providers.llm + And I import GoogleChatProvider directly from cleveragents.providers.llm.google_provider + Then both GoogleChatProvider imports should refer to the same class + + @unit @providers @llm + Scenario: Package-level import matches direct module import for OpenRouterChatProvider + Given I import OpenRouterChatProvider from cleveragents.providers.llm + And I import OpenRouterChatProvider directly from cleveragents.providers.llm.openrouter_provider + Then both OpenRouterChatProvider imports should refer to the same class + + @unit @providers @llm + Scenario: Package-level import matches direct module import for LangChainChatProvider + Given I import LangChainChatProvider from cleveragents.providers.llm + And I import LangChainChatProvider directly from cleveragents.providers.llm.langchain_chat_provider + Then both LangChainChatProvider imports should refer to the same class diff --git a/features/steps/llm_provider_subpackage_exports_steps.py b/features/steps/llm_provider_subpackage_exports_steps.py new file mode 100644 index 000000000..62b664ac8 --- /dev/null +++ b/features/steps/llm_provider_subpackage_exports_steps.py @@ -0,0 +1,177 @@ +"""Step definitions for llm_provider_subpackage_exports.feature.""" + +from __future__ import annotations + +import importlib + +from behave import given, then + +# --------------------------------------------------------------------------- +# Given steps — import from package level +# --------------------------------------------------------------------------- + + +@given("I import LangChainChatProvider from cleveragents.providers.llm") +def step_import_langchain_chat_provider(context): + from cleveragents.providers.llm import LangChainChatProvider + + context.pkg_LangChainChatProvider = LangChainChatProvider + + +@given("I import OpenAIChatProvider from cleveragents.providers.llm") +def step_import_openai_chat_provider(context): + from cleveragents.providers.llm import OpenAIChatProvider + + context.pkg_OpenAIChatProvider = OpenAIChatProvider + + +@given("I import AnthropicChatProvider from cleveragents.providers.llm") +def step_import_anthropic_chat_provider(context): + from cleveragents.providers.llm import AnthropicChatProvider + + context.pkg_AnthropicChatProvider = AnthropicChatProvider + + +@given("I import GoogleChatProvider from cleveragents.providers.llm") +def step_import_google_chat_provider(context): + from cleveragents.providers.llm import GoogleChatProvider + + context.pkg_GoogleChatProvider = GoogleChatProvider + + +@given("I import OpenRouterChatProvider from cleveragents.providers.llm") +def step_import_openrouter_chat_provider(context): + from cleveragents.providers.llm import OpenRouterChatProvider + + context.pkg_OpenRouterChatProvider = OpenRouterChatProvider + + +@given("I inspect the cleveragents.providers.llm module") +def step_inspect_llm_module(context): + context.llm_module = importlib.import_module("cleveragents.providers.llm") + + +# --------------------------------------------------------------------------- +# Given steps — direct module imports (for identity checks) +# --------------------------------------------------------------------------- + + +@given( + "I import OpenAIChatProvider directly from cleveragents.providers.llm.openai_provider" +) +def step_import_openai_direct(context): + from cleveragents.providers.llm.openai_provider import OpenAIChatProvider + + context.direct_OpenAIChatProvider = OpenAIChatProvider + + +@given( + "I import AnthropicChatProvider directly from cleveragents.providers.llm.anthropic_provider" +) +def step_import_anthropic_direct(context): + from cleveragents.providers.llm.anthropic_provider import AnthropicChatProvider + + context.direct_AnthropicChatProvider = AnthropicChatProvider + + +@given( + "I import GoogleChatProvider directly from cleveragents.providers.llm.google_provider" +) +def step_import_google_direct(context): + from cleveragents.providers.llm.google_provider import GoogleChatProvider + + context.direct_GoogleChatProvider = GoogleChatProvider + + +@given( + "I import OpenRouterChatProvider directly from cleveragents.providers.llm.openrouter_provider" +) +def step_import_openrouter_direct(context): + from cleveragents.providers.llm.openrouter_provider import OpenRouterChatProvider + + context.direct_OpenRouterChatProvider = OpenRouterChatProvider + + +@given( + "I import LangChainChatProvider directly from cleveragents.providers.llm.langchain_chat_provider" +) +def step_import_langchain_direct(context): + from cleveragents.providers.llm.langchain_chat_provider import LangChainChatProvider + + context.direct_LangChainChatProvider = LangChainChatProvider + + +# --------------------------------------------------------------------------- +# Then steps — assertions +# --------------------------------------------------------------------------- + + +@then("the imported LangChainChatProvider class should not be None") +def step_assert_langchain_not_none(context): + assert context.pkg_LangChainChatProvider is not None + + +@then("the imported OpenAIChatProvider class should not be None") +def step_assert_openai_not_none(context): + assert context.pkg_OpenAIChatProvider is not None + + +@then("the imported AnthropicChatProvider class should not be None") +def step_assert_anthropic_not_none(context): + assert context.pkg_AnthropicChatProvider is not None + + +@then("the imported GoogleChatProvider class should not be None") +def step_assert_google_not_none(context): + assert context.pkg_GoogleChatProvider is not None + + +@then("the imported OpenRouterChatProvider class should not be None") +def step_assert_openrouter_not_none(context): + assert context.pkg_OpenRouterChatProvider is not None + + +@then('__all__ should contain "{class_name}"') +def step_assert_all_contains(context, class_name): + module = context.llm_module + all_exports = getattr(module, "__all__", []) + assert class_name in all_exports, ( + f"Expected '{class_name}' in __all__, but __all__ = {all_exports!r}" + ) + + +@then("both OpenAIChatProvider imports should refer to the same class") +def step_assert_openai_same_class(context): + assert context.pkg_OpenAIChatProvider is context.direct_OpenAIChatProvider, ( + "Package-level OpenAIChatProvider is not the same object as the direct import" + ) + + +@then("both AnthropicChatProvider imports should refer to the same class") +def step_assert_anthropic_same_class(context): + assert context.pkg_AnthropicChatProvider is context.direct_AnthropicChatProvider, ( + "Package-level AnthropicChatProvider is not the same object as the direct import" + ) + + +@then("both GoogleChatProvider imports should refer to the same class") +def step_assert_google_same_class(context): + assert context.pkg_GoogleChatProvider is context.direct_GoogleChatProvider, ( + "Package-level GoogleChatProvider is not the same object as the direct import" + ) + + +@then("both OpenRouterChatProvider imports should refer to the same class") +def step_assert_openrouter_same_class(context): + assert ( + context.pkg_OpenRouterChatProvider is context.direct_OpenRouterChatProvider + ), ( + "Package-level OpenRouterChatProvider is not the same object as the direct import" + ) + + +@then("both LangChainChatProvider imports should refer to the same class") +def step_assert_langchain_same_class(context): + assert context.pkg_LangChainChatProvider is context.direct_LangChainChatProvider, ( + "Package-level LangChainChatProvider is not the same object as the direct import" + ) diff --git a/src/cleveragents/providers/llm/__init__.py b/src/cleveragents/providers/llm/__init__.py index 9a07b2bed..5de854dd8 100644 --- a/src/cleveragents/providers/llm/__init__.py +++ b/src/cleveragents/providers/llm/__init__.py @@ -2,16 +2,26 @@ Exports all concrete LangChain-backed provider classes so that consumers can import them directly from the ``cleveragents.providers.llm`` subpackage. + +Example +------- +>>> from cleveragents.providers.llm import OpenAIChatProvider +>>> OpenAIChatProvider # doctest: +ELLIPSIS + """ -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 +from __future__ import annotations + +from .anthropic_provider import AnthropicChatProvider +from .google_provider import GoogleChatProvider +from .langchain_chat_provider import LangChainChatProvider +from .openai_provider import OpenAIChatProvider +from .openrouter_provider import OpenRouterChatProvider __all__ = [ "AnthropicChatProvider", "GoogleChatProvider", + "LangChainChatProvider", "OpenAIChatProvider", "OpenRouterChatProvider", ]