Skip to Content
FeaturesCustom embeddings

Custom embedding providers

Pass embedding_provider= to MemoryEngine.create(), MemoryEngine.open() or MemoryClient(). A provider needs stable model_name, model_version, and positive integer dimension attributes, plus async embed(texts) returning one float vector per input in the same order. Its metadata determines the effective embedding configuration and the new index dimension; the caller’s config object is not modified. Storage rejects incompatible existing vectors rather than silently treating different model spaces as comparable.

Optionally implement async embed_query(text) returning one float vector. Both local and PostgreSQL search use it; ingestion, recovery preparation and rebuilds continue to use embed(texts) for documents. Providers without the optional method retain the same encoding and cache behavior for documents and queries. A query encoder failure remains a failed vector path; PRME does not silently substitute document encoding.

The distinction matters for models with different query/document recipes. FastEmbed’s retrieval guide  describes its separate methods. The BGE model card  describes query instructions for short-query passage retrieval. This runnable example makes that instruction explicit using the existing local model:

from prme import CachedEmbeddingProvider, MemoryClient from prme.storage.embedding import FastEmbedProvider class BGEWithQueryInstruction(FastEmbedProvider): @property def model_version(self) -> str: # Identify the document AND query recipe. Changing either requires # a new version, even when the underlying weights stay the same. return super().model_version + ":bge-query-instruction-v1" async def embed_query(self, text: str) -> list[float]: instruction = "Represent this sentence for searching relevant passages: " return (await self.embed([instruction + text]))[0] provider = CachedEmbeddingProvider(BGEWithQueryInstruction()) with MemoryClient("./custom_embedding_memory", embedding_provider=provider) as memory: memory.store("Aurora needs approval before deployment.", user_id="alice") response = memory.retrieve("Aurora deployment policy", user_id="alice") print(response.bundle.render())

This demonstrates the interface; it is not a benchmark-validated replacement for the default encoding. Existing defaults are unchanged. EmbeddingProvider and QueryEmbeddingProvider are importable protocols for annotations; inheritance is optional. CachedEmbeddingProvider is an optional LRU wrapper. It separates query/document entries when the underlying provider distinguishes them, preserves legacy shared caching otherwise, and returns independently owned vector lists. Provider metadata must remain stable for its lifetime; create a new provider and cache when switching models or preprocessing. Query cache errors do not store failed values.

PRME validates the complete response before admitting any batch to the cache: exactly one vector per input, the declared dimension, and finite numbers that fit float32 storage. Numeric strings, NaN/infinity, incorrect shapes and changed provider identity raise ValueError. A malformed batch admits no partial results, so a repaired provider can retry all failed inputs. Uncached encoding and both storage backends apply the same boundary checks. Earlier successful cache entries remain available. The checks cannot establish that a provider returned the right semantic vector or preserved input ordering; those remain provider contracts.

Built-in FastEmbed and OpenAI configuration infers the output dimension for registered model names when it is omitted. This metadata lookup does not load or download model weights. An unregistered model must declare its dimension explicitly; FastEmbedProvider enforces the same rule when constructed directly.

Supply the provider on every reopen. Its Python implementation, credentials and model resources are not serialized into the memory pack. Saved numerical vectors and identity metadata remain portable. Reusing a saved effective config whose provider is custom without supplying the object raises an explicit error. When changing encoding versions, rebuild indexes with the intended provider; changing dimensions can require backend/index migration and is not automatic. CLI commands cannot recreate an arbitrary Python provider; use the Python APIs for model-dependent operations on these packs.

The provider remains caller-owned: PRME neither closes its HTTP clients nor unloads its models. For MemoryClient, async methods execute on the client’s worker loop. Create loop-bound resources lazily there, or use the async engine when sharing an existing async client. Never reuse an HTTP client bound to a different event loop. Built-in factory behavior is unchanged when no provider is supplied.

Retrieval receipts report the actual provider/model/version/dimension and mark the use of embed_query. A version must cover weights, normalization, truncation, prefixes and both encoding tasks. Those are provider-reported identities, not independent verification that a remote service still uses the same weights.


Source: docs/CUSTOM-EMBEDDINGS.md  in PRME at 58caddb1.