Merge branch 'master' into patch-1

This commit is contained in:
Mike Jang
2023-11-23 06:11:21 -08:00
committed by GitHub
18 changed files with 2652 additions and 74 deletions
@@ -27,6 +27,11 @@ Our official Qdrant clients for Python, TypeScript, Go, Rust, and .NET all suppo
curl \
-X GET https://xyz-example.eu-central.aws.cloud.qdrant.io:6333 \
--header 'api-key: <provide-your-own-key>'
# Alternatively, you can use the `Authorization` header with the `Bearer` prefix
curl \
-X GET https://xyz-example.eu-central.aws.cloud.qdrant.io:6333 \
--header 'Authorization: Bearer <provide-your-own-key>'
```
```python
@@ -56,3 +61,12 @@ var client = new QdrantClient(
apiKey: "<paste-your-api-key-here>"
);
```
```rust
use qdrant_client::client::QdrantClient;
let client = QdrantClient::from_url("xyz-example.eu-central.aws.cloud.qdrant.io:6334")
.with_api_key("<paste-your-api-key-here>")
.build()
.unwrap();
```
@@ -64,6 +64,30 @@ client.createCollection("{collection_name}", {
});
```
```rust
use qdrant_client::{
client::QdrantClient,
qdrant::{vectors_config::Config, CreateCollection, Distance, VectorParams, VectorsConfig},
};
//The Rust client uses Qdrant's GRPC interface
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.create_collection(&CreateCollection {
collection_name: "{collection_name}".to_string(),
vectors_config: Some(VectorsConfig {
config: Some(Config::Params(VectorParams {
size: 100,
distance: Distance::Cosine.into(),
..Default::default()
})),
}),
..Default::default()
})
.await?;
```
In addition to the required options, you can also specify custom values for the following collection options:
* `hnsw_config` - see [indexing](../indexing/#vector-index) for details.
@@ -100,7 +124,7 @@ PUT /collections/{collection_name}
{
"vectors": {
"size": 300,
"size": 100,
"distance": "Cosine"
},
"init_from": {
@@ -133,6 +157,30 @@ client.createCollection("{collection_name}", {
});
```
```rust
use qdrant_client::{
client::QdrantClient,
qdrant::{vectors_config::Config, CreateCollection, Distance, VectorParams, VectorsConfig},
};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.create_collection(&CreateCollection {
collection_name: "{collection_name}".to_string(),
vectors_config: Some(VectorsConfig {
config: Some(Config::Params(VectorParams {
size: 100,
distance: Distance::Cosine.into(),
..Default::default()
})),
}),
init_from_collection: Some("{from_collection_name}".to_string()),
..Default::default()
})
.await?;
```
### Collection with multiple vectors
*Available as of v0.10.0*
@@ -187,6 +235,48 @@ client.createCollection("{collection_name}", {
});
```
```rust
use qdrant_client::{
client::QdrantClient,
qdrant::{
vectors_config::Config, CreateCollection, Distance, VectorParams, VectorParamsMap,
VectorsConfig,
},
};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.create_collection(&CreateCollection {
collection_name: "{collection_name}".to_string(),
vectors_config: Some(VectorsConfig {
config: Some(Config::ParamsMap(VectorParamsMap {
map: [
(
"image".to_string(),
VectorParams {
size: 4,
distance: Distance::Dot.into(),
..Default::default()
},
),
(
"text".to_string(),
VectorParams {
size: 8,
distance: Distance::Cosine.into(),
..Default::default()
},
),
]
.into(),
})),
}),
..Default::default()
})
.await?;
```
For rare use cases, it is possible to create a collection without any vector storage.
*Available as of v1.1.1*
@@ -219,6 +309,10 @@ client.delete_collection(collection_name="{collection_name}")
client.deleteCollection("{collection_name}");
```
```rust
client.delete_collection("{collection_name}").await?;
```
### Update collection parameters
Dynamic parameter updates may be helpful, for example, for more efficient initial loading of vectors.
@@ -252,6 +346,20 @@ client.updateCollection("{collection_name}", {
});
```
```rust
use qdrant_client::qdrant::OptimizersConfigDiff;
client
.update_collection(
"{collection_name}",
&OptimizersConfigDiff {
indexing_threshold: Some(10000),
..Default::default()
},
)
.await?;
```
The following parameters can be updated:
* `optimizers_config` - see [optimizer](../optimizer/) for details.
@@ -402,6 +510,57 @@ client.updateCollection("{collection_name}", {
});
```
<!---
```rust
// Available as of Rust client 1.7.0
// See: <https://github.com/qdrant/rust-client/issues/75>
use qdrant_client::client::QdrantClient;
use qdrant_client::qdrant::{
quantization_config_diff::Quantization, vectors_config_diff::Config, HnswConfigDiff,
QuantizationConfigDiff, QuantizationType, ScalarQuantization, VectorParamsDiff,
VectorsConfigDiff,
};
client
.update_collection(
"{collection_name}",
None,
None,
Some(&HnswConfigDiff {
ef_construct: Some(123),
..Default::default()
}),
Some(&VectorsConfigDiff {
config: Some(Config::ParamsMap(
qdrant_client::qdrant::VectorParamsDiffMap {
map: HashMap::from([(
("my_vector".into()),
VectorParamsDiff {
hnsw_config: Some(HnswConfigDiff {
m: Some(32),
ef_construct: Some(123),
..Default::default()
}),
..Default::default()
},
)]),
},
)),
}),
Some(&QuantizationConfigDiff {
quantization: Some(Quantization::Scalar(ScalarQuantization {
r#type: QuantizationType::Int8 as i32,
quantile: Some(0.8),
always_ram: Some(true),
..Default::default()
})),
}),
)
.await?;
```
--->
## Collection info
Qdrant allows determining the configuration parameters of an existing collection to better understand how the points are
@@ -465,6 +624,10 @@ client.get_collection(collection_name="{collection_name}")
client.getCollection("{collection_name}");
```
```rust
client.collection_info("{collection_name}").await?;
```
If you insert the vectors into the collection, the `status` field may become
`yellow` whilst it is optimizing. It will become `green` once all the points are
successfully processed.
@@ -544,6 +707,10 @@ client.updateCollectionAliases({
});
```
```rust
client.create_alias("example_collection", "production_collection").await?;
```
### Remove alias
```http
@@ -582,6 +749,10 @@ client.updateCollectionAliases({
});
```
```rust
client.delete_alias("production_collection").await?;
```
### Switch collection
Multiple alias actions are performed atomically.
@@ -640,6 +811,11 @@ client.updateCollectionAliases({
});
```
```rust
client.delete_alias("production_collection").await?;
client.create_alias("example_collection", "production_collection").await?;
```
### List collection aliases
```http
@@ -662,6 +838,14 @@ const client = new QdrantClient({ host: "localhost", port: 6333 });
client.getCollectionAliases("{collection_name}");
```
```rust
use qdrant_client::client::QdrantClient;
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client.list_collection_aliases("{collection_name}").await?;
```
### List all aliases
```http
@@ -684,6 +868,14 @@ const client = new QdrantClient({ host: "localhost", port: 6333 });
client.getAliases();
```
```rust
use qdrant_client::client::QdrantClient;
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client.list_aliases().await?;
```
### List all collections
```http
@@ -704,4 +896,12 @@ import { QdrantClient } from "@qdrant/js-client-rest";
const client = new QdrantClient({ host: "localhost", port: 6333 });
client.getCollections();
```
```
```rust
use qdrant_client::client::QdrantClient;
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client.list_collections().await?;
```
@@ -96,6 +96,26 @@ client.scroll("{collection_name}", {
});
```
```rust
use qdrant_client::{
client::QdrantClient,
qdrant::{Condition, Filter, ScrollPoints},
};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.scroll(&ScrollPoints {
collection_name: "test_collection".to_string(),
filter: Some(Filter::must([
Condition::matches("city", "london".to_string()),
Condition::matches("color", "red".to_string()),
])),
..Default::default()
})
.await?;
```
Filtered points would be:
```json
@@ -157,6 +177,21 @@ client.scroll("{collection_name}", {
});
```
```rust
use qdrant_client::qdrant::{Condition, Filter, ScrollPoints};
client
.scroll(&ScrollPoints {
collection_name: "test_collection".to_string(),
filter: Some(Filter::should([
Condition::matches("city", "london".to_string()),
Condition::matches("color", "red".to_string()),
])),
..Default::default()
})
.await?;
```
Filtered points would be:
```json
@@ -217,6 +252,21 @@ client.scroll("{collection_name}", {
});
```
```rust
use qdrant_client::qdrant::{Condition, Filter, ScrollPoints};
client
.scroll(&ScrollPoints {
collection_name: "test_collection".to_string(),
filter: Some(Filter::must_not([
Condition::matches("city", "london".to_string()),
Condition::matches("color", "red".to_string()),
])),
..Default::default()
})
.await?;
```
Filtered points would be:
```json
@@ -281,6 +331,22 @@ client.scroll("{collection_name}", {
});
```
```rust
use qdrant_client::qdrant::{Condition, Filter, ScrollPoints};
client
.scroll(&ScrollPoints {
collection_name: "{collection_name}".to_string(),
filter: Some(Filter {
must: vec![Condition::matches("city", "London".to_string())],
must_not: vec![Condition::matches("color", "red".to_string())],
..Default::default()
}),
..Default::default()
})
.await?;
```
Filtered points would be:
```json
@@ -352,6 +418,22 @@ client.scroll("{collection_name}", {
});
```
```rust
use qdrant_client::qdrant::{Condition, Filter, ScrollPoints};
client
.scroll(&ScrollPoints {
collection_name: "{collection_name}".to_string(),
filter: Some(Filter::must_not([Filter::must([
Condition::matches("city", "London".to_string()),
Condition::matches("color", "Red".to_string()),
])
.into()])),
..Default::default()
})
.await?;
```
Filtered points would be:
```json
@@ -394,6 +476,10 @@ models.FieldCondition(
}
```
```rust
Condition::matches("color", "red".to_string())
```
For the other types, the match condition will look exactly the same, except for the type used:
```json
@@ -419,6 +505,10 @@ models.FieldCondition(
}
```
```rust
Condition::matches("count", 0)
```
The simplest kind of condition is one that checks if the stored value equals the given one.
If several values are stored, at least one of them should match the condition.
You can apply it to [keyword](../payload/#keyword), [integer](../payload/#integer) and [bool](../payload/#bool) payloads.
@@ -457,6 +547,10 @@ FieldCondition(
}
```
```rust
Condition::matches("color", vec!["black".to_string(), "yellow".to_string()])
```
In this example, the condition will be satisfied if the stored value is either `black` or `yellow`.
If the stored value is an array, it should have at least one value matching any of the given values. E.g. if the stored value is `["black", "green"]`, the condition will be satisfied, because `"black"` is in `["black", "yellow"]`.
@@ -497,6 +591,13 @@ FieldCondition(
}
```
```rust
Condition::matches(
"color",
!MatchValue::from(vec!["black".to_string(), "yellow".to_string()]),
)
```
In this example, the condition will be satisfied if the stored value is neither `black` nor `yellow`.
If the stored value is an array, it should have at least one value not matching any of the given values. E.g. if the stored value is `["black", "green"]`, the condition will be satisfied, because `"green"` does not match `"black"` nor `"yellow"`.
@@ -597,6 +698,21 @@ client.scroll("{collection_name}", {
});
```
```rust
use qdrant_client::qdrant::{Condition, Filter, ScrollPoints};
client
.scroll(&ScrollPoints {
collection_name: "{collection_name}".to_string(),
filter: Some(Filter::should([Condition::matches(
"country.name",
"Germany".to_string(),
)])),
..Default::default()
})
.await?;
```
You can also search through arrays by projecting inner values using the `[]` syntax.
```http
@@ -653,6 +769,24 @@ client.scroll("{collection_name}", {
});
```
```rust
use qdrant_client::qdrant::{Condition, Filter, Range, ScrollPoints};
client
.scroll(&ScrollPoints {
collection_name: "{collection_name}".to_string(),
filter: Some(Filter::should([Condition::range(
"country.cities[].population",
Range {
gte: Some(9.0),
..Default::default()
},
)])),
..Default::default()
})
.await?;
```
This query would only output the point with id 2 as only Japan has a city with population greater than 9.0.
And the leaf nested field can also be an array.
@@ -701,6 +835,21 @@ client.scroll("{collection_name}", {
});
```
```rust
use qdrant_client::qdrant::{Condition, Filter, ScrollPoints};
client
.scroll(&ScrollPoints {
collection_name: "{collection_name}".to_string(),
filter: Some(Filter::should([Condition::matches(
"country.cities[].sightseeing",
"Osaka Castle".to_string(),
)])),
..Default::default()
})
.await?;
```
This query would only output the point with id 2 as only Japan has a city with the "Osaka castke" as part of the sightseeing.
### Nested object filter
@@ -790,6 +939,21 @@ client.scroll("{collection_name}", {
});
```
```rust
use qdrant_client::qdrant::{Condition, Filter, ScrollPoints};
client
.scroll(&ScrollPoints {
collection_name: "test_collection".to_string(),
filter: Some(Filter::must([
Condition::matches("diet[].food", "meat".to_string()),
Condition::matches("diet[].likes", true),
])),
..Default::default()
})
.await?;
```
This happens because both points are matching the two conditions:
- the "t-rex" matches food=meat on `diet[1].food` and likes=true on `diet[1].likes`
@@ -886,6 +1050,25 @@ client.scroll("{collection_name}", {
});
```
```rust
use qdrant_client::qdrant::{Condition, Filter, NestedCondition, ScrollPoints};
client
.scroll(&ScrollPoints {
collection_name: "{collection_name}".to_string(),
filter: Some(Filter::must([NestedCondition {
key: "diet".to_string(),
filter: Some(Filter::must([
Condition::matches("food", "meat".to_string()),
Condition::matches("likes", true),
])),
}
.into()])),
..Default::default()
})
.await?;
```
The matching logic is modified to be applied at the level of an array element within the payload.
Nested filters work in the same way as if the nested filter was applied to a single element of the array at a time.
@@ -983,6 +1166,28 @@ client.scroll("{collection_name}", {
});
```
```rust
use qdrant_client::qdrant::{Condition, Filter, NestedCondition, ScrollPoints};
client
.scroll(&ScrollPoints {
collection_name: "{collection_name}".to_string(),
filter: Some(Filter::must([
NestedCondition {
key: "diet".to_string(),
filter: Some(Filter::must([
Condition::matches("food", "meat".to_string()),
Condition::matches("likes", true),
])),
}
.into(),
Condition::has_id([1]),
])),
..Default::default()
})
.await?;
```
### Full Text Match
*Available as of v0.10.0*
@@ -1018,6 +1223,12 @@ models.FieldCondition(
}
```
```rust
// If the match string contains a white-space, full text match is performed.
// Otherwise a keyword match is performed.
Condition::matches("description", "good cheap".to_string())
```
If the query has several words, then the condition will be satisfied only if all of them are present in the text.
### Range
@@ -1058,6 +1269,18 @@ models.FieldCondition(
}
```
```rust
Condition::range(
"price",
Range {
gt: None,
gte: Some(100.0),
lt: None,
lte: Some(450.0),
},
)
```
The `range` condition sets the range of possible values for stored payload values.
If several values are stored, at least one of them should match the condition.
@@ -1122,6 +1345,22 @@ models.FieldCondition(
}
```
```rust
Condition::geo_bounding_box(
"location",
GeoBoundingBox {
bottom_right: Some(GeoPoint {
lon: 13.455868,
lat: 52.495862,
}),
top_left: Some(GeoPoint {
lon: 13.403683,
lat: 52.520711,
}),
},
)
```
It matches with `location`s inside a rectangle with the coordinates of the upper left corner in `bottom_right` and the coordinates of the lower right corner in `top_left`.
#### Geo Radius
@@ -1165,6 +1404,19 @@ models.FieldCondition(
}
```
```rust
Condition::geo_radius(
"location",
GeoRadius {
center: Some(GeoPoint {
lon: 13.403683,
lat: 52.520711,
}),
radius: 1000.0,
},
)
```
It matches with `location`s inside a circle with the `center` at the center and a radius of `radius` meters.
If several values are stored, at least one of them should match the condition.
@@ -1320,6 +1572,59 @@ models.FieldCondition(
}
```
```rust
Condition::geo_polygon(
"location",
GeoPolygon {
exterior: Some(GeoLineString {
points: vec![
GeoPoint {
lon: -70.0,
lat: -70.0,
},
GeoPoint {
lon: 60.0,
lat: -70.0,
},
GeoPoint {
lon: 60.0,
lat: 60.0,
},
GeoPoint {
lon: -70.0,
lat: 60.0,
},
GeoPoint {
lon: -70.0,
lat: -70.0,
},
],
}),
interiors: vec![GeoLineString {
points: vec![
GeoPoint {
lon: -65.0,
lat: -65.0,
},
GeoPoint {
lon: 0.0,
lat: -65.0,
},
GeoPoint { lon: 0.0, lat: 0.0 },
GeoPoint {
lon: -65.0,
lat: 0.0,
},
GeoPoint {
lon: -65.0,
lat: -65.0,
},
],
}],
},
)
```
A match is considered any point location inside or on the boundaries of the given polygon's exterior but not inside any interiors.
If several location values are stored for a point, then any of them matching will include that point as a candidate in the resultset.
@@ -1363,6 +1668,16 @@ models.FieldCondition(
}
```
```rust
Condition::values_count(
"comments",
ValuesCount {
gt: Some(2),
..Default::default()
},
)
```
The result would be:
```json
@@ -1398,6 +1713,10 @@ models.IsEmptyCondition(
}
```
```rust
Condition::is_empty("reports")
```
This condition will match all records where the field `reports` either does not exist, or has `null` or `[]` value.
<aside role="status">The <b>IsEmpty</b> is often useful together with the logical negation <b>must_not</b>. In this case all non-empty values will be selected.</aside>
@@ -1429,6 +1748,10 @@ models.IsNullCondition(
}
```
```rust
Condition::is_null("reports")
```
This condition will match all records where the field `reports` exists and has `NULL` value.
@@ -1473,6 +1796,18 @@ client.scroll("{collection_name}", {
});
```
```rust
use qdrant_client::qdrant::{Condition, Filter, ScrollPoints};
client
.scroll(&ScrollPoints {
collection_name: "{collection_name}".to_string(),
filter: Some(Filter::must([Condition::has_id([1, 3, 5, 7, 9, 11])])),
..Default::default()
})
.await?;
```
Filtered points would be:
```json
@@ -57,6 +57,22 @@ client.createPayloadIndex("{collection_name}", {
});
```
```rust
use qdrant_client::{client::QdrantClient, qdrant::FieldType};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.create_field_index(
"{collection_name}",
"name_of_the_field_to_index",
FieldType::Keyword,
None,
None,
)
.await?;
```
Available field types are:
* `keyword` - for [keyword](../payload/#keyword) payload, affects [Match](../filtering/#match) filtering conditions.
@@ -133,6 +149,35 @@ client.createPayloadIndex("{collection_name}", {
});
```
```rust
use qdrant_client::{
client::QdrantClient,
qdrant::{
payload_index_params::IndexParams, FieldType, PayloadIndexParams, TextIndexParams,
TokenizerType,
},
};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.create_field_index(
"{collection_name}",
"name_of_the_field_to_index",
FieldType::Text,
Some(&PayloadIndexParams {
index_params: Some(IndexParams::TextIndexParams(TextIndexParams {
tokenizer: TokenizerType::Word as i32,
min_token_len: Some(2),
max_token_len: Some(10),
lowercase: Some(true),
})),
}),
None,
)
.await?;
```
Available tokenizers are:
* `word` - splits the string into words, separated by spaces, punctuation marks, and special characters.
@@ -236,6 +236,47 @@ client.upsert("{collection_name}", {
});
```
```rust
use qdrant_client::{client::QdrantClient, qdrant::PointStruct};
use serde_json::json;
let client = QdrantClient::from_url("http://localhost:6334").build()?;
let points = vec![
PointStruct::new(
1,
vec![0.05, 0.61, 0.76, 0.74],
json!(
{"city": "Berlin", "price": 1.99}
)
.try_into()
.unwrap(),
),
PointStruct::new(
2,
vec![0.19, 0.81, 0.75, 0.11],
json!(
{"city": ["Berlin", "London"]}
)
.try_into()
.unwrap(),
),
PointStruct::new(
3,
vec![0.36, 0.55, 0.47, 0.94],
json!(
{"city": ["Berlin", "Moscow"], "price": [1.99, 2.99]}
)
.try_into()
.unwrap(),
),
];
client
.upsert_points("{collection_name}".to_string(), points, None)
.await?;
```
## Update payload
### Set payload
@@ -277,6 +318,31 @@ client.setPayload("{collection_name}", {
});
```
```rust
use qdrant_client::qdrant::{
points_selector::PointsSelectorOneOf, PointsIdsList, PointsSelector,
};
use serde_json::json;
client
.set_payload(
"{collection_name}",
&PointsSelector {
points_selector_one_of: Some(PointsSelectorOneOf::Points(PointsIdsList {
ids: vec![0.into(), 3.into(), 10.into()],
})),
},
json!({
"property1": "string",
"property2": "string",
})
.try_into()
.unwrap(),
None,
)
.await?;
```
### Delete payload
This method removes specified payload keys from specified points
@@ -303,10 +369,29 @@ client.delete_payload(
```typescript
client.deletePayload("{collection_name}", {
keys: ["color", "price"],
points: [0, 3, 10],
points: [0, 3, 100],
});
```
```rust
use qdrant_client::qdrant::{
points_selector::PointsSelectorOneOf, PointsIdsList, PointsSelector,
};
client
.delete_payload(
"{collection_name}",
&PointsSelector {
points_selector_one_of: Some(PointsSelectorOneOf::Points(PointsIdsList {
ids: vec![0.into(), 3.into(), 100.into()],
})),
},
vec!["color".to_string(), "price".to_string()],
None,
)
.await?;
```
### Clear payload
This method removes all payload keys from specified points
@@ -336,6 +421,24 @@ client.clearPayload("{collection_name}", {
});
```
```rust
use qdrant_client::qdrant::{
points_selector::PointsSelectorOneOf, PointsIdsList, PointsSelector,
};
client
.clear_payload(
"{collection_name}",
Some(PointsSelector {
points_selector_one_of: Some(PointsSelectorOneOf::Points(PointsIdsList {
ids: vec![0.into(), 3.into(), 100.into()],
})),
}),
None,
)
.await?;
```
<aside role="status">You can also use `models.FilterSelector` to remove the points matching given filter criteria, instead of providing the ids.</aside>
## Payload indexing
@@ -377,6 +480,20 @@ client.createPayloadIndex("{collection_name}", {
});
```
```rust
use qdrant_client::qdrant::FieldType;
client
.create_field_index(
"{collection_name}",
"name_of_the_field_to_index",
FieldType::Keyword,
None,
None,
)
.await?;
```
The index usage flag is displayed in the payload schema with the [collection info API](https://qdrant.github.io/qdrant/redoc/index.html#operation/get_collection).
Payload schema example:
@@ -120,6 +120,29 @@ client.upsert("{collection_name}", {
});
```
```rust
use qdrant_client::{client::QdrantClient, qdrant::PointStruct};
use serde_json::json;
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.upsert_points_blocking(
"{collection_name}".to_string(),
vec![PointStruct::new(
"5c56c793-69f3-4fbf-87e6-c4bf54c28c26".to_string(),
vec![0.05, 0.61, 0.76, 0.74],
json!(
{"color": "Red"}
)
.try_into()
.unwrap(),
)],
None,
)
.await?;
```
and
```http
@@ -165,6 +188,27 @@ client.upsert("{collection_name}", {
});
```
```rust
use qdrant_client::qdrant::PointStruct;
use serde_json::json;
client
.upsert_points_blocking(
1,
vec![PointStruct::new(
"5c56c793-69f3-4fbf-87e6-c4bf54c28c26".to_string(),
vec![0.05, 0.61, 0.76, 0.74],
json!(
{"color": "Red"}
)
.try_into()
.unwrap(),
)],
None,
)
.await?;
```
are both possible.
## Upload points
@@ -230,6 +274,7 @@ client.upsert("{collection_name}", {
});
```
or record-oriented equivalent:
```http
@@ -307,6 +352,48 @@ client.upsert("{collection_name}", {
});
```
```rust
use qdrant_client::qdrant::PointStruct;
use serde_json::json;
client
.upsert_points_batch_blocking(
"{collection_name}".to_string(),
vec![
PointStruct::new(
1,
vec![0.9, 0.1, 0.1],
json!(
{"color": "red"}
)
.try_into()
.unwrap(),
),
PointStruct::new(
2,
vec![0.1, 0.9, 0.1],
json!(
{"color": "green"}
)
.try_into()
.unwrap(),
),
PointStruct::new(
3,
vec![0.1, 0.1, 0.9],
json!(
{"color": "blue"}
)
.try_into()
.unwrap(),
),
],
None,
100,
)
.await?;
```
<!--
The Python client has additional features for loading points.
@@ -394,6 +481,42 @@ client.upsert("{collection_name}", {
});
```
```rust
use qdrant_client::qdrant::PointStruct;
use std::collections::HashMap;
client
.upsert_points_blocking(
"{collection_name}".to_string(),
vec![
PointStruct::new(
1,
HashMap::from([
("image".to_string(), vec![0.9, 0.1, 0.1, 0.2]),
(
"text".to_string(),
vec![0.4, 0.7, 0.1, 0.8, 0.1, 0.1, 0.9, 0.2],
),
]),
HashMap::new().into(),
),
PointStruct::new(
2,
HashMap::from([
("image".to_string(), vec![0.2, 0.1, 0.3, 0.9]),
(
"text".to_string(),
vec![0.5, 0.2, 0.7, 0.4, 0.7, 0.2, 0.3, 0.9],
),
]),
HashMap::new().into(),
),
],
None,
)
.await?;
```
*Available as of v1.2.0*
Named vectors are optional. When uploading points, some vectors may be omitted.
@@ -479,6 +602,36 @@ client.updateVectors("{collection_name}", {
});
```
```rust
use qdrant_client::qdrant::PointVectors;
use std::collections::HashMap;
client
.update_vectors_blocking(
"{collection_name}",
&[
PointVectors {
id: Some(1.into()),
vectors: Some(
HashMap::from([("image".to_string(), vec![0.1, 0.2, 0.3, 0.4])]).into(),
),
},
PointVectors {
id: Some(2.into()),
vectors: Some(
HashMap::from([(
"text".to_string(),
vec![0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2],
)])
.into(),
),
},
],
None,
)
.await?;
```
To update points and replace all of its vectors, see [uploading
points](#upload-points).
@@ -517,6 +670,27 @@ client.deleteVectors("{collection_name}", {
});
```
```rust
use qdrant_client::qdrant::{
points_selector::PointsSelectorOneOf, PointsIdsList, PointsSelector, VectorsSelector,
};
client
.delete_vectors_blocking(
"{collection_name}",
&PointsSelector {
points_selector_one_of: Some(PointsSelectorOneOf::Points(PointsIdsList {
ids: vec![0.into(), 3.into(), 10.into()],
})),
},
&VectorsSelector {
names: vec!["text".into(), "image".into()],
},
None,
)
.await?;
```
To delete entire points, see [deleting points](#delete-points).
### Set payload
@@ -560,6 +734,31 @@ client.setPayload("{collection_name}", {
});
```
```rust
use qdrant_client::qdrant::{
points_selector::PointsSelectorOneOf, PointsIdsList, PointsSelector,
};
use serde_json::json;
client
.set_payload_blocking(
"{collection_name}",
&PointsSelector {
points_selector_one_of: Some(PointsSelectorOneOf::Points(PointsIdsList {
ids: vec![0.into(), 3.into(), 10.into()],
})),
},
json!({
"property1": "string",
"property2": "string",
})
.try_into()
.unwrap(),
None,
)
.await?;
```
You don't need to know the ids of the points you want to modify. The alternative
is to use filters.
@@ -621,6 +820,31 @@ client.setPayload("{collection_name}", {
});
```
```rust
use qdrant_client::qdrant::{
points_selector::PointsSelectorOneOf, Condition, Filter, PointsSelector,
};
use serde_json::json;
client
.set_payload_blocking(
"{collection_name}",
&PointsSelector {
points_selector_one_of: Some(PointsSelectorOneOf::Filter(Filter::must([
Condition::matches("color", "red".to_string()),
]))),
},
json!({
"property1": "string",
"property2": "string",
})
.try_into()
.unwrap(),
None,
)
.await?;
```
### Overwrite payload
Fully replace any existing payload with the given one.
@@ -662,7 +886,32 @@ client.overwritePayload("{collection_name}", {
});
```
Like [set payload](#set-payload], you don't need to know the ids of the points
```rust
use qdrant_client::qdrant::{
points_selector::PointsSelectorOneOf, PointsIdsList, PointsSelector,
};
use serde_json::json;
client
.overwrite_payload_blocking(
"{collection_name}",
&PointsSelector {
points_selector_one_of: Some(PointsSelectorOneOf::Points(PointsIdsList {
ids: vec![0.into(), 3.into(), 10.into()],
})),
},
json!({
"property1": "string",
"property2": "string",
})
.try_into()
.unwrap(),
None,
)
.await?;
```
Like [set payload](#set-payload), you don't need to know the ids of the points
you want to modify. The alternative is to use filters.
### Delete payload keys
@@ -689,10 +938,29 @@ client.delete_payload(
```typescript
client.deletePayload("{collection_name}", {
keys: ["color", "price"],
points: [0, 3, 10],
points: [0, 3, 100],
});
```
```rust
use qdrant_client::qdrant::{
points_selector::PointsSelectorOneOf, PointsIdsList, PointsSelector,
};
client
.delete_payload_blocking(
"{collection_name}",
&PointsSelector {
points_selector_one_of: Some(PointsSelectorOneOf::Points(PointsIdsList {
ids: vec![0.into(), 3.into(), 100.into()],
})),
},
vec!["color".to_string(), "price".to_string()],
None,
)
.await?;
```
Alternatively, you can use filters to delete payload keys from the points.
```http
@@ -744,6 +1012,25 @@ client.deletePayload("{collection_name}", {
});
```
```rust
use qdrant_client::qdrant::{
points_selector::PointsSelectorOneOf, Condition, Filter, PointsSelector,
};
client
.delete_payload_blocking(
"{collection_name}",
&PointsSelector {
points_selector_one_of: Some(PointsSelectorOneOf::Filter(Filter::must([
Condition::matches("color", "red".to_string()),
]))),
},
vec!["color".to_string(), "price".to_string()],
None,
)
.await?;
```
### Clear payload
This method removes all payload keys from specified points
@@ -769,10 +1056,28 @@ client.clear_payload(
```typescript
client.clearPayload("{collection_name}", {
points: [0, 3, 10],
points: [0, 3, 100],
});
```
```rust
use qdrant_client::qdrant::{
points_selector::PointsSelectorOneOf, PointsIdsList, PointsSelector,
};
client
.clear_payload_blocking(
"{collection_name}",
Some(PointsSelector {
points_selector_one_of: Some(PointsSelectorOneOf::Points(PointsIdsList {
ids: vec![0.into(), 3.into(), 100.into()],
})),
}),
None,
)
.await?;
```
## Delete points
REST API ([Schema](https://qdrant.github.io/qdrant/redoc/index.html#operation/delete_points)):
@@ -796,10 +1101,28 @@ client.delete(
```typescript
client.delete("{collection_name}", {
points: [0, 3, 10],
points: [0, 3, 100],
});
```
```rust
use qdrant_client::qdrant::{
points_selector::PointsSelectorOneOf, PointsIdsList, PointsSelector,
};
client
.delete_points_blocking(
"{collection_name}",
&PointsSelector {
points_selector_one_of: Some(PointsSelectorOneOf::Points(PointsIdsList {
ids: vec![0.into(), 3.into(), 100.into()],
})),
},
None,
)
.await?;
```
Alternative way to specify which points to remove is to use filter.
```http
@@ -850,6 +1173,24 @@ client.delete("{collection_name}", {
});
```
```rust
use qdrant_client::qdrant::{
points_selector::PointsSelectorOneOf, Condition, Filter, PointsSelector,
};
client
.delete_points_blocking(
"{collection_name}",
&PointsSelector {
points_selector_one_of: Some(PointsSelectorOneOf::Filter(Filter::must([
Condition::matches("color", "red".to_string()),
]))),
},
None,
)
.await?;
```
This example removes all points with `{ "color": "red" }` from the collection.
## Retrieve points
@@ -869,16 +1210,28 @@ POST /collections/{collection_name}/points
```python
client.retrieve(
collection_name="{collection_name}",
ids=[0, 3, 10],
ids=[0, 3, 100],
)
```
```typescript
client.retrieve("{collection_name}", {
ids: [0, 3, 10],
ids: [0, 3, 100],
});
```
```rust
client
.get_points(
"{collection_name}",
&[0.into(), 30.into(), 100.into()],
Some(false),
Some(false),
None,
)
.await?;
```
This method has additional parameters `with_vectors` and `with_payload`.
Using these parameters, you can select parts of the point you want as a result.
Excluding helps you not to waste traffic transmitting useless data.
@@ -955,6 +1308,24 @@ client.scroll("{collection_name}", {
});
```
```rust
use qdrant_client::qdrant::{Condition, Filter, ScrollPoints};
client
.scroll(&ScrollPoints {
collection_name: "{collection_name}".to_string(),
filter: Some(Filter::must([Condition::matches(
"color",
"red".to_string(),
)])),
limit: Some(1),
with_payload: Some(true.into()),
with_vectors: Some(false.into()),
..Default::default()
})
.await?;
```
Returns all point with `color` = `red`.
```json
@@ -1047,6 +1418,21 @@ client.count("{collection_name}", {
});
```
```rust
use qdrant_client::qdrant::{Condition, CountPoints, Filter};
client
.count(&CountPoints {
collection_name: "{collection_name}".to_string(),
filter: Some(Filter::must([Condition::matches(
"color",
"red".to_string(),
)])),
exact: Some(true),
})
.await?;
```
Returns number of counts matching given filtering conditions:
```json
@@ -1262,5 +1648,111 @@ client.batchUpdate("{collection_name}", {
});
```
```rust
use qdrant_client::qdrant::{
points_selector::PointsSelectorOneOf,
points_update_operation::{
DeletePayload, DeleteVectors, Operation, PointStructList, SetPayload, UpdateVectors,
},
PointStruct, PointVectors, PointsIdsList, PointsSelector, PointsUpdateOperation,
VectorsSelector,
};
use serde_json::json;
use std::collections::HashMap;
client
.batch_updates_blocking(
"{collection_name}",
&[
PointsUpdateOperation {
operation: Some(Operation::Upsert(PointStructList {
points: vec![PointStruct::new(
1,
vec![1.0, 2.0, 3.0, 4.0],
json!({}).try_into().unwrap(),
)],
})),
},
PointsUpdateOperation {
operation: Some(Operation::UpdateVectors(UpdateVectors {
points: vec![PointVectors {
id: Some(1.into()),
vectors: Some(vec![1.0, 2.0, 3.0, 4.0].into()),
}],
})),
},
PointsUpdateOperation {
operation: Some(Operation::DeleteVectors(DeleteVectors {
points_selector: Some(PointsSelector {
points_selector_one_of: Some(PointsSelectorOneOf::Points(
PointsIdsList {
ids: vec![1.into()],
},
)),
}),
vectors: Some(VectorsSelector {
names: vec!["".into()],
}),
})),
},
PointsUpdateOperation {
operation: Some(Operation::OverwritePayload(SetPayload {
points_selector: Some(PointsSelector {
points_selector_one_of: Some(PointsSelectorOneOf::Points(
PointsIdsList {
ids: vec![1.into()],
},
)),
}),
payload: HashMap::from([("test_payload".to_string(), 1.into())]),
})),
},
PointsUpdateOperation {
operation: Some(Operation::SetPayload(SetPayload {
points_selector: Some(PointsSelector {
points_selector_one_of: Some(PointsSelectorOneOf::Points(
PointsIdsList {
ids: vec![1.into()],
},
)),
}),
payload: HashMap::from([
("test_payload_2".to_string(), 2.into()),
("test_payload_3".to_string(), 3.into()),
]),
})),
},
PointsUpdateOperation {
operation: Some(Operation::DeletePayload(DeletePayload {
points_selector: Some(PointsSelector {
points_selector_one_of: Some(PointsSelectorOneOf::Points(
PointsIdsList {
ids: vec![1.into()],
},
)),
}),
keys: vec!["test_payload_2".to_string()],
})),
},
PointsUpdateOperation {
operation: Some(Operation::ClearPayload(PointsSelector {
points_selector_one_of: Some(PointsSelectorOneOf::Points(PointsIdsList {
ids: vec![1.into()],
})),
})),
},
PointsUpdateOperation {
operation: Some(Operation::Delete(PointsSelector {
points_selector_one_of: Some(PointsSelectorOneOf::Points(PointsIdsList {
ids: vec![1.into()],
})),
})),
},
],
None,
)
.await?;
```
To batch many points with a single operation type, please use batching
functionality in that operation directly.
@@ -131,6 +131,33 @@ client.search("{collection_name}", {
});
```
```rust
use qdrant_client::{
client::QdrantClient,
qdrant::{Condition, Filter, SearchParams, SearchPoints},
};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.search_points(&SearchPoints {
collection_name: "{collection_name}".to_string(),
filter: Some(Filter::must([Condition::matches(
"city",
"London".to_string(),
)])),
params: Some(SearchParams {
hnsw_ef: Some(128),
exact: Some(false),
..Default::default()
}),
vector: vec![0.2, 0.1, 0.9, 0.7],
limit: 3,
..Default::default()
})
.await?;
```
In this example, we are looking for vectors similar to vector `[0.2, 0.1, 0.9, 0.7]`.
Parameter `limit` (or its alias - `top`) specifies the amount of most similar results we would like to retrieve.
@@ -207,6 +234,22 @@ client.search("{collection_name}", {
});
```
```rust
use qdrant_client::{client::QdrantClient, qdrant::SearchPoints};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.search_points(&SearchPoints {
collection_name: "{collection_name}".to_string(),
vector: vec![0.2, 0.1, 0.9, 0.7],
vector_name: Some("image".to_string()),
limit: 3,
..Default::default()
})
.await?;
```
Search is processing only among vectors with the same name.
### Filtering results by score
@@ -253,6 +296,23 @@ client.search("{collection_name}", {
});
```
```rust
use qdrant_client::{client::QdrantClient, qdrant::SearchPoints};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.search_points(&SearchPoints {
collection_name: "{collection_name}".to_string(),
vector: vec![0.2, 0.1, 0.9, 0.7],
with_payload: Some(true.into()),
with_vectors: Some(true.into()),
limit: 3,
..Default::default()
})
.await?;
```
You can use `with_payload` to scope to or filter a specific payload subset.
You can even specify an array of items to include, such as `city`,
`village`, and `town`:
@@ -290,6 +350,22 @@ client.search("{collection_name}", {
});
```
```rust
use qdrant_client::{client::QdrantClient, qdrant::SearchPoints};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.search_points(&SearchPoints {
collection_name: "{collection_name}".to_string(),
vector: vec![0.2, 0.1, 0.9, 0.7],
with_payload: Some(vec!["city", "village", "town"].into()),
limit: 3,
..Default::default()
})
.await?;
```
Or use `include` or `exclude` explicitly. For example, to exclude `city`:
```http
@@ -331,6 +407,32 @@ client.search("{collection_name}", {
});
```
```rust
use qdrant_client::{
client::QdrantClient,
qdrant::{
with_payload_selector::SelectorOptions, PayloadIncludeSelector, SearchPoints,
WithPayloadSelector,
},
};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.search_points(&SearchPoints {
collection_name: "{collection_name}".to_string(),
vector: vec![0.2, 0.1, 0.9, 0.7],
with_payload: Some(WithPayloadSelector {
selector_options: Some(SelectorOptions::Include(PayloadIncludeSelector {
fields: vec!["city".to_string()],
})),
}),
limit: 3,
..Default::default()
})
.await?;
```
It is possible to target nested fields using a dot notation:
- `payload.nested_field` - for a nested field
- `payload.nested_array[].sub_field` - for projecting nested fields within an array
@@ -449,6 +551,42 @@ client.searchBatch("{collection_name}", {
});
```
```rust
use qdrant_client::{
client::QdrantClient,
qdrant::{Condition, Filter, SearchBatchPoints, SearchPoints},
};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
let filter = Filter::must([Condition::matches("city", "London".to_string())]);
let searches = vec![
SearchPoints {
collection_name: "{collection_name}".to_string(),
vector: vec![0.2, 0.1, 0.9, 0.7],
filter: Some(filter.clone()),
limit: 3,
..Default::default()
},
SearchPoints {
collection_name: "{collection_name}".to_string(),
vector: vec![0.5, 0.3, 0.2, 0.3],
filter: Some(filter),
limit: 3,
..Default::default()
},
];
client
.search_batch_points(&SearchBatchPoints {
collection_name: "{collection_name}".to_string(),
search_points: searches,
read_consistency: None,
})
.await?;
```
The result of this API contains one array per search requests.
```json
@@ -544,6 +682,32 @@ client.recommend("{collection_name}", {
});
```
```rust
use qdrant_client::{
client::QdrantClient,
qdrant::{Condition, Filter, RecommendPoints, RecommendStrategy},
};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.recommend(&RecommendPoints {
collection_name: "{collection_name}".to_string(),
positive: vec![100.into(), 200.into()],
positive_vectors: vec![vec![100.0, 231.0].into()],
negative: vec![718.into()],
negative_vectors: vec![vec![0.2, 0.3, 0.4, 0.5].into()],
strategy: Some(RecommendStrategy::AverageVector.into()),
filter: Some(Filter::must([Condition::matches(
"city",
"London".to_string(),
)])),
limit: 3,
..Default::default()
})
.await?;
```
Example result of this API would be
```json
@@ -582,11 +746,11 @@ A new strategy introduced in v1.6, is called `best_score`. It is based on the id
The way it works is that each candidate is measured against every example, then we select the best positive and best negative scores. The final score is chosen with this step formula:
```rust
if best_positive_score > best_negative_score {
score = best_positive_score
let score = if best_positive_score > best_negative_score {
best_positive_score;
} else {
score = -(best_negative_score * best_negative_score)
}
-(best_negative_score * best_negative_score);
};
```
<aside role="alert">The performance of `best_score` strategy will be linearly impacted by the amount of examples.</aside>
@@ -637,6 +801,21 @@ client.recommend("{collection_name}", {
});
```
```rust
use qdrant_client::qdrant::RecommendPoints;
client
.recommend(&RecommendPoints {
collection_name: "{collection_name}".to_string(),
positive: vec![100.into(), 231.into()],
negative: vec![718.into()],
using: Some("image".to_string()),
limit: 10,
..Default::default()
})
.await?;
```
Parameter `using` specifies which stored vectors to use for the recommendation.
## Batch recommendation API
@@ -746,6 +925,44 @@ client.recommend_batch("{collection_name}", {
});
```
```rust
use qdrant_client::{
client::QdrantClient,
qdrant::{Condition, Filter, RecommendBatchPoints, RecommendPoints},
};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
let filter = Filter::must([Condition::matches("city", "London".to_string())]);
let recommend_queries = vec![
RecommendPoints {
collection_name: "{collection_name}".to_string(),
positive: vec![100.into(), 231.into()],
negative: vec![718.into()],
filter: Some(filter.clone()),
limit: 3,
..Default::default()
},
RecommendPoints {
collection_name: "{collection_name}".to_string(),
positive: vec![200.into(), 67.into()],
negative: vec![300.into()],
filter: Some(filter),
limit: 3,
..Default::default()
},
];
client
.recommend_batch(&RecommendBatchPoints {
collection_name: "{collection_name}".to_string(),
recommend_points: recommend_queries,
..Default::default()
})
.await?;
```
The result of this API contains one array per recommendation requests.
```json
@@ -816,6 +1033,24 @@ client.search("{collection_name}", {
});
```
```rust
use qdrant_client::{client::QdrantClient, qdrant::SearchPoints};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.search_points(&SearchPoints {
collection_name: "{collection_name}".to_string(),
vector: vec![0.2, 0.1, 0.9, 0.7],
with_vectors: Some(true.into()),
with_payload: Some(true.into()),
limit: 10,
offset: Some(100),
..Default::default()
})
.await?;
```
Is equivalent to retrieving the 11th page with 10 records per page.
<aside role="alert">Large offset values may cause performance issues</aside>
@@ -914,7 +1149,7 @@ POST /collections/{collection_name}/points/search/groups
client.search_groups(
collection_name="{collection_name}",
# Same as in the regular search() API
query_vector=[1.1],
query_vector=g,
# Grouping parameters
group_by="document_id", # Path of the field to group by
limit=4, # Max amount of groups
@@ -931,6 +1166,21 @@ client.searchPointGroups("{collection_name}", {
});
```
```rust
use qdrant_client::qdrant::SearchPointGroups;
client
.search_groups(&SearchPointGroups {
collection_name: "{collection_name}".to_string(),
vector: vec![1.1],
group_by: "document_id".to_string(),
limit: 4,
group_size: 2,
..Default::default()
})
.await?;
```
### Recommend groups
REST API ([Schema](https://qdrant.github.io/qdrant/redoc/index.html#tag/points/operation/recommend_point_groups)):
@@ -973,6 +1223,22 @@ client.recommendPointGroups("{collection_name}", {
});
```
```rust
use qdrant_client::qdrant::RecommendPointGroups;
client
.recommend_groups(&RecommendPointGroups {
collection_name: "{collection_name}".to_string(),
positive: vec![1.into()],
negative: vec![2.into(), 5.into()],
group_by: "document_id".to_string(),
limit: 4,
group_size: 10,
..Default::default()
})
.await?;
```
In either case (search or recommend), the output would look like this:
```json
@@ -983,13 +1249,13 @@ In either case (search or recommend), the output would look like this:
"id": "a",
"hits": [
{ "id": 0, "score": 0.91 },
{ "id": 1, "score": 0.85 },
{ "id": 1, "score": 0.85 }
]
},
{
"id": "b",
"hits": [
{ "id": 1, "score": 0.85 },
{ "id": 1, "score": 0.85 }
]
},
{
@@ -1097,13 +1363,33 @@ client.searchPointGroups("{collection_name}", {
limit: 2,
group_size: 2,
with_lookup: {
collection: "documents",
collection: w,
with_payload: ["title", "text"],
with_vectors: false,
},
});
```
```rust
use qdrant_client::qdrant::{SearchPointGroups, WithLookup};
client
.search_groups(&SearchPointGroups {
collection_name: "{collection_name}".to_string(),
vector: vec![1.1],
group_by: "document_id".to_string(),
limit: 2,
group_size: 2,
with_lookup: Some(WithLookup {
collection: "documents".to_string(),
with_payload: Some(vec!["title", "text"].into()),
with_vectors: Some(false.into()),
}),
..Default::default()
})
.await?;
```
For the `with_lookup` parameter, you can also use the shorthand `with_lookup="documents"` to bring the whole payload and vector(s) without explicitly specifying it.
The looked up result will show up under `lookup` in each group.
@@ -59,6 +59,14 @@ const client = new QdrantClient({ host: "localhost", port: 6333 });
client.createSnapshot("{collection_name}");
```
```rust
use qdrant_client::client::QdrantClient;
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client.create_snapshot("{collection_name}").await?;
```
This is a synchronous operation for which a `tar` archive file will be generated into the `snapshot_path`.
### Delete snapshot
@@ -87,6 +95,14 @@ const client = new QdrantClient({ host: "localhost", port: 6333 });
client.deleteSnapshot("{collection_name}", "{snapshot_name}");
```
```rust
use qdrant_client::client::QdrantClient;
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client.delete_snapshot("{collection_name}", "{snapshot_name}").await?;
```
## List snapshot
List of snapshots for a collection:
@@ -111,6 +127,14 @@ const client = new QdrantClient({ host: "localhost", port: 6333 });
client.listSnapshots("{collection_name}");
```
```rust
use qdrant_client::client::QdrantClient;
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client.list_snapshots("{collection_name}").await?;
```
## Retrieve snapshot
<aside role="status">Only available through the REST API for the time being.</aside>
@@ -229,6 +253,14 @@ const client = new QdrantClient({ host: "localhost", port: 6333 });
client.createFullSnapshot();
```
```rust
use qdrant_client::client::QdrantClient;
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client.create_full_snapshot().await?;
```
### Delete full storage snapshot
*Available as of v1.0.0*
@@ -253,6 +285,14 @@ const client = new QdrantClient({ host: "localhost", port: 6333 });
client.deleteFullSnapshot("{snapshot_name}");
```
```rust
use qdrant_client::client::QdrantClient;
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client.delete_full_snapshot("{snapshot_name}").await?;
```
### List full storage snapshots
```http
@@ -275,6 +315,14 @@ const client = new QdrantClient({ host: "localhost", port: 6333 });
client.listFullSnapshots();
```
```rust
use qdrant_client::client::QdrantClient;
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client.list_full_snapshots().await?;
```
### Download full storage snapshot
<aside role="status">Only available through the REST API for the time being.</aside>
@@ -81,6 +81,30 @@ client.createCollection("{collection_name}", {
});
```
```rust
use qdrant_client::{
client::QdrantClient,
qdrant::{vectors_config::Config, CreateCollection, Distance, VectorParams, VectorsConfig},
};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.create_collection(&CreateCollection {
collection_name: "{collection_name}".to_string(),
vectors_config: Some(VectorsConfig {
config: Some(Config::Params(VectorParams {
size: 768,
distance: Distance::Cosine.into(),
on_disk: Some(true),
..Default::default()
})),
}),
..Default::default()
})
.await?;
```
This will create a collection with all vectors immediately stored in memmap storage.
This is the recommended way, in case your Qdrant instance operates with fast disks and you are working with large collections.
@@ -134,6 +158,36 @@ client.createCollection("{collection_name}", {
});
```
```rust
use qdrant_client::{
client::QdrantClient,
qdrant::{
vectors_config::Config, CreateCollection, Distance, OptimizersConfigDiff, VectorParams,
VectorsConfig,
},
};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.create_collection(&CreateCollection {
collection_name: "{collection_name}".to_string(),
vectors_config: Some(VectorsConfig {
config: Some(Config::Params(VectorParams {
size: 768,
distance: Distance::Cosine.into(),
..Default::default()
})),
}),
optimizers_config: Some(OptimizersConfigDiff {
memmap_threshold: Some(20000),
..Default::default()
}),
..Default::default()
})
.await?;
```
The rule of thumb to set the memmap threshold parameter is simple:
- if you have a balanced use scenario - set memmap threshold the same as `indexing_threshold` (default is 20000). In this case the optimizer will not make any extra runs and will optimize all thresholds at once.
@@ -191,6 +245,40 @@ client.createCollection("{collection_name}", {
});
```
```rust
use qdrant_client::{
client::QdrantClient,
qdrant::{
vectors_config::Config, CreateCollection, Distance, HnswConfigDiff,
OptimizersConfigDiff, VectorParams, VectorsConfig,
},
};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.create_collection(&CreateCollection {
collection_name: "{collection_name}".to_string(),
vectors_config: Some(VectorsConfig {
config: Some(Config::Params(VectorParams {
size: 768,
distance: Distance::Cosine.into(),
..Default::default()
})),
}),
optimizers_config: Some(OptimizersConfigDiff {
memmap_threshold: Some(20000),
..Default::default()
}),
hnsw_config: Some(HnswConfigDiff {
on_disk: Some(true),
..Default::default()
}),
..Default::default()
})
.await?;
```
## Payload storage
Qdrant supports two types of payload storages: InMemory and OnDisk.
@@ -31,11 +31,10 @@ cluster:
tick_period_ms: 100
```
With default configuration, Qdrant will use port `6335` for its internal communication.
By default, Qdrant will use port `6335` for its internal communication.
All peers should be accessible on this port from within the cluster, but make sure to isolate this port from outside access, as it might be used to perform write operations.
Additionally, the first peer of the cluster should be provided with its URL, so it could tell other nodes how it should be reached.
Use the `uri` CLI argument to provide the URL to the peer:
Additionally, you must provide the `--uri` flag to the first peer so it can tell other nodes how it should be reached:
```bash
./qdrant --uri 'http://qdrant_node_1:6335'
@@ -50,7 +49,7 @@ To do this, they need to be provided with a bootstrap URL:
```
The URL of the new peers themselves will be calculated automatically from the IP address of their request.
But it is also possible to provide them individually using `--uri` argument.
But it is also possible to provide them individually using the `--uri` argument.
```text
USAGE:
@@ -63,9 +62,9 @@ OPTIONS:
--uri <URI>
Uri of this peer. Other peers should be able to reach it by this uri.
This value has to be supplied if this is the first peer in a new deployment.
In case this is not the first peer and it bootstraps the value is optional. If not
supplied then qdrant will take internal grpc port from config and derive the IP address
of this peer on bootstrap peer (receiving side)
@@ -108,14 +107,14 @@ Example result:
## Raft
Qdrant is using the [Raft](https://raft.github.io/) consensus protocol to maintain consistency regarding the cluster topology and the collections structure.
Qdrant uses the [Raft](https://raft.github.io/) consensus protocol to maintain consistency regarding the cluster topology and the collections structure.
Operation with points, on the other hand, are not going through the consensus infrastructure.
Operations on points, on the other hand, do not go through the consensus infrastructure.
Qdrant is not intended to have strong transaction guarantees, which allows it to perform point operations with low overhead.
In practice, it means that Qdrant does not guarantee atomic distributed updates but allows you to wait until the [operation is complete](../../concepts/points/#awaiting-result) to see the results of your writes.
Collection operations, on the contrary, are part of the consensus which guarantees that all operations are durable and eventually executed by all nodes.
In practice it means that a majority of node agree on what operations should be applied before the service will perform them.
Operations on collections, on the contrary, are part of the consensus which guarantees that all operations are durable and eventually executed by all nodes.
In practice it means that a majority of nodes agree on what operations should be applied before the service will perform them.
Practically, it means that if the cluster is in a transition state - either electing a new leader after a failure or starting up, the collection update operations will be denied.
@@ -123,14 +122,13 @@ You may use the cluster [REST API](https://qdrant.github.io/qdrant/redoc/index.h
## Sharding
A Collection in Qdrant is made of one or several shards.
Each shard is an independent storage of points which is able to perform all operations provided by collections.
Points are distributed among shards according to the [consistent hashing](https://en.wikipedia.org/wiki/Consistent_hashing) algorithm, so that shards are managing non-intersecting subsets of points.
A Collection in Qdrant is made of one or more shards.
A shard is an independent store of points which is able to perform all operations provided by collections.
Points are distributed among shards by using a [consistent hashing](https://en.wikipedia.org/wiki/Consistent_hashing) algorithm, so that shards are managing non-intersecting subsets of points.
During the creation of the collection, shards are evenly distributed across all existing nodes.
Each node knows where all parts of the collection are stored through the [consensus protocol](./#raft), so if it is time to search - each node could query all other nodes to obtain the full search result.
Each node knows where all parts of the collection are stored through the [consensus protocol](./#raft), so when you send a search request to one Qdrant node, it automatically queries all other nodes to obtain the full search result.
You can define number of shards in your create-collection request:
When you create a collection, Qdrant splits the collection into `shard_number` shards. If left unset, `shard_number` is set to the number of nodes in your cluster:
```http
PUT /collections/{collection_name}
@@ -171,16 +169,41 @@ client.createCollection("{collection_name}", {
});
```
We recommend selecting the number of shards as a factor of the number of nodes you are currently running in your cluster.
```rust
use qdrant_client::{
client::QdrantClient,
qdrant::{vectors_config::Config, CreateCollection, Distance, VectorParams, VectorsConfig},
};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.create_collection(&CreateCollection {
collection_name: "{collection_name}".to_string(),
vectors_config: Some(VectorsConfig {
config: Some(Config::Params(VectorParams {
size: 300,
distance: Distance::Cosine.into(),
..Default::default()
})),
}),
shard_number: Some(6),
..Default::default()
})
.await?;
```
We recommend setting the number of shards to be a multiple of the number of nodes you are currently running in your cluster.
For example, if you have 3 nodes, 6 shards could be a good option.
### Cluster scaling
Shards are evenly distributed across all existing nodes when a collection is first created, but Qdrant does not automatically rebalance shards if your cluster size or replication factor changes (since this is an expensive operation on large clusters). See the next section for how to move shards after scaling operations.
If you want to extend your cluster with new nodes or some nodes become slower than the others, it might be helpful to re-balance the shard distribution in the cluster.
### Moving shards
*As of v0.9.0:* Qdrant allows moving shards between nodes in the cluster and removing nodes from the cluster.
*Available as of v0.9.0*
This functionality unlocks the ability to dynamically scale the cluster size without downtime.
Qdrant allows moving shards between nodes in the cluster and removing nodes from the cluster. This functionality unlocks the ability to dynamically scale the cluster size without downtime. It also allows you to upgrade or migrate nodes without downtime.
Qdrant provides the information regarding the current shard distribution in the cluster with the [Collection Cluster info API](https://qdrant.github.io/qdrant/redoc/index.html#tag/cluster/operation/collection_cluster_info).
@@ -198,27 +221,31 @@ POST /collections/{collection_name}/cluster
}
```
After the transfer is initiated, the service will keep both copies of the shard updated until the transfer is complete.
It will also make sure the transferred shard indexing process is keeping up before performing a final switch. This way, Qdrant ensures that there will be no degradation in performance at the end of the transfer.
After the transfer is initiated, the service will keep both copies of the shard in sync until the transfer is complete.
It will also make sure the transferred shard indexing process is keeping up before performing a final switch. This way, Qdrant ensures that there will be no degradation in performance at the end of the transfer. Once the transfer is completed, the old shard is deleted from the original node.
In case you want to downscale the cluster, you can move all shards away from a peer and then remove the peer using [Remove peer from the cluster API](https://qdrant.github.io/qdrant/redoc/index.html#tag/cluster/operation/remove_peer).
In case you want to downscale the cluster, you can move all shards away from a peer and then remove the peer using the [remove peer API](https://qdrant.github.io/qdrant/redoc/index.html#tag/cluster/operation/remove_peer).
```http
DELETE /cluster/peer/{peer_id}
```
After that, Qdrant will exclude the node from the consensus, and the instance will be ready for the shutdown.
After that, Qdrant will exclude the node from the consensus, and the instance will be ready for shutdown.
## Replication
*As of v0.11.0:* Qdrant allows to replicate shards between nodes in the cluster.
*Available as of v0.11.0*
Shard replication increases the reliability of the cluster by keeping several copies of a shard spread among the cluster.
This ensure the availability of the shards in case of node failures, except if all replicas are lost.
Qdrant allows you to replicate shards between nodes in the cluster.
By default, all the shards in a cluster have a replication factor of one, meaning no additional copy is maintained.
Shard replication increases the reliability of the cluster by keeping several copies of a shard spread across the cluster.
This ensures the availability of the data in case of node failures, except if all replicas are lost.
The replication factor of a collection can be configured at creation time.
### Replication factor
When you create a collection, you can control how many shard replicas you'd like to store by changing the `replication_factor`. By default, `replication_factor` is set to "1", meaning no additional copy is maintained automatically. You can change that by setting the `replication_factor` when you create a collection.
Currently, the replication factor of a collection can only be configured at creation time.
```http
PUT /collections/{collection_name}
@@ -262,11 +289,36 @@ client.createCollection("{collection_name}", {
});
```
```rust
use qdrant_client::{
client::QdrantClient,
qdrant::{vectors_config::Config, CreateCollection, Distance, VectorParams, VectorsConfig},
};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.create_collection(&CreateCollection {
collection_name: "{collection_name}".to_string(),
vectors_config: Some(VectorsConfig {
config: Some(Config::Params(VectorParams {
size: 300,
distance: Distance::Cosine.into(),
..Default::default()
})),
}),
shard_number: Some(6),
replication_factor: Some(2),
..Default::default()
})
.await?;
```
This code sample creates a collection with a total of 6 logical shards backed by a total of 12 physical shards.
It is advised to make sure the hardware can host the additional shards beforehand.
Since a replication factor of "2" would require twice as much storage space, it is advised to make sure the hardware can host the additional shard replicas beforehand.
### Scaling replication factor
### Creating new shard replicas
It is possible to create or delete replicas manually on an existing collection using the [Update collection cluster setup API](https://qdrant.github.io/qdrant/redoc/index.html?v=v0.11.0#tag/cluster/operation/update_collection_cluster).
@@ -301,7 +353,7 @@ Keep in mind that a collection must contain at least one active replica of a sha
### Error handling
Replicas can be in different state:
Replicas can be in different states:
- Active: healthy and ready to serve traffic
- Dead: unhealthy and not ready to serve traffic
@@ -315,59 +367,57 @@ This mechanism ensures data consistency and availability if a subset of the repl
### Node Failure Recovery
Sometimes hardware malfunctions might render some nodes of the qdrant cluster unrecoverable.
Sometimes hardware malfunctions might render some nodes of the Qdrant cluster unrecoverable.
No system is immune to this.
But several recovery scenarios allow qdrant to stay available for requests and even avoid performance degradation.
Let's walk throw them from best to worst.
Let's walk through them from best to worst.
**Recover with replicated collection**
If the number of failed nodes is less than the replication factor of the collection, you are service, then no data is lost.
If the number of failed nodes is less than the replication factor of the collection, then no data is lost.
Your cluster should still be able to perform read, search and update queries.
Now, if the failed node restarts, consensus will trigger the replication process to update the recovering node with the newest updates it has missed.
**Recreate node with replicated collections**
If it is impossible to recover, you should exclude the dead node from the consensus and create an empty node.
If a node fails and it is impossible to recover it, you should exclude the dead node from the consensus and create an empty node.
To exclude failed nodes from the consensus, use [remove peer](https://qdrant.github.io/qdrant/redoc/index.html#tag/cluster/operation/remove_peer) API.
Apply the `force` flag if necessary.
When you create a new node, make sure to attach it to the existing cluster by specifying `--bootstrap` CLI parameter with the URL of any of the running cluster nodes.
When you create a new node, make sure to attach it to the existing cluster by specifying `--bootstrap` CLI parameter with the URL of any of the running cluster nodes.
Once the new node is ready and synchronized with the cluster, you might want to ensure that the collection shards are replicated enough.
Use [Replicate Shard Operation](https://qdrant.github.io/qdrant/redoc/index.html#tag/cluster/operation/update_collection_cluster) to create another copy of the shard on the newly connected node.
Once the new node is ready and synchronized with the cluster, you might want to ensure that the collection shards are replicated enough. Remember that Qdrant will not automatically balance shards since this is an expensive operation.
Use the [Replicate Shard Operation](https://qdrant.github.io/qdrant/redoc/index.html#tag/cluster/operation/update_collection_cluster) to create another copy of the shard on the newly connected node.
Worth mentioning that Qdrant only provides the necessary building blocks to create an automated failure recovery.
It's worth mentioning that Qdrant only provides the necessary building blocks to create an automated failure recovery.
Building a completely automatic process of collection scaling would require control over the cluster machines themself.
Check out our [cloud solution](https://qdrant.to/cloud), where we made exactly that.
**Recover from snapshot**
If there are no copies of data in the cluster, it is still possible to recover from a snapshot.
Follow the same steps to detach failed node and create a new one in the cluster:
* To exclude failed nodes from the consensus, use [remove peer](https://qdrant.github.io/qdrant/redoc/index.html#tag/cluster/operation/remove_peer) API. Apply the `force` flag if necessary.
* Create a new node, making sure to attach it to the existing cluster by specifying `--bootstrap` CLI parameter with the URL of any of the running cluster nodes.
* Create a new node, making sure to attach it to the existing cluster by specifying the `--bootstrap` CLI parameter with the URL of any of the running cluster nodes.
Snapshot recovery, used in single-node deployment, is different from cluster one.
Consensus manages all metadata about all collections and does not require snapshots to recover it.
But you can use snapshots to recover missing shards of the collections.
Use [Collection Snapshot Recovery API](../../concepts/snapshots/#recover-in-cluster-deployment) to do it.
The service will download specified snapshot of the collection and recover shards with data from it.
Use the [Collection Snapshot Recovery API](../../concepts/snapshots/#recover-in-cluster-deployment) to do it.
The service will download the specified snapshot of the collection and recover shards with data from it.
Once all shards of the collection are recovered, the collection will become operational again.
## Consistency guarantees
By default, qdrant focuses on availability and maximum throughput of search operations.
By default, Qdrant focuses on availability and maximum throughput of search operations.
For the majority of use cases, this is a preferable trade-off.
During the normal state of operation, it is possible to search and modify data from any peers in the cluster.
@@ -384,8 +434,8 @@ However, in some cases, it is necessary to ensure additional guarantees during p
Qdrant provides a few options to control consistency guarantees:
- `write_consistency_factor` - defines the number of replicas that must acknowledge a write operation before responding to the client. Increasing this value will make write operations tolerant to network partitions in the cluster, but will require a higher number of replicas to be active to perform write operations.
- Read `consistency` param, can be used with search and retrieve operations to ensure that the results obtained from all replicas are the same. If this option is used, qdrant will perform the read operation on multiple replicas and resolve the result according to the selected strategy. This option is useful to avoid data inconsistency in case of concurrent updates of the same documents. This options is preferred if the update operations are frequent and the number of replicas is low.
- Write `ordering` param, can be used with update and delete operations to ensure that the operations are executed in the same order on all replicas. If this option is used, qdrant will route the operation to the leader replica of the shard and wait for the response before responding to the client. This option is useful to avoid data inconsistency in case of concurrent updates of the same documents. This options is preferred if read operations are more frequent than update and if search performance is critical.
- Read `consistency` param, can be used with search and retrieve operations to ensure that the results obtained from all replicas are the same. If this option is used, Qdrant will perform the read operation on multiple replicas and resolve the result according to the selected strategy. This option is useful to avoid data inconsistency in case of concurrent updates of the same documents. This options is preferred if the update operations are frequent and the number of replicas is low.
- Write `ordering` param, can be used with update and delete operations to ensure that the operations are executed in the same order on all replicas. If this option is used, Qdrant will route the operation to the leader replica of the shard and wait for the response before responding to the client. This option is useful to avoid data inconsistency in case of concurrent updates of the same documents. This options is preferred if read operations are more frequent than update and if search performance is critical.
### Write consistency factor
@@ -438,6 +488,32 @@ client.createCollection("{collection_name}", {
});
```
```rust
use qdrant_client::{
client::QdrantClient,
qdrant::{vectors_config::Config, CreateCollection, Distance, VectorParams, VectorsConfig},
};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.create_collection(&CreateCollection {
collection_name: "{collection_name}".to_string(),
vectors_config: Some(VectorsConfig {
config: Some(Config::Params(VectorParams {
size: 300,
distance: Distance::Cosine.into(),
..Default::default()
})),
}),
shard_number: Some(6),
replication_factor: Some(2),
write_consistency_factor: Some(2),
..Default::default()
})
.await?;
```
Write operations will fail if the number of active replicas is less than the `write_consistency_factor`.
### Read consistency
@@ -509,6 +585,39 @@ client.search("{collection_name}", {
});
```
```rust
use qdrant_client::{
client::QdrantClient,
qdrant::{
read_consistency::Value, Condition, Filter, ReadConsistency, ReadConsistencyType,
SearchParams, SearchPoints,
},
};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.search_points(&SearchPoints {
collection_name: "{collection_name}".to_string(),
filter: Some(Filter::must([Condition::matches(
"city",
"London".to_string(),
)])),
params: Some(SearchParams {
hnsw_ef: Some(128),
exact: Some(false),
..Default::default()
}),
vector: vec![0.2, 0.1, 0.9, 0.7],
limit: 3,
read_consistency: Some(ReadConsistency {
value: Some(Value::Type(ReadConsistencyType::Majority.into())),
}),
..Default::default()
})
.await?;
```
### Write ordering
Write `ordering` can be specified for any write request to serialize it through a single "leader" node,
@@ -575,12 +684,54 @@ client.upsert("{collection_name}", {
});
```
```rust
use qdrant_client::qdrant::{PointStruct, WriteOrdering, WriteOrderingType};
use serde_json::json;
client
.upsert_points_blocking(
"{collection_name}",
vec![
PointStruct::new(
1,
vec![0.9, 0.1, 0.1],
json!({
"color": "red"
})
.try_into()
.unwrap(),
),
PointStruct::new(
2,
vec![0.1, 0.9, 0.1],
json!({
"color": "green"
})
.try_into()
.unwrap(),
),
PointStruct::new(
3,
vec![0.1, 0.1, 0.9],
json!({
"color": "blue"
})
.try_into()
.unwrap(),
),
],
Some(WriteOrdering {
r#type: WriteOrderingType::Strong.into(),
}),
)
.await?;
```
## Listener mode
<aside role="alert">This is an experimental feature, its behavior may change in the future.</aside>
In some cases it might be useful to have a qdrant node that only accumulates data and does not participate in search operations.
In some cases it might be useful to have a Qdrant node that only accumulates data and does not participate in search operations.
There are several scenarios where this can be useful:
- Listener option can be used to store data in a separate node, which can be used for backup purposes or to store data for a long time.
@@ -621,4 +772,3 @@ POST /cluster/recover
This API can be triggered on any non-leader node, it will send a request to the current consensus leader to create a snapshot. The leader will in turn send the snapshot back to the requesting node for application.
In some cases, this API can be used to recover from an inconsistent cluster state by forcing a snapshot creation.
@@ -89,6 +89,49 @@ client.upsert("{collection_name}", {
});
```
```rust
use qdrant_client::{client::QdrantClient, qdrant::PointStruct};
use serde_json::json;
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.upsert_points_blocking(
"{collection_name}".to_string(),
vec![
PointStruct::new(
1,
vec![0.9, 0.1, 0.1],
json!(
{"group_id": "user_1"}
)
.try_into()
.unwrap(),
),
PointStruct::new(
2,
vec![0.1, 0.9, 0.1],
json!(
{"group_id": "user_1"}
)
.try_into()
.unwrap(),
),
PointStruct::new(
3,
vec![0.1, 0.1, 0.9],
json!(
{"group_id": "user_2"}
)
.try_into()
.unwrap(),
),
],
None,
)
.await?;
```
2. Use a filter along with `group_id` to filter vectors for each user.
```http
@@ -146,6 +189,28 @@ client.search("{collection_name}", {
});
```
```rust
use qdrant_client::{
client::QdrantClient,
qdrant::{Condition, Filter, SearchPoints},
};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.search_points(&SearchPoints {
collection_name: "{collection_name}".to_string(),
filter: Some(Filter::must([Condition::matches(
"group_id",
"user_1".to_string(),
)])),
vector: vec![0.1, 0.1, 0.9],
limit: 10,
..Default::default()
})
.await?;
```
## Calibrate performance
The speed of indexation may become a bottleneck in this case, as each user's vector will be indexed into the same collection. To avoid this bottleneck, consider _bypassing the construction of a global vector index_ for the entire collection and building it only for individual groups instead.
@@ -204,6 +269,37 @@ client.createCollection("{collection_name}", {
});
```
```rust
use qdrant_client::{
client::QdrantClient,
qdrant::{
vectors_config::Config, CreateCollection, Distance, HnswConfigDiff, VectorParams,
VectorsConfig,
},
};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.create_collection(&CreateCollection {
collection_name: "{collection_name}".to_string(),
vectors_config: Some(VectorsConfig {
config: Some(Config::Params(VectorParams {
size: 768,
distance: Distance::Cosine.into(),
..Default::default()
})),
}),
hnsw_config: Some(HnswConfigDiff {
payload_m: Some(16),
m: Some(0),
..Default::default()
}),
..Default::default()
})
.await?;
```
3. Create keyword payload index for `group_id` field.
```http
@@ -230,6 +326,22 @@ client.createPayloadIndex("{collection_name}", {
});
```
```rust
use qdrant_client::{client::QdrantClient, qdrant::FieldType};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.create_field_index(
"{collection_name}",
"group_id",
FieldType::Keyword,
None,
None,
)
.await?;
```
## Limitations
One downside to this approach is that global requests (without the `group_id` filter) will be slower since they will necessitate scanning all groups to identify the nearest neighbors.
@@ -83,6 +83,44 @@ client.createCollection("{collection_name}", {
});
```
```rust
use qdrant_client::{
client::QdrantClient,
qdrant::{
quantization_config::Quantization, vectors_config::Config, CreateCollection, Distance,
OptimizersConfigDiff, QuantizationConfig, QuantizationType, ScalarQuantization,
VectorParams, VectorsConfig,
},
};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.create_collection(&CreateCollection {
collection_name: "{collection_name}".to_string(),
vectors_config: Some(VectorsConfig {
config: Some(Config::Params(VectorParams {
size: 768,
distance: Distance::Cosine.into(),
..Default::default()
})),
}),
optimizers_config: Some(OptimizersConfigDiff {
memmap_threshold: Some(20000),
..Default::default()
}),
quantization_config: Some(QuantizationConfig {
quantization: Some(Quantization::Scalar(ScalarQuantization {
r#type: QuantizationType::Int8.into(),
always_ram: Some(true),
..Default::default()
})),
}),
..Default::default()
})
.await?;
```
`mmmap_threshold` will ensure that vectors will be stored on disk, while `always_ram` will ensure that quantized vectors will be stored in RAM.
Optionally, you can disable rescoring with search `params`, which will reduce the number of disk reads even further, but potentially slightly decrease the precision.
@@ -131,6 +169,31 @@ client.search("{collection_name}", {
});
```
```rust
use qdrant_client::{
client::QdrantClient,
qdrant::{QuantizationSearchParams, SearchParams, SearchPoints},
};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.search_points(&SearchPoints {
collection_name: "{collection_name}".to_string(),
vector: vec![0.2, 0.1, 0.9, 0.7],
params: Some(SearchParams {
quantization: Some(QuantizationSearchParams {
rescore: Some(false),
..Default::default()
}),
..Default::default()
}),
limit: 3,
..Default::default()
})
.await?;
```
## Prefer high precision with low memory footprint
In case you need high precision, but don't have enough RAM to store vectors in memory, you can enable on-disk vectors and HNSW index.
@@ -184,6 +247,40 @@ client.createCollection("{collection_name}", {
});
```
```rust
use qdrant_client::{
client::QdrantClient,
qdrant::{
vectors_config::Config, CreateCollection, Distance, HnswConfigDiff, OptimizersConfigDiff,
VectorParams, VectorsConfig,
},
};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.create_collection(&CreateCollection {
collection_name: "{collection_name}".to_string(),
vectors_config: Some(VectorsConfig {
config: Some(Config::Params(VectorParams {
size: 768,
distance: Distance::Cosine.into(),
..Default::default()
})),
}),
optimizers_config: Some(OptimizersConfigDiff {
memmap_threshold: Some(20000),
..Default::default()
}),
hnsw_config: Some(HnswConfigDiff {
on_disk: Some(true),
..Default::default()
}),
..Default::default()
})
.await?;
```
In this scenario you can increase the precision of the search by increasing the `ef` and `m` parameters of the HNSW index, even with limited RAM.
```json
@@ -267,6 +364,44 @@ client.createCollection("{collection_name}", {
});
```
```rust
use qdrant_client::{
client::QdrantClient,
qdrant::{
quantization_config::Quantization, vectors_config::Config, CreateCollection, Distance,
OptimizersConfigDiff, QuantizationConfig, QuantizationType, ScalarQuantization,
VectorParams, VectorsConfig,
},
};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.create_collection(&CreateCollection {
collection_name: "{collection_name}".to_string(),
vectors_config: Some(VectorsConfig {
config: Some(Config::Params(VectorParams {
size: 768,
distance: Distance::Cosine.into(),
..Default::default()
})),
}),
optimizers_config: Some(OptimizersConfigDiff {
memmap_threshold: Some(20000),
..Default::default()
}),
quantization_config: Some(QuantizationConfig {
quantization: Some(Quantization::Scalar(ScalarQuantization {
r#type: QuantizationType::Int8.into(),
always_ram: Some(true),
..Default::default()
})),
}),
..Default::default()
})
.await?;
```
There are also some search-time parameters you can use to tune the search accuracy and speed:
```http
@@ -310,6 +445,29 @@ client.search("{collection_name}", {
});
```
```rust
use qdrant_client::{
client::QdrantClient,
qdrant::{SearchParams, SearchPoints},
};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.search_points(&SearchPoints {
collection_name: "{collection_name}".to_string(),
vector: vec![0.2, 0.1, 0.9, 0.7],
params: Some(SearchParams {
hnsw_ef: Some(128),
exact: Some(false),
..Default::default()
}),
limit: 3,
..Default::default()
})
.await?;
```
- `hnsw_ef` - controls the number of neighbors to visit during search. The higher the value, the more accurate and slower the search will be. Recommended range is 32-512.
- `exact` - if set to `true`, will perform exact search, which will be slower, but more accurate. You can use it to compare results of the search with different `hnsw_ef` values versus the ground truth.
@@ -367,6 +525,36 @@ client.createCollection("{collection_name}", {
});
```
```rust
use qdrant_client::{
client::QdrantClient,
qdrant::{
vectors_config::Config, CreateCollection, Distance, OptimizersConfigDiff, VectorParams,
VectorsConfig,
},
};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.create_collection(&CreateCollection {
collection_name: "{collection_name}".to_string(),
vectors_config: Some(VectorsConfig {
config: Some(Config::Params(VectorParams {
size: 768,
distance: Distance::Cosine.into(),
..Default::default()
})),
}),
optimizers_config: Some(OptimizersConfigDiff {
default_segment_number: Some(16),
..Default::default()
}),
..Default::default()
})
.await?;
```
To prefer throughput, you can set up Qdrant to use as many cores as possible for processing multiple requests in parallel.
To do that, you can configure qdrant to use minimal number of segments, which is usually 2.
Large segments benefit from the size of the index and overall smaller number of vector comparisons required to find the nearest neighbors. But at the same time require more time to build index.
@@ -411,4 +599,34 @@ client.createCollection("{collection_name}", {
default_segment_number: 2,
},
});
```
```rust
use qdrant_client::{
client::QdrantClient,
qdrant::{
vectors_config::Config, CreateCollection, Distance, OptimizersConfigDiff, VectorParams,
VectorsConfig,
},
};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.create_collection(&CreateCollection {
collection_name: "{collection_name}".to_string(),
vectors_config: Some(VectorsConfig {
config: Some(Config::Params(VectorParams {
size: 768,
distance: Distance::Cosine.into(),
..Default::default()
})),
}),
optimizers_config: Some(OptimizersConfigDiff {
default_segment_number: Some(2),
..Default::default()
}),
..Default::default()
})
.await?;
```
@@ -204,6 +204,39 @@ client.createCollection("{collection_name}", {
});
```
```rust
use qdrant_client::{
client::QdrantClient,
qdrant::{
quantization_config::Quantization, vectors_config::Config, CreateCollection, Distance,
QuantizationConfig, QuantizationType, ScalarQuantization, VectorParams, VectorsConfig,
},
};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.create_collection(&CreateCollection {
collection_name: "{collection_name}".to_string(),
vectors_config: Some(VectorsConfig {
config: Some(Config::Params(VectorParams {
size: 768,
distance: Distance::Cosine.into(),
..Default::default()
})),
}),
quantization_config: Some(QuantizationConfig {
quantization: Some(Quantization::Scalar(ScalarQuantization {
r#type: QuantizationType::Int8.into(),
quantile: Some(0.99),
always_ram: Some(true),
})),
}),
..Default::default()
})
.await?;
```
There are 3 parameters that you can specify in the `quantization_config` section:
`type` - the type of the quantized vector components. Currently, Qdrant supports only `int8`.
@@ -276,6 +309,37 @@ client.createCollection("{collection_name}", {
});
```
```rust
use qdrant_client::{
client::QdrantClient,
qdrant::{
quantization_config::Quantization, vectors_config::Config, BinaryQuantization,
CreateCollection, Distance, QuantizationConfig, VectorParams, VectorsConfig,
},
};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.create_collection(&CreateCollection {
collection_name: "{collection_name}".to_string(),
vectors_config: Some(VectorsConfig {
config: Some(Config::Params(VectorParams {
size: 1536,
distance: Distance::Cosine.into(),
..Default::default()
})),
}),
quantization_config: Some(QuantizationConfig {
quantization: Some(Quantization::Binary(BinaryQuantization {
always_ram: Some(true),
})),
}),
..Default::default()
})
.await?;
```
`always_ram` - whether to keep quantized vectors always cached in RAM or not. By default, quantized vectors are loaded in the same way as the original vectors.
However, in some setups you might want to keep quantized vectors in RAM to speed up the search process.
@@ -339,6 +403,39 @@ client.createCollection("{collection_name}", {
});
```
```rust
use qdrant_client::{
client::QdrantClient,
qdrant::{
quantization_config::Quantization, vectors_config::Config, CompressionRatio,
CreateCollection, Distance, ProductQuantization, QuantizationConfig, VectorParams,
VectorsConfig,
},
};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.create_collection(&CreateCollection {
collection_name: "{collection_name}".to_string(),
vectors_config: Some(VectorsConfig {
config: Some(Config::Params(VectorParams {
size: 768,
distance: Distance::Cosine.into(),
..Default::default()
})),
}),
quantization_config: Some(QuantizationConfig {
quantization: Some(Quantization::Product(ProductQuantization {
compression: CompressionRatio::X16.into(),
always_ram: Some(true),
})),
}),
..Default::default()
})
.await?;
```
There are two parameters that you can specify in the `quantization_config` section:
`compression` - compression ratio.
@@ -408,6 +505,33 @@ client.search("{collection_name}", {
});
```
```rust
use qdrant_client::{
client::QdrantClient,
qdrant::{QuantizationSearchParams, SearchParams, SearchPoints},
};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.search_points(&SearchPoints {
collection_name: "{collection_name}".to_string(),
vector: vec![0.2, 0.1, 0.9, 0.7],
params: Some(SearchParams {
quantization: Some(QuantizationSearchParams {
ignore: Some(false),
rescore: Some(true),
oversampling: Some(2.0),
..Default::default()
}),
..Default::default()
}),
limit: 10,
..Default::default()
})
.await?;
```
`ignore` - Toggle whether to ignore quantized vectors during the search process. By default, Qdrant will use quantized vectors if they are available.
`rescore` - Having the original vectors available, Qdrant can re-evaluate top-k search results using the original vectors.
@@ -475,6 +599,31 @@ client.search("{collection_name}", {
});
```
```rust
use qdrant_client::{
client::QdrantClient,
qdrant::{QuantizationSearchParams, SearchParams, SearchPoints},
};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.search_points(&SearchPoints {
collection_name: "{collection_name}".to_string(),
vector: vec![0.2, 0.1, 0.9, 0.7],
params: Some(SearchParams {
quantization: Some(QuantizationSearchParams {
ignore: Some(true),
..Default::default()
}),
..Default::default()
}),
limit: 3,
..Default::default()
})
.await?;
```
- **Adjust the quantile parameter**: The quantile parameter in scalar quantization determines the quantization bounds.
By setting it to a value lower than 1.0, you can exclude extreme values (outliers) from the quantization bounds.
For example, if you set the quantile to 0.99, 1% of the extreme values will be excluded.
@@ -555,6 +704,44 @@ client.createCollection("{collection_name}", {
});
```
```rust
use qdrant_client::{
client::QdrantClient,
qdrant::{
quantization_config::Quantization, vectors_config::Config, CreateCollection, Distance,
OptimizersConfigDiff, QuantizationConfig, QuantizationType, ScalarQuantization,
VectorParams, VectorsConfig,
},
};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.create_collection(&CreateCollection {
collection_name: "{collection_name}".to_string(),
vectors_config: Some(VectorsConfig {
config: Some(Config::Params(VectorParams {
size: 768,
distance: Distance::Cosine.into(),
..Default::default()
})),
}),
optimizers_config: Some(OptimizersConfigDiff {
memmap_threshold: Some(20000),
..Default::default()
}),
quantization_config: Some(QuantizationConfig {
quantization: Some(Quantization::Scalar(ScalarQuantization {
r#type: QuantizationType::Int8.into(),
always_ram: Some(true),
..Default::default()
})),
}),
..Default::default()
})
.await?;
```
In this scenario, the number of disk reads may play a significant role in the search speed.
In a system with high disk latency, the re-scoring step may become a bottleneck.
@@ -603,6 +790,31 @@ client.search("{collection_name}", {
});
```
```rust
use qdrant_client::{
client::QdrantClient,
qdrant::{QuantizationSearchParams, SearchParams, SearchPoints},
};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.search_points(&SearchPoints {
collection_name: "{collection_name}".to_string(),
vector: vec![0.2, 0.1, 0.9, 0.7],
params: Some(SearchParams {
quantization: Some(QuantizationSearchParams {
rescore: Some(true),
..Default::default()
}),
..Default::default()
}),
limit: 3,
..Default::default()
})
.await?;
```
- **All on Disk** - all vectors, original and quantized, are stored on disk. This mode allows to achieve the smallest memory footprint, but at the cost of the search speed.
It is recommended to use this mode if you have a large collection and fast storage (e.g. SSD or NVMe).
@@ -667,4 +879,42 @@ client.createCollection("{collection_name}", {
},
},
});
```
```rust
use qdrant_client::{
client::QdrantClient,
qdrant::{
quantization_config::Quantization, vectors_config::Config, CreateCollection, Distance,
OptimizersConfigDiff, QuantizationConfig, QuantizationType, ScalarQuantization,
VectorParams, VectorsConfig,
},
};
let client = QdrantClient::from_url("http://localhost:6334").build()?;
client
.create_collection(&CreateCollection {
collection_name: "{collection_name}".to_string(),
vectors_config: Some(VectorsConfig {
config: Some(Config::Params(VectorParams {
size: 768,
distance: Distance::Cosine.into(),
..Default::default()
})),
}),
optimizers_config: Some(OptimizersConfigDiff {
memmap_threshold: Some(20000),
..Default::default()
}),
quantization_config: Some(QuantizationConfig {
quantization: Some(Quantization::Scalar(ScalarQuantization {
r#type: QuantizationType::Int8.into(),
always_ram: Some(false),
..Default::default()
})),
}),
..Default::default()
})
.await?;
```
@@ -83,6 +83,14 @@ var client = new QdrantClient(
);
```
```rust
use qdrant_client::client::QdrantClient;
let client = QdrantClient::from_url("https://xyz-example.eu-central.aws.cloud.qdrant.io:6334")
.with_api_key("<paste-your-api-key-here>")
.build()?;
```
<aside role="alert">Internal communication channels are <strong>never</strong> protected by an API key. Internal gRPC uses port 6335 by default if running in distributed mode. You must ensure that this port is not publicly reachable and can only be used for node communication. By default, this setting is disabled for Qdrant Cloud and the Qdrant Helm chart.</aside>
## TLS
@@ -148,6 +156,12 @@ import { QdrantClient } from "@qdrant/js-client-rest";
const client = new QdrantClient({ url: "https://localhost", port: 6333 });
```
```rust
use qdrant_client::client::QdrantClient;
let client = QdrantClient::from_url("https://localhost:6334").build()?;
```
Certificate rotation is enabled with a default refresh time of one hour. This
reloads certificate files every hour while Qdrant is running. This way changed
certificates are picked up when they get updated externally. The refresh time
@@ -0,0 +1,41 @@
---
title: PrivateGPT
weight: 1600
---
# PrivateGPT
[PrivateGPT](https://docs.privategpt.dev/) is a production-ready AI project that allows you to inquire about your documents using Large Language Models (LLMs) with offline support.
PrivateGPT uses Qdrant as the default vectorstore for ingesting and retrieving documents.
## Configuration
Qdrant settings can be configured by setting values to the qdrant property in the `settings.yaml` file. By default, Qdrant tries to connect to an instance at http://localhost:3000.
Example:
```yaml
qdrant:
url: "https://xyz-example.eu-central.aws.cloud.qdrant.io:6333"
api_key: "<your-api-key>"
```
The available [configuration options](https://docs.privategpt.dev/manual/storage/vector-stores#qdrant-configuration) are:
| Field | Description |
|--------------|-------------|
| location | If `:memory:` - use in-memory Qdrant instance.<br>If `str` - use it as a `url` parameter.|
| url | Either host or str of `Optional[scheme], host, Optional[port], Optional[prefix]`.<br> Eg. `http://localhost:6333` |
| port | Port of the REST API interface. Default: `6333` |
| grpc_port | Port of the gRPC interface. Default: `6334` |
| prefer_grpc | If `true` - use gRPC interface whenever possible in custom methods. |
| https | If `true` - use HTTPS(SSL) protocol.|
| api_key | API key for authentication in Qdrant Cloud.|
| prefix | If set, add `prefix` to the REST URL path.<br>Example: `service/v1` will result in `http://localhost:6333/service/v1/{qdrant-endpoint}` for REST API.|
| timeout | Timeout for REST and gRPC API requests.<br>Default: 5.0 seconds for REST and unlimited for gRPC |
| host | Host name of Qdrant service. If url and host are not set, defaults to 'localhost'.|
| path | Persistence path for QdrantLocal. Eg. `local_data/private_gpt/qdrant`|
| force_disable_check_same_thread | Force disable check_same_thread for QdrantLocal sqlite connection.|
## Next steps
Find the PrivateGPT docs [here](https://docs.privategpt.dev/).
@@ -21,7 +21,7 @@ docker pull qdrant/qdrant
Then, run the service:
```bash
docker run -p 6333:6333 \
docker run -p 6333:6333 -p 6334:6334 \
-v $(pwd)/qdrant_storage:/qdrant/storage:z \
qdrant/qdrant
```
@@ -29,8 +29,9 @@ docker run -p 6333:6333 \
Under the default configuration all data will be stored in the `./qdrant_storage` directory. This will also be the only directory that both the Container and the host machine can both see.
Qdrant is now accessible:
- API: [localhost:6333](http://localhost:6333)
- REST API: [localhost:6333](http://localhost:6333)
- Web UI: [localhost:6333/dashboard](http://localhost:6333/dashboard)
- GRPC API: [localhost:6334](http://localhost:6334)
## Initialize the client
@@ -46,6 +47,13 @@ import { QdrantClient } from "@qdrant/js-client-rest";
const client = new QdrantClient({ host: "localhost", port: 6333 });
```
```rust
use qdrant_client::client::QdrantClient;
// The Rust client uses Qdrant's GRPC interface
let client = QdrantClient::from_url("http://localhost:6334").build()?;
```
<aside role="status">By default, Qdrant starts with no encryption or authentication . This means anyone with network access to your machine can access your Qdrant container instance. Please read <a href="https://qdrant.tech/documentation/security/">Security</a> carefully for details on how to secure your instance.</aside>
## Create a collection
@@ -67,7 +75,24 @@ await client.createCollection("test_collection", {
});
```
<aside role="status">TypeScript examples use async/await syntax, so should be called in an async function.</aside>
```rust
use qdrant_client::qdrant::{vectors_config::Config, VectorParams, VectorsConfig};
client
.create_collection(&CreateCollection {
collection_name: "test_collection".to_string(),
vectors_config: Some(VectorsConfig {
config: Some(Config::Params(VectorParams {
size: 4,
distance: Distance::Dot.into(),
..Default::default()
})),
}),
..Default::default()
})
.await?;
```
<aside role="status">TypeScript, Rust examples use async/await syntax, so should be used in an async block.</aside>
## Add vectors
@@ -108,6 +133,37 @@ const operationInfo = await client.upsert("test_collection", {
console.debug(operationInfo);
```
```rust
use qdrant_client::qdrant::PointStruct;
use serde_json::json;
let points = vec![
PointStruct::new(
1,
vec![0.05, 0.61, 0.76, 0.74],
json!(
{"city": "Berlin"}
)
.try_into()
.unwrap(),
),
PointStruct::new(
2,
vec![0.19, 0.81, 0.75, 0.11],
json!(
{"city": "London"}
)
.try_into()
.unwrap(),
),
// ..truncated
];
let operation_info = client
.upsert_points_blocking("test_collection".to_string(), points, None)
.await?;
dbg!(operation_info);
```
**Response:**
```python
@@ -118,6 +174,16 @@ operation_id=0 status=<UpdateStatus.COMPLETED: 'completed'>
{ operation_id: 0, status: 'completed' }
```
```rust
PointsOperationResponse {
result: Some(UpdateResult {
operation_id: 0,
status: Completed,
}),
time: 0.006347708,
}
```
## Run a query
Let's ask a basic question - Which of our stored vectors are most similar to the query vector `[0.2, 0.1, 0.9, 0.7]`?
@@ -138,6 +204,22 @@ let searchResult = await client.search("test_collection", {
console.debug(searchResult);
```
```rust
use qdrant_client::qdrant::SearchPoints;
let search_result = client
.search_points(&SearchPoints {
collection_name: "test_collection".to_string(),
vector: vec![0.2, 0.1, 0.9, 0.7],
limit: 3,
with_payload: Some(true.into()),
..Default::default()
})
.await?;
dbg!(search_result);
```
**Response:**
```python
@@ -152,26 +234,61 @@ ScoredPoint(id=3, version=0, score=1.208, payload={"city": "Moscow"}, vector=Non
id: 4,
version: 0,
score: 1.362,
payload: { city: "New York" },
payload: null,
vector: null,
},
{
id: 1,
version: 0,
score: 1.273,
payload: { city: "Berlin" },
payload: null,
vector: null,
},
{
id: 3,
version: 0,
score: 1.208,
payload: { city: "Moscow" },
payload: null,
vector: null,
},
];
```
```rust
SearchResponse {
result: [
ScoredPoint {
id: Some(PointId {
point_id_options: Some(Num(4)),
}),
payload: {},
score: 1.362,
version: 0,
vectors: None,
},
ScoredPoint {
id: Some(PointId {
point_id_options: Some(Num(1)),
}),
payload: {},
score: 1.273,
version: 0,
vectors: None,
},
ScoredPoint {
id: Some(PointId {
point_id_options: Some(Num(3)),
}),
payload: {},
score: 1.208,
version: 0,
vectors: None,
},
],
time: 0.003635125,
}
```
The results are returned in decreasing similarity order. Note that payload and vector data is missing in these results by default.
See [payload and vector in the result](../concepts/search#payload-and-vector-in-the-result) on how to enable it.
@@ -188,6 +305,7 @@ search_result = client.search(
query_filter=Filter(
must=[FieldCondition(key="city", match=MatchValue(value="London"))]
),
with_payload=True,
limit=3,
)
@@ -200,12 +318,32 @@ searchResult = await client.search("test_collection", {
filter: {
must: [{ key: "city", match: { value: "London" } }],
},
with_payload: true,
limit: 3,
});
console.debug(searchResult);
```
```rust
use qdrant_client::qdrant::{Condition, Filter, SearchPoints};
let search_result = client
.search_points(&SearchPoints {
collection_name: "test_collection".to_string(),
vector: vec![0.2, 0.1, 0.9, 0.7],
filter: Some(Filter::all([Condition::matches(
"city",
"London".to_string(),
)])),
limit: 2,
..Default::default()
})
.await?;
dbg!(search_result);
```
**Response:**
```python
@@ -224,6 +362,36 @@ ScoredPoint(id=2, version=0, score=0.871, payload={"city": "London"}, vector=Non
];
```
```rust
SearchResponse {
result: [
ScoredPoint {
id: Some(
PointId {
point_id_options: Some(
Num(
2,
),
),
},
),
payload: {
"city": Value {
kind: Some(
StringValue(
"London",
),
),
},
},
score: 0.871,
version: 0,
vectors: None,
},
],
time: 0.004001083,
}
```
You have just conducted vector search. You loaded vectors into a database and queried the database with a vector of your own. Qdrant found the closest results and presented you with a similarity score.
## Next steps
Binary file not shown.

After

Width:  |  Height:  |  Size: 596 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 593 KiB