Adds the seven §10.2 hook-point modules + five env vars so Astrolabe Cloud can offload document processing to the external document-processor / embedding gateway. Purely additive: with every setting unset the server behaves exactly as today, so self-hosters are unaffected (Deck #92). Hook points (all default to current monolith behavior): - config: EMBEDDING_PROVIDER, INGEST_MODE, STATUS_BACKEND, COLLECTION_METADATA_SOURCE, FACT_EVENT_EMITTER (+ supporting settings), validated in Settings.__post_init__ (fail-fast STATUS_BACKEND=local with INGEST_MODE=external); shared canonical.py. - vector/payload_keys.py + acl_hash.py: cross-impl NAMESPACE/point_id (§2.2) and BLAKE2b-128 ACL hash (§11), pinned by fixtures shared with the document-processor repo. - embedding/gateway_client.py: OpenAI-compatible GatewayProvider authenticating via M2M OIDC client-credentials (separate realm); manual-only registry entry. - vector/collection_metadata.py: sentinel-point / API metadata source with env fallback. - vector/queue/: hexagonal ingest producer ports + memory/NATS adapters (Postgres seam); INGEST_MODE=external publishes mcp.ingest.requested.{tenant} instead of the in-memory stream and skips the in-process processor pool. The lifespan becomes a composition root across both deployment branches. - vector/queue/status.py: STATUS_BACKEND=bus subscriber feeding a StatusStore the vector-sync status endpoint reads. - admin/payload_backfill.py: POST /api/v1/admin/payload-backfill (admin scope); processor writes the new payload keys; query-side ACL pre-filter gated behind ACL_PREFILTER_ENABLED (default off). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
191 lines
6.9 KiB
Python
191 lines
6.9 KiB
Python
"""Provider registry and factory for auto-detection and instantiation."""
|
|
|
|
import logging
|
|
|
|
from ..config import get_settings
|
|
from .base import Provider
|
|
from .bedrock import BedrockProvider
|
|
from .mistral import MistralProvider
|
|
from .ollama import OllamaProvider
|
|
from .openai import OpenAIProvider
|
|
from .simple import SimpleProvider
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ProviderRegistry:
|
|
"""
|
|
Registry for provider auto-detection and instantiation.
|
|
|
|
Reads configuration via dynaconf-backed Settings (see ``config.py``).
|
|
Checks provider settings in priority order and creates the appropriate
|
|
provider:
|
|
|
|
1. Bedrock (``AWS_REGION`` or ``BEDROCK_*_MODEL``)
|
|
2. OpenAI (``OPENAI_API_KEY``)
|
|
3. Mistral (``MISTRAL_API_KEY``)
|
|
4. Ollama (``OLLAMA_BASE_URL``)
|
|
5. Simple (fallback for testing/development)
|
|
"""
|
|
|
|
@staticmethod
|
|
def create_provider() -> Provider:
|
|
"""
|
|
Auto-detect and create provider based on configured settings.
|
|
|
|
Settings are sourced via :func:`nextcloud_mcp_server.config.get_settings`,
|
|
which reads from settings files and environment variables (env vars
|
|
always win, see ADR-024/025).
|
|
|
|
Priority order:
|
|
|
|
1. Bedrock - if ``aws_region`` or ``bedrock_embedding_model`` is set
|
|
2. OpenAI - if ``openai_api_key`` is set
|
|
3. Mistral - if ``mistral_api_key`` is set
|
|
4. Ollama - if ``ollama_base_url`` is set
|
|
5. Simple - fallback for testing/development
|
|
|
|
Returns:
|
|
Provider instance
|
|
"""
|
|
settings = get_settings()
|
|
|
|
# 0. Gateway (manual-only; never autodetected). When
|
|
# EMBEDDING_PROVIDER=gateway, bypass the autodetect chain entirely
|
|
# and route embeddings through the Astrolabe Cloud embedding gateway
|
|
# (design §10.1/§10.2). Lazy import to avoid an import cycle
|
|
# (embedding/gateway_client → providers/openai → ... → registry).
|
|
if settings.embedding_provider == "gateway":
|
|
from ..embedding.gateway_client import ( # noqa: PLC0415
|
|
GatewayProvider,
|
|
GatewayTokenProvider,
|
|
)
|
|
|
|
# Settings.__post_init__ guarantees the URL is set when the
|
|
# provider is gateway; assert narrows the type for the checker.
|
|
assert settings.embedding_gateway_url is not None
|
|
# __post_init__ enforces all-or-nothing on the M2M creds, so
|
|
# checking one is enough to know the full triple is present.
|
|
token_provider = None
|
|
if settings.embedding_gateway_client_id:
|
|
assert settings.embedding_gateway_token_url is not None
|
|
assert settings.embedding_gateway_client_secret is not None
|
|
token_provider = GatewayTokenProvider(
|
|
token_url=settings.embedding_gateway_token_url,
|
|
client_id=settings.embedding_gateway_client_id,
|
|
client_secret=settings.embedding_gateway_client_secret,
|
|
scope=settings.embedding_gateway_scope,
|
|
)
|
|
logger.info(
|
|
"Using embedding gateway provider: url=%s, model=%s, auth=%s",
|
|
settings.embedding_gateway_url,
|
|
settings.embedding_gateway_model,
|
|
"oidc-m2m" if token_provider else "none",
|
|
)
|
|
return GatewayProvider(
|
|
base_url=settings.embedding_gateway_url,
|
|
embedding_model=settings.embedding_gateway_model,
|
|
token_provider=token_provider,
|
|
)
|
|
|
|
# 1. Bedrock
|
|
if (
|
|
settings.aws_region
|
|
or settings.bedrock_embedding_model
|
|
or settings.bedrock_generation_model
|
|
):
|
|
logger.info(
|
|
"Using Bedrock provider: region=%s, embedding_model=%s, "
|
|
"generation_model=%s",
|
|
settings.aws_region,
|
|
settings.bedrock_embedding_model,
|
|
settings.bedrock_generation_model,
|
|
)
|
|
return BedrockProvider(
|
|
region_name=settings.aws_region,
|
|
embedding_model=settings.bedrock_embedding_model,
|
|
generation_model=settings.bedrock_generation_model,
|
|
aws_access_key_id=settings.aws_access_key_id,
|
|
aws_secret_access_key=settings.aws_secret_access_key,
|
|
)
|
|
|
|
# 2. OpenAI
|
|
if settings.openai_api_key:
|
|
logger.info(
|
|
"Using OpenAI provider: base_url=%s, embedding_model=%s, "
|
|
"generation_model=%s",
|
|
settings.openai_base_url or "default",
|
|
settings.openai_embedding_model,
|
|
settings.openai_generation_model,
|
|
)
|
|
return OpenAIProvider(
|
|
api_key=settings.openai_api_key,
|
|
base_url=settings.openai_base_url,
|
|
embedding_model=settings.openai_embedding_model,
|
|
generation_model=settings.openai_generation_model,
|
|
)
|
|
|
|
# 3. Mistral
|
|
if settings.mistral_api_key:
|
|
logger.info(
|
|
"Using Mistral provider: base_url=%s, embedding_model=%s",
|
|
settings.mistral_base_url or "default",
|
|
settings.mistral_embedding_model,
|
|
)
|
|
return MistralProvider(
|
|
api_key=settings.mistral_api_key,
|
|
base_url=settings.mistral_base_url,
|
|
embedding_model=settings.mistral_embedding_model,
|
|
)
|
|
|
|
# 4. Ollama
|
|
if settings.ollama_base_url:
|
|
logger.info(
|
|
"Using Ollama provider: %s, embedding_model=%s, generation_model=%s",
|
|
settings.ollama_base_url,
|
|
settings.ollama_embedding_model,
|
|
settings.ollama_generation_model,
|
|
)
|
|
return OllamaProvider(
|
|
base_url=settings.ollama_base_url,
|
|
embedding_model=settings.ollama_embedding_model,
|
|
generation_model=settings.ollama_generation_model,
|
|
verify_ssl=settings.ollama_verify_ssl,
|
|
)
|
|
|
|
# 5. Simple (fallback)
|
|
logger.warning(
|
|
"No provider configured (AWS_REGION, OPENAI_API_KEY, "
|
|
"MISTRAL_API_KEY, OLLAMA_BASE_URL not set). "
|
|
"Using SimpleProvider for testing/development. "
|
|
"For production, configure Bedrock, OpenAI, Mistral, or Ollama."
|
|
)
|
|
return SimpleProvider(dimension=settings.simple_embedding_dimension)
|
|
|
|
|
|
# Singleton instance
|
|
_provider: Provider | None = None
|
|
|
|
|
|
def get_provider() -> Provider:
|
|
"""
|
|
Get singleton provider instance.
|
|
|
|
Returns:
|
|
Global Provider instance (auto-detected on first call)
|
|
"""
|
|
global _provider
|
|
if _provider is None:
|
|
_provider = ProviderRegistry.create_provider()
|
|
return _provider
|
|
|
|
|
|
def reset_provider():
|
|
"""
|
|
Reset singleton provider instance.
|
|
|
|
Useful for testing or reconfiguration.
|
|
"""
|
|
global _provider
|
|
_provider = None
|