Embedders
An embedder turns chunk text into vectors for semantic and hybrid search. RCE Code defines a tiny Embedder protocol and ships four implementations behind it — a zero-dependency offline default, an OpenAI-compatible HTTP adapter, a local sentence-transformers adapter, and a content-addressed disk cache that wraps any of them.
You pick an embedder when you open the index, by passing it to CodeEngine.open_or_create. If you pass nothing, RCE Code uses the offline HashingEmbedder, so semantic and hybrid search work out of the box with no network and no extra dependencies.
The Embedder protocol
Any object with three attributes and one method satisfies the protocol — there is no base class to subclass. embed takes a list of texts and returns an (n, dim) float32 numpy array, one row per input.
The three identity attributes matter for freshness. An embedding row is stored with its model_id, model_version, and the chunk's content hash, so the embed pipeline re-embeds a chunk only when its content changed or the model identity changed. Bump model_version and the next engine.embed() transparently re-embeds everything.
All embeddings are L2-normalized
The built-in adapters
| Adapter | Import | Extra needed | When to use |
|---|---|---|---|
HashingEmbedder | rce.code.embed.hashing | none (default) | Offline tests, CI, getting started — coarse but free |
OpenAICompatEmbedder | rce.code.embed.openai_compat | embeddings-api | OpenAI / Voyage / local Ollama or LM Studio over HTTP |
SentenceTransformerEmbedder | rce.code.embed.sentence_transformers | embeddings-local | Fully local, GPU/CPU model, no network |
CachedEmbedder | rce.code.embed.cached | none | Wrap any embedder to skip re-embedding identical content |
HashingEmbedder (the default)
A deterministic feature-hashing stub: it hashes word tokens into a fixed dim=256 signed vector and L2-normalizes. There is no model and no network, so texts that share tokens get correlated vectors — meaningful, if coarse, similarity for exercising the whole vector pipeline offline. It takes no constructor arguments.
OpenAICompatEmbedder
Speaks the OpenAI /embeddings HTTP API, which means it works against OpenAI itself, Voyage, or any local server that mirrors that API (Ollama, LM Studio, llama.cpp). You pass the model name and its vector dim positionally, plus a base_url and (optionally) an api_key as keyword arguments. It batches inputs (128 at a time by default) and L2-normalizes the returned vectors.
The constructor signature is OpenAICompatEmbedder(model, dim, *, base_url=None, api_key=None, client=None, batch_size=128, timeout=60.0). You must supply a base_url unless you inject your own httpx.Client. The reported model_id is openai-compat:<model>, and the adapter raises if the server returns a different vector dimension than the dim you declared.
SentenceTransformerEmbedder
Runs a sentence-transformers model fully locally. The heavy dependency is lazy-imported, so this module imports fine without it; instantiating the adapter without the package installed raises a clear ImportError pointing you at the optional extra. If you omit dim, it is read from the model.
The constructor signature is SentenceTransformerEmbedder(model_name, *, dim=None, model=None). It encodes with normalize_embeddings=True so output is L2-normalized, and reports model_id = st:<model_name>. Install it with the embeddings-local extra:
CachedEmbedder
A wrapper, not an embedder of its own: it takes any inner embedder plus a cache directory and embeds identical content only once — across chunks and across runs. The cache key is blake3(model_id + model_version + text), and each vector is written to a raw .f32 file atomically (temp-then-rename), so an interrupted run cannot leave a torn entry. A missing, unreadable, or wrong-length entry is simply treated as a miss and re-embedded, so the cache self-heals. It mirrors the inner embedder's model_id, model_version, and dim.
The constructor signature is CachedEmbedder(inner, cache_dir). Because the cache is keyed by model identity, two models, or two versions of one model, never collide in the same directory.
Combine adapters
CachedEmbedder is the common production setup: you pay to embed a given chunk once, and every later re-index of unchanged content is served from disk.How embedding fits the pipeline
Indexing populates chunks; engine.embed() embeds the ones that need it. The embed pipeline asks the vector index which chunk ids are pending — those with no vector, a stale content hash, or a different model identity — fetches their content in batches, calls your embedder, and upserts the vectors. It returns the number of chunks embedded, so a no-op re-embed returns 0.
Where that cosine KNN runs — in numpy or inside SQLite via sqlite-vec — is the subject of Vector Backends.