- Rewrites ai/__init__.py with _REGISTRY dict (10 entries) replacing the if/elif chain - get_provider() accepts ProviderConfig (not raw dict), raises ValueError on unknown - Resolves effective values: api_key fallback to "not-needed", model/base_url/context_chars from PROVIDER_DEFAULTS when config fields are empty/zero - SUPPORTS_JSON_MODE lookup sets supports_json_mode on GenericOpenAIProvider instances - AnthropicProvider instantiated without base_url (Plan 03 will widen the ctor) - provider_config.py: context_chars default changed to 0 (sentinel for PROVIDER_DEFAULTS resolution); 8000 default was inconsistent with O(1) factory pattern (Rule 1 fix)
108 lines
3.4 KiB
Python
108 lines
3.4 KiB
Python
"""ProviderConfig Pydantic model and per-provider defaults.
|
|
|
|
Loaded by get_provider() in ai/__init__.py; populated by load_provider_config()
|
|
in services/ai_config.py.
|
|
|
|
This file is a pure data module — it does NOT import any provider class.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from typing import Optional
|
|
|
|
from pydantic import BaseModel
|
|
|
|
|
|
class ProviderConfig(BaseModel):
|
|
"""Typed configuration for a single AI provider instance.
|
|
|
|
Fields:
|
|
provider_id: One of the keys in PROVIDER_DEFAULTS (e.g. "openai", "groq").
|
|
api_key: Decrypted API key; empty string for local providers (Ollama, LMStudio).
|
|
base_url: Override for the provider's base URL; None means use the default.
|
|
model: Model name; empty string means use the default from PROVIDER_DEFAULTS.
|
|
context_chars: Character budget for input truncation; 0 means use the default.
|
|
"""
|
|
|
|
model_config = {"extra": "forbid"}
|
|
|
|
provider_id: str
|
|
api_key: str = ""
|
|
base_url: Optional[str] = None
|
|
model: str = ""
|
|
context_chars: int = 0 # 0 means "unset — use PROVIDER_DEFAULTS in get_provider()"
|
|
|
|
|
|
# Named preset defaults for all 10 supported providers.
|
|
# Values are [ASSUMED] approximations based on well-known context window sizes.
|
|
# Admins can override all fields via the system_settings DB table (D-04).
|
|
PROVIDER_DEFAULTS: dict[str, dict] = {
|
|
"openai": {
|
|
"base_url": None,
|
|
"model": "gpt-4o",
|
|
"context_chars": 120_000,
|
|
},
|
|
"anthropic": {
|
|
"base_url": None,
|
|
"model": "claude-sonnet-4-6",
|
|
"context_chars": 180_000,
|
|
},
|
|
"gemini": {
|
|
"base_url": "https://generativelanguage.googleapis.com/v1beta/openai/",
|
|
"model": "gemini-2.0-flash",
|
|
"context_chars": 800_000,
|
|
},
|
|
"groq": {
|
|
"base_url": "https://api.groq.com/openai/v1",
|
|
"model": "llama-3.3-70b-versatile",
|
|
"context_chars": 128_000,
|
|
},
|
|
"xai": {
|
|
"base_url": "https://api.x.ai/v1",
|
|
"model": "grok-3-mini",
|
|
"context_chars": 128_000,
|
|
},
|
|
"deepseek": {
|
|
"base_url": "https://api.deepseek.com",
|
|
"model": "deepseek-chat",
|
|
"context_chars": 60_000,
|
|
},
|
|
"openrouter": {
|
|
"base_url": "https://openrouter.ai/api/v1",
|
|
"model": "anthropic/claude-3.5-sonnet",
|
|
"context_chars": 180_000,
|
|
},
|
|
"mistral": {
|
|
"base_url": "https://api.mistral.ai/v1",
|
|
"model": "mistral-large-latest",
|
|
"context_chars": 128_000,
|
|
},
|
|
"ollama": {
|
|
"base_url": "http://host.docker.internal:11434/v1",
|
|
"model": "llama3.2",
|
|
"context_chars": 8_000,
|
|
},
|
|
"lmstudio": {
|
|
"base_url": "http://host.docker.internal:1234/v1",
|
|
"model": "gemma-4-e4b-it",
|
|
"context_chars": 8_000,
|
|
},
|
|
}
|
|
|
|
# Whether the provider honours response_format={"type": "json_object"}.
|
|
# Gemini's OpenAI-compat endpoint does NOT support the string form (D-02/D-03).
|
|
# Ollama and LMStudio accept the parameter but some models ignore it — the
|
|
# GenericOpenAIProvider always wraps the raw response with parse_classification()
|
|
# regardless, so they are left as True (the parameter is still sent).
|
|
SUPPORTS_JSON_MODE: dict[str, bool] = {
|
|
"openai": True,
|
|
"anthropic": True,
|
|
"gemini": False,
|
|
"groq": True,
|
|
"xai": True,
|
|
"deepseek": True,
|
|
"openrouter": True,
|
|
"mistral": True,
|
|
"ollama": True,
|
|
"lmstudio": True,
|
|
}
|