fix(core): harden LiteLLM provider configuration

Signed-off-by: phernandez <paul@basicmachines.co>
This commit is contained in:
phernandez
2026-05-28 18:38:48 -05:00
parent 2c8975e9bb
commit a758657537
5 changed files with 101 additions and 10 deletions
+5 -1
View File
@@ -245,7 +245,11 @@ class BasicMemoryConfig(BaseSettings):
)
semantic_embedding_dimensions: int | None = Field(
default=None,
description="Embedding vector dimensions. Auto-detected from provider if not set (384 for FastEmbed, 1536 for OpenAI).",
description=(
"Embedding vector dimensions. Uses provider defaults when unset "
"(384 for FastEmbed, 1536 for OpenAI and LiteLLM OpenAI default); "
"required for custom LiteLLM models."
),
)
# Trigger: full local rebuilds spend most of their time waiting behind shared
# embed flushes, not constructing vectors themselves.
@@ -107,8 +107,11 @@ def reset_embedding_provider_cache() -> None:
def create_embedding_provider(app_config: BasicMemoryConfig) -> EmbeddingProvider:
"""Create an embedding provider based on semantic config.
When semantic_embedding_dimensions is set in config, it overrides
the provider's default dimensions (384 for FastEmbed, 1536 for OpenAI).
When semantic_embedding_dimensions is set in config, it overrides the
provider's default dimensions (384 for FastEmbed, 1536 for OpenAI and
the LiteLLM OpenAI default). Custom LiteLLM models require an explicit
dimension because the vector table schema is created before the first
embedding response is available.
"""
cache_key = _provider_cache_key(app_config)
with _EMBEDDING_PROVIDER_CACHE_LOCK:
@@ -161,6 +164,15 @@ def create_embedding_provider(app_config: BasicMemoryConfig) -> EmbeddingProvide
model_name = app_config.semantic_embedding_model or "openai/text-embedding-3-small"
if model_name == "bge-small-en-v1.5":
model_name = "openai/text-embedding-3-small"
if (
app_config.semantic_embedding_dimensions is None
and model_name != "openai/text-embedding-3-small"
):
raise ValueError(
"semantic_embedding_dimensions must be set when "
"semantic_embedding_provider='litellm' uses a non-default model. "
f"Configured model: {model_name!r}."
)
provider = LiteLLMEmbeddingProvider(
model_name=model_name,
batch_size=app_config.semantic_embedding_batch_size,
@@ -15,6 +15,7 @@ from __future__ import annotations
import asyncio
import math
import os
from typing import Any
from basic_memory.repository.embedding_provider import EmbeddingProvider
@@ -45,6 +46,24 @@ def _default_input_types(model_name: str) -> tuple[str | None, str | None]:
return None, None
def _import_litellm() -> Any:
"""Import LiteLLM without letting its import-time dotenv hook read cwd secrets."""
# Constraint: LiteLLM 1.85.0 loads .env files at import time when
# LITELLM_MODE defaults to DEV. Basic Memory intentionally does not load
# arbitrary cwd .env files, so set the production mode before importing
# unless the caller already made an explicit LiteLLM choice.
os.environ.setdefault("LITELLM_MODE", "PRODUCTION")
try:
import litellm
except ImportError as exc:
raise SemanticDependenciesMissingError(
"litellm dependency is missing. Install with: pip install litellm"
) from exc
return litellm
class LiteLLMEmbeddingProvider(EmbeddingProvider):
"""Embedding provider backed by the litellm SDK."""
@@ -86,12 +105,7 @@ class LiteLLMEmbeddingProvider(EmbeddingProvider):
if not texts:
return []
try:
import litellm
except ImportError as exc:
raise SemanticDependenciesMissingError(
"litellm dependency is missing. Install with: pip install litellm"
) from exc
litellm = _import_litellm()
batches = [
texts[start : start + self.batch_size]