1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
//! # embedrs
//!
//! Unified embedding solution — cloud APIs + local inference through one interface.
//! Opinionated defaults backed by [benchmark data](https://github.com/goliajp/airs/tree/develop/crates/benchrs).
//!
//! ## Design: 用就要好用 (if we build it, it must be great)
//!
//! - **`local()`** → all-MiniLM-L6-v2 (23MB, free, no API key)
//! - **`cloud(key)`** → OpenAI text-embedding-3-small (best discrimination, cheapest)
//! - Both produce the same `EmbedResult` — write code once, switch backends in one line
//!
//! Defaults chosen by 8-dimension benchmark across 8 models. See [benchrs](https://github.com/goliajp/airs/tree/develop/crates/benchrs).
//!
//! ## Quick start
//!
//! ```rust,no_run
//! # async fn run() -> embedrs::Result<()> {
//! // cloud — one key, done
//! let client = embedrs::cloud("sk-...");
//! let result = client.embed(vec!["hello world".into()]).await?;
//! println!("dimensions: {}", result.embeddings[0].len());
//! # Ok(())
//! # }
//! ```
//!
//! With the `local` feature enabled:
//!
//! ```rust,ignore
//! // local — zero config, free, 23MB model downloaded on first use
//! let client = embedrs::local();
//! let result = client.embed(vec!["hello world".into()]).await?;
//! ```
//!
//! ## Batch embedding
//!
//! ```rust,no_run
//! # async fn run() -> embedrs::Result<()> {
//! let client = embedrs::cloud("sk-...");
//! let texts: Vec<String> = (0..5000).map(|i| format!("text {i}")).collect();
//! let result = client.embed_batch(texts)
//! .concurrency(5)
//! .await?;
//! # Ok(())
//! # }
//! ```
//!
//! ## Provider fallback
//!
//! Chain fallback providers for automatic failover:
//!
//! ```rust,no_run
//! # async fn run() -> embedrs::Result<()> {
//! let client = embedrs::Client::openai("sk-...")
//! .with_fallback(embedrs::Client::cohere("cohere-key"));
//! // if OpenAI fails, automatically tries Cohere
//! let result = client.embed(vec!["hello".into()]).await?;
//! # Ok(())
//! # }
//! ```
//!
//! ## Cost tracking
//!
//! Enable the `cost-tracking` feature to get estimated cost per request via `tiktoken` pricing data.
//! The `Usage::cost` field will be `Some(f64)` for models with pricing info, `None` otherwise.
//!
//! ```toml
//! embedrs = { version = "0.2", features = ["cost-tracking"] }
//! ```
//!
//! ## Error handling
//!
//! All fallible operations return [`Result<T>`]. Match on [`Error`] variants for fine-grained control:
//!
//! - [`Error::Api`] — API returned an error status (e.g., 429 rate limit, 401 unauthorized)
//! - [`Error::Timeout`] — request exceeded the configured timeout
//! - [`Error::Http`] — network-level failure
//! - [`Error::Json`] — response body could not be parsed
//! - [`Error::InputTooLarge`] — input exceeded the provider's batch size limit
//!
//! ## Similarity
//!
//! ```rust
//! let a = vec![1.0, 0.0, 0.0];
//! let b = vec![0.0, 1.0, 0.0];
//! let sim = embedrs::cosine_similarity(&a, &b);
//! assert!(sim.abs() < 1e-6);
//! ```
pub
pub use BackoffConfig;
pub use ;
pub use ;
pub use InputType;
pub use ;
pub use Usage;
/// Create a local embedding client with the recommended default model (all-MiniLM-L6-v2).
///
/// 23MB model, 384 dimensions, free, no API key needed.
/// Model weights downloaded from HuggingFace Hub on first use and cached locally.
///
/// Backed by benchrs experiment: best clustering separation (8.73x), 100% retrieval,
/// EN ρ=0.92, and the only model small enough for app embedding (<50MB).
///
/// ```rust,no_run
/// # async fn run() -> embedrs::Result<()> {
/// let client = embedrs::local();
/// let result = client.embed(vec!["hello world".into()]).await?;
/// # Ok(())
/// # }
/// ```
/// Create a cloud embedding client with the recommended default provider (OpenAI text-embedding-3-small).
///
/// 1536 dimensions, best discrimination gap (0.58), 100% retrieval, balanced multilingual,
/// cheapest cloud option at $0.02/1M tokens.
///
/// Backed by benchrs experiment: best discrimination means dissimilar texts get cosine ≈ 0.09
/// (closest to zero), making similarity thresholds reliable.
///
/// ```rust,no_run
/// # async fn run() -> embedrs::Result<()> {
/// let client = embedrs::cloud("sk-...");
/// let result = client.embed(vec!["hello world".into()]).await?;
/// # Ok(())
/// # }
/// ```
/// Prelude for convenient imports.