lean-embed
A lean, provider-agnostic text-embeddings client for Rust. One small client turns batches of text into vectors against OpenAI (or any OpenAI-compatible endpoint), Gemini, Voyage AI, or Ollama (local, offline, no API key) - and nothing more. No vector store, no chunking, no agent loop: just the embeddings HTTP call, so your retrieval stack stays yours.
The wire is reqwest on rustls + ring only - never OpenSSL, never
aws-lc. That is the whole reason this crate exists instead of reaching for a
full agent/RAG framework: the dependency tree stays small and cross-compiles
cleanly to musl and aarch64. cargo tree -i aws-lc-sys and -i openssl-sys are
empty, and staying that way is a standing commitment.
Why not rig / async-openai / a RAG framework?
rigis the only crate with first-class Voyage + Ollama, but its TLS is hardwired to reqwest's rustls default (aws-lc-rs) or native-tls (OpenSSL) with no ring path - and it is a full agent/RAG framework for oneembed()call.async-openaiships clean deps (evenrustls-no-provider) but is OpenAI-only - no Voyage, Gemini, or Ollama, each of which has its own wire shape and asymmetry knob.- A RAG framework's value is its store + pipeline; but each real consumer already owns a different store (pgvector-in-Postgres here, an offline brute-force index there), so the framework part is exactly what can't be shared. This crate lifts only the portable part: the client.
Install
[]
= "0.1"
Usage
use ;
async
Notes
EmbedKindselects each provider's asymmetric retrieval knob - Voyage'sinput_typeand Gemini'staskType(DocumentvsQuery), which retrieves better. OpenAI is symmetric and Ollama is local; both ignore it.output_dimensionis requested where the provider supports it (Voyageoutput_dimension, OpenAIdimensions, GeminioutputDimensionality) and, for every provider, validated against every returned vector - so a model drifting off your stored width becomes anError::DimMismatchinstead of a silent schema desync.max_batchcaps inputs per HTTP request; larger batches are split into sequential requests and concatenated in input order.- No retry/backoff - a transient or
429failure returnsErr(and a mid-batch failure discards that call's already-fetched vectors). Wrapembedyourself if you need resilience;Error::Api { status, .. }exposes the code. - A
Clientowns its HTTP client (cheap to clone,Arcinside) - build it once and reuse it.Debugredacts the API key.
Providers
| Provider | Endpoint | Key env | Offline | EmbedKind |
|---|---|---|---|---|
| Ollama | {base}/api/embed (default http://localhost:11434) |
none | yes | ignored |
| Voyage | {base}/embeddings (default https://api.voyageai.com/v1) |
VOYAGE_API_KEY |
no | input_type |
| OpenAI | {base}/embeddings (default https://api.openai.com/v1) |
OPENAI_API_KEY |
no | symmetric (ignored) |
| Gemini | {base}/models/{model}:batchEmbedContents (default …/v1beta) |
GEMINI_API_KEY |
no | taskType |
OpenAI's entry doubles as the client for any OpenAI-compatible /v1/embeddings
server (together.ai, vLLM, LocalAI, …) via a base_url override.
License
Dual-licensed under MIT or Apache-2.0, at your option.