Files
provenance/backend/tests/test_model_providers.py
T
justin de50f2c803 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>
2026-06-09 18:39:19 -04:00

85 lines
3.3 KiB
Python

"""Model-provider registry: configure several vendors at once, select by name,
default selection, and the null fail-loud behavior. No network — we only assert
which provider the factory returns and that null providers raise.
"""
import pytest
from app.api.deps import (
build_embedding_providers,
build_llm_providers,
get_embedding_provider,
get_llm_provider,
)
from app.core.config import get_settings
from app.integrations.models.anthropic_provider import AnthropicLLMProvider
from app.integrations.models.base import ModelProviderNotConfigured
from app.integrations.models.null import NullEmbeddingProvider, NullLLMProvider
from app.integrations.models.openai_compat import (
OpenAICompatibleEmbeddingProvider,
OpenAICompatibleLLMProvider,
)
def _reset(monkeypatch):
s = get_settings()
for attr, val in {
"default_llm_provider": "null",
"default_embedding_provider": "null",
"anthropic_api_key": None,
"openai_api_key": None,
"xai_api_key": None,
"ollama_enabled": False,
}.items():
monkeypatch.setattr(s, attr, val)
return s
async def test_default_is_null_and_fails_loud(monkeypatch):
_reset(monkeypatch)
provider = get_llm_provider()
assert isinstance(provider, NullLLMProvider)
with pytest.raises(ModelProviderNotConfigured):
await provider.complete(prompt="hello")
assert isinstance(get_embedding_provider(), NullEmbeddingProvider)
async def test_multiple_llm_providers_at_once(monkeypatch):
s = _reset(monkeypatch)
monkeypatch.setattr(s, "anthropic_api_key", "sk-ant-x")
monkeypatch.setattr(s, "openai_api_key", "sk-openai-x")
monkeypatch.setattr(s, "xai_api_key", "xai-x")
monkeypatch.setattr(s, "ollama_enabled", True)
monkeypatch.setattr(s, "default_llm_provider", "anthropic")
registry = build_llm_providers()
assert set(registry) == {"anthropic", "openai", "xai", "ollama"}
# Select any by name.
assert isinstance(get_llm_provider("anthropic"), AnthropicLLMProvider)
assert isinstance(get_llm_provider("openai"), OpenAICompatibleLLMProvider)
assert isinstance(get_llm_provider("xai"), OpenAICompatibleLLMProvider)
assert isinstance(get_llm_provider("ollama"), OpenAICompatibleLLMProvider)
# Default resolves to the configured default.
assert isinstance(get_llm_provider(), AnthropicLLMProvider)
# Unknown name → null.
assert isinstance(get_llm_provider("nope"), NullLLMProvider)
async def test_provider_disabled_without_credentials(monkeypatch):
s = _reset(monkeypatch)
monkeypatch.setattr(s, "default_llm_provider", "openai") # default names openai…
# …but no openai key → registry empty → null fallback.
assert build_llm_providers() == {}
assert isinstance(get_llm_provider(), NullLLMProvider)
async def test_embedding_providers(monkeypatch):
s = _reset(monkeypatch)
monkeypatch.setattr(s, "openai_api_key", "sk-openai-x")
monkeypatch.setattr(s, "ollama_enabled", True)
monkeypatch.setattr(s, "default_embedding_provider", "openai")
registry = build_embedding_providers()
assert set(registry) == {"openai", "ollama"}
assert isinstance(get_embedding_provider(), OpenAICompatibleEmbeddingProvider)
assert isinstance(get_embedding_provider("ollama"), OpenAICompatibleEmbeddingProvider)