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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions examples/serverless.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@

use qdrant_client::qdrant::{PointStruct, QueryPointsBuilder, UpsertPointsBuilder};
use qdrant_client::serverless::{
CollectionConfig, DenseVectorConfig, Distance, KeywordIndex, QdrantServerless,
CollectionConfig, DenseVectorConfig, Distance, KeywordIndex, ListCollectionsBuilder,
QdrantServerless,
};
use qdrant_client::{Payload, QdrantError};

Expand All @@ -38,12 +39,17 @@ async fn main() -> Result<(), QdrantError> {
collection_name,
CollectionConfig::new()
.dense_vector(DenseVectorConfig::new(4, Distance::Cosine))
.payload_index("color", KeywordIndex),
.payload_index("color", KeywordIndex::new()),
)
.await?;
println!("create_collection: {result}");

println!("collections: {:?}", client.list_collections().await?);
println!(
"collections: {:?}",
client
.list_collections(ListCollectionsBuilder::new())
.await?
);
println!("info: {:?}", client.get_collection(collection_name).await?);

let points = vec![
Expand Down
68 changes: 61 additions & 7 deletions proto/serverless_collections.proto
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
// Source: https://github.com/qdrant/qdrant-cloud-public-api/blob/main/proto/qdrant/serverless/collections.proto
// Renamed to serverless_collections.proto to sit alongside the regular collections.proto.
// Regenerate with: cargo test --test serverless_protos -- --ignored
// Client copy: buf.validate options are stripped (server-side only; wire format unchanged).
// Regenerate with: cargo test --test serverless_protos -- --ignored --nocapture

syntax = "proto3";

package qdrant.serverless;


// CollectionsService manages the collections of a qdrant serverless space.
// Unlike the qdrant server API, it exposes only a simplified configuration:
// the manager turns it into concrete qdrant settings, which are never
Expand Down Expand Up @@ -85,7 +88,15 @@ enum Tokenizer {
}

// Exact match on string values, e.g. `color: "red"`.
message KeywordIndex {}
message KeywordIndex {
// If set, enable prefix matching (`match: { "prefix": ... }`) on this field.
// Presence of this message enables prefix matching; it has no options yet.
optional KeywordPrefixParams prefix = 1;
}

// Prefix matching options for the keyword index. Has no options yet:
// presence of this message enables prefix matching.
message KeywordPrefixParams {}

// Exact match and/or range filters on integers, e.g. `age: 25`. Both are on
// by default; turning one off shrinks the index.
Expand All @@ -105,6 +116,35 @@ message UuidIndex {}
// Range filters on RFC 3339 datetimes, e.g. `created_at: "2023-02-08T10:49:00Z"`.
message DatetimeIndex {}

// Tokens ignored by a full-text index. Language names match qdrant (e.g.
// "english"); predefined lists and custom tokens are merged.
message StopwordsSet {
// Languages whose predefined stopword lists to apply.
repeated string languages = 1;
// Extra stopwords to ignore, merged with the language lists.
repeated string custom = 2;
}

// Snowball stemming for a full-text index.
message SnowballParams {
// Language for the snowball algorithm, e.g. "english".
string language = 1;
}

// Explicitly disable stemming (overrides any language default).
message DisabledStemmer {}

// Stemming algorithm for a full-text index. Unset: no stemming.
message StemmingAlgorithm {
// Which stemming algorithm to use.
oneof stemming_params {
// Snowball stemmer for the given language.
SnowballParams snowball = 1;
// Explicitly disable stemming.
DisabledStemmer disabled = 2;
}
}

// Full-text filtering on string values.
message TextIndex {
// Tokenizer to split text with. Unset: WHITESPACE.
Expand All @@ -117,6 +157,12 @@ message TextIndex {
optional uint64 min_token_len = 4;
// Maximum token length to index.
optional uint64 max_token_len = 5;
// Fold accented characters to ASCII. Default false.
optional bool ascii_folding = 6;
// Tokens to ignore at index and query time.
optional StopwordsSet stopwords = 7;
// Stemming algorithm. Unset: engine default (no stemming).
optional StemmingAlgorithm stemmer = 8;
}

// Geo radius / bounding box / polygon filters on `{lon, lat}` values.
Expand Down Expand Up @@ -211,9 +257,15 @@ message GetCollectionResponse {
optional uint64 point_count = 3;
}

// Lists the caller's collections. The tenant travels in metadata, so there is
// nothing to name here.
message ListCollectionsRequest {}
// Lists the caller's collections. The tenant travels in metadata.
message ListCollectionsRequest {
// Maximum number of collections to return. Defaults to 20 and must not
// exceed 100.
optional uint32 limit = 1;
// Opaque token returned as `next_offset_token` by the previous page. Clients
// must not interpret this value.
optional string offset_token = 2;
}

// One collection in a listing.
message CollectionSummary {
Expand All @@ -226,7 +278,9 @@ message CollectionSummary {

// The caller's collections.
message ListCollectionsResponse {
// Ordered by name. A collection whose creation never published a manifest is
// not listed: it is not servable.
// Collections in this page.
repeated CollectionSummary collections = 1;
// Opaque token to pass as `offset_token` to retrieve the next page. Absent
// when there are no more results.
optional string next_offset_token = 2;
}
64 changes: 47 additions & 17 deletions src/serverless/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ use crate::serverless::grpc::collections_service_client::CollectionsServiceClien
use crate::serverless::grpc::{
CreateCollectionRequest, DeleteCollectionRequest, GetCollectionRequest, ListCollectionsRequest,
};
use crate::serverless::models::{CollectionConfig, CollectionInfo, CollectionSummary};
use crate::serverless::models::{
CollectionConfig, CollectionInfo, CollectionSummary, CollectionsList, ListCollections,
};
use crate::Qdrant;

/// Default gRPC port for serverless when the URL omits an explicit port.
Expand Down Expand Up @@ -70,7 +72,7 @@ pub const DEFAULT_SERVERLESS_GRPC_PORT: u16 = 443;
/// "my-collection",
/// CollectionConfig::new()
/// .dense_vector(DenseVectorConfig::new(4, Distance::Cosine))
/// .payload_index("color", KeywordIndex),
/// .payload_index("color", KeywordIndex::new()),
/// )
/// .await?;
///
Expand Down Expand Up @@ -288,23 +290,51 @@ impl QdrantServerless {
Ok(self.get_collection(collection_name).await?.exists)
}

/// Lists the collections of the space.
/// Lists a page of collections in the space.
///
/// Defaults to the server page size (20, max 100). Pass `offset_token` from a
/// previous response's `next_offset_token` to fetch the next page.
///
/// Returns summaries (name and eventually consistent point count), ordered by name.
pub async fn list_collections(&self) -> QdrantResult<Vec<CollectionSummary>> {
/// ```no_run
/// # use qdrant_client::serverless::{ListCollectionsBuilder, QdrantServerless};
/// # async fn run(client: QdrantServerless) -> Result<(), qdrant_client::QdrantError> {
/// let page = client
/// .list_collections(ListCollectionsBuilder::new().limit(50))
/// .await?;
/// if let Some(token) = page.next_offset_token {
/// let next = client
/// .list_collections(ListCollectionsBuilder::new().offset_token(token))
/// .await?;
/// }
/// # Ok(())
/// # }
/// ```
pub async fn list_collections(
&self,
request: impl Into<ListCollections>,
) -> QdrantResult<CollectionsList> {
let ListCollections {
limit,
offset_token,
} = request.into();
let request = ListCollectionsRequest {
limit,
offset_token,
};
let request = &request;
self.with_collections_client(|mut api| async move {
let response = api
.list_collections(ListCollectionsRequest {})
.await?
.into_inner();
Ok(response
.collections
.into_iter()
.map(|c| CollectionSummary {
collection_name: c.collection_name,
point_count: c.point_count,
})
.collect())
let response = api.list_collections(request.clone()).await?.into_inner();
Ok(CollectionsList {
collections: response
.collections
.into_iter()
.map(|c| CollectionSummary {
collection_name: c.collection_name,
point_count: c.point_count,
})
.collect(),
next_offset_token: response.next_offset_token,
})
})
.await
}
Expand Down
Loading
Loading