ef.embedder_wrappers¶
Composition wrappers for Embedder.
Each wrapper takes an inner embedder and is itself an embedder — they compose
freely (CachedEmbedder(RetryingEmbedder(NormalizingEmbedder(inner)), store)).
This keeps the core protocol tiny: caching, retries, routing and normalization
are opt-in layers, not protocol obligations.
CachedEmbedder— memoize vectors in anyMutableMappingstore.RetryingEmbedder— exponential backoff + jitter, retryable-aware.MultiEmbedder— route texts to different embedders by a predicate.NormalizingEmbedder— L2-normalize an inner embedder’s output.InstructionEmbedder— prepend a per-input_typeinstruction (task-conditioned, asymmetric query/document embedding).
They delegate their metadata (model_id / dim / normalized /
honored_input_types) to the inner embedder, except where the wrapper changes
the vector (NormalizingEmbedder reports normalized=True;
InstructionEmbedder bakes its instructions into model_id and adds the
conditioned roles to honored_input_types).
The inner argument must already be an Embedder; use
ef.embedder_adapters.as_embedder() to coerce a string / callable first.
Example — cache an embedder so repeats are free:
>>> import numpy as np
>>> from ef.embedders import FunctionEmbedder
>>> calls = []
>>> def toy(texts):
... calls.extend(texts)
... return np.ones((len(texts), 3))
>>> cached = CachedEmbedder(FunctionEmbedder(toy, model_id='toy@3'), store={})
>>> _ = cached(['a', 'b'])
>>> _ = cached(['b', 'c']) # 'b' served from cache
>>> calls # 'b' embedded once
['a', 'b', 'c']
Classes
|
Memoize embeddings in a |
|
Exponential-backoff retry policy for |
|
Retry an inner embedder's calls on transient failures. |
|
Route each text to one of several embedders by a predicate. |
|
L2-normalize an inner embedder's output. |
|
Prepend a per- |
- class ef.embedder_wrappers.CachedEmbedder(inner, store, *, cache_queries=False)[source]¶
Bases:
BaseEmbedderMemoize embeddings in a
MutableMappingstore.On each call, texts already in the store are served from it; only the misses are forwarded to the inner embedder (in a single batch) and then written back. The store can be any
dol-style mapping — an in-RAMdict, a directory of.npyfiles, a key-value DB.Document embeddings are cached unconditionally; query embeddings are skipped by default (they are rarely repeated) — pass
cache_queries=Trueto cache them too.- Parameters:
inner (
Embedder) – The embedder to memoize.store (
MutableMapping[str,ndarray]) – AMutableMapping[str, ndarray]keyed bycache_key().cache_queries (
bool) – Whether to also cacheinput_type="query"calls.
- property honored_input_types: tuple[Literal['query', 'document', 'classification', 'clustering'], ...]¶
Built-in immutable sequence.
If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable’s items.
If the argument is a tuple, the return value is the same object.
- class ef.embedder_wrappers.InstructionEmbedder(inner, *, query_instruction=None, document_instruction=None, classification_instruction=None, clustering_instruction=None)[source]¶
Bases:
BaseEmbedderPrepend a per-
input_typeinstruction before delegating toinner.Instruction-tuned embedding models (E5-instruct, BGE, GTE, Qwen3-Embedding…) are trained to read a task description in front of the text, typically on the query side only, with documents embedded bare. That asymmetry is where the gain comes from: the query says what kind of relevance is wanted (topic vs. keyword), which a plain model cannot be told. This wrapper adds that to any embedder: a text embedded with
input_type="query"becomesquery_instruction + text, and so on per role. Roles without an instruction (and calls withinput_type=None) pass through unchanged.Only trust the prefix on a genuinely instruction-tuned model. On a plain model (e.g.
all-MiniLM-L6-v2) the prefix is expected to be a near no-op or noise: use that as the control arm of an experiment, not as an improvement.The instruction strings are used verbatim, so include the model’s expected separator (e.g.
"Instruct: ...\nQuery: "for Qwen3/E5-instruct,"Represent this sentence for searching relevant passages: "for BGE).Metadata:
model_idbakes in a digest of the instructions (they change the vector), soCachedEmbedderand content-addressed artifacts never mix vectors made with different instructions.honored_input_typesis the inner embedder’s plus every instructed role.input_typeis still forwarded toinner; a backend that also acts on it natively (Cohere, Voyage, Gemini, or a sentence-transformers adapter built withquery_prompt=) would then be conditioned twice, so use this wrapper for backends that ignore the hint. For sentence-transformers models prefer the adapter’s ownquery_prompt=.
- Parameters:
inner (
Embedder) – The embedder to condition.query_instruction (
str|None) – Prefix forinput_type="query".document_instruction (
str|None) – Prefix forinput_type="document"(usually none).classification_instruction (
str|None) – Prefix forinput_type="classification".clustering_instruction (
str|None) – Prefix forinput_type="clustering".
>>> import numpy as np >>> from ef.embedders import FunctionEmbedder >>> seen = [] >>> def toy(texts): ... seen.extend(texts) ... return np.ones((len(texts), 2)) >>> base = FunctionEmbedder(toy, model_id='toy@2') >>> emb = InstructionEmbedder(base, query_instruction='Find packages about: ') >>> _ = emb(['graphs'], input_type='query') >>> _ = emb(['a DAG library'], input_type='document') >>> seen ['Find packages about: graphs', 'a DAG library'] >>> emb.honored_input_types ('query',) >>> emb.model_id.startswith('toy@2+instruct:') True- embed_batch(texts, *, input_type=None, **backend)[source]¶
Prefix, then use the inner embedder’s own (possibly async) batch API.
- class ef.embedder_wrappers.MultiEmbedder(routes, predicate, *, default=None)[source]¶
Bases:
BaseEmbedderRoute each text to one of several embedders by a predicate.
Useful for domain-specific models (e.g. a code embedder for code, a prose embedder for prose). All routes must share the same
dim— the output is a single(n, dim)array, with each row produced by its text’s route.- Parameters:
>>> import numpy as np >>> from ef.embedders import FunctionEmbedder >>> short = FunctionEmbedder(lambda ts: np.zeros((len(ts), 2)), model_id='s@2') >>> long = FunctionEmbedder(lambda ts: np.ones((len(ts), 2)), model_id='l@2') >>> m = MultiEmbedder({'s': short, 'l': long}, ... predicate=lambda t: 's' if len(t) < 4 else 'l') >>> m(['hi', 'hello']).tolist() [[0.0, 0.0], [1.0, 1.0]]
- class ef.embedder_wrappers.NormalizingEmbedder(inner)[source]¶
Bases:
BaseEmbedderL2-normalize an inner embedder’s output.
Use this for MRL-truncated vectors (which lose unit norm when sliced to a smaller dimension) or any backend that does not normalize by construction. Reports
normalized=True;cache_key()keys onnormalized, so a normalized and an un-normalized view of the same model never collide in a shared cache.- Parameters:
inner (
Embedder) – The embedder whose output to renormalize.
- property honored_input_types: tuple[Literal['query', 'document', 'classification', 'clustering'], ...]¶
Built-in immutable sequence.
If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable’s items.
If the argument is a tuple, the return value is the same object.
- class ef.embedder_wrappers.RetryPolicy(max_attempts=4, base_delay=1.0, max_delay=30.0, jitter=0.3, retry_on=<function _default_is_retryable>)[source]¶
Bases:
objectExponential-backoff retry policy for
RetryingEmbedder.- max_attempts¶
Total tries (the first call counts as attempt 1).
- base_delay¶
Seconds before the first retry; doubles each attempt.
- max_delay¶
Upper bound on any single backoff sleep.
- jitter¶
Random fraction (
0..1) of the delay added on top, to de-correlate concurrent clients.
- class ef.embedder_wrappers.RetryingEmbedder(inner, policy=None, *, sleep=<built-in function sleep>)[source]¶
Bases:
BaseEmbedderRetry an inner embedder’s calls on transient failures.
- Parameters:
inner (
Embedder) – The embedder to guard.policy (
RetryPolicy|None) – ARetryPolicy(sensible defaults if omitted).sleep (
Callable[[float],None]) – The sleep function — injectable so tests run instantly.
- property honored_input_types: tuple[Literal['query', 'document', 'classification', 'clustering'], ...]¶
Built-in immutable sequence.
If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable’s items.
If the argument is a tuple, the return value is the same object.