ef.embedders¶
The Embedder facade — ef’s embed layer (L3).
An embedder is, fundamentally, a batch callable:
Iterable[str] -> ndarray of shape (n, dim)
Everything else — batching, async/batch-APIs, normalization, task hints, caching, retries — is layered scaffolding over that one contract.
This module defines the small, structural core of the facade:
Embedder— the@runtime_checkableprotocol (a callable plus four introspectable metadata attributes). It is a protocol, not an ABC: any object with the right shape is an embedder.BatchHandle/ready_handle()— a uniform poll/result shape that collapses synchronous embedding and provider Batch APIs into one thing.BaseEmbedder— an implementation convenience base (supplies a default synchronousembed_batch()and arepr). Subclassing it is optional; satisfyingEmbedderstructurally is what matters.FunctionEmbedder— wraps a baretexts -> vectorscallable into a fullEmbedder(the bridge for e.g.imbed’s embedder functions).HashingEmbedder— a dependency-free embedder (the feature-hashing trick, numpy only);ef’s zero-install default when no embedder is given.cache_key()— a deterministic key pinning everything that changes a vector, forCachedEmbedder.embed_length_sorted()— length-sorted batching with order reconstruction; a 5–10× throughput win thatefowns so users never have to think about padding or sort order.
Composition wrappers live in ef.embedder_wrappers; concrete provider /
local adapters and the as_embedder() dependency
-injection seam live in ef.embedder_adapters.
Example — wrap a plain function and embed two strings:
>>> import numpy as np
>>> def toy(texts): # a bare callable: list[str] -> array(n, 3)
... return np.array([[len(t), t.count(' '), 1.0] for t in texts])
>>> embedder = FunctionEmbedder(toy, model_id='toy@3')
>>> vectors = embedder(['hello', 'a b c'])
>>> vectors.shape
(2, 3)
>>> embedder.dim # inferred on first call
3
>>> isinstance(embedder, Embedder)
True
Module Attributes
Canonical embedding-task vocabulary. |
|
The lifecycle states of a |
Functions
|
Wrap an already-computed array in a finished |
|
Deterministic cache key for one |
|
Encode |
Classes
|
A poll/result handle over an in-flight (or already-finished) embed job. |
|
Structural type for an embedder: a batch callable plus metadata. |
Implementation base for embedders (optional — |
|
|
Promote a bare |
|
A dependency-free embedder — the feature-hashing trick, numpy only. |
Exceptions
Raised for embedder-specific failures (shape mismatch, batch job failure). |
- class ef.embedders.BaseEmbedder[source]¶
Bases:
objectImplementation base for embedders (optional —
Embedderis structural).Subclasses set
model_id/dim/normalized/honored_input_typesand implement__call__; they then satisfyEmbedderstructurally. In return they get:a default synchronous
embed_batch()(ready_handle(self(...))) — override it only for a genuinely async provider Batch API;a readable
repr.
- class ef.embedders.BatchHandle(poll, result, cancel=<function BatchHandle.<lambda>>)[source]¶
Bases:
objectA poll/result handle over an in-flight (or already-finished) embed job.
Batch-and-poll is just a special case of async. A synchronous backend returns a ready handle (via
ready_handle()); a provider Batch API returns a genuinely pending one. Either way the caller sees one shape.- poll¶
() -> BatchStatus— non-blocking status check.
- result¶
() -> np.ndarray— blocks until done, returns(n, dim).
- cancel¶
() -> None— best-effort cancellation.
- ef.embedders.BatchStatus¶
The lifecycle states of a
BatchHandle.alias of
Literal[‘pending’, ‘done’, ‘failed’]
- class ef.embedders.Embedder(*args, **kwargs)[source]¶
Bases:
ProtocolStructural type for an embedder: a batch callable plus metadata.
An embedder maps
Iterable[str]to anndarrayof shape(n, dim). The four metadata attributes are the single source of truth *on the object itself* — no global registry, no config singleton.- model_id¶
Identity that bakes in everything affecting the vector —
"openai:text-embedding-3-large@1024"(provider:model@dim). Works directly as a cache namespace.
- dim¶
Output dimensionality. May be
Noneuntil first call for lazily-probed embedders (e.g. a bare callable of unknown width).
- normalized¶
Trueiff||v|| == 1by construction.
- honored_input_types¶
The
InputTypevalues this embedder acts on (empty if it ignores the task hint).
- embed_batch(texts, *, input_type=None, **backend)[source]¶
Submit an embed job, returning a
BatchHandle.- Return type:
- exception ef.embedders.EmbedderError[source]¶
Bases:
ExceptionRaised for embedder-specific failures (shape mismatch, batch job failure).
- class ef.embedders.FunctionEmbedder(func, *, model_id=None, dim=None, normalized=False, honored_input_types=(), pass_backend=True)[source]¶
Bases:
BaseEmbedderPromote a bare
texts -> vectorscallable to a fullEmbedder.This is the bridge for embedder functions that carry no metadata — most importantly
imbed’s registered embedders. The wrapped callable is asked only forfunc(texts)(it knows nothing ofinput_type); the wrapper advertiseshonored_input_types=()accordingly.- Parameters:
func (
Callable[...,Any]) – A callable mapping a list of strings to an array-like of shape(n, dim)(a numpy array or a nested sequence — both accepted).model_id (
str|None) – Vector-identity string. Defaults to"function:<name>".dim (
int|None) – Output width. IfNoneit is inferred (and frozen) on the first call.normalized (
bool) – Whetherfuncalready returns unit vectors.honored_input_types (
Iterable[Literal['query','document','classification','clustering']]) – Task hintsfunchonors (usually none).pass_backend (
bool) – IfTrue(default), forward any**backendkwargs tofunc; setFalsefor callables with a strict(texts,)signature.
>>> import numpy as np >>> e = FunctionEmbedder(lambda ts: np.ones((len(ts), 4)), model_id='ones@4') >>> e(['a', 'b', 'c']).shape (3, 4)
- class ef.embedders.HashingEmbedder(*, dim=512, ngram_range=(1, 2), sublinear_tf=True)[source]¶
Bases:
BaseEmbedderA dependency-free embedder — the feature-hashing trick, numpy only.
HashingEmbedderisef’s zero-install default: the embedderingest()resolves to when none is given, so the headlineingest([...]).search(query)works on a barepip install ef— no torch, no model download, no network.It is a lexical embedder, not a neural one. Each text is tokenized into lowercased word n-grams; every token is hashed into one of
dimbuckets with a sign (the hashing trick — no vocabulary, no fitting, fixed memory); the signed, sublinear-tf-weighted bucket counts are L2-normalized. The cosine similarity of two vectors then ranks by shared vocabulary — enough for demos, doctests and offline tests, and a sane fallback in production. For genuine semantic quality install a real embedder (ef[sentence-transformers],ef[openai]) and pass it explicitly.Every parameter that changes a vector is baked into
model_id, so aHashingEmbedder’s artifacts stay correctly content-addressed in theArtifactGraph.- Parameters:
dim (
int) – Output width. Wider → fewer hash collisions, larger vectors.ngram_range (
tuple[int,int]) – Inclusive(min, max)word-n-gram sizes. The default(1, 2)mixes single words with adjacent pairs — a little phrase sensitivity at no real cost.sublinear_tf (
bool) – Weight a token by1 + log(count)rather than its rawcount, damping a word repeated many times in one text.
>>> e = HashingEmbedder() >>> e.model_id 'hashing:v1@512' >>> import numpy as np >>> v = e(['the quick brown fox']) >>> v.shape (1, 512) >>> bool(np.isclose(np.linalg.norm(v[0]), 1.0)) # rows are L2-normalized True >>> a, b = e(['hello world']), e(['hello world']) >>> round(float(a[0] @ b[0]), 5) # identical text → cosine similarity 1.0 1.0 >>> isinstance(e, Embedder) True
- ef.embedders.InputType¶
Canonical embedding-task vocabulary. Every provider names the same distinction differently (
input_type/task_type/task/prompt_name);efdefines one canonical set and each adapter translates it at its boundary. This is the facade’s single most valuable normalization. Backends that ignore the hint advertise an emptyhonored_input_types.alias of
Literal[‘query’, ‘document’, ‘classification’, ‘clustering’]
- ef.embedders.cache_key(embedder, text, input_type=None)[source]¶
Deterministic cache key for one
(embedder, text, input_type)triple.The key pins everything that changes the resulting vector.
model_idis the embedder-identity SSOT — it already bakes in provider, model anddim— sodimis not keyed separately (keying it would also breakFunctionEmbedder’s lazydiminference).normalizedis keyed because aNormalizingEmbeddershares its inner’smodel_idyet yields different vectors.Returns
"<model_id>/<sha256>"— a namespaced key safe for anyMutableMappingstore.- Return type:
>>> class _E: ... model_id, normalized = 'm@8', False >>> k1 = cache_key(_E(), 'hello', 'document') >>> k2 = cache_key(_E(), 'hello', 'query') >>> k1 == k2 # input_type participates False >>> k1.startswith('m@8/') True
- ef.embedders.embed_length_sorted(texts, encode, *, batch_size=128)[source]¶
Encode
textsin length-sorted batches; return vectors in caller order.Transformer self-attention is O(n²) in sequence length and a batch is padded to its longest member, so grouping similar-length texts together avoids wasted compute — a 5–10× speedup on skewed corpora.
efowns this so callers never sort, pad, or un-sort themselves.- Parameters:
- Return type:
ndarray- Returns:
A
float32array of shape(len(texts), dim)in the original order oftexts.
>>> import numpy as np >>> def enc(batch): # toy encoder: vector = [length] ... return np.array([[len(t)] for t in batch], dtype=np.float32) >>> out = embed_length_sorted(['aaa', 'a', 'aaaaa', 'aa'], enc, batch_size=2) >>> out.ravel().tolist() # order preserved despite internal sorting [3.0, 1.0, 5.0, 2.0]
- ef.embedders.ready_handle(array)[source]¶
Wrap an already-computed array in a finished
BatchHandle.- Return type:
>>> import numpy as np >>> h = ready_handle(np.zeros((2, 3))) >>> h.poll() 'done' >>> h.result().shape (2, 3)