Qdrant & Rust Client

create a Collection, load data into it and run a basic search query.

Once you have Docker installed, you can simply run Qdrant with an API key

For testing, just make your own value

sudo docker run -d \
  -p 6333:6333 -p 6334:6334 \
  -v "$(pwd)/qdrant_storage:/qdrant/storage:z" \
  --name qdrant \
  --restart unless-stopped \
  -e QDRANT__SERVICE__API_KEY=my-secret-key \
  qdrant/qdrant
curl http://localhost:6333curl http://localhost:6333/collections \
  -H "api-key: my-secret-key"
{"result":{"collections":[]},"status":"ok","time":0.000022774}

without the API key you chose ->

Must provide an API key or an Authorization bearer token

Qdrant Rust Client

cargo add qdrant-client

Create a “collection” and “upsert” some data

use qdrant_client::Qdrant;
use qdrant_client::qdrant::Distance;
use qdrant_client::qdrant::{PointStruct, UpsertPointsBuilder};
use qdrant_client::qdrant::{CreateCollectionBuilder, VectorParamsBuilder};
use qdrant_client::qdrant::SearchPoints;

#[tokio::main]
async fn main()-> Result<(), Box<dyn std::error::Error>> {
    // The Rust client uses Qdrant's gRPC interface
    let client = Qdrant::from_url("http://localhost:6334")
     .api_key("my-secret-key") // πŸ‘ˆ Add your API key
     .build()?;

    let collections = client.list_collections().await?.collections;

    let collection_exists = collections.iter().any(|c| c.name == "test_collection");
        if ! collection_exists{
    client
        .create_collection(
            CreateCollectionBuilder::new("test_collection")
                .vectors_config(VectorParamsBuilder::new(4, Distance::Dot)),
        )
        .await?;
        }


    let points = vec![
        PointStruct::new(1, vec![0.05, 0.61, 0.76, 0.74], [("city", "Berlin".into())]),
        PointStruct::new(2, vec![0.19, 0.81, 0.75, 0.11], [("city", "London".into())]),
        PointStruct::new(3, vec![0.36, 0.55, 0.47, 0.94], [("city", "Moscow".into())]),
        // ..truncated
    ];

    let response = client
        .upsert_points(UpsertPointsBuilder::new("test_collection", points).wait(true))
        .await?;

    dbg!(response);

 Ok(())


}

Do a vector search

use qdrant_client::Qdrant;
use qdrant_client::qdrant::Distance;
use qdrant_client::qdrant::QueryPointsBuilder;
use qdrant_client::qdrant::{CreateCollectionBuilder, VectorParamsBuilder};
use qdrant_client::qdrant::{PointStruct, UpsertPointsBuilder};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // The Rust client uses Qdrant's gRPC interface
    let client = Qdrant::from_url("http://localhost:6334")
        .api_key("my-secret-key") // πŸ‘ˆ Add your API key
        .build()?;

    let search_result = client
        .query(QueryPointsBuilder::new("test_collection").query(vec![0.2, 0.2, 0.9, 0.7]))
        .await?;

    dbg!(search_result);

    Ok(())
}
[src/main.rs:33:5] search_result = QueryResponse {
    result: [
        ScoredPoint {
            id: Some(
                PointId {
                    point_id_options: Some(
                        Num(
                            1,
                        ),
                    ),
                },
            ),
            payload: {},
            score: 1.334,
            version: 3,
            vectors: None,
            shard_key: None,
            order_value: None,
        },
        ScoredPoint {
            id: Some(
                PointId {
                    point_id_options: Some(
                        Num(
                            3,
                        ),
                    ),
                },
            ),
            payload: {},
            score: 1.263,
            version: 3,
            vectors: None,
            shard_key: None,
            order_value: None,
        },
        ScoredPoint {
            id: Some(
                PointId {
                    point_id_options: Some(
                        Num(
                            2,
                        ),
                    ),
                },
            ),
            payload: {},
            score: 0.95199996,
            version: 3,
            vectors: None,
            shard_key: None,
            order_value: None,
        },
    ],
    time: 0.001076587,
    usage: None,
}

Filter search

