RICS / app /embeddings /factory.py
StormShadow308's picture
feat: async pipeline, job queue, generation hardening, and docs
732b14f
Raw
History Blame Contribute Delete
3.58 kB
"""Factory that returns a LangChain-compatible Embeddings object.
This replaces the custom EmbeddingClient interface with the standard
LangChain ``langchain_core.embeddings.Embeddings`` protocol, enabling
seamless integration with LangChain vector stores, retrievers, and chains.
Default priority (with ``prefer_local_embeddings=True``):
1. HuggingFace β€” ``langchain_huggingface.HuggingFaceEmbeddings`` (free, on-device).
2. OpenAI fallback only if HuggingFace import fails AND an API key is present.
3. Fake β€” deterministic ``FakeEmbeddings`` for unit tests only.
Setting ``prefer_local_embeddings=False`` restores the legacy behaviour
(OpenAI first if a key is configured, HuggingFace as fallback).
"""
import logging
from langchain_core.embeddings import Embeddings
from app.config import settings
logger = logging.getLogger(__name__)
_instance: Embeddings | None = None
def _build_huggingface() -> Embeddings | None:
"""Try to construct a HuggingFace embeddings client, returning ``None`` on failure."""
try:
from langchain_huggingface import HuggingFaceEmbeddings
except ImportError:
logger.warning(
"langchain-huggingface not installed β€” cannot use local embeddings. "
"Run: pip install langchain-huggingface sentence-transformers"
)
return None
logger.info(
"Using LangChain HuggingFaceEmbeddings (model=%s, free/local)",
settings.local_embedding_model,
)
return HuggingFaceEmbeddings(
model_name=settings.local_embedding_model,
model_kwargs={"device": "cpu"},
encode_kwargs={"normalize_embeddings": True},
)
def _build_openai() -> Embeddings | None:
"""Try to construct an OpenAI embeddings client, returning ``None`` on failure."""
if not settings.openai_api_key:
return None
try:
from langchain_openai import OpenAIEmbeddings
except ImportError:
return None
logger.info("Using LangChain OpenAIEmbeddings (model=%s)", settings.embedding_model)
return OpenAIEmbeddings(
model=settings.embedding_model,
openai_api_key=settings.openai_api_key,
)
def get_embedding_client() -> Embeddings:
"""Return a singleton LangChain :class:`Embeddings` instance.
The client is constructed once and reused β€” critical for the
HuggingFace backend which loads a ~90 MB model at construction time.
Default order (``prefer_local_embeddings=True``):
1. :class:`langchain_huggingface.HuggingFaceEmbeddings` (free, local).
2. :class:`langchain_openai.OpenAIEmbeddings` (only if HF unavailable).
3. :class:`langchain_community.embeddings.FakeEmbeddings` as a test fallback.
Set ``prefer_local_embeddings=False`` to invert the first two.
Returns:
A configured LangChain :class:`Embeddings` object.
"""
global _instance
if _instance is not None:
return _instance
if getattr(settings, "prefer_local_embeddings", False):
_instance = _build_huggingface() or _build_openai()
else:
_instance = _build_openai() or _build_huggingface()
if _instance is None:
logger.warning(
"No embedding backend available β€” falling back to FakeEmbeddings (TEST ONLY)."
)
from langchain_community.embeddings import FakeEmbeddings
_instance = FakeEmbeddings(size=384)
return _instance
def reset_embedding_client() -> None:
"""Reset the cached singleton β€” useful for tests or hot config reloads."""
global _instance
_instance = None