FAQ & Known Issues

On this page

Which embedder should I use?

This can't be answered generically without knowing your constraints, but purely from what the source supports:

  • No API key, no network dependency, Arabic-focused → ArabicEmbedder.
  • No API key, no network dependency, general multilingual → HuggingFaceEmbedder.
  • Hosted, highest quality, willing to pay → OpenAIEmbedder (text-embedding-3-large) or GeminiEmbedder (gemini-embedding-001, 3072-dim by default).
  • Hosted, cost-sensitive → MistralEmbedder (mistral-embed, $0.10/1M tokens per MODEL_SPECS) or OpenAIEmbedder with text-embedding-3-small ($0.02/1M tokens).
  • Local model server, want to swap models without redeploying code → OllamaEmbedder.

Do all embedders return the same output shape?

Mostly. encode() on a single string returns a 1D array on every embedder except OpenAIEmbedder, GeminiEmbedder, and MistralEmbedder, which always return a 2D array of shape (1, dim) even for single-string input (their encode() wraps the string in a list internally and never unwraps the result). ArabicEmbedder, HuggingFaceEmbedder, and OllamaEmbedder explicitly return a 1D array for single-string input. Check the "Returns" section on each class's API reference page before assuming shape.

Known issues found in source

These are documented here rather than silently worked around, since this documentation is meant to reflect the implementation as written.

  1. __all__ references an undefined name. embeddings/__init__.py includes "EmbedderMode" in __all__, but no such class/function/constant exists anywhere in the package. from embeddings import * will raise ImportError: cannot import name 'EmbedderMode'. Import the specific classes you need instead (e.g. from embeddings import OpenAIEmbedder).

  2. A broken exception-chaining line in ArabicEmbedder._load_model(). The fallback branch when a model fails to load and auto_download=False reads:

    Python
    raise   Exception.add_note("the model is not available locally and auto_download is disabled ") from e
    

    Exception.add_note is an unbound instance method being called on the class itself with a string argument, which does not construct or raise a new exception the way the surrounding code appears to intend. This is very likely to raise a TypeError at that call site instead of a clear message about auto-download being disabled, when that specific failure path is hit.

  3. BaseEmbedder.encode_with_cache() is effectively dead code for every current subclass. It implements a generic MD5-keyed caching layer, but none of the six concrete embedders call it from within their own encode(); each implements its own separate caching logic instead (see Architecture → Execution flow). Calling embedder.encode_with_cache(...) directly still works as documented — it just isn't used internally.

  4. Filename typo. The HuggingFaceEmbedder class lives in a file named hugginface_embedder.py (missing an h), not huggingface_embedder.py. This doesn't affect from embeddings import HuggingFaceEmbedder since __init__.py re-exports it, but it will matter if you ever import the submodule path directly.

  5. Return-value inconsistency for single-item stats. BaseEmbedder.get_stats() branches on whether self._stats is a dict or a dataclass (to support HuggingFaceEmbedder, which replaces self._stats with an EmbeddingStats dataclass instance rather than the dict BaseEmbedder.__init__ creates). This works correctly today but means self._stats's type is not uniform across the class hierarchy — anything overriding _stats on a subclass needs to either use a dict with the same five keys or expose a .to_dict() method, or get_stats()'s vars(self._stats).copy() fallback will be used instead.

Is this thread-safe? Async-safe?

From reading the source:

  • Local model embedders (ArabicEmbedder, HuggingFaceEmbedder) delegate to sentence_transformers.SentenceTransformer.encode(), which is not documented as thread-safe in this codebase — no locks are taken around self.model.encode(...) calls here.
  • MistralEmbedder and OpenAIEmbedder's rate limiters (MistralRateLimiter, RateLimiter) explicitly use threading.RLock, making the rate-limiting bookkeeping itself thread-safe. The rest of each class (cache dict/OrderedDict mutation) is not additionally locked.
  • No embedder class uses asyncio internally in its own encode(). Async support is provided uniformly by BaseEmbedder.aencode() / aencode_with_cache() / abatch_similarity(), which just run the synchronous method via asyncio.to_thread. This makes async calls safe to await but does not make concurrent synchronous calls into the same embedder instance safe with respect to shared mutable state like self._cache or self._stats.

None of the classes document or test concurrent access explicitly, so treat any of the above as "as implemented," not as a guarantee.

Related pages