use qdrant_client::Qdrant;
use qdrant_client::qdrant::{Condition, Filter, QueryPointsBuilder};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // The Rust client uses Qdrant's gRPC interface
    let client = Qdrant::from_url("http://localhost:6334")
        .api_key("my-secret-key") // πŸ‘ˆ Add your API key
        .build()?;

    let search_result = client
        .query(
            QueryPointsBuilder::new("test_collection")
                .query(vec![0.2, 0.1, 0.9, 0.7])
                .filter(Filter::must([Condition::matches(
                    "city",
                    "London".to_string(),
                )]))
                .with_payload(true),
        )
        .await?;

    dbg!(search_result);

    Ok(())
}
[src/main.rs:23:5] search_result = QueryResponse {
    result: [
        ScoredPoint {
            id: Some(
                PointId {
                    point_id_options: Some(
                        Num(
                            2,
                        ),
                    ),
                },
            ),
            payload: {
                "city": Value {
                    kind: Some(
                        StringValue(
                            "London",
                        ),
                    ),
                },
            },
            score: 0.871,
            version: 3,
            vectors: None,
            shard_key: None,
            order_value: None,
        },
    ],
    time: 0.000704456,
    usage: None,
}

Prepare a model to do embeddings with Qdrant (fastembed)

*Each model will have its own dimensions and recommended distance


VectorParamsBuilder::new(384, Distance::Cosine)
    let model = TextEmbedding::try_new(
        InitOptions::new(EmbeddingModel::AllMiniLML6V2).with_show_download_progress(true),
    )?;

cargo add fastembed
ComponentPurpose
fastembed::TextEmbeddingEmbeds sentences into vectors on-device
EmbeddingModel::AllMiniLML6V2Pretrained model that outputs 384-dim sentence embeddings
VectorParamsBuilder::new(384, Distance::Cosine)Configures Qdrant collection to store 384-dim vectors and compare using cosine
with_show_download_progress(true)Shows download progress for the embedding model
    let collection_exists = collections.iter().any(|c| c.name == "knowledge_base");
    if !collection_exists {
        client
            .create_collection(
                CreateCollectionBuilder::new("knowledge_base")
                    .vectors_config(VectorParamsBuilder::new(384, Distance::Cosine)),
            )
            .await?;
    }

    let model = TextEmbedding::try_new(
        InitOptions::new(EmbeddingModel::AllMiniLML6V2).with_show_download_progress(true),
    )?;

Full code (so far) :

use qdrant_client::Qdrant;
use qdrant_client::qdrant::Distance;
use qdrant_client::qdrant::{CreateCollectionBuilder, VectorParamsBuilder};

use fastembed::{EmbeddingModel, InitOptions, TextEmbedding};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // The Rust client uses Qdrant's gRPC interface
    let client = Qdrant::from_url("http://localhost:6334")
        .api_key("my-secret-key") // πŸ‘ˆ Add your API key
        .build()?;

    let collections = client.list_collections().await?.collections;
    dbg!(&collections);

    let collection_exists = collections.iter().any(|c| c.name == "knowledge_base");
    if !collection_exists {
        client
            .create_collection(
                CreateCollectionBuilder::new("knowledge_base")
                    .vectors_config(VectorParamsBuilder::new(384, Distance::Cosine)),
            )
            .await?;
    }

    let model = TextEmbedding::try_new(
        InitOptions::new(EmbeddingModel::AllMiniLML6V2).with_show_download_progress(true),
    )?;

    Ok(())
}

PointStruct

pub struct PointStruct {
    pub id: Option<PointId>,
    pub payload: HashMap<String, Value>,
    pub vectors: Option<Vectors>,
}

https://docs.rs/qdrant-client/latest/qdrant_client/qdrant/struct.PointStruct.html

FieldTypePurpose
idOption<PointId>Unique identifier for the point
payloadHashMap<String, Value>Extra searchable/filterable metadata
vectorsOption<Vectors>The embedding vector used for similarity search

The payload is the human-readable metadata that you attach to a vector β€” it’s what gives context and semantic meaning to the raw numbers in the vector.

  • Vectors are for finding similar things
  • Payload is for understanding what those things are

We’ll put all of the “Points” into a Vec and that’s how they get “Upserted”

let points: Vec = /* your points */;

use qdrant_client::qdrant::{PointStruct, UpsertPoints};
...
    let upsert = UpsertPoints {
        collection_name: collection_name.to_string(),
        points,
        ..Default::default()
    };

    client.upsert_points(upsert).await?;
...

Embeddings

We still need to runs the embedding model on our chosen text chunks to get some vectors however…

So the workflow looks like this:

  1. Load your raw texts (e.g., from AG News or any dataset).
  2. Run the embedding model on each text β†’ get a vector of floats.
  3. Build PointStructs using those vectors plus any metadata/payload.
  4. Create an UpsertPoints struct with your collection name and the vector points.
  5. Call client.upsert_points(upsert).await? to upload.

Assuming your texts are short enough to embed as-is (like news headlines or short articles from AG News), you don’t need to chunk them.

Embedding works best on reasonably sized text pieces β€” typically up to a few hundred tokens. AG News entries are short enough that embedding the whole text at once is perfectly fine.