new: QdrantServerless client prototype - #1393
Draft
generall wants to merge 14 commits into
Draft
Conversation
✅ Deploy Preview for poetic-froyo-8baba7 ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
generall
force-pushed
the
serverless-client-prototype
branch
from
September 1, 2026 19:20
de48a43 to
0e27d09
Compare
generall
force-pushed
the
serverless-client-prototype
branch
from
September 1, 2026 19:33
0e27d09 to
eb092f1
Compare
5 tasks
Serverless exposes the same point-level API as a regular cluster (minus read consistency, shard selection, write ordering and filtered updates), but a simplified tenant-facing collection management API. - qdrant_client/serverless: dedicated module, nothing added to the top-level package - gRPC stubs generated from qdrant-cloud-public-api's serverless/collections.proto (renamed to serverless_collections.proto to avoid a descriptor-pool filename clash with the regular client's collections.proto), kept internal - hand-written pydantic models for the serverless collection config, reusing the existing Distance/TokenizerType enums - point operations delegate to an internal QdrantRemote(prefer_grpc=True) with trimmed signatures; collection operations talk to the serverless CollectionsService on the same channel Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R25zh9xS78xMHgPcoFaUdw
Same treatment as qdrant_client/grpc: generated stubs have untyped defs, and the ListCollectionsResponse.collections field shadows the collections module in the .pyi. Also import PointStruct from qdrant_client.http.models in the example, matching the rest of the codebase. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R25zh9xS78xMHgPcoFaUdw
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R25zh9xS78xMHgPcoFaUdw
Match the Args/Returns docstring style of the regular client; each method notes where the serverless API diverges from it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R25zh9xS78xMHgPcoFaUdw
Same approach as the regular client: the sync client is the source of truth and tools/async_client_generator produces the async version. The serverless generator delegates to AsyncQdrantRemote, awaits the CollectionsService stub RPCs (awaitable on the aio channel the async remote already builds), and drops the sync context manager. The file is covered by the async-client-consistency-check like the other generated clients. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R25zh9xS78xMHgPcoFaUdw
query_points delegated straight to QdrantRemote, which expects a resolved Query model, so a plain list like [0.1, 0.2] failed with "invalid Query model". Apply the same QdrantFastembedMixin._resolve_query type normalization the regular client applies - type resolution only, no client-side embedding inference: Document/Image inputs go to the server as-is, serverless inference is server-side only. Verified live against a serverless dev space: create/list/get/upsert/ query/delete all pass, sync and async. Along the way: create_collection docstring now documents that an existing collection raises gRPC ALREADY_EXISTS (the service errors instead of returning the "already exists" result string the proto comment mentions), and the example deletes a leftover collection first so it can be rerun. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R25zh9xS78xMHgPcoFaUdw
Thin alias module re-exporting qdrant_client.serverless.models, matching the qdrant_client.models convention of the regular client; the old import path keeps working. Examples and tests use the new path. Also replace an API key that slipped into the committed example with the placeholder, and exclude examples/ from mypy so examples can use the dynamic qdrant_client.models namespace. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R25zh9xS78xMHgPcoFaUdw
Drop the qdrant_client.models.serverless alias module and the model re-exports in qdrant_client.serverless: both were manually maintained re-export lists. qdrant_client.serverless.models is the single public import path; the serverless package itself exports only the clients. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R25zh9xS78xMHgPcoFaUdw
query_batch_points and query_points_groups (both implemented by the service, verified live) with the usual serverless trims: no consistency, no shard selection, no cross-collection lookups. Add the update operations the service implements that were still missing: update_vectors, delete_vectors, overwrite_payload, clear_payload, batch_update_points (ids-only selectors; the service rejects filters). Update methods default to wait=False: serverless reads are eventually consistent with writes, so waiting does not provide read-your-write. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R25zh9xS78xMHgPcoFaUdw
All id-selecting update methods take a uniform `points` parameter, typed narrowly as Sequence[PointId] to match what the service accepts today. When serverless adds filtered updates, the type widens to PointsSelector without breaking callers: same name, same position, strictly wider input. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R25zh9xS78xMHgPcoFaUdw
Warn that the serverless client is experimental and should not be used yet.
|
Documented the serverless client as in development — do not use yet in package/client/models/conversions docs, the async client (regenerated), and the example. |
joein
force-pushed
the
serverless-client-prototype
branch
from
September 2, 2026 09:00
b25151f to
9920605
Compare
Bind every model field via structural pattern matching so new fields force an update instead of being silently ignored.
Member
|
The example code crashes with the following error: created
[CollectionSummary(collection_name='my-collection', point_count=None)]
exists=True config=CollectionConfig(dense_vectors={'': DenseVectorConfig(size=4, distance=<Distance.COSINE: 'Cosine'>, multivector=False, precision_tier=None)}, sparse_vectors={}, payload_indexes={'color': KeywordIndex(type='keyword')}) point_count=None
Traceback (most recent call last):
File "../qdrant/qdrant_client/examples/serverless_client.py", line 52, in <module>
main()
File "../qdrant/qdrant_client/examples/serverless_client.py", line 45, in main
print(client.query_points("my-collection", query=[0.1, 0.2, 0.3, 0.4]))
File "../qdrant/qdrant_client/qdrant_client/serverless/client.py", line 355, in query_points
return self._remote.query_points(
File "../qdrant/qdrant_client/qdrant_client/qdrant_remote.py", line 535, in query_points
res: grpc.QueryResponse = self.grpc_points.Query(
File "../qdrant/qdrant_client/venv/lib/python3.10/site-packages/grpc/_interceptor.py", line 276, in __call__
response, ignored_call = self._with_call(
File "../qdrant/qdrant_client/venv/lib/python3.10/site-packages/grpc/_interceptor.py", line 331, in _with_call
return call.result(), call
File "../qdrant/qdrant_client/venv/lib/python3.10/site-packages/grpc/_channel.py", line 447, in result
raise self
File "../qdrant/qdrant_client/venv/lib/python3.10/site-packages/grpc/_interceptor.py", line 314, in continuation
response, call = self._thunk(new_method).with_call(
File "../qdrant/qdrant_client/venv/lib/python3.10/site-packages/grpc/_channel.py", line 1182, in with_call
return _end_unary_response_blocking(state, call, True, None)
File "../qdrant/qdrant_client/venv/lib/python3.10/site-packages/grpc/_channel.py", line 999, in _end_unary_response_blocking
raise _InactiveRpcError(state) # pytype: disable=not-instantiable
grpc._channel._InactiveRpcError: <_InactiveRpcError of RPC that terminated with:
status = StatusCode.INVALID_ARGUMENT
details = "Not existing vector name error: "
debug_error_string = "INVALID_ARGUMENT:Not existing vector name error: "
>In the beginning, it failed just on the first attempt and then worked when I reran it. |
Align with the latest public-api collections.proto: paginated ListCollections (limit/offset_token/next_offset_token) plus keyword prefix and text analysis fields (stopwords, stemmer, ascii_folding).
|
Updated this PR to match the latest serverless collections API in qdrant-cloud-public-api:
Tests: |
joein
approved these changes
Sep 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Prototype of a dedicated client for Qdrant Serverless, parallel to the regular
QdrantClient, per the serverless client proposal.Why
Serverless is not 100% identical to a regular cluster deployment: collection management exposes only a simplified tenant-facing config (no quantization/WAL/segments), and point operations don't support read consistency, shard selection, write ordering, or filtered updates. A separate client keeps those parameters out of the serverless interface instead of forcing one client to paper over the divergence.
How
Everything lives in a dedicated
qdrant_client/serverless/module; nothing is added to the top-level package, so regular users don't pay any import cost.qdrant-cloud-public-api'sserverless/collections.protowith the same pinned grpcio-tools 1.62.0 as the existing stubs (tools/generate_serverless_grpc_client.shregenerates). The proto is renamed toserverless_collections.protobecause the protobuf descriptor pool registers files by name andcollections.protois already taken by the regular client — without the rename, importing both clients in one process crashes. Generated types stay internal.DenseVectorConfig,SparseVectorConfig,PrecisionTier, the eight payload-index types,CollectionConfig,CollectionInfo,CollectionSummary. The existingDistance/TokenizerTypeenums are reused so values look familiar. These may later be replaced with models generated from a serverless OpenAPI spec.QdrantServerlesswraps an internalQdrantRemote(prefer_grpc=True, check_compatibility=False)and delegates point operations to it, reusing all existing conversion machinery; collection methods talk to the newCollectionsServicestub on the same channel (one place for TLS,api-keymetadata, user-agent). The API divergence is expressed purely in signatures:query_points,upsert,retrieve,scroll,count,delete,set_payload,delete_payload) are the regular ones minusconsistency/shard_key_selector/orderingdelete/ payload methods takeidsonly, since serverless rejects filtered updatescreate_collectionaccepts a bare config as the unnamed""vector and returns the service's result string ("created"/"already exists");get_collectionreturnsCollectionInfowithexists/point_countinstead of raising on missing collectionsgrpc_portis 443, not 6334Not included (mechanical follow-ups)
query_batch_points/batch_update_points/update_vectors,update_collection(no such RPC yet).Async client
AsyncQdrantServerlessis generated from the sync client the same way the regular async client is:tools/async_client_generator/serverless_generator.pyruns as part oftools/generate_async_client.sh, delegates toAsyncQdrantRemote, awaits the CollectionsService stub RPCs on the aio channel, and drops the sync context manager (the regular async client has none either). The generated file is verified bytests/async-client-consistency-check.shalongside the other generated clients.Testing
tests/test_serverless.py: model↔gRPC round-trips, proto3 optional-field presence, offline client construction (headers, port, TLS)🤖 Generated with Claude Code