Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
500 changes: 486 additions & 14 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ members = [
"processors",
"rust",
"python",
"server"
]
# Python package needs to be built by maturin.
exclude = ["python"]
Expand Down
126 changes: 126 additions & 0 deletions docs/guides/actix_server.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# EmbedAnything OpenAI-Compatible Server

This server provides an OpenAI-compatible API for generating embeddings using the EmbedAnything library. We choose Actix Server for:

1. Blazing fast: Consistently ranks among the fastest web frameworks in benchmarks like TechEmpower.
2. Asynchronous by default: Built on Rust’s async/await, enabling efficient I/O-bound workloads.
3. Lightweight & modular: Minimal core with extensible middleware, plugins, and integrations.
4. Type-safe: Strong type guarantees ensure fewer runtime surprises.
5. Production-ready: Stable, mature, and already used in industries like fintech, IoT, and SaaS platforms.


For benchmarks between python and rust servers, you check out this blog: https://www.jonvet.com/blog/benchmarking-python-rust-web-servers

## Features

- OpenAI-compatible `/v1/embeddings` endpoint
- Support for multiple embedding models (Jina, BERT, etc.)
- Health check endpoint

## Running the Server

```bash
cargo run -p server --release
```

The server will start on `http://0.0.0.0:8080`.

## API Usage

### Create Embeddings

**Endpoint:** `POST /v1/embeddings`

**Request:**
```json
{
"model": "sentence-transformers/all-MiniLM-L12-v2",
"input": ["The quick brown fox jumps over the lazy dog"]
}
```

**Response:**
```json
{
"object": "list",
"data": [
{
"object": "embedding",
"index": 0,
"embedding": [0.0023064255, -0.009327292, ...]
}
],
"model": "sentence-transformers/all-MiniLM-L12-v2",
"usage": {
"prompt_tokens": 9,
"total_tokens": 9
}
}
```

### Health Check

**Endpoint:** `GET /health_check`

Returns a 200 OK status if the server is running.

## Supported Models

The server maps model names to EmbedAnything model architectures:

- `text-embedding-ada-002` → Jina embeddings
- `text-embedding-3-small` → Jina embeddings
- `text-embedding-3-large` → Jina embeddings
- `text-embedding-ada-001` → BERT embeddings
- Unknown models → Default to Jina embeddings

## Error Handling

The API returns OpenAI-compatible error responses:

```json
{
"error": {
"message": "Error description",
"type": "error_type",
"code": "error_code"
}
}
```

## Example Usage with curl

```bash
# Create embeddings
curl -X POST http://localhost:8080/v1/embeddings \
-H "Content-Type: application/json" \
-d '{
"model": "sentence-transformers/all-MiniLM-L12-v2",
"input": ["Hello world", "How are you?"]
}'

# Health check
curl http://localhost:8080/health_check
```

## Example Usage with Python

```python
import requests

# Create embeddings
response = requests.post(
"http://localhost:8080/v1/embeddings",
json={
"model": "sentence-transformers/all-MiniLM-L12-v2",
"input": ["The quick brown fox jumps over the lazy dog"]
}
)

if response.status_code == 200:
data = response.json()
print(f"Generated {len(data['data'])} embeddings")
print(f"First embedding dimension: {len(data['data'][0]['embedding'])}")
else:
print(f"Error: {response.json()}")
```
1 change: 0 additions & 1 deletion rust/examples/audio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ async fn main() {

let bert_model = Arc::new(
EmbedderBuilder::new()
.model_architecture("bert")
.model_id(Some("sentence-transformers/all-MiniLM-L6-v2"))
.revision(None)
.token(None)
Expand Down
1 change: 0 additions & 1 deletion rust/examples/bert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ use std::{path::PathBuf, time::Instant};
async fn main() {
let model = Arc::new(
EmbedderBuilder::new()
.model_architecture("jina")
.model_id(Some("jinaai/jina-embeddings-v2-small-en"))
.revision(None)
.token(None)
Expand Down
1 change: 0 additions & 1 deletion rust/examples/clip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ async fn main() {
let now = Instant::now();

let model = EmbedderBuilder::new()
.model_architecture("clip")
.model_id(Some("google/siglip-base-patch16-224"))
.revision(None)
.token(None)
Expand Down
86 changes: 86 additions & 0 deletions rust/examples/dino.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use candle_core::{Device, Tensor};
use embed_anything::{
embed_file, embed_image_directory, embeddings::embed::{Embedder, EmbedderBuilder}
};
use std::{path::PathBuf, sync::Arc, time::Instant};

#[tokio::main]
async fn main() {
let now = Instant::now();

let model = EmbedderBuilder::new()
.model_id(Some("facebook/dinov2-small"))
.revision(None)
.token(None)
.from_pretrained_hf()
.unwrap();
let model: Arc<Embedder> = Arc::new(model);
// let out = embed_image_directory(PathBuf::from("test_files"), &model, None, None)
// .await
// .unwrap()
// .unwrap();

let query_emb_data = embed_file("test_files/clip/cat3.jpg", &model, None, None)
.await
.unwrap().unwrap();
// println!("query_emb_data: {:?}", query_emb_data[0].embedding);
// let n_vectors = out.len();

// let vector = out
// .iter()
// .map(|embed| embed.embedding.clone())
// .collect::<Vec<_>>()
// .into_iter()
// .flat_map(|x| x.to_dense().unwrap())
// .collect::<Vec<_>>();

// let out_embeddings = Tensor::from_vec(
// vector,
// (n_vectors, out[0].embedding.to_dense().unwrap().len()),
// &Device::Cpu,
// )
// .unwrap();

// let image_paths = out
// .iter()
// .map(|embed| embed.text.clone().unwrap())
// .collect::<Vec<_>>();

// let query_embeddings = Tensor::from_vec(
// query_emb_data
// .iter()
// .map(|embed| embed.embedding.clone())
// .collect::<Vec<_>>()
// .into_iter()
// .flat_map(|x| x.to_dense().unwrap())
// .collect::<Vec<_>>(),
// (1, query_emb_data[0].embedding.to_dense().unwrap().len()),
// &Device::Cpu,
// )
// .unwrap();

// let similarities = out_embeddings
// .matmul(&query_embeddings.transpose(0, 1).unwrap())
// .unwrap()
// .detach()
// .squeeze(1)
// .unwrap()
// .to_vec1::<f32>()
// .unwrap();

// let mut indices: Vec<usize> = (0..similarities.len()).collect();
// indices.sort_by(|a, b| similarities[*b].partial_cmp(&similarities[*a]).unwrap());

// println!("Descending order of similarity: ");
// for idx in &indices {
// println!("{}", image_paths[*idx]);
// println!("Similarity: {}", similarities[*idx]);
// }

// println!("-----------");

// println!("Most similar image: {}", image_paths[indices[0]]);

// let elapsed_time = now.elapsed();
// println!("Elapsed Time: {}", elapsed_time.as_secs_f32());
}
1 change: 0 additions & 1 deletion rust/examples/late_chunking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ use embed_anything::{config::TextEmbedConfig, embeddings::embed::EmbedderBuilder
async fn main() {
let model = Arc::new(
EmbedderBuilder::new()
.model_architecture("jina")
.model_id(Some("jinaai/jina-embeddings-v2-small-en"))
.revision(None)
.path_in_repo(Some("model.onnx"))
Expand Down
1 change: 0 additions & 1 deletion rust/examples/model2vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ use std::{path::PathBuf, time::Instant};
async fn main() {
let model = Arc::new(
EmbedderBuilder::new()
.model_architecture("model2vec")
.model_id(Some("minishlab/potion-base-8M"))
.revision(None)
.token(None)
Expand Down
1 change: 0 additions & 1 deletion rust/examples/splade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ async fn main() -> anyhow::Result<()> {
),
ModelType::Normal => Arc::new(
EmbedderBuilder::new()
.model_architecture("sparse-bert")
.model_id(Some("prithivida/Splade_PP_en_v1"))
.revision(None)
.from_pretrained_hf()
Expand Down
1 change: 0 additions & 1 deletion rust/examples/web_embed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ async fn main() {

let embedder = Arc::new(
EmbedderBuilder::new()
.model_architecture("bert")
.model_id(Some("sentence-transformers/all-MiniLM-L6-v2"))
.revision(None)
.from_pretrained_hf()
Expand Down
Loading
Loading