mirror of
https://github.com/qdrant/landing_page.git
synced 2026-09-28 07:28:30 +02:00
Merge pull request #257 from qdrant/tutorial/async-api
Add the tutorial about async API
This commit is contained in:
@@ -12,16 +12,17 @@ aliases:
|
||||
|
||||
These tutorials demonstrate different ways you can build vector search into your applications.
|
||||
|
||||
| Tutorial | Description | Stack |
|
||||
|-----------------------|----------------------------------------------|--------|
|
||||
| [Configure Optimal Use](../tutorials/optimize/) | Configure Qdrant collections for best resource use. | Qdrant |
|
||||
| [Separate Partitions](../tutorials/multiple-partitions/) | Serve vectors for many independent users. | Qdrant |
|
||||
| [Bulk Upload Vectors](../tutorials/bulk-upload/) | Upload a large scale dataset. | Qdrant |
|
||||
| [Create Dataset Snapshots](../tutorials/create-snapshot/) | Turn a dataset into a snapshot by exporting it from a collection. | Qdrant |
|
||||
| [Semantic Search for Beginners](../tutorials/search-beginners/) | Create a simple search engine locally in minutes. | Qdrant |
|
||||
| [Simple Neural Search](../tutorials/neural-search/) | Build and deploy a neural search that browses startup data. | Qdrant, BERT, FastAPI |
|
||||
| [Aleph Alpha Search](../tutorials/aleph-alpha-search/) | Build a multimodal search that combines text and image data. | Qdrant, Aleph Alpha |
|
||||
| [Mighty Semantic Search](../tutorials/mighty/) | Build a simple semantic search with an on-demand NLP service. | Qdrant, Mighty |
|
||||
| [Troubleshooting](../tutorials/common-errors/) | Solutions to common errors and fixes | Qdrant |
|
||||
| Tutorial | Description | Stack |
|
||||
|-----------------------------------------------------------------|-------------------------------------------------------------------|----------------------|
|
||||
| [Configure Optimal Use](../tutorials/optimize/) | Configure Qdrant collections for best resource use. | Qdrant |
|
||||
| [Separate Partitions](../tutorials/multiple-partitions/) | Serve vectors for many independent users. | Qdrant |
|
||||
| [Bulk Upload Vectors](../tutorials/bulk-upload/) | Upload a large scale dataset. | Qdrant |
|
||||
| [Create Dataset Snapshots](../tutorials/create-snapshot/) | Turn a dataset into a snapshot by exporting it from a collection. | Qdrant |
|
||||
| [Semantic Search for Beginners](../tutorials/search-beginners/) | Create a simple search engine locally in minutes. | Qdrant |
|
||||
| [Simple Neural Search](../tutorials/neural-search/) | Build and deploy a neural search that browses startup data. | Qdrant, BERT, FastAPI |
|
||||
| [Aleph Alpha Search](../tutorials/aleph-alpha-search/) | Build a multimodal search that combines text and image data. | Qdrant, Aleph Alpha |
|
||||
| [Mighty Semantic Search](../tutorials/mighty/) | Build a simple semantic search with an on-demand NLP service. | Qdrant, Mighty |
|
||||
| [Asynchronous API](../tutorials/async-api/) | Communicate with Qdrant server asynchronously with Python SDK. | Qdrant, Python |
|
||||
| [Troubleshooting](../tutorials/common-errors/) | Solutions to common errors and fixes | Qdrant |
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,383 @@
|
||||
---
|
||||
title: Asynchronous API
|
||||
weight: 18
|
||||
---
|
||||
|
||||
# Using Qdrant asynchronously
|
||||
|
||||
Asynchronous programming is being broadly adopted in the Python ecosystem. Tools such as FastAPI [have embraced this new
|
||||
paradigm](https://fastapi.tiangolo.com/async/), but it is also becoming a standard for ML models served as SaaS. For example, the Cohere SDK
|
||||
[provides an async client](https://cohere-sdk.readthedocs.io/en/latest/cohere.html#asyncclient) next to its synchronous counterpart.
|
||||
|
||||
Databases are often launched as separate services and are accessed via a network. All the interactions with them are IO-bound and can
|
||||
be performed asynchronously so as not to waste time actively waiting for a server response. In Python, this is achieved by
|
||||
using [`async/await`](https://docs.python.org/3/library/asyncio-task.html) syntax. That lets the interpreter switch to another task
|
||||
while waiting for a response from the server.
|
||||
|
||||
Qdrant exposes two interfaces: HTTP and gRPC. The official SDKs for different languages are based on the autogenerated clients, and
|
||||
in Python [qdrant-client](https://github.com/qdrant/qdrant-client), you can call all the methods asynchronously. This tutorial will teach you how to do just that.
|
||||
|
||||
## When to use async API
|
||||
|
||||
There is no need to use async API if the application you are writing will never support multiple users at once (e.g it is a script that runs once per day). However, if you are writing a web service that multiple users will use simultaneously, you shouldn't be
|
||||
blocking the threads of the web server as it limits the number of concurrent requests it can handle. In this case, you should use
|
||||
the async API.
|
||||
|
||||
Modern web frameworks like [FastAPI](https://fastapi.tiangolo.com/) and [Quart](https://quart.palletsprojects.com/en/latest/) support
|
||||
async API out of the box. Mixing asynchronous code with an existing synchronous codebase might be a challenge. The `async/await` syntax
|
||||
cannot be used in synchronous functions. On the other hand, calling an IO-bound operation synchronously in async code is considered
|
||||
an antipattern. Therefore, if you build an async web service, exposed through an [ASGI](https://asgi.readthedocs.io/en/latest/) server,
|
||||
you should use the async API for all the interactions with Qdrant.
|
||||
|
||||
<aside role="status">
|
||||
All the code examples below have to be launched in an async context. Usually, it means you have to use <code>asyncio.run</code> or <code>asyncio.create_task</code> to run them.
|
||||
Please refer to the <a href="https://docs.python.org/3/library/asyncio.html">asyncio documentation</a> for more details.
|
||||
</aside>
|
||||
|
||||
### Running async code in Python
|
||||
|
||||
The simplest way of running any of the examples from that tutorial is to use define `async` function and use the `asyncio.run` in the following way to run it:
|
||||
|
||||
```python
|
||||
import qdrant_client
|
||||
import asyncio
|
||||
|
||||
async def main():
|
||||
client = qdrant_client.QdrantClient("localhost")
|
||||
# Your async code using QdrantClient might be put here
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## How to use async API
|
||||
|
||||
Calling any method of Qdrant requires establishing a connection to the server. You need to create an instance of `QdrantClient`
|
||||
that will act as a gateway. If you want to do it locally, please make sure Qdrant server is running. If it's not, then you can launch it
|
||||
in a Docker container:
|
||||
|
||||
```bash
|
||||
docker run -p "6333:6333" -p "6334:6334" qdrant/qdrant:latest
|
||||
```
|
||||
|
||||
Alternatively, you can also use a [Qdrant Cloud](https://cloud.qdrant.io/) cluster. With a Cloud cluster you will pass the API key to the
|
||||
client constructor.
|
||||
|
||||
With that, we are ready to create a [`QdrantClient`](https://github.com/qdrant/qdrant-client/blob/master/qdrant_client/qdrant_client.py#L13)
|
||||
instance, given you have the [`qdrant-client`](https://pypi.org/project/qdrant-client/) package installed:
|
||||
|
||||
```python
|
||||
from qdrant_client import QdrantClient
|
||||
|
||||
client = QdrantClient("localhost")
|
||||
# If you use Qdrant Cloud, then pass the API key to the constructor:
|
||||
# client = QdrantClient("https://your-cluster-url.cloud.com", api_key="your-api-key")
|
||||
```
|
||||
|
||||
The default client only exposes the synchronous methods for interacting with the server. To access the async client and it's underlying methods, you need to use autogenerated async clients.
|
||||
It is possible to use asynchronous HTTP API by using `qdrant_client.http.api_client.AsyncApis`. We recommend you use the asynchronous gRPC API, as it is more efficient than the HTTP API.
|
||||
|
||||
## Using the gRPC API
|
||||
|
||||
Every instance of `QdrantClient` has two properties: `async_grpc_collections` and `async_grpc_points`. They expose
|
||||
the autogenerated clients for [collections](/documentation/concepts/collections/) and [points](/documentation/concepts/points/) respectively.
|
||||
|
||||
The gRPC client uses type definitions for all interactions with the server. They are autogenerated from the source code of Qdrant
|
||||
server, and you have to import them if you want to call any of the available methods.
|
||||
|
||||
```python
|
||||
from qdrant_client import grpc
|
||||
```
|
||||
|
||||
If your IDE does not support autocompletion for the autogenerated clients, you can refer to the documentation and [see the available
|
||||
fields for each of them](https://github.com/qdrant/qdrant/blob/master/docs/grpc/docs.md).
|
||||
|
||||
## Step 1: Create a collection
|
||||
|
||||
Let's create a collection and add some points to it.
|
||||
|
||||
```python
|
||||
response = await client.async_grpc_collections.Create(
|
||||
grpc.CreateCollection(
|
||||
collection_name="my_collection",
|
||||
vectors_config=grpc.VectorsConfig(
|
||||
params=grpc.VectorParams(
|
||||
size=100,
|
||||
distance=grpc.Distance.Cosine,
|
||||
)
|
||||
),
|
||||
quantization_config=grpc.QuantizationConfig(
|
||||
scalar=grpc.ScalarQuantization(
|
||||
type=grpc.QuantizationType.Int8,
|
||||
always_ram=False,
|
||||
)
|
||||
),
|
||||
timeout=10,
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
Our collection was set up for 100-dimensional vectors, cosine distance and 8-bit quantization. The timeout is set to 10 seconds, so
|
||||
if the server does not respond within 10 seconds, the request will be aborted.
|
||||
|
||||
### Verify collection info
|
||||
|
||||
You can check the collection info to see if it was created and properly configured:
|
||||
|
||||
```python
|
||||
response = await client.async_grpc_collections.Get(
|
||||
grpc.GetCollectionInfoRequest(
|
||||
collection_name="my_collection"
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
The `response` object is going to be an instance of `grpc.GetCollectionInfoResponse` and you can access the fields you need or simply
|
||||
display its content.
|
||||
|
||||
```python
|
||||
print(response)
|
||||
```
|
||||
|
||||
```text
|
||||
result {
|
||||
status: Green
|
||||
optimizer_status {
|
||||
ok: true
|
||||
}
|
||||
segments_count: 8
|
||||
config {
|
||||
params {
|
||||
shard_number: 1
|
||||
on_disk_payload: true
|
||||
vectors_config {
|
||||
params {
|
||||
size: 100
|
||||
distance: Cosine
|
||||
}
|
||||
}
|
||||
replication_factor: 1
|
||||
write_consistency_factor: 1
|
||||
}
|
||||
hnsw_config {
|
||||
m: 16
|
||||
ef_construct: 100
|
||||
full_scan_threshold: 10000
|
||||
max_indexing_threads: 0
|
||||
on_disk: false
|
||||
}
|
||||
optimizer_config {
|
||||
deleted_threshold: 0.2
|
||||
vacuum_min_vector_number: 1000
|
||||
default_segment_number: 0
|
||||
indexing_threshold: 20000
|
||||
flush_interval_sec: 5
|
||||
max_optimization_threads: 1
|
||||
}
|
||||
wal_config {
|
||||
wal_capacity_mb: 32
|
||||
wal_segments_ahead: 0
|
||||
}
|
||||
quantization_config {
|
||||
scalar {
|
||||
type: Int8
|
||||
always_ram: false
|
||||
}
|
||||
}
|
||||
}
|
||||
indexed_vectors_count: 0
|
||||
}
|
||||
time: 1.3022e-05
|
||||
```
|
||||
|
||||
As you can see, the collection was created properly and is ready to be used. To understand more of the output, please refer to the
|
||||
[API specification](https://qdrant.github.io/qdrant/redoc/index.html#tag/collections/operation/get_collection) that describes the
|
||||
meaning of each parameter.
|
||||
|
||||
### Handling exceptions
|
||||
|
||||
If the collection does not exist, the server will return an error. We can handle it by catching the exception. We may expect
|
||||
some specific exceptions, or simply catch all the errors that subclass `RpcError` from `grpc` package:
|
||||
|
||||
```python
|
||||
from grpc import RpcError
|
||||
|
||||
try:
|
||||
response = await client.async_grpc_collections.Get(
|
||||
grpc.GetCollectionInfoRequest(
|
||||
collection_name="my_collection_2"
|
||||
)
|
||||
)
|
||||
except RpcError as e:
|
||||
print(e)
|
||||
```
|
||||
|
||||
Our exception contain the details about the error, including the status code and the message:
|
||||
|
||||
```text
|
||||
<AioRpcError of RPC that terminated with:
|
||||
status = StatusCode.NOT_FOUND
|
||||
details = "Not found: Collection `my_collection_2` doesn't exist!"
|
||||
debug_error_string = "UNKNOWN:Error received from peer {created_time:"2023-08-29T12:14:05.885634788+02:00", grpc_status:5, grpc_message:"Not found: Collection `my_collection_2` doesn\'t exist!"}"
|
||||
>
|
||||
```
|
||||
|
||||
We can use those details to properly handle some specific cases. For example, if we want to create a collection only if it does not
|
||||
exist, we can check the status code and create it if it is `StatusCode.NOT_FOUND`:
|
||||
|
||||
## Step 2: Update collection configuration
|
||||
|
||||
It's quite likely that we will want to update the collection configuration. For example, we may want to change the HNSW parameters to improve
|
||||
the search precision. Let's change the `ef_construct` parameter to 200:
|
||||
|
||||
```python
|
||||
await client.async_grpc_collections.Update(
|
||||
grpc.UpdateCollection(
|
||||
collection_name="my_collection",
|
||||
hnsw_config=grpc.HnswConfigDiff(
|
||||
ef_construct=200,
|
||||
),
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
There are plenty of methods available for collections, such as creating aliases or checking the status of the cluster. This tutorial won't use all of them, but you can always [check the list of available methods and their parameters in the
|
||||
documentation](https://github.com/qdrant/qdrant/blob/master/docs/grpc/docs.md#collections_serviceproto).
|
||||
|
||||
That's it when it comes to collection management. Let's move on to points (individual vectors) allowing us to load data into our collection
|
||||
|
||||
## Step 3: Add points to the collection
|
||||
|
||||
Assuming your collection was created successfuly, you can now add some
|
||||
points to it. Let's add our first two points into the collection:
|
||||
|
||||
```python
|
||||
import numpy as np
|
||||
|
||||
points = [
|
||||
grpc.PointStruct(
|
||||
id=grpc.PointId(num=1),
|
||||
payload={
|
||||
"int_param": grpc.Value(integer_value=32),
|
||||
"bool_param": grpc.Value(bool_value=True),
|
||||
},
|
||||
vectors=grpc.Vectors(
|
||||
vector=grpc.Vector(data=np.random.random(100).tolist()),
|
||||
),
|
||||
),
|
||||
grpc.PointStruct(
|
||||
id=grpc.PointId(num=2),
|
||||
payload={
|
||||
"int_param": grpc.Value(integer_value=64),
|
||||
"bool_param": grpc.Value(bool_value=False),
|
||||
},
|
||||
vectors=grpc.Vectors(
|
||||
vector=grpc.Vector(data=np.random.random(100).tolist()),
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
response = await client.async_grpc_points.Upsert(
|
||||
grpc.UpsertPoints(
|
||||
collection_name="my_collection",
|
||||
points=points,
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
Those points are automatically indexed and available for search. In the future, you can add more points, or update the existing ones. All point related operations are [documented in the Qdrant server repository](https://github.com/qdrant/qdrant/blob/master/docs/grpc/docs.md#points_serviceproto).
|
||||
|
||||
The main operation you need from the vector database is semantic search. Let's see how to perform it.
|
||||
|
||||
## Step 4: Run a query
|
||||
|
||||
The search operation requires a query vector and a collection name. You can also specify the number of results you want to retrieve and some other parameters. A minimal example might look like this:
|
||||
|
||||
```python
|
||||
response = await client.async_grpc_points.Search(
|
||||
grpc.SearchPoints(
|
||||
collection_name="my_collection",
|
||||
vector=np.random.rand(100).tolist(),
|
||||
limit=1,
|
||||
)
|
||||
)
|
||||
|
||||
print(response)
|
||||
```
|
||||
|
||||
The response is going to contain just the point id and similarity score for the vector most similar to the random vector generated in the code. For example, that might be the output of the code above:
|
||||
|
||||
```text
|
||||
result {
|
||||
id {
|
||||
num: 1
|
||||
}
|
||||
score: 0.786471784
|
||||
version: 1
|
||||
}
|
||||
time: 0.000456134
|
||||
```
|
||||
|
||||
### Return vectors and payloads
|
||||
|
||||
If you need the vectors and payloads of the points, you can specify it in the request:
|
||||
|
||||
```python
|
||||
response = await client.async_grpc_points.Search(
|
||||
grpc.SearchPoints(
|
||||
collection_name="my_collection",
|
||||
vector=np.random.rand(100).tolist(),
|
||||
limit=1,
|
||||
with_payload=grpc.WithPayloadSelector(enable=True),
|
||||
with_vectors=grpc.WithVectorsSelector(enable=True),
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
That's all the code you need to bring back the vector and payload.
|
||||
|
||||
## Step 5: Add a filter
|
||||
|
||||
If you ever used SQL, you are probably familiar with the `WHERE` clause. It lets you filter the results of the query. Qdrant has a similar functionality. Imagine you want to find all the points similar to given vector, but with `int_param` equal to 32. You can do it like this:
|
||||
|
||||
```python
|
||||
response = await client.async_grpc_points.Search(
|
||||
grpc.SearchPoints(
|
||||
collection_name="my_collection",
|
||||
vector=np.random.rand(100).tolist(),
|
||||
limit=1,
|
||||
with_payload=grpc.WithPayloadSelector(enable=True),
|
||||
filter=grpc.Filter(
|
||||
must=[
|
||||
grpc.Condition(
|
||||
field=grpc.FieldCondition(
|
||||
key="int_param",
|
||||
match=grpc.Match(integer=32)
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
You can build even more sophisticated filters with the `must`, `should` and `must_not` clauses. You can find more details in the [filtering documentation](https://qdrant.tech/documentation/concepts/filtering/#filtering).
|
||||
|
||||
Analogous to search, you can also [scroll our collection](https://qdrant.tech/documentation/concepts/points/#scroll-points) to retrieve all the points or use the [recommendation API](https://qdrant.tech/documentation/concepts/search/#recommendation-api) to find points similar to positive and dissimilar to negative examples.
|
||||
|
||||
## Step 6: Delete a collection
|
||||
|
||||
Right now, you can just delete the collection you created, so there is nothing left dangling:
|
||||
|
||||
```python
|
||||
await client.async_grpc_collections.Delete(
|
||||
grpc.DeleteCollection(
|
||||
collection_name="my_collection"
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
## Supported Python libraries
|
||||
|
||||
Qdrant integrates with numerous Python libraries. Until recently, only [Langchain]() provided async Python API support.
|
||||
Qdrant is the only vector database with full coverage of async API in Langchain. Their documentation [describes how to use
|
||||
it](https://python.langchain.com/docs/modules/data_connection/vectorstores/#asynchronous-operations).
|
||||
Reference in New Issue
Block a user