Skip to main content

ef — Embedding Flow

A facade for boilerplate-less semantic search, corpus indexing, and RAG-plug-in readiness.

ef makes the modern embedding pipeline — corpus → segment → embed → vector store → retrieve — usable with progressive disclosure: the light case (a list of strings, search in one or two lines) and the heavy case (huge corpora, many segmentations and embedders, varied sources and vector DBs) share one facade. ef is not a RAG framework — it returns ranked context; you bring your own LLM.

import ef

index = ef.ingest(
    [
        "The cat sat on the mat",
        "Dogs are loyal companions",
        "Neural networks learn from data",
    ]
)

for hit in index.search("loyal dogs", limit=2):
    print(hit.score, hit.segment["text"])

That is the whole light path — no configuration, no install beyond ef itself. ingest returns a SearchableCorpus ready to search.

Installation

pip install ef                       # core: search, indexing, refresh, eval
pip install "ef[openai]"             # OpenAI embeddings
pip install "ef[sentence-transformers]"  # local sentence-transformers embeddings
pip install "ef[explore]"            # the L5 explore layer (UMAP, HDBSCAN)
pip install "ef[imbed]"              # imbed-backed components & cluster labelling

The core install needs only numpy (plus dol, i2, vd). The default embedder is dependency-free — feature hashing, lexical not semantic; for real semantic search pass a sentence-transformers or provider embedder (see below).

What ef is

ef is a facade, not a framework. It owns the schemas (Segment, Embedder, Segmenter, Corpus), the indexing core, refresh, and the RAG-plug-in surface — and it stops there: no agent loops, no prompt templating, no answer synthesis. "Bring your own LLM, your own agent framework, your own UI."

It is built on five layers, the same facade covering all of them:

L0 Sources    Corpus = MutableMapping[source_id, Source]   (dol store: fs/S3/API/RAM)
L1 Parse      pluggable text extraction
L2 Segment    Segmenter facade (chunkers)
L3 Embed      Embedder facade (provider / local adapters)
L4 Index      vd.Collection  (ef writes; vd owns the index)
L5 Derive     project / cluster / label   ("explore the corpus")
──────────────────────────────────────────────────────────────
   Search     search(query) -> ranked SearchHits
   RAG plug   retrieve(query) -> list[Segment]  handed to your LLM/agent

Choosing an embedder

ingest and SourceManager take an embedder= — a string, a callable, a URL, or an Embedder. The as_embedder seam normalizes all of them:

from ef import as_embedder, openai_embedder, sentence_transformers_embedder

index = ef.ingest(corpus, embedder=sentence_transformers_embedder("all-MiniLM-L6-v2"))
index = ef.ingest(corpus, embedder=openai_embedder("text-embedding-3-small"))
index = ef.ingest(
    corpus, embedder=as_embedder("cohere:embed-v4.0")
)  # also voyage:/gemini:
index = ef.ingest(corpus, embedder=as_embedder(my_callable, model_id="custom@768"))

Hosted-API adapters: openai_embedder (needs ef[openai]) plus cohere_embedder, voyage_embedder and gemini_embedder — the latter three speak their providers' REST endpoints directly, so they need only an API key, no SDK. Each translates ef's canonical input_type (query / document / classification / clustering) to the vendor's own task name. Local options: sentence_transformers_embedder, http_embedder (any TEI-style service), and the dependency-free HashingEmbedder default.

An Embedder is just a batch callable Iterable[str] -> ndarray(n, dim) with a little metadata. Composition wrappers — CachedEmbedder, RetryingEmbedder, MultiEmbedder, NormalizingEmbedder, InstructionEmbedder — each wrap an inner embedder.

For instruction-tuned models (E5-instruct, BGE, GTE, Qwen3-Embedding), condition the query side on the task: InstructionEmbedder(inner, query_instruction="Represent this sentence for searching relevant passages: ") prefixes query texts and leaves documents bare, or pass query_prompt= / prompt_names={"query": "query"} to sentence_transformers_embedder. Either way the conditioned roles show up in honored_input_types and the instruction text is baked into model_id, so caches never mix vectors. Only trust this on an instruction-tuned model; on all-MiniLM it should be a near no-op.

The heavy case — SourceManager

For large or changing corpora, multiple segmentations/embedders, and explicit control, use SourceManager. Configs that share a pipeline step share its artifacts for free — the indexing core is a content-addressed producer graph, so a second embedder or segmenter re-uses everything upstream of it.

from ef import SourceManager

manager = SourceManager(corpus, store="my_vectors")
manager.ingest(segmenter="recursive", embedder="openai:text-embedding-3-small")
index = manager.searchable()

Keeping an index fresh

As sources change, an index drifts. SourceManager diagnoses and repairs it:

report = manager.diagnose()  # the four staleness conditions
manager.refresh(mode="incremental")  # none | incremental | full | scoped_full
manager = SourceManager(corpus, store="my_vectors", auto_refresh=True)  # live

RAG plug-in & evaluation

ef hands a corpus to your RAG/agent framework and measures retrieval quality — it does not synthesize answers.

segments = index.retrieve("how do neural networks learn?", limit=5)
context = "\n\n".join(s["text"] for s in segments)  # feed context to your LLM

from ef import evaluate_retrieval, evaluate_rag

retrieval = evaluate_retrieval(index.retrieve, qrels, queries)  # BEIR-shaped, NDCG@10
rag = evaluate_rag(samples)  # deterministic lexical metrics

retrieve() returns plain Segments (provenance preserved in metadata["source"]); search() returns scored SearchHits. with_reranker adds a two-stage reranking pass. as_ragas_dataset bridges to Ragas for LLM-judged metrics.

Exploring a corpus (layer L5)

The secondary "see the shape of the corpus" surface — ef's visualization heritage, the backend an app_ef corpus map consumes. Three functions, each taking a corpus or a vector matrix:

coords = ef.project(index, dims=2)  # PCA -> UMAP, 2-D coordinates
labels = ef.cluster(index, n_clusters=8)  # k-means (or method="hdbscan")
titles = ef.label_clusters(segments, labels)  # LLM-titled clusters (via imbed)

project and cluster import numpy-only; their default paths (PCA, k-means) need no extra. method="umap", method="hdbscan" and label_clusters use the ef[explore] / ef[imbed] extras, imported lazily.

What ef is not

No agent loops, no tool-calling, no conversation memory, no prompt templating, no LLM answer synthesis, no bundled UI, no global config singleton. The RAG-plug-in surface is the boundary: ef returns retrieve(query) -> list[Segment]; the application (or srag / raglab / LangGraph) takes it from there.

Skills

ef ships a developer agent skill — install it into your agent host with gh skill (don't have it? install gh) before working in this repo:

gh skill install thorwhalen/ef ef-architecture --agent claude-code
Skill Use it when…
ef-architecture (developer) starting any non-trivial change in the ef repo — refactoring, the Embedder/Segmenter facades, the Corpus abstraction

License

MIT

Metadata

Release files for ef 0.1.23

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ef 0.1.23
File Size Uploaded
ef-0.1.23.tar.gz 211.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ef 0.1.23
File Interpreter ABI Platform
ef-0.1.23-py3-none-any.whl Python 3 none any Details

Total release size: 331.5 kB

Release files / ef-0.1.23.tar.gz

Download URL ef-0.1.23.tar.gz
Size 211.1 kB
Tags Source
SHA-256 checksum
How to use checksums
92e1a3c38d69b644d3b692eeac66506b43d83898bcf8c0d57c656b0655a1aac2
BLAKE2b-256 checksum
How to use checksums
e1ce496295e7e5b907192ac09fadcce2bf75e4e3f5123e87dc4a77cc135478b9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / ef-0.1.23-py3-none-any.whl

Download URL ef-0.1.23-py3-none-any.whl
Size 120.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
736b7f04280bfc37f7ac12600abffb23a1c77bf5f316f94eb1b63c1c84a3b699
BLAKE2b-256 checksum
How to use checksums
e074a9307445915a8742df7bd7550877f0d5dac6072627f02637defaf5f0cb05
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

0.1.23 This release

2 release files

0.1.22

2 release files

0.1.21

2 release files

0.1.20

2 release files

0.1.19

2 release files

0.1.18

2 release files

0.1.17

2 release files

0.1.16

2 release files

0.1.15

2 release files

0.1.14

2 release files

0.1.13

2 release files

0.1.12

2 release files

0.1.11

2 release files

0.1.10

2 release files

0.1.9

2 release files

0.1.8

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.0.6

2 release files

0.0.5

2 release files

0.0.4

2 release files

0.0.3

2 release files

0.0.2

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page