Model providers: OpenAI/xAI/Ollama + run several at once (registry)
Extends the #215 abstraction: - OpenAICompatibleLLMProvider / OpenAICompatibleEmbeddingProvider — one impl (via the official openai SDK) covers OpenAI, xAI (api.x.ai/v1), Ollama (…:11434/v1), OpenRouter, etc.; they differ only by base_url, key, and model. - Registry factory: build_llm_providers() / build_embedding_providers() return every provider whose credentials are configured, so you can run several concurrently. get_llm_provider(name)/get_embedding_provider(name) select by name, falling back to default_*_provider, then Null. - Per-provider env config (ANTHROPIC_*, OPENAI_*, XAI_*, OLLAMA_*) + DEFAULT_LLM_PROVIDER / DEFAULT_EMBEDDING_PROVIDER; documented in .env.example. Defaults keep AI off (empty registry). Embeddings now have real backends (OpenAI/Ollama), still separate from the LLM since Anthropic offers no embeddings endpoint. Tests cover multi-provider selection, default resolution, disabled-without-credentials, and null fail-loud. Full suite 87 passed. Relates to #215. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Justin Paul <justin@jpaul.me>
This commit is contained in:
+53
-13
@@ -71,26 +71,66 @@ def get_objectstore() -> ObjectStore:
|
||||
ObjectStoreDep = Annotated[ObjectStore, Depends(get_objectstore)]
|
||||
|
||||
|
||||
def get_llm_provider() -> LLMProvider:
|
||||
settings = get_settings()
|
||||
if settings.model_provider == "anthropic" and settings.anthropic_api_key:
|
||||
from app.integrations.models.anthropic_provider import AnthropicLLMProvider
|
||||
def build_llm_providers() -> dict[str, LLMProvider]:
|
||||
"""Every LLM provider whose credentials are configured, keyed by name. Run
|
||||
several at once; pick one with get_llm_provider(name)."""
|
||||
from app.integrations.models.anthropic_provider import AnthropicLLMProvider
|
||||
from app.integrations.models.openai_compat import OpenAICompatibleLLMProvider
|
||||
|
||||
return AnthropicLLMProvider(
|
||||
api_key=settings.anthropic_api_key,
|
||||
model=settings.llm_model,
|
||||
max_tokens=settings.llm_max_tokens,
|
||||
s = get_settings()
|
||||
providers: dict[str, LLMProvider] = {}
|
||||
if s.anthropic_api_key:
|
||||
providers["anthropic"] = AnthropicLLMProvider(
|
||||
api_key=s.anthropic_api_key, model=s.anthropic_model, max_tokens=s.llm_max_tokens
|
||||
)
|
||||
return NullLLMProvider()
|
||||
if s.openai_api_key:
|
||||
providers["openai"] = OpenAICompatibleLLMProvider(
|
||||
api_key=s.openai_api_key, base_url=s.openai_base_url, model=s.openai_model,
|
||||
max_tokens=s.llm_max_tokens,
|
||||
)
|
||||
if s.xai_api_key:
|
||||
providers["xai"] = OpenAICompatibleLLMProvider(
|
||||
api_key=s.xai_api_key, base_url=s.xai_base_url, model=s.xai_model,
|
||||
max_tokens=s.llm_max_tokens,
|
||||
)
|
||||
if s.ollama_enabled:
|
||||
providers["ollama"] = OpenAICompatibleLLMProvider(
|
||||
api_key=None, base_url=s.ollama_base_url, model=s.ollama_model,
|
||||
max_tokens=s.llm_max_tokens,
|
||||
)
|
||||
return providers
|
||||
|
||||
|
||||
def get_llm_provider(name: str | None = None) -> LLMProvider:
|
||||
"""The named LLM provider, or the configured default, or Null if unconfigured."""
|
||||
providers = build_llm_providers()
|
||||
return providers.get(name or get_settings().default_llm_provider) or NullLLMProvider()
|
||||
|
||||
|
||||
LLMProviderDep = Annotated[LLMProvider, Depends(get_llm_provider)]
|
||||
|
||||
|
||||
def get_embedding_provider() -> EmbeddingProvider:
|
||||
# Only the null provider exists today; concrete embedders (Ollama/Voyage)
|
||||
# implement the same interface and are selected here by settings.embedding_provider.
|
||||
return NullEmbeddingProvider()
|
||||
def build_embedding_providers() -> dict[str, EmbeddingProvider]:
|
||||
from app.integrations.models.openai_compat import OpenAICompatibleEmbeddingProvider
|
||||
|
||||
s = get_settings()
|
||||
providers: dict[str, EmbeddingProvider] = {}
|
||||
if s.openai_api_key:
|
||||
providers["openai"] = OpenAICompatibleEmbeddingProvider(
|
||||
api_key=s.openai_api_key, base_url=s.openai_base_url,
|
||||
model=s.openai_embedding_model, dimensions=s.embedding_dimensions,
|
||||
)
|
||||
if s.ollama_enabled:
|
||||
providers["ollama"] = OpenAICompatibleEmbeddingProvider(
|
||||
api_key=None, base_url=s.ollama_base_url,
|
||||
model=s.ollama_embedding_model, dimensions=s.embedding_dimensions,
|
||||
)
|
||||
return providers
|
||||
|
||||
|
||||
def get_embedding_provider(name: str | None = None) -> EmbeddingProvider:
|
||||
providers = build_embedding_providers()
|
||||
return providers.get(name or get_settings().default_embedding_provider) or NullEmbeddingProvider()
|
||||
|
||||
|
||||
EmbeddingProviderDep = Annotated[EmbeddingProvider, Depends(get_embedding_provider)]
|
||||
|
||||
Reference in New Issue
Block a user