7164b04019
CI / lint (push) Successful in 1m1s
CI / build (push) Successful in 53s
CI / quality (push) Successful in 1m23s
CI / typecheck (push) Successful in 1m24s
CI / benchmark-regression (push) Has been skipped
CI / security (push) Successful in 1m42s
CI / push-validation (push) Successful in 21s
CI / helm (push) Successful in 25s
CI / e2e_tests (push) Successful in 4m6s
CI / integration_tests (push) Successful in 4m12s
CI / unit_tests (push) Successful in 6m39s
CI / docker (push) Successful in 1m44s
CI / coverage (push) Successful in 10m51s
CI / status-check (push) Successful in 2s
CI / benchmark-publish (push) Successful in 1h17m10s
CI / benchmark-publish (pull_request) Has been skipped
CI / helm (pull_request) Successful in 33s
CI / build (pull_request) Successful in 50s
CI / lint (pull_request) Successful in 1m7s
CI / quality (pull_request) Successful in 1m9s
CI / typecheck (pull_request) Successful in 1m20s
CI / security (pull_request) Successful in 1m25s
CI / push-validation (pull_request) Successful in 19s
CI / integration_tests (pull_request) Successful in 3m20s
CI / e2e_tests (pull_request) Failing after 5m3s
CI / unit_tests (pull_request) Successful in 6m40s
CI / docker (pull_request) Successful in 1m30s
CI / coverage (pull_request) Successful in 11m9s
CI / status-check (pull_request) Failing after 3s
CI / benchmark-regression (pull_request) Failing after 1m11s
Extract _get_api_key() and _create_provider_instance() as a single internal factory that handles all provider types, including MOCK. Both create_llm() and create_ai_provider() now delegate to _create_provider_instance(), eliminating the duplicated if/elif provider-switching chains and the divergence between the two code paths. Adding a new provider now requires touching exactly one place. Key changes: - New _get_api_key(provider_type) consolidates API key lookup and validation that was previously duplicated across create_llm(), _create_provider_llm(), and every branch of create_ai_provider(). - New _create_provider_instance() is the unified raw-LLM factory, replacing _create_provider_llm() and the specialised adapter constructors in create_ai_provider(). MOCK is now handled here via FakeListLLM. - create_ai_provider() wraps all providers uniformly in LangChainChatProvider instead of using specialised adapters (OpenAIChatProvider, AnthropicChatProvider, GoogleChatProvider, OpenRouterChatProvider) for the four primary providers. - create_llm() drops its own API key validation block; validation now happens inside _create_provider_instance() via _get_api_key(). - All BDD scenarios updated: _create_provider_llm references become _create_provider_instance, and provider-type assertions on create_ai_provider results are updated to LangChainChatProvider. Review fixes (Cycle 2): - Add _coerce_env_bool() for consistent boolean env-var coercion in CLEVERAGENTS_ALLOW_MOCK_PROVIDER checks. - Introduce _ApiKeyMissing sentinel to distinguish 'not provided' from None in provider configuration. - Set MOCK supports_streaming=False in DEFAULT_CAPABILITIES to align with FakeListLLM semantics. - Make MOCK is_configured conditional on CLEVERAGENTS_ALLOW_MOCK_PROVIDER. - Use FakeListLLM(responses=['mock response'] * 10) to prevent IndexError in multi-step workflows. - Strip max_retries from kwargs before passing to LangChain constructors. - Extract _resolve_provider_type() helper shared by create_llm() and create_ai_provider() to reduce duplication and improve readability. - Ensure __api_key_sentinel flows through factory_kwargs even when api_key is None, avoiding double _get_api_key() calls. - Add CHANGELOG env-var documentation and providers.md env-var table. - Resolve template DB lock issues by persisting in-memory SQLite via VACUUM INTO instead of direct file creation on tmpfs. ISSUES CLOSED: #10949
289 lines
8.4 KiB
Markdown
289 lines
8.4 KiB
Markdown
# `cleveragents.providers` — AI Provider Registry
|
|
|
|
The `providers` package implements the AI provider registry that discovers,
|
|
selects, and instantiates LLM backends. It follows the provider plugin
|
|
architecture described in ADR-008 and wires into the actor system via
|
|
`AIProviderInterface`.
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
The registry discovers configured providers from environment variables and
|
|
`Settings`, selects a default using a configurable fallback chain, and
|
|
creates `LangChain` LLM instances or `AIProviderInterface` wrappers on
|
|
demand.
|
|
|
|
```python
|
|
from cleveragents.providers.registry import get_provider_registry
|
|
|
|
registry = get_provider_registry()
|
|
provider = registry.create_ai_provider() # uses default provider
|
|
llm = registry.create_llm() # returns a LangChain BaseLanguageModel
|
|
```
|
|
|
|
---
|
|
|
|
## `ProviderType`
|
|
|
|
```python
|
|
class ProviderType(StrEnum):
|
|
OPENAI = "openai"
|
|
ANTHROPIC = "anthropic"
|
|
GOOGLE = "google"
|
|
AZURE = "azure"
|
|
OPENROUTER = "openrouter"
|
|
GEMINI = "gemini"
|
|
COHERE = "cohere"
|
|
GROQ = "groq"
|
|
TOGETHER = "together"
|
|
MOCK = "mock"
|
|
```
|
|
|
|
String enum of all supported provider identifiers. Values are used in
|
|
`CLEVERAGENTS_DEFAULT_PROVIDER` and in actor YAML `provider:` fields.
|
|
|
|
---
|
|
|
|
## `ProviderCapabilities`
|
|
|
|
```python
|
|
@dataclass(frozen=True)
|
|
class ProviderCapabilities:
|
|
supports_streaming: bool = True
|
|
supports_tool_calls: bool = False
|
|
supports_vision: bool = False
|
|
max_context_length: int = 4096
|
|
supports_json_mode: bool = False
|
|
```
|
|
|
|
Immutable capability metadata for a provider. Used by the actor system to
|
|
gate features (e.g., tool calls, vision inputs) at runtime.
|
|
|
|
### Default capabilities by provider
|
|
|
|
| Provider | Streaming | Tool calls | Vision | Context (tokens) | JSON mode |
|
|
|----------|-----------|------------|--------|-----------------|-----------|
|
|
| OpenAI | ✓ | ✓ | ✓ | 128 000 | ✓ |
|
|
| Anthropic | ✓ | ✓ | ✓ | 200 000 | ✗ |
|
|
| Google / Gemini | ✓ | ✓ | ✓ | 1 000 000 | ✓ |
|
|
| Azure OpenAI | ✓ | ✓ | ✓ | 128 000 | ✓ |
|
|
| OpenRouter | ✓ | ✓ | ✓ | 128 000 | ✓ |
|
|
| Cohere | ✓ | ✓ | ✗ | 128 000 | ✗ |
|
|
| Groq | ✓ | ✓ | ✗ | 32 000 | ✓ |
|
|
| Together | ✓ | ✓ | ✗ | 32 000 | ✗ |
|
|
| Mock | ✗ | ✗ | ✗ | 4 096 | ✗ |
|
|
|
|
---
|
|
|
|
## `ProviderInfo`
|
|
|
|
```python
|
|
class ProviderInfo(BaseModel):
|
|
provider_type: ProviderType
|
|
name: str
|
|
api_key_env_var: str
|
|
default_model: str
|
|
capabilities: ProviderCapabilities
|
|
is_configured: bool
|
|
```
|
|
|
|
Read-only snapshot of a provider's registration state. Returned by
|
|
`ProviderRegistry.get_provider_info()` and `get_all_providers()`.
|
|
|
|
---
|
|
|
|
## `ProviderRegistry`
|
|
|
|
Central registry for discovering and managing AI providers.
|
|
|
|
```python
|
|
from cleveragents.providers.registry import ProviderRegistry
|
|
|
|
registry = ProviderRegistry() # uses global Settings
|
|
registry = ProviderRegistry(settings=my_cfg) # override settings
|
|
```
|
|
|
|
### Default models
|
|
|
|
| Provider | Default model |
|
|
|----------|--------------|
|
|
| OpenAI | `gpt-4o` |
|
|
| Anthropic | `claude-sonnet-4-20250514` |
|
|
| Google / Gemini | `gemini-2.0-flash` |
|
|
| Azure | `gpt-4o` |
|
|
| OpenRouter | `anthropic/claude-sonnet-4-20250514` |
|
|
| Cohere | `command-r-plus` |
|
|
| Groq | `llama-3.1-70b-versatile` |
|
|
| Together | `meta-llama/Llama-3.1-70B-Instruct-Turbo` |
|
|
| Mock | `mock-gpt` |
|
|
|
|
### Provider fallback order
|
|
|
|
When `CLEVERAGENTS_DEFAULT_PROVIDER` is not set, the registry selects the
|
|
first configured provider in this order:
|
|
|
|
`openai → anthropic → google → azure → openrouter → groq → together → cohere`
|
|
|
|
### Methods
|
|
|
|
#### `get_configured_providers() → list[ProviderInfo]`
|
|
|
|
Returns only providers that have valid API credentials configured.
|
|
|
|
#### `get_all_providers() → list[ProviderInfo]`
|
|
|
|
Returns all known providers regardless of configuration state.
|
|
|
|
#### `get_provider_info(provider_type) → ProviderInfo | None`
|
|
|
|
Look up a provider by `ProviderType` or string name. Returns `None` if
|
|
the provider is unknown.
|
|
|
|
#### `is_provider_configured(provider_type) → bool`
|
|
|
|
Returns `True` if the provider has a non-empty API key in `Settings`.
|
|
|
|
#### `get_default_provider_type() → ProviderType | None`
|
|
|
|
Resolves the default provider using this precedence:
|
|
|
|
1. `CLEVERAGENTS_DEFAULT_PROVIDER` environment variable
|
|
2. `Settings.default_provider`
|
|
3. First configured provider in fallback order
|
|
|
|
Returns `None` if no provider is configured.
|
|
|
|
#### `get_default_model(provider_type=None) → str | None`
|
|
|
|
Resolves the default model using this precedence:
|
|
|
|
1. `CLEVERAGENTS_DEFAULT_MODEL` environment variable
|
|
2. `Settings.default_model`
|
|
3. Provider's built-in default model
|
|
|
|
#### `create_llm(provider_type=None, model_id=None, **kwargs) → BaseLanguageModel`
|
|
|
|
Creates a LangChain `BaseLanguageModel` for the specified provider and model.
|
|
|
|
```python
|
|
llm = registry.create_llm("anthropic", model_id="claude-3-haiku-20240307")
|
|
```
|
|
|
|
Raises `ValueError` if no provider is configured or the provider name is
|
|
unknown.
|
|
|
|
#### `create_ai_provider(provider_type=None, model_id=None, max_retries=3) → AIProviderInterface`
|
|
|
|
Creates an `AIProviderInterface` implementation. **All** providers are now
|
|
wrapped uniformly in `LangChainChatProvider`; the raw LLM instantiation is
|
|
delegated to `_create_provider_instance()` internally. Previously, some
|
|
providers (Google, OpenRouter) returned specialised adapter classes, but the
|
|
unified factory now handles every provider type consistently.
|
|
|
|
```python
|
|
provider = registry.create_ai_provider("openai", model_id="gpt-4o-mini")
|
|
```
|
|
|
|
---
|
|
|
|
## `LangChainChatProvider`
|
|
|
|
**Module:** `cleveragents.providers.llm.langchain_chat_provider`
|
|
|
|
Wraps a LangChain chat model with the `AIProviderInterface` protocol and
|
|
wires it into `PlanGenerationGraph`.
|
|
|
|
```python
|
|
class LangChainChatProvider(AIProviderInterface):
|
|
def __init__(
|
|
self,
|
|
*,
|
|
name: str,
|
|
model_id: str,
|
|
llm_factory: Callable[[str], BaseLanguageModel],
|
|
max_retries: int = 3,
|
|
supports_streaming: bool = True,
|
|
progress_map: dict[str, int] | None = None,
|
|
) -> None: ...
|
|
|
|
@property
|
|
def name(self) -> str: ...
|
|
|
|
@name.setter
|
|
def name(self, value: str) -> None: ...
|
|
|
|
@property
|
|
def model_id(self) -> str: ...
|
|
|
|
@model_id.setter
|
|
def model_id(self, value: str) -> None: ...
|
|
```
|
|
|
|
> **Note (v3.7.0+):** `name` and `model_id` are now mutable properties with
|
|
> setters, fixing an `AttributeError` when `PlanService` attempted to resolve
|
|
> provider names after instantiation. (#1553)
|
|
|
|
---
|
|
|
|
## Module-level helpers
|
|
|
|
### `get_provider_registry(settings=None) → ProviderRegistry`
|
|
|
|
Returns the global singleton `ProviderRegistry`. Pass `settings` to force
|
|
a fresh instance (useful in tests).
|
|
|
|
### `reset_provider_registry() → None`
|
|
|
|
Clears the global singleton. Call this between tests to ensure clean state.
|
|
|
|
### `resolve_provider_by_name(name, settings=None) → AIProviderInterface`
|
|
|
|
Convenience function that resolves a provider by string name and raises a
|
|
descriptive `ValueError` if the provider is not configured.
|
|
|
|
```python
|
|
from cleveragents.providers.registry import resolve_provider_by_name
|
|
|
|
provider = resolve_provider_by_name("anthropic")
|
|
```
|
|
|
|
---
|
|
|
|
## Environment variables
|
|
|
|
| Variable | Description |
|
|
|----------|-------------|
|
|
| `CLEVERAGENTS_DEFAULT_PROVIDER` | Pin the global provider (e.g. `openai`) |
|
|
| `CLEVERAGENTS_DEFAULT_MODEL` | Pin the global model ID |
|
|
| `OPENAI_API_KEY` | OpenAI credentials |
|
|
| `ANTHROPIC_API_KEY` | Anthropic credentials |
|
|
| `GOOGLE_API_KEY` / `GOOGLE_GENAI_API_KEY` | Google credentials |
|
|
| `AZURE_OPENAI_API_KEY` | Azure OpenAI credentials |
|
|
| `AZURE_OPENAI_ENDPOINT` | Azure OpenAI endpoint URL |
|
|
| `AZURE_OPENAI_DEPLOYMENT` | Azure deployment name |
|
|
| `OPENROUTER_API_KEY` | OpenRouter credentials |
|
|
| `GEMINI_API_KEY` / `GOOGLE_GEMINI_API_KEY` | Gemini credentials |
|
|
| `COHERE_API_KEY` | Cohere credentials |
|
|
| `GROQ_API_KEY` | Groq credentials |
|
|
| `TOGETHER_API_KEY` | Together AI credentials |
|
|
| `CLEVERAGENTS_TESTING_USE_MOCK_AI` | Force mock provider in tests |
|
|
| `CLEVERAGENTS_ALLOW_MOCK_PROVIDER` | Enable MOCK provider for testing (set to true) |
|
|
|
|
---
|
|
|
|
## ASV Performance Benchmarks
|
|
|
|
The `benchmarks/` directory contains ASV (airspeed velocity) benchmark
|
|
suites for the providers module. Run them with:
|
|
|
|
```bash
|
|
nox -s benchmarks
|
|
# or directly:
|
|
asv run --config asv.conf.json
|
|
```
|
|
|
|
Benchmarks cover provider discovery, registry initialization, and LLM
|
|
factory creation latency. Results are tracked over time to detect
|
|
performance regressions.
|