mirror of
https://github.com/qdrant/landing_page.git
synced 2026-09-28 07:28:30 +02:00
Merge branch 'master' into patch-1
This commit is contained in:
@@ -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 |
Reference in New Issue
Block a user