Skip to main content

mnem_embed_providers/
lib.rs

1// OpenAI, Ollama proper-noun identifiers appear throughout the
2// provider docs; backticking them adds no signal.
3#![allow(clippy::doc_markdown)]
4
5//! # mnem-embed-providers
6//!
7//! Embedding-provider adapters for mnem. Ships OpenAI and Ollama out
8//! of the box; both behind opt-in (on-by-default) cargo features.
9//!
10//! ## Scope
11//!
12//! This crate turns a user-configured provider into a concrete
13//! [`Embedder`] that the mnem CLI, MCP server, and Python bindings use
14//! to (a) auto-embed node summaries on write and (b) auto-embed query
15//! strings on retrieve. That is the piece that makes `mnem retrieve
16//! --text ...` semantic-hybrid by default once a provider is
17//! configured.
18//!
19//! ## Invariants
20//!
21//! - **No tokio / no async.** All adapters are sync, built on top of
22//!   [`ureq`] (rustls-backed). Mnem cannot afford to drag an async
23//!   runtime into the CLI or the MCP server.
24//! - **No API keys in config / on disk.** The config stores the *name*
25//!   of the env var holding the key (`api_key_env`). The key itself is
26//!   read from the environment at adapter-construction time and is
27//!   never persisted by this crate.
28//! - **Deterministic outputs.** Adapters only wrap providers whose
29//!   `embed(text)` is a pure function of `(provider, model, text)`.
30//!   Randomised projections would break mnem's agent-replay guarantee.
31//! - **`mnem-core` is not a dependency of the HTTP layer.** `mnem-core`
32//!   still has zero network / HTTP / tokio in its dep tree, preserving
33//!   the WASM-embeddability promises.
34//!
35//! ## Usage
36//!
37//! ```no_run
38//! # use mnem_embed_providers::{open, Embedder, ProviderConfig, OpenAiConfig};
39//! # fn demo() -> Result<(), Box<dyn std::error::Error>> {
40//! let cfg = ProviderConfig::Openai(OpenAiConfig {
41//!     model: "text-embedding-3-small".into(),
42//!     ..Default::default()
43//! });
44//! let embedder = open(&cfg)?;
45//! let v = embedder.embed("Alice lives in Berlin")?;
46//! assert_eq!(v.len(), embedder.dim() as usize);
47//! # Ok(()) }
48//! ```
49
50#![forbid(unsafe_code)]
51#![deny(missing_docs)]
52
53pub mod config;
54pub mod embedder;
55pub mod error;
56pub(crate) mod http;
57pub mod manifest;
58
59#[cfg(any(test, feature = "mock"))]
60pub mod mock;
61#[cfg(feature = "ollama")]
62pub mod ollama;
63// `onnx` and `onnx-bundled` differ only in the ort runtime source
64// (load-dynamic vs download-binaries). The Rust-level adapter is
65// identical, so the module compiles on either feature. The
66// `compile_error!` at the top of `onnx.rs` rejects the
67// both-enabled combination at compile time.
68#[cfg(any(feature = "onnx", feature = "onnx-bundled"))]
69pub mod onnx;
70#[cfg(feature = "openai")]
71pub mod openai;
72
73pub use config::{OllamaConfig, OnnxConfig, OpenAiConfig, ProviderConfig, open};
74pub use embedder::{Embedder, to_embedding};
75pub use error::EmbedError;
76pub use manifest::{
77    DEFAULT_LATENCY_BUDGET_MS, EmbedderManifest, derive_max_cooccurrence_ms,
78    derive_max_knn_ingest_per_node_ms,
79};
80
81#[cfg(any(test, feature = "mock"))]
82pub use mock::MockEmbedder;
83
84#[cfg(any(feature = "onnx", feature = "onnx-bundled"))]
85pub use onnx::{ModelKind as OnnxModelKind, OnnxEmbedder};