Qdrant + FastEmbed with Rust #1

So none, I repeat none of this is “AI” generated!

(The racing car image started off from a real photo of mine!) . Let’s get that out of the way first and learn how to do the Rust code so that we can use the FastEmbed crate to create our own embeddings to put into Qdrant

I have put together the following useful notes on how to do embeddings for Qdrant using Rust with the FastEmbed crate ‘for generating vector embeddings, reranking locally’. This saves money on tokens, many tutorials use OpenAI for doing the embeddings!

Pierluigi Martini – Brands Hatch F3000 1988

Qdrant stores the embeddings for semantic retrieval, think “recommendation” or “similarity search”. The Qdrant docs are great, the FastEmbed docs are great, but we need to weave the 2 things together, that’s the purpose of this article.

(In a future article – called “part 2” – I will show how to do chunking, but for now let’s focus on creating the embeddings and getting them into Qdrant).

The code

Dependencies (Cargo.toml)

[dependencies]
dotenvy = "0.15.7"
fastembed = "5.8.1"
qdrant-client = "1.16.0"
serde_json = "1.0.149"
tokio = { version = "1.49.0", features = ["macros", "rt-multi-thread"] }

The “difficult” part is linking the examples from Qdrant for making a client and a collection to the FastEmbed examples on how to do embeddings.

Embedding Model Choice

In the example code I used EmbeddingModel::AllMiniLML6V2 but you can choose whichever one you desire, based on the variants offered to you from the EmbeddingModel enum.

Supported Models:

https://github.com/Anush008/fastembed-rs?tab=readme-ov-file#models

Support the project if you have the means πŸ‘πŸ»

Prepare the ‘Points’

Most of the code will seem relatively straightforward to work out, especially from the IDE hints, but this is the key part you won’t find.

Line 43, we create our Vec of PointStruct where Vec is the Rust standard library type and PointStruct is a Qdrant type provided by the Qdrant client crate.

// Prepare points with embeddings and corresponding documents as payload
    let points: Vec<PointStruct> = embeddings
        .into_iter()
        .enumerate()
        .filter(|(id, _)| !documents[*id].is_empty())
        .map(|(id, vector)| {
            let payload: Payload = serde_json::json!({ "document": documents[id] })
                .try_into()
                .unwrap();
            PointStruct::new(id as u64, vector, payload)
        })
        .collect();
documents[ ]  ─┐
               β”œβ”€ same index β†’ (vector + payload) β†’ PointStruct
embeddings[ ] β”€β”˜

We’re strapping on the appropriate document to the id + embedding

Note the filter line?

Only keep the embeddings that have a real document attached, we skip the empty ones.

Also, note the * to deref the id?

Necessary because Rust doesn’t auto-deref tuple elements in closures.

Then, with map, we take a pair and turn it into a single “thing” that contains three bits of data, ready for the upsert to Qdrant!

Upserted Points viewed in Qdrant Dashboard : http://localhost:6333/dashboard#/collections/collection_4/graph

Links:
https://qdrant.tech/documentation/fastembed/fastembed-semantic-search/

https://qdrant.tech/documentation/fastembed

https://crates.io/crates/fastembed

Massive thanks to Anush for making the FastEmbed crate!

If you liked this article and want to read part 2, here it is:

Rust Programming

Previous article

“Mismatched Types” in Rust