mirror of
https://github.com/qdrant/landing_page.git
synced 2026-09-26 14:38:30 +02:00
Add subsection about stable ordering to pagination section (#2463)
* Add 'Stable Ordering' section to 'Pagination' section * Add FAQ entry * Small edits to the Pagination section * Apply title case to all headers on page * Small edit
This commit is contained in:
@@ -183,6 +183,18 @@ Results are generally expected to be consistent for the overlapping portion. How
|
||||
|
||||
The time value is in seconds and represents the total duration the Qdrant server spent processing the request. It does not include network round-trip time between the client and the server.
|
||||
|
||||
### Why do I get duplicate results when paginating through search results?
|
||||
|
||||
Because HNSW is an approximate algorithm, the ranking of results can shift slightly between requests. As a result, paginating with `offset` can return the same point on multiple pages or skip points entirely. This is expected behavior, not a bug.
|
||||
|
||||
There are three ways to work around this:
|
||||
|
||||
- **Client-side pagination** — retrieve a large batch in a single request (for example, the top 100 results) and paginate through it on the client. This avoids multiple round-trips and guarantees no duplicates, at the cost of returning more data than the user sees at once.
|
||||
- **Exact search** — use exact searches to bypass HNSW and scan all vectors, returning results in a stable, deterministic order. This ensures offset-based pagination works correctly. This is practical only for small collections due to higher latency.
|
||||
- **Exclude seen IDs** — on each subsequent page, pass a `must_not: has_id` filter containing all point IDs from previous pages. The exclusion list grows by `limit` entries per page, so this works well for sequential, forward-only pagination but isn't practical for jumping to an arbitrary page.
|
||||
|
||||
See also: [Stable Ordering](/documentation/search/search/#stable-ordering)
|
||||
|
||||
### If `limit` is higher than `hnsw_ef`, does Qdrant automatically adjust `hnsw_ef`?
|
||||
|
||||
Yes. Qdrant internally sets `ef = max(ef, limit)` so that the candidate list is always at least as large as the requested result count.
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
This code snippet demonstrates how to run an exact nearest neighbor search by setting the `exact` parameter to `true`. Unlike the default approximate HNSW search, exact search scans all vectors and returns results in a stable, deterministic order.
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
using Qdrant.Client;
|
||||
using Qdrant.Client.Grpc;
|
||||
|
||||
public class Snippet
|
||||
{
|
||||
public static async Task Run()
|
||||
{
|
||||
var client = new QdrantClient("localhost", 6334); // @hide
|
||||
|
||||
await client.QueryAsync(
|
||||
collectionName: "{collection_name}",
|
||||
query: new float[] { 0.2f, 0.1f, 0.9f, 0.7f },
|
||||
searchParams: new SearchParams { Exact = true },
|
||||
limit: 10
|
||||
);
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
```csharp
|
||||
using Qdrant.Client;
|
||||
using Qdrant.Client.Grpc;
|
||||
|
||||
await client.QueryAsync(
|
||||
collectionName: "{collection_name}",
|
||||
query: new float[] { 0.2f, 0.1f, 0.9f, 0.7f },
|
||||
searchParams: new SearchParams { Exact = true },
|
||||
limit: 10
|
||||
);
|
||||
```
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
```go
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/qdrant/go-client/qdrant"
|
||||
)
|
||||
|
||||
client.Query(context.Background(), &qdrant.QueryPoints{
|
||||
CollectionName: "{collection_name}",
|
||||
Query: qdrant.NewQuery(0.2, 0.1, 0.9, 0.7),
|
||||
Params: &qdrant.SearchParams{
|
||||
Exact: qdrant.PtrOf(true),
|
||||
},
|
||||
})
|
||||
```
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
```java
|
||||
import static io.qdrant.client.QueryFactory.nearest;
|
||||
|
||||
import io.qdrant.client.QdrantClient;
|
||||
import io.qdrant.client.QdrantGrpcClient;
|
||||
import io.qdrant.client.grpc.Points.QueryPoints;
|
||||
import io.qdrant.client.grpc.Points.SearchParams;
|
||||
|
||||
client.queryAsync(
|
||||
QueryPoints.newBuilder()
|
||||
.setCollectionName("{collection_name}")
|
||||
.setQuery(nearest(0.2f, 0.1f, 0.9f, 0.7f))
|
||||
.setParams(SearchParams.newBuilder().setExact(true).build())
|
||||
.setLimit(10)
|
||||
.build())
|
||||
.get();
|
||||
```
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
```python
|
||||
from qdrant_client import QdrantClient, models
|
||||
|
||||
client.query_points(
|
||||
collection_name="{collection_name}",
|
||||
query=[0.2, 0.1, 0.9, 0.7],
|
||||
search_params=models.SearchParams(exact=True),
|
||||
limit=10,
|
||||
)
|
||||
```
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
```rust
|
||||
use qdrant_client::qdrant::{QueryPointsBuilder, SearchParamsBuilder};
|
||||
use qdrant_client::Qdrant;
|
||||
|
||||
client
|
||||
.query(
|
||||
QueryPointsBuilder::new("{collection_name}")
|
||||
.query(vec![0.2, 0.1, 0.9, 0.7])
|
||||
.limit(10)
|
||||
.params(SearchParamsBuilder::default().exact(true)),
|
||||
)
|
||||
.await?;
|
||||
```
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
```typescript
|
||||
client.query("{collection_name}", {
|
||||
query: [0.2, 0.1, 0.9, 0.7],
|
||||
params: {
|
||||
exact: true,
|
||||
},
|
||||
limit: 10,
|
||||
});
|
||||
```
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package snippet
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/qdrant/go-client/qdrant"
|
||||
)
|
||||
|
||||
func Main() {
|
||||
// @hide-start
|
||||
client, err := qdrant.NewClient(&qdrant.Config{
|
||||
Host: "localhost",
|
||||
Port: 6334,
|
||||
})
|
||||
|
||||
if err != nil { panic(err) }
|
||||
// @hide-end
|
||||
|
||||
client.Query(context.Background(), &qdrant.QueryPoints{
|
||||
CollectionName: "{collection_name}",
|
||||
Query: qdrant.NewQuery(0.2, 0.1, 0.9, 0.7),
|
||||
Params: &qdrant.SearchParams{
|
||||
Exact: qdrant.PtrOf(true),
|
||||
},
|
||||
})
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
```http
|
||||
POST /collections/{collection_name}/points/query
|
||||
{
|
||||
"query": [0.2, 0.1, 0.9, 0.7],
|
||||
"params": {
|
||||
"exact": true
|
||||
},
|
||||
"limit": 10
|
||||
}
|
||||
```
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package com.example.snippets_amalgamation;
|
||||
|
||||
import static io.qdrant.client.QueryFactory.nearest;
|
||||
|
||||
import io.qdrant.client.QdrantClient;
|
||||
import io.qdrant.client.QdrantGrpcClient;
|
||||
import io.qdrant.client.grpc.Points.QueryPoints;
|
||||
import io.qdrant.client.grpc.Points.SearchParams;
|
||||
|
||||
public class Snippet {
|
||||
public static void run() throws Exception {
|
||||
// @hide-start
|
||||
QdrantClient client =
|
||||
new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());
|
||||
// @hide-end
|
||||
|
||||
client.queryAsync(
|
||||
QueryPoints.newBuilder()
|
||||
.setCollectionName("{collection_name}")
|
||||
.setQuery(nearest(0.2f, 0.1f, 0.9f, 0.7f))
|
||||
.setParams(SearchParams.newBuilder().setExact(true).build())
|
||||
.setLimit(10)
|
||||
.build())
|
||||
.get();
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
from qdrant_client import QdrantClient, models
|
||||
|
||||
client = QdrantClient(url="http://localhost:6333") # @hide
|
||||
|
||||
client.query_points(
|
||||
collection_name="{collection_name}",
|
||||
query=[0.2, 0.1, 0.9, 0.7],
|
||||
search_params=models.SearchParams(exact=True),
|
||||
limit=10,
|
||||
)
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
use qdrant_client::qdrant::{QueryPointsBuilder, SearchParamsBuilder};
|
||||
use qdrant_client::Qdrant;
|
||||
|
||||
pub async fn main() -> anyhow::Result<()> {
|
||||
let client = Qdrant::from_url("http://localhost:6334").build()?; // @hide
|
||||
|
||||
client
|
||||
.query(
|
||||
QueryPointsBuilder::new("{collection_name}")
|
||||
.query(vec![0.2, 0.1, 0.9, 0.7])
|
||||
.limit(10)
|
||||
.params(SearchParamsBuilder::default().exact(true)),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { QdrantClient } from "@qdrant/js-client-rest"; // @hide
|
||||
|
||||
const client = new QdrantClient({ host: "localhost", port: 6333 }); // @hide
|
||||
|
||||
client.query("{collection_name}", {
|
||||
query: [0.2, 0.1, 0.9, 0.7],
|
||||
params: {
|
||||
exact: true,
|
||||
},
|
||||
limit: 10,
|
||||
});
|
||||
+1
@@ -0,0 +1 @@
|
||||
This code snippet demonstrates how to paginate search results without duplicate points. By collecting the point IDs returned on each page and passing them to a `must_not: has_id` filter on the next request, each subsequent page excludes all previously seen results.
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
using Qdrant.Client;
|
||||
using static Qdrant.Client.Grpc.Conditions;
|
||||
|
||||
public class Snippet
|
||||
{
|
||||
public static async Task Run()
|
||||
{
|
||||
var client = new QdrantClient("localhost", 6334); // @hide
|
||||
|
||||
ulong[] seenIds = [83461, 19284, 57392, 44017, 91825]; // IDs returned on previous pages
|
||||
|
||||
// The ! operator negates the condition (must not)
|
||||
await client.QueryAsync(
|
||||
collectionName: "{collection_name}",
|
||||
query: new float[] { 0.2f, 0.1f, 0.9f, 0.7f },
|
||||
filter: !HasId(seenIds),
|
||||
limit: 5
|
||||
);
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
```csharp
|
||||
using Qdrant.Client;
|
||||
using static Qdrant.Client.Grpc.Conditions;
|
||||
|
||||
ulong[] seenIds = [83461, 19284, 57392, 44017, 91825]; // IDs returned on previous pages
|
||||
|
||||
// The ! operator negates the condition (must not)
|
||||
await client.QueryAsync(
|
||||
collectionName: "{collection_name}",
|
||||
query: new float[] { 0.2f, 0.1f, 0.9f, 0.7f },
|
||||
filter: !HasId(seenIds),
|
||||
limit: 5
|
||||
);
|
||||
```
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
```go
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/qdrant/go-client/qdrant"
|
||||
)
|
||||
|
||||
seenIds := []uint64{83461, 19284, 57392, 44017, 91825} // IDs returned on previous pages
|
||||
|
||||
pointIds := make([]*qdrant.PointId, len(seenIds))
|
||||
for i, id := range seenIds {
|
||||
pointIds[i] = qdrant.NewIDNum(id)
|
||||
}
|
||||
|
||||
client.Query(context.Background(), &qdrant.QueryPoints{
|
||||
CollectionName: "{collection_name}",
|
||||
Query: qdrant.NewQuery(0.2, 0.1, 0.9, 0.7),
|
||||
Filter: &qdrant.Filter{
|
||||
MustNot: []*qdrant.Condition{
|
||||
qdrant.NewHasID(pointIds...),
|
||||
},
|
||||
},
|
||||
Limit: qdrant.PtrOf(uint64(5)),
|
||||
})
|
||||
```
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
```java
|
||||
import static io.qdrant.client.ConditionFactory.hasId;
|
||||
import static io.qdrant.client.PointIdFactory.id;
|
||||
import static io.qdrant.client.QueryFactory.nearest;
|
||||
|
||||
import io.qdrant.client.QdrantClient;
|
||||
import io.qdrant.client.QdrantGrpcClient;
|
||||
import io.qdrant.client.grpc.Common.Filter;
|
||||
import io.qdrant.client.grpc.Points.QueryPoints;
|
||||
import java.util.List;
|
||||
|
||||
var seenIds = List.of(id(83461), id(19284), id(57392), id(44017), id(91825)); // IDs returned on previous pages
|
||||
|
||||
client.queryAsync(
|
||||
QueryPoints.newBuilder()
|
||||
.setCollectionName("{collection_name}")
|
||||
.setQuery(nearest(0.2f, 0.1f, 0.9f, 0.7f))
|
||||
.setFilter(
|
||||
Filter.newBuilder()
|
||||
.addMustNot(hasId(seenIds))
|
||||
.build())
|
||||
.setLimit(5)
|
||||
.build())
|
||||
.get();
|
||||
```
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
```python
|
||||
from uuid import UUID
|
||||
|
||||
from qdrant_client import QdrantClient, models
|
||||
|
||||
seen_ids: list[int | str | UUID] = [83461, 19284, 57392, 44017, 91825] # IDs returned on previous pages
|
||||
|
||||
client.query_points(
|
||||
collection_name="{collection_name}",
|
||||
query=[0.2, 0.1, 0.9, 0.7],
|
||||
query_filter=models.Filter(
|
||||
must_not=[
|
||||
models.HasIdCondition(has_id=seen_ids),
|
||||
]
|
||||
),
|
||||
limit=5,
|
||||
)
|
||||
```
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
```rust
|
||||
use qdrant_client::qdrant::{Condition, Filter, QueryPointsBuilder};
|
||||
use qdrant_client::Qdrant;
|
||||
|
||||
let seen_ids = vec![83461u64, 19284, 57392, 44017, 91825]; // IDs returned on previous pages
|
||||
|
||||
client
|
||||
.query(
|
||||
QueryPointsBuilder::new("{collection_name}")
|
||||
.query(vec![0.2, 0.1, 0.9, 0.7])
|
||||
.filter(Filter::must_not([Condition::has_id(seen_ids)]))
|
||||
.limit(5),
|
||||
)
|
||||
.await?;
|
||||
```
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
```typescript
|
||||
const seenIds = [83461, 19284, 57392, 44017, 91825]; // IDs returned on previous pages
|
||||
|
||||
client.query("{collection_name}", {
|
||||
query: [0.2, 0.1, 0.9, 0.7],
|
||||
filter: {
|
||||
must_not: [
|
||||
{
|
||||
has_id: seenIds,
|
||||
},
|
||||
],
|
||||
},
|
||||
limit: 5,
|
||||
});
|
||||
```
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package snippet
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/qdrant/go-client/qdrant"
|
||||
)
|
||||
|
||||
func Main() {
|
||||
// @hide-start
|
||||
client, err := qdrant.NewClient(&qdrant.Config{
|
||||
Host: "localhost",
|
||||
Port: 6334,
|
||||
})
|
||||
|
||||
if err != nil { panic(err) }
|
||||
// @hide-end
|
||||
|
||||
seenIds := []uint64{83461, 19284, 57392, 44017, 91825} // IDs returned on previous pages
|
||||
|
||||
pointIds := make([]*qdrant.PointId, len(seenIds))
|
||||
for i, id := range seenIds {
|
||||
pointIds[i] = qdrant.NewIDNum(id)
|
||||
}
|
||||
|
||||
client.Query(context.Background(), &qdrant.QueryPoints{
|
||||
CollectionName: "{collection_name}",
|
||||
Query: qdrant.NewQuery(0.2, 0.1, 0.9, 0.7),
|
||||
Filter: &qdrant.Filter{
|
||||
MustNot: []*qdrant.Condition{
|
||||
qdrant.NewHasID(pointIds...),
|
||||
},
|
||||
},
|
||||
Limit: qdrant.PtrOf(uint64(5)),
|
||||
})
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
```http
|
||||
POST /collections/{collection_name}/points/query
|
||||
{
|
||||
"query": [0.2, 0.1, 0.9, 0.7],
|
||||
"filter": {
|
||||
"must_not": [
|
||||
{ "has_id": [83461, 19284, 57392, 44017, 91825] }
|
||||
]
|
||||
},
|
||||
"limit": 5
|
||||
}
|
||||
```
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package com.example.snippets_amalgamation;
|
||||
|
||||
import static io.qdrant.client.ConditionFactory.hasId;
|
||||
import static io.qdrant.client.PointIdFactory.id;
|
||||
import static io.qdrant.client.QueryFactory.nearest;
|
||||
|
||||
import io.qdrant.client.QdrantClient;
|
||||
import io.qdrant.client.QdrantGrpcClient;
|
||||
import io.qdrant.client.grpc.Common.Filter;
|
||||
import io.qdrant.client.grpc.Points.QueryPoints;
|
||||
import java.util.List;
|
||||
|
||||
public class Snippet {
|
||||
public static void run() throws Exception {
|
||||
// @hide-start
|
||||
QdrantClient client =
|
||||
new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());
|
||||
// @hide-end
|
||||
|
||||
var seenIds = List.of(id(83461), id(19284), id(57392), id(44017), id(91825)); // IDs returned on previous pages
|
||||
|
||||
client.queryAsync(
|
||||
QueryPoints.newBuilder()
|
||||
.setCollectionName("{collection_name}")
|
||||
.setQuery(nearest(0.2f, 0.1f, 0.9f, 0.7f))
|
||||
.setFilter(
|
||||
Filter.newBuilder()
|
||||
.addMustNot(hasId(seenIds))
|
||||
.build())
|
||||
.setLimit(5)
|
||||
.build())
|
||||
.get();
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
from uuid import UUID
|
||||
|
||||
from qdrant_client import QdrantClient, models
|
||||
|
||||
client = QdrantClient(url="http://localhost:6333") # @hide
|
||||
|
||||
seen_ids: list[int | str | UUID] = [83461, 19284, 57392, 44017, 91825] # IDs returned on previous pages
|
||||
|
||||
client.query_points(
|
||||
collection_name="{collection_name}",
|
||||
query=[0.2, 0.1, 0.9, 0.7],
|
||||
query_filter=models.Filter(
|
||||
must_not=[
|
||||
models.HasIdCondition(has_id=seen_ids),
|
||||
]
|
||||
),
|
||||
limit=5,
|
||||
)
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
use qdrant_client::qdrant::{Condition, Filter, QueryPointsBuilder};
|
||||
use qdrant_client::Qdrant;
|
||||
|
||||
pub async fn main() -> anyhow::Result<()> {
|
||||
let client = Qdrant::from_url("http://localhost:6334").build()?; // @hide
|
||||
|
||||
let seen_ids = vec![83461u64, 19284, 57392, 44017, 91825]; // IDs returned on previous pages
|
||||
|
||||
client
|
||||
.query(
|
||||
QueryPointsBuilder::new("{collection_name}")
|
||||
.query(vec![0.2, 0.1, 0.9, 0.7])
|
||||
.filter(Filter::must_not([Condition::has_id(seen_ids)]))
|
||||
.limit(5),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import { QdrantClient } from "@qdrant/js-client-rest"; // @hide
|
||||
|
||||
const client = new QdrantClient({ host: "localhost", port: 6333 }); // @hide
|
||||
|
||||
const seenIds = [83461, 19284, 57392, 44017, 91825]; // IDs returned on previous pages
|
||||
|
||||
client.query("{collection_name}", {
|
||||
query: [0.2, 0.1, 0.9, 0.7],
|
||||
filter: {
|
||||
must_not: [
|
||||
{
|
||||
has_id: seenIds,
|
||||
},
|
||||
],
|
||||
},
|
||||
limit: 5,
|
||||
});
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
using Qdrant.Client;
|
||||
using Qdrant.Client; // @hide
|
||||
|
||||
public class Snippet
|
||||
{
|
||||
public static async Task Run()
|
||||
{
|
||||
var client = new QdrantClient("localhost", 6334);
|
||||
var client = new QdrantClient("localhost", 6334); // @hide
|
||||
|
||||
await client.QueryAsync(
|
||||
collectionName: "{collection_name}",
|
||||
|
||||
-4
@@ -1,8 +1,4 @@
|
||||
```csharp
|
||||
using Qdrant.Client;
|
||||
|
||||
var client = new QdrantClient("localhost", 6334);
|
||||
|
||||
await client.QueryAsync(
|
||||
collectionName: "{collection_name}",
|
||||
query: new float[] { 0.2f, 0.1f, 0.9f, 0.7f },
|
||||
|
||||
+1
-5
@@ -5,16 +5,12 @@ import (
|
||||
"github.com/qdrant/go-client/qdrant"
|
||||
)
|
||||
|
||||
client, err := qdrant.NewClient(&qdrant.Config{
|
||||
Host: "localhost",
|
||||
Port: 6334,
|
||||
})
|
||||
|
||||
client.Query(context.Background(), &qdrant.QueryPoints{
|
||||
CollectionName: "{collection_name}",
|
||||
Query: qdrant.NewQuery(0.2, 0.1, 0.9, 0.7),
|
||||
WithPayload: qdrant.NewWithPayload(true),
|
||||
WithVectors: qdrant.NewWithVectors(true),
|
||||
Limit: qdrant.PtrOf(uint64(10)),
|
||||
Offset: qdrant.PtrOf(uint64(100)),
|
||||
})
|
||||
```
|
||||
|
||||
-3
@@ -8,9 +8,6 @@ import io.qdrant.client.WithVectorsSelectorFactory;
|
||||
import io.qdrant.client.grpc.Points.QueryPoints;
|
||||
import java.util.List;
|
||||
|
||||
QdrantClient client =
|
||||
new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());
|
||||
|
||||
client.queryAsync(
|
||||
QueryPoints.newBuilder()
|
||||
.setCollectionName("{collection_name}")
|
||||
|
||||
-4
@@ -1,8 +1,4 @@
|
||||
```python
|
||||
from qdrant_client import QdrantClient
|
||||
|
||||
client = QdrantClient(url="http://localhost:6333")
|
||||
|
||||
client.query_points(
|
||||
collection_name="{collection_name}",
|
||||
query=[0.2, 0.1, 0.9, 0.7],
|
||||
|
||||
-2
@@ -2,8 +2,6 @@
|
||||
use qdrant_client::qdrant::QueryPointsBuilder;
|
||||
use qdrant_client::Qdrant;
|
||||
|
||||
let client = Qdrant::from_url("http://localhost:6334").build()?;
|
||||
|
||||
client
|
||||
.query(
|
||||
QueryPointsBuilder::new("{collection_name}")
|
||||
|
||||
-4
@@ -1,8 +1,4 @@
|
||||
```typescript
|
||||
import { QdrantClient } from "@qdrant/js-client-rest";
|
||||
|
||||
const client = new QdrantClient({ host: "localhost", port: 6333 });
|
||||
|
||||
client.query("{collection_name}", {
|
||||
query: [0.2, 0.1, 0.9, 0.7],
|
||||
with_vector: true,
|
||||
|
||||
+4
-1
@@ -7,18 +7,21 @@ import (
|
||||
)
|
||||
|
||||
func Main() {
|
||||
// @hide-start
|
||||
client, err := qdrant.NewClient(&qdrant.Config{
|
||||
Host: "localhost",
|
||||
Port: 6334,
|
||||
})
|
||||
|
||||
if err != nil { panic(err) } // @hide
|
||||
if err != nil { panic(err) }
|
||||
// @hide-end
|
||||
|
||||
client.Query(context.Background(), &qdrant.QueryPoints{
|
||||
CollectionName: "{collection_name}",
|
||||
Query: qdrant.NewQuery(0.2, 0.1, 0.9, 0.7),
|
||||
WithPayload: qdrant.NewWithPayload(true),
|
||||
WithVectors: qdrant.NewWithVectors(true),
|
||||
Limit: qdrant.PtrOf(uint64(10)),
|
||||
Offset: qdrant.PtrOf(uint64(100)),
|
||||
})
|
||||
}
|
||||
|
||||
+2
@@ -11,8 +11,10 @@ import java.util.List;
|
||||
|
||||
public class Snippet {
|
||||
public static void run() throws Exception {
|
||||
// @hide-start
|
||||
QdrantClient client =
|
||||
new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());
|
||||
// @hide-end
|
||||
|
||||
client.queryAsync(
|
||||
QueryPoints.newBuilder()
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
from qdrant_client import QdrantClient
|
||||
from qdrant_client import QdrantClient # @hide
|
||||
|
||||
client = QdrantClient(url="http://localhost:6333")
|
||||
client = QdrantClient(url="http://localhost:6333") # @hide
|
||||
|
||||
client.query_points(
|
||||
collection_name="{collection_name}",
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ use qdrant_client::qdrant::QueryPointsBuilder;
|
||||
use qdrant_client::Qdrant;
|
||||
|
||||
pub async fn main() -> anyhow::Result<()> {
|
||||
let client = Qdrant::from_url("http://localhost:6334").build()?;
|
||||
let client = Qdrant::from_url("http://localhost:6334").build()?; // @hide
|
||||
|
||||
client
|
||||
.query(
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import { QdrantClient } from "@qdrant/js-client-rest";
|
||||
import { QdrantClient } from "@qdrant/js-client-rest"; // @hide
|
||||
|
||||
const client = new QdrantClient({ host: "localhost", port: 6333 });
|
||||
const client = new QdrantClient({ host: "localhost", port: 6333 }); // @hide
|
||||
|
||||
client.query("{collection_name}", {
|
||||
query: [0.2, 0.1, 0.9, 0.7],
|
||||
|
||||
@@ -8,7 +8,7 @@ aliases:
|
||||
- /documentation/concepts/search/
|
||||
---
|
||||
|
||||
# Similarity search
|
||||
# Similarity Search
|
||||
|
||||
Searching for the nearest vectors is at the core of many representational learning applications.
|
||||
Modern neural networks are trained to transform objects into vectors so that objects close in the real world appear close in vector space.
|
||||
@@ -142,7 +142,7 @@ In general, the speed of the search is proportional to the number of non-zero va
|
||||
|
||||
{{< code-snippet path="/documentation/headless/snippets/query-points/sparse-vectors/" >}}
|
||||
|
||||
### Filtering results by score
|
||||
### Filtering Results by Score
|
||||
|
||||
In addition to payload filtering, it might be useful to filter out results with a low similarity score.
|
||||
For example, if you know the minimal acceptance score for your model and do not want any results which are less similar than the threshold.
|
||||
@@ -151,7 +151,7 @@ It will exclude all results with a score worse than the given.
|
||||
|
||||
<aside role="status">This parameter may exclude lower or higher scores depending on the used metric. For example, higher scores of Euclidean metric are considered more distant and, therefore, will be excluded.</aside>
|
||||
|
||||
### Payload and vector in the result
|
||||
### Payload and Vector in the Result
|
||||
|
||||
By default, retrieval methods do not return any stored information such as
|
||||
payload and vectors. Additional parameters `with_vectors` and `with_payload`
|
||||
@@ -205,7 +205,7 @@ $$ \text{Estimated filter selectivity} =
|
||||
$$
|
||||
Since ACORN is significantly slower (approximately 2-10x in typical scenarios) but improves recall for restrictive filters, tuning this parameter is about deciding when the accuracy improvement justifies the performance cost.
|
||||
|
||||
## Batch search API
|
||||
## Batch Search API
|
||||
|
||||
The batch search API enables to perform multiple search requests via a single request.
|
||||
|
||||
@@ -266,22 +266,47 @@ collection `another_collection`.
|
||||
|
||||
## Pagination
|
||||
|
||||
Search and [recommendation](/documentation/search/explore/#recommendation-api) APIs allow to skip first results of the search and return only the result starting from some specified offset:
|
||||
The Search and [recommendation](/documentation/search/explore/#recommendation-api) APIs allow you to skip the first results and return only the results starting from a specified offset:
|
||||
|
||||
Example:
|
||||
|
||||
{{< code-snippet path="/documentation/headless/snippets/query-points/with-offset/" >}}
|
||||
|
||||
Is equivalent to retrieving the 11th page with 10 records per page.
|
||||
This is equivalent to retrieving the 11th page with 10 records per page.
|
||||
|
||||
<aside role="alert">Large offset values may cause performance issues</aside>
|
||||
|
||||
Vector-based retrieval in general and HNSW index in particular, are not designed to be paginated.
|
||||
It is impossible to retrieve Nth closest vector without retrieving the first N vectors first.
|
||||
Vector-based retrieval in general, and the HNSW index in particular, are not designed to be paginated. It is impossible to retrieve the Nth closest vector without internally retrieving the first N vectors first. However, using the `offset` parameter saves resources by reducing network traffic and the number of times the storage is accessed. Using the `offset` parameter internally retrieves `offset + limit` points, but only accesses the payload and vector of those points that are actually returned.
|
||||
|
||||
However, using the offset parameter saves the resources by reducing network traffic and the number of times the storage is accessed.
|
||||
### Stable Ordering
|
||||
|
||||
Using an `offset` parameter, will require to internally retrieve `offset + limit` points, but only access payload and vector from the storage those points which are going to be actually returned.
|
||||
Because HNSW search is approximate, the ranking of results can shift slightly between requests. As a result, paginating with `offset` can return the same point on multiple pages or skip points entirely.
|
||||
|
||||
There are several ways to work around this:
|
||||
|
||||
#### Client-Side Pagination
|
||||
|
||||
Retrieve a large batch in a single request and paginate through it on the client. For example, fetch the top 100 results at once and let the user browse them 10 at a time. This avoids multiple round-trips and guarantees no duplicates.
|
||||
|
||||
The trade-off is increased latency, and returning more data than the user actually needs.
|
||||
|
||||
#### Exact Search
|
||||
|
||||
Use exact searches to bypass HNSW and scan all vectors, returning results in a stable, deterministic order. This ensures that offset-based pagination works correctly.
|
||||
|
||||
The trade-off is higher latency, which makes this practical only for small collections.
|
||||
|
||||
{{< code-snippet path="/documentation/headless/snippets/query-points/with-exact-search/" >}}
|
||||
|
||||
#### Exclude Seen IDs
|
||||
|
||||
To avoid duplicates, on subsequent pages, add a `must_not: has_id` filter containing all point IDs collected from previous pages. This excludes all previously seen points from the results:
|
||||
|
||||
{{< code-snippet path="/documentation/headless/snippets/query-points/with-id-exclusion-pagination/" >}}
|
||||
|
||||
Repeat this pattern on every page, expanding the exclusion list with each set of results.
|
||||
|
||||
<aside role="status">The exclusion list grows by <code>limit</code> entries per page. This approach works well for sequential, forward-only pagination. It isn't practical for jumping directly to an arbitrary page.</aside>
|
||||
|
||||
## Grouping API
|
||||
|
||||
@@ -346,7 +371,7 @@ Consider having points with the following payloads:
|
||||
|
||||
With the ***groups*** API, you will be able to get the best *N* points for each document, assuming that the payload of the points contains the document ID. Of course there will be times where the best *N* points cannot be fulfilled due to lack of points or a big distance with respect to the query. In every case, the `group_size` is a best-effort parameter, akin to the `limit` parameter.
|
||||
|
||||
### Search groups
|
||||
### Search Groups
|
||||
|
||||
REST API ([Schema](https://api.qdrant.tech/api-reference/search/query-points-groups)):
|
||||
|
||||
@@ -402,7 +427,7 @@ If the `group_by` field of a point is an array (e.g. `"document_id": ["a", "b"]`
|
||||
* Only [keyword](/documentation/manage-data/payload/#keyword) and [integer](/documentation/manage-data/payload/#integer) payload values are supported for the `group_by` parameter. Payload values with other types will be ignored.
|
||||
* At the moment, pagination is not enabled when using **groups**, so the `offset` parameter is not allowed.
|
||||
|
||||
### Lookup in groups
|
||||
### Lookup in Groups
|
||||
|
||||
When the points in a group share large fields like titles, abstracts, or full document vectors, copying that data onto every point inflates storage and forces you to rewrite every chunk whenever a shared field changes.
|
||||
|
||||
@@ -484,7 +509,7 @@ Random sampling API is a part of [Universal Query API](#query-api) and can be us
|
||||
|
||||
{{< code-snippet path="/documentation/headless/snippets/query-points/random-sample/" >}}
|
||||
|
||||
## Query planning
|
||||
## Query Planning
|
||||
|
||||
Depending on the filter used in the search - there are several possible scenarios for query execution.
|
||||
Qdrant chooses one of the query execution options depending on the available indexes, the complexity of the conditions and the cardinality of the filtering result.
|
||||
|
||||
Reference in New Issue
Block a user