Управление точками#
Точки являются центральной сущностью, с которой работает Platform V Vector DB (далее - Vector DB). Точка представляет собой запись, состоящую из вектора и необязательной полезной нагрузки.
Например:
// This is a simple point
{
"id": 129,
"vector": [0.1, 0.2, 0.3, 0.4],
"payload": {"color": "red"},
}
Искать можно среди точек, сгруппированных в одной коллекции, основываясь на сходстве векторов. Эта процедура подробно описана в разделах поиск и фильтрация.
В данном разделе представлено как создавать и управлять векторами.
Любая операция модификации точки является асинхронной и выполняется за два шага. На первом этапе операция записывается в журнал упреждающей записи (Write-ahead-log). После чего сервис не потеряет данные даже при отключении питания машины.
Идентификаторы точек#
Vector DB поддерживает использование как 64-bit unsigned integers, так и UUID в качестве идентификаторов для точек.
Примеры представлений строк UUID:
простой:
{uuid};с дефисами:
{uuid-with-dashes};URN:
urn:uuid:{uuid-with-dashes}.
Это означает, что во всех запросах вместо числового идентификатора можно использовать строку UUID.
Например:
PUT /collections/{collection_name}/points
{
"points": [
{
"id": "5c56c793-69f3-4fbf-87e6-c4bf54c28c26",
"payload": {"color": "red"},
"vector": [0.9, 0.1, 0.1]
}
]
}
from qdrant_client import QdrantClient, models
client = QdrantClient(url="http://localhost:6333")
client.upsert(
collection_name="{collection_name}",
points=[
models.PointStruct(
id="5c56c793-69f3-4fbf-87e6-c4bf54c28c26",
payload={
"color": "red",
},
vector=[0.9, 0.1, 0.1],
),
],
)
import { QdrantClient } from "@qdrant/js-client-rest";
const client = new QdrantClient({ host: "localhost", port: 6333 });
client.upsert("{collection_name}", {
points: [
{
id: "5c56c793-69f3-4fbf-87e6-c4bf54c28c26",
payload: {
color: "red",
},
vector: [0.9, 0.1, 0.1],
},
],
});
use qdrant_client::qdrant::{PointStruct, UpsertPointsBuilder};
use qdrant_client::Qdrant;
let client = Qdrant::from_url("http://localhost:6334").build()?;
client
.upsert_points(
UpsertPointsBuilder::new(
"{collection_name}",
vec![PointStruct::new(
"5c56c793-69f3-4fbf-87e6-c4bf54c28c26",
vec![0.9, 0.1, 0.1],
[("color", "Red".into())],
)],
)
.wait(true),
)
.await?;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import static io.qdrant.client.PointIdFactory.id;
import static io.qdrant.client.ValueFactory.value;
import static io.qdrant.client.VectorsFactory.vectors;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Points.PointStruct;
QdrantClient client =
new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());
client
.upsertAsync(
"{collection_name}",
List.of(
PointStruct.newBuilder()
.setId(id(UUID.fromString("5c56c793-69f3-4fbf-87e6-c4bf54c28c26")))
.setVectors(vectors(0.05f, 0.61f, 0.76f, 0.74f))
.putAllPayload(Map.of("color", value("Red")))
.build()))
.get();
using Qdrant.Client;
using Qdrant.Client.Grpc;
var client = new QdrantClient("localhost", 6334);
await client.UpsertAsync(
collectionName: "{collection_name}",
points: new List<PointStruct>
{
new()
{
Id = Guid.Parse("5c56c793-69f3-4fbf-87e6-c4bf54c28c26"),
Vectors = new[] { 0.05f, 0.61f, 0.76f, 0.74f },
Payload = { ["color"] = "Red" }
}
}
);
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
client, err := qdrant.NewClient(&qdrant.Config{
Host: "localhost",
Port: 6334,
})
client.Upsert(context.Background(), &qdrant.UpsertPoints{
CollectionName: "{collection_name}",
Points: []*qdrant.PointStruct{
{
Id: qdrant.NewID("5c56c793-69f3-4fbf-87e6-c4bf54c28c26"),
Vectors: qdrant.NewVectors(0.05, 0.61, 0.76, 0.74),
Payload: qdrant.NewValueMap(map[string]any{"color": "Red"}),
},
},
})
и
PUT /collections/{collection_name}/points
{
"points": [
{
"id": 1,
"payload": {"color": "red"},
"vector": [0.9, 0.1, 0.1]
}
]
}
client.upsert(
collection_name="{collection_name}",
points=[
models.PointStruct(
id=1,
payload={
"color": "red",
},
vector=[0.9, 0.1, 0.1],
),
],
)
client.upsert("{collection_name}", {
points: [
{
id: 1,
payload: {
color: "red",
},
vector: [0.9, 0.1, 0.1],
},
],
});
use qdrant_client::qdrant::{PointStruct, UpsertPointsBuilder};
use qdrant_client::Qdrant;
let client = Qdrant::from_url("http://localhost:6334").build()?;
client
.upsert_points(
UpsertPointsBuilder::new(
"{collection_name}",
vec![PointStruct::new(
1,
vec![0.9, 0.1, 0.1],
[("color", "Red".into())],
)],
)
.wait(true),
)
.await?;
import java.util.List;
import java.util.Map;
import static io.qdrant.client.PointIdFactory.id;
import static io.qdrant.client.ValueFactory.value;
import static io.qdrant.client.VectorsFactory.vectors;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Points.PointStruct;
QdrantClient client =
new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());
client
.upsertAsync(
"{collection_name}",
List.of(
PointStruct.newBuilder()
.setId(id(1))
.setVectors(vectors(0.05f, 0.61f, 0.76f, 0.74f))
.putAllPayload(Map.of("color", value("Red")))
.build()))
.get();
using Qdrant.Client;
using Qdrant.Client.Grpc;
var client = new QdrantClient("localhost", 6334);
await client.UpsertAsync(
collectionName: "{collection_name}",
points: new List<PointStruct>
{
new()
{
Id = 1,
Vectors = new[] { 0.05f, 0.61f, 0.76f, 0.74f },
Payload = { ["color"] = "Red" }
}
}
);
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
client, err := qdrant.NewClient(&qdrant.Config{
Host: "localhost",
Port: 6334,
})
client.Upsert(context.Background(), &qdrant.UpsertPoints{
CollectionName: "{collection_name}",
Points: []*qdrant.PointStruct{
{
Id: qdrant.NewIDNum(1),
Vectors: qdrant.NewVectors(0.05, 0.61, 0.76, 0.74),
Payload: qdrant.NewValueMap(map[string]any{"color": "Red"}),
},
},
})
Оба представленных варианта допустимы.
Векторы#
Каждая точка в Vector DB может иметь один или несколько векторов. Векторы являются центральным компонентом архитектуры Vector DB, обеспечивая различные типы исследования данных и поиска.
Vector DB поддерживаемых несколько типов векторов:
Тип вектора |
Описание |
|---|---|
Плотные векторы |
Обычные векторы, генерируемые большинством моделей встраивания |
Разреженные векторы |
Векторы без фиксированной длины, но только с несколькими ненулевыми элементами. Полезны для точного совпадения токенов и рекомендаций по совместному фильтру |
Мультивекторы |
Матрицы чисел с фиксированной длиной, но переменной высотой. Обычно получаются из поздних интерактивных моделей, таких как ColBERT |
К одной точке можно прикрепить более одного типа вектора. В Vector DB это называются именованными векторами.
Подробнее о типах векторов, способах их хранения и оптимизации читайте в разделе Управление векторами.
Загрузка точек#
Для повышения производительности Vector DB поддерживает пакетную загрузку точек. Можно загрузить сразу несколько точек в службу одним вызовом API. Пакетная обработка позволяет минимизировать накладные расходы на создание сетевого соединения.
API Vector DB поддерживает два способа создания пакетов — ориентированный на записи и ориентированный на столбцы. Внутренне они ничем не отличаются и созданы исключительно ради удобства взаимодействия.
Пример создания точки с помощью пакета:
PUT /collections/{collection_name}/points
{
"batch": {
"ids": [1, 2, 3],
"payloads": [
{"color": "red"},
{"color": "green"},
{"color": "blue"}
],
"vectors": [
[0.9, 0.1, 0.1],
[0.1, 0.9, 0.1],
[0.1, 0.1, 0.9]
]
}
}
client.upsert(
collection_name="{collection_name}",
points=models.Batch(
ids=[1, 2, 3],
payloads=[
{"color": "red"},
{"color": "green"},
{"color": "blue"},
],
vectors=[
[0.9, 0.1, 0.1],
[0.1, 0.9, 0.1],
[0.1, 0.1, 0.9],
],
),
)
client.upsert("{collection_name}", {
batch: {
ids: [1, 2, 3],
payloads: [{ color: "red" }, { color: "green" }, { color: "blue" }],
vectors: [
[0.9, 0.1, 0.1],
[0.1, 0.9, 0.1],
[0.1, 0.1, 0.9],
],
},
});
или эквивалентный вариант, ориентированный на записи:
PUT /collections/{collection_name}/points
{
"points": [
{
"id": 1,
"payload": {"color": "red"},
"vector": [0.9, 0.1, 0.1]
},
{
"id": 2,
"payload": {"color": "green"},
"vector": [0.1, 0.9, 0.1]
},
{
"id": 3,
"payload": {"color": "blue"},
"vector": [0.1, 0.1, 0.9]
}
]
}
client.upsert(
collection_name="{collection_name}",
points=[
models.PointStruct(
id=1,
payload={
"color": "red",
},
vector=[0.9, 0.1, 0.1],
),
models.PointStruct(
id=2,
payload={
"color": "green",
},
vector=[0.1, 0.9, 0.1],
),
models.PointStruct(
id=3,
payload={
"color": "blue",
},
vector=[0.1, 0.1, 0.9],
),
],
)
client.upsert("{collection_name}", {
points: [
{
id: 1,
payload: { color: "red" },
vector: [0.9, 0.1, 0.1],
},
{
id: 2,
payload: { color: "green" },
vector: [0.1, 0.9, 0.1],
},
{
id: 3,
payload: { color: "blue" },
vector: [0.1, 0.1, 0.9],
},
],
});
use qdrant_client::qdrant::{PointStruct, UpsertPointsBuilder};
client
.upsert_points(
UpsertPointsBuilder::new(
"{collection_name}",
vec![
PointStruct::new(1, vec![0.9, 0.1, 0.1], [("city", "red".into())]),
PointStruct::new(2, vec![0.1, 0.9, 0.1], [("city", "green".into())]),
PointStruct::new(3, vec![0.1, 0.1, 0.9], [("city", "blue".into())]),
],
)
.wait(true),
)
.await?;
import java.util.List;
import java.util.Map;
import static io.qdrant.client.PointIdFactory.id;
import static io.qdrant.client.ValueFactory.value;
import static io.qdrant.client.VectorsFactory.vectors;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import io.qdrant.client.grpc.Points.PointStruct;
QdrantClient client =
new QdrantClient(QdrantGrpcClient.newBuilder("localhost", 6334, false).build());
client
.upsertAsync(
"{collection_name}",
List.of(
PointStruct.newBuilder()
.setId(id(1))
.setVectors(vectors(0.9f, 0.1f, 0.1f))
.putAllPayload(Map.of("color", value("red")))
.build(),
PointStruct.newBuilder()
.setId(id(2))
.setVectors(vectors(0.1f, 0.9f, 0.1f))
.putAllPayload(Map.of("color", value("green")))
.build(),
PointStruct.newBuilder()
.setId(id(3))
.setVectors(vectors(0.1f, 0.1f, 0.9f))
.putAllPayload(Map.of("color", value("blue")))
.build()))
.get();
using Qdrant.Client;
using Qdrant.Client.Grpc;
var client = new QdrantClient("localhost", 6334);
await client.UpsertAsync(
collectionName: "{collection_name}",
points: new List<PointStruct>
{
new()
{
Id = 1,
Vectors = new[] { 0.9f, 0.1f, 0.1f },
Payload = { ["color"] = "red" }
},
new()
{
Id = 2,
Vectors = new[] { 0.1f, 0.9f, 0.1f },
Payload = { ["color"] = "green" }
},
new()
{
Id = 3,
Vectors = new[] { 0.1f, 0.1f, 0.9f },
Payload = { ["color"] = "blue" }
}
}
);
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
client, err := qdrant.NewClient(&qdrant.Config{
Host: "localhost",
Port: 6334,
})
client.Upsert(context.Background(), &qdrant.UpsertPoints{
CollectionName: "{collection_name}",
Points: []*qdrant.PointStruct{
{
Id: qdrant.NewIDNum(1),
Vectors: qdrant.NewVectors(0.9, 0.1, 0.1),
Payload: qdrant.NewValueMap(map[string]any{"color": "red"}),
},
{
Id: qdrant.NewIDNum(2),
Vectors: qdrant.NewVectors(0.1, 0.9, 0.1),
Payload: qdrant.NewValueMap(map[string]any{"color": "green"}),
},
{
Id: qdrant.NewIDNum(3),
Vectors: qdrant.NewVectors(0.1, 0.1, 0.9),
Payload: qdrant.NewValueMap(map[string]any{"color": "blue"}),
},
},
})
У клиента Python есть дополнительные возможности для загрузки точек, включая:
параллелизацию;
механизм повторных попыток;
поддержка ленивой пакетной обработки.
Например, можно читать свои данные непосредственно с жестких дисков, чтобы избежать необходимости хранить все данные в оперативной памяти. Эти функции можно использовать с методами upload_collection и upload_points. Как и базовый upsert API, эти методы поддерживают оба формата — ориентированные на записи и ориентированные на столбцы.
Информация
Метод upload_points заменил метод upload_records, который теперь считается устаревшим.
Формат, ориентированный на столбцы:
client.upload_collection(
collection_name="{collection_name}",
ids=[1, 2],
payload=[
{"color": "red"},
{"color": "green"},
],
vectors=[
[0.9, 0.1, 0.1],
[0.1, 0.9, 0.1],
],
parallel=4,
max_retries=3,
)
Информация
Если ids не указаны, клиент Vector DB автоматически создаст их случайными UUID.
Формат, ориентированный на записи:
client.upload_points(
collection_name="{collection_name}",
points=[
models.PointStruct(
id=1,
payload={
"color": "red",
},
vector=[0.9, 0.1, 0.1],
),
models.PointStruct(
id=2,
payload={
"color": "green",
},
vector=[0.1, 0.9, 0.1],
),
],
parallel=4,
max_retries=3,
)
Все API-интерфейсы Vector DB, включая загрузку точек, идемпотентны. Это значит, что выполнение одного метода несколько раз подряд эквивалентно однократному выполнению.
В данном случае это означает, что точки с одинаковым идентификатором будут перезаписаны при повторной загрузке.
Свойство идемпотентности полезно, если используется, например, очередь сообщений, которая не гарантирует точное одноразовое исполнение. Даже с такой системой Vector DB обеспечивает согласованность данных.
Если коллекция была создана с несколькими векторами, каждый вектор можно предоставить, используя имя вектора:
PUT /collections/{collection_name}/points
{
"points": [
{
"id": 1,
"vector": {
"image": [0.9, 0.1, 0.1, 0.2],
"text": [0.4, 0.7, 0.1, 0.8, 0.1, 0.1, 0.9, 0.2]
}
},
{
"id": 2,
"vector": {
"image": [0.2, 0.1, 0.3, 0.9],
"text": [0.5, 0.2, 0.7, 0.4, 0.7, 0.2, 0.3, 0.9]
}
}
]
}
client.upsert(
collection_name="{collection_name}",
points=[
models.PointStruct(
id=1,
vector={
"image": [0.9, 0.1, 0.1, 0.2],
"text": [0.4, 0.7, 0.1, 0.8, 0.1, 0.1, 0.9, 0.2],
},
),
models.PointStruct(
id=2,
vector={
"image": [0.2, 0.1, 0.3, 0.9],
"text": [0.5, 0.2, 0.7, 0.4, 0.7, 0.2, 0.3, 0.9],
},
),
],
)
client.upsert("{collection_name}", {
points: [
{
id: 1,
vector: {
image: [0.9, 0.1, 0.1, 0.2],
text: [0.4, 0.7, 0.1, 0.8, 0.1, 0.1, 0.9, 0.2],
},
},
{
id: 2,
vector: {
image: [0.2, 0.1, 0.3, 0.9],
text: [0.5, 0.2, 0.7, 0.4, 0.7, 0.2, 0.3, 0.9],
},
},
],
});
use std::collections::HashMap;
use qdrant_client::qdrant::{PointStruct, UpsertPointsBuilder};
use qdrant_client::Payload;
client
.upsert_points(
UpsertPointsBuilder::new(
"{collection_name}",
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],
),
]),
Payload::default(),
),
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],
),
]),
Payload::default(),
),
],
)
.wait(true),
)
.await?;
import java.util.List;
import java.util.Map;
import static io.qdrant.client.PointIdFactory.id;
import static io.qdrant.client.VectorFactory.vector;
import static io.qdrant.client.VectorsFactory.namedVectors;
import io.qdrant.client.grpc.Points.PointStruct;
client
.upsertAsync(
"{collection_name}",
List.of(
PointStruct.newBuilder()
.setId(id(1))
.setVectors(
namedVectors(
Map.of(
"image",
vector(List.of(0.9f, 0.1f, 0.1f, 0.2f)),
"text",
vector(List.of(0.4f, 0.7f, 0.1f, 0.8f, 0.1f, 0.1f, 0.9f, 0.2f)))))
.build(),
PointStruct.newBuilder()
.setId(id(2))
.setVectors(
namedVectors(
Map.of(
"image",
List.of(0.2f, 0.1f, 0.3f, 0.9f),
"text",
List.of(0.5f, 0.2f, 0.7f, 0.4f, 0.7f, 0.2f, 0.3f, 0.9f))))
.build()))
.get();
using Qdrant.Client;
using Qdrant.Client.Grpc;
var client = new QdrantClient("localhost", 6334);
await client.UpsertAsync(
collectionName: "{collection_name}",
points: new List<PointStruct>
{
new()
{
Id = 1,
Vectors = new Dictionary<string, float[]>
{
["image"] = [0.9f, 0.1f, 0.1f, 0.2f],
["text"] = [0.4f, 0.7f, 0.1f, 0.8f, 0.1f, 0.1f, 0.9f, 0.2f]
}
},
new()
{
Id = 2,
Vectors = new Dictionary<string, float[]>
{
["image"] = [0.2f, 0.1f, 0.3f, 0.9f],
["text"] = [0.5f, 0.2f, 0.7f, 0.4f, 0.7f, 0.2f, 0.3f, 0.9f]
}
}
}
);
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
client, err := qdrant.NewClient(&qdrant.Config{
Host: "localhost",
Port: 6334,
})
client.Upsert(context.Background(), &qdrant.UpsertPoints{
CollectionName: "{collection_name}",
Points: []*qdrant.PointStruct{
{
Id: qdrant.NewIDNum(1),
Vectors: qdrant.NewVectorsMap(map[string]*qdrant.Vector{
"image": qdrant.NewVector(0.9, 0.1, 0.1, 0.2),
"text": qdrant.NewVector(0.4, 0.7, 0.1, 0.8, 0.1, 0.1, 0.9, 0.2),
}),
},
{
Id: qdrant.NewIDNum(2),
Vectors: qdrant.NewVectorsMap(map[string]*qdrant.Vector{
"image": qdrant.NewVector(0.2, 0.1, 0.3, 0.9),
"text": qdrant.NewVector(0.5, 0.2, 0.7, 0.4, 0.7, 0.2, 0.3, 0.9),
}),
},
},
})
Названия векторов являются опциональными. При загрузке точек некоторые векторы могут отсутствовать. Например, можно загрузить одну точку только с вектором image и вторую точку только с вектором text.
При загрузке точки с существующим идентификатором сначала удаляется существующая точка, затем вставляется новая точка со специально указанными векторами. Другими словами, вся точка замещается, а любые невыбранные векторы устанавливаются в значение null. Чтобы сохранить существующие векторы неизмененными и обновить только указанные векторы необходимо выполнить обновление векторов (подробнее в секции Обновление векторов).
Точки могут содержать плотные и разреженные векторы.
Разреженный вектор — массив, большинство элементов которого имеют нулевое значение.
Этим свойством можно воспользоваться для оптимизированного представления, поэтому у них другая форма, чем у плотных векторов.
Они представлены списком пар (index, value), где index — целое число, а value — вещественное число. Значение index указывает позицию ненулевого значения в векторе. Значение values — значение ненулевого элемента.
Например, следующий вектор:
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 2.0, 0.0, 0.0]
может быть представлен как разреженный вектор:
[(6, 1.0), (7, 2.0)]
Vector DB использует следующее представление JSON во всех своих API.
{
"indices": [6, 7],
"values": [1.0, 2.0]
}
Массивы indices и values должны иметь одинаковую длину. А элементы массива indices должны быть уникальными.
Если массивы indices не отсортированы, Vector DB отсортирует их внутренне, в связи с чем нельзя полагаться на порядок элементов.
Разреженные векторы должны иметь наименование и загружаться таким же образом, как и плотные векторы.
PUT /collections/{collection_name}/points
{
"points": [
{
"id": 1,
"vector": {
"text": {
"indices": [6, 7],
"values": [1.0, 2.0]
}
}
},
{
"id": 2,
"vector": {
"text": {
"indices": [1, 2, 4, 15, 33, 34],
"values": [0.1, 0.2, 0.3, 0.4, 0.5]
}
}
}
]
}
client.upsert(
collection_name="{collection_name}",
points=[
models.PointStruct(
id=1,
vector={
"text": models.SparseVector(
indices=[6, 7],
values=[1.0, 2.0],
)
},
),
models.PointStruct(
id=2,
vector={
"text": models.SparseVector(
indices=[1, 2, 3, 4, 5],
values=[0.1, 0.2, 0.3, 0.4, 0.5],
)
},
),
],
)
client.upsert("{collection_name}", {
points: [
{
id: 1,
vector: {
text: {
indices: [6, 7],
values: [1.0, 2.0],
},
},
},
{
id: 2,
vector: {
text: {
indices: [1, 2, 3, 4, 5],
values: [0.1, 0.2, 0.3, 0.4, 0.5],
},
},
},
],
});
use std::collections::HashMap;
use qdrant_client::qdrant::{PointStruct, UpsertPointsBuilder, Vector};
use qdrant_client::Payload;
client
.upsert_points(
UpsertPointsBuilder::new(
"{collection_name}",
vec![
PointStruct::new(
1,
HashMap::from([("text".to_string(), vec![(6, 1.0), (7, 2.0)])]),
Payload::default(),
),
PointStruct::new(
2,
HashMap::from([(
"text".to_string(),
vec![(1, 0.1), (2, 0.2), (3, 0.3), (4, 0.4), (5, 0.5)],
)]),
Payload::default(),
),
],
)
.wait(true),
)
.await?;
import java.util.List;
import java.util.Map;
import static io.qdrant.client.PointIdFactory.id;
import static io.qdrant.client.VectorFactory.vector;
import io.qdrant.client.grpc.Points.NamedVectors;
import io.qdrant.client.grpc.Points.PointStruct;
import io.qdrant.client.grpc.Points.Vectors;
client
.upsertAsync(
"{collection_name}",
List.of(
PointStruct.newBuilder()
.setId(id(1))
.setVectors(
Vectors.newBuilder()
.setVectors(
NamedVectors.newBuilder()
.putAllVectors(
Map.of(
"text", vector(List.of(1.0f, 2.0f), List.of(6, 7))))
.build())
.build())
.build(),
PointStruct.newBuilder()
.setId(id(2))
.setVectors(
Vectors.newBuilder()
.setVectors(
NamedVectors.newBuilder()
.putAllVectors(
Map.of(
"text",
vector(
List.of(0.1f, 0.2f, 0.3f, 0.4f, 0.5f),
List.of(1, 2, 3, 4, 5))))
.build())
.build())
.build()))
.get();
using Qdrant.Client;
using Qdrant.Client.Grpc;
var client = new QdrantClient("localhost", 6334);
await client.UpsertAsync(
collectionName: "{collection_name}",
points: new List<PointStruct>
{
new()
{
Id = 1,
Vectors = new Dictionary<string, Vector> { ["text"] = ([1.0f, 2.0f], [6, 7]) }
},
new()
{
Id = 2,
Vectors = new Dictionary<string, Vector>
{
["text"] = ([0.1f, 0.2f, 0.3f, 0.4f, 0.5f], [1, 2, 3, 4, 5])
}
}
}
);
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
client, err := qdrant.NewClient(&qdrant.Config{
Host: "localhost",
Port: 6334,
})
client.Upsert(context.Background(), &qdrant.UpsertPoints{
CollectionName: "{collection_name}",
Points: []*qdrant.PointStruct{
{
Id: qdrant.NewIDNum(1),
Vectors: qdrant.NewVectorsMap(map[string]*qdrant.Vector{
"text": qdrant.NewVectorSparse(
[]uint32{6, 7},
[]float32{1.0, 2.0}),
}),
},
{
Id: qdrant.NewIDNum(2),
Vectors: qdrant.NewVectorsMap(map[string]*qdrant.Vector{
"text": qdrant.NewVectorSparse(
[]uint32{1, 2, 3, 4, 5},
[]float32{0.1, 0.2, 0.3, 0.4, 0.5}),
}),
},
},
})
Изменение точек#
Чтобы изменить точку, можно модифицировать ее векторы или полезную нагрузку. Есть несколько способов это сделать.
Обновление векторов#
Этот метод обновляет заданные векторы указанных точек. Невыбранные векторы остаются неизменными. Все предоставленные точки должны существовать.
PUT /collections/{collection_name}/points/vectors
{
"points": [
{
"id": 1,
"vector": {
"image": [0.1, 0.2, 0.3, 0.4]
}
},
{
"id": 2,
"vector": {
"text": [0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2]
}
}
]
}
client.update_vectors(
collection_name="{collection_name}",
points=[
models.PointVectors(
id=1,
vector={
"image": [0.1, 0.2, 0.3, 0.4],
},
),
models.PointVectors(
id=2,
vector={
"text": [0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2],
},
),
],
)
client.updateVectors("{collection_name}", {
points: [
{
id: 1,
vector: {
image: [0.1, 0.2, 0.3, 0.4],
},
},
{
id: 2,
vector: {
text: [0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2],
},
},
],
});
use std::collections::HashMap;
use qdrant_client::qdrant::{
PointVectors, UpdatePointVectorsBuilder,
};
client
.update_vectors(
UpdatePointVectorsBuilder::new(
"{collection_name}",
vec![
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(),
),
},
],
)
.wait(true),
)
.await?;
import java.util.List;
import java.util.Map;
import static io.qdrant.client.PointIdFactory.id;
import static io.qdrant.client.VectorFactory.vector;
import static io.qdrant.client.VectorsFactory.namedVectors;
client
.updateVectorsAsync(
"{collection_name}",
List.of(
PointVectors.newBuilder()
.setId(id(1))
.setVectors(namedVectors(Map.of("image", vector(List.of(0.1f, 0.2f, 0.3f, 0.4f)))))
.build(),
PointVectors.newBuilder()
.setId(id(2))
.setVectors(
namedVectors(
Map.of(
"text", vector(List.of(0.9f, 0.8f, 0.7f, 0.6f, 0.5f, 0.4f, 0.3f, 0.2f)))))
.build()))
.get();
using Qdrant.Client;
using Qdrant.Client.Grpc;
var client = new QdrantClient("localhost", 6334);
await client.UpdateVectorsAsync(
collectionName: "{collection_name}",
points: new List<PointVectors>
{
new() { Id = 1, Vectors = ("image", new float[] { 0.1f, 0.2f, 0.3f, 0.4f }) },
new()
{
Id = 2,
Vectors = ("text", new float[] { 0.9f, 0.8f, 0.7f, 0.6f, 0.5f, 0.4f, 0.3f, 0.2f })
}
}
);
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
client, err := qdrant.NewClient(&qdrant.Config{
Host: "localhost",
Port: 6334,
})
client.UpdateVectors(context.Background(), &qdrant.UpdatePointVectors{
CollectionName: "{collection_name}",
Points: []*qdrant.PointVectors{
{
Id: qdrant.NewIDNum(1),
Vectors: qdrant.NewVectorsMap(map[string]*qdrant.Vector{
"image": qdrant.NewVector(0.1, 0.2, 0.3, 0.4),
}),
},
{
Id: qdrant.NewIDNum(2),
Vectors: qdrant.NewVectorsMap(map[string]*qdrant.Vector{
"text": qdrant.NewVector(0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2),
}),
},
},
})
Чтобы обновить точки, и заменить все их векторы обратите внимание на раздел загрузка точек.
Удаление векторов#
Этот метод удаляет только указанные векторы из указанных точек. Остальные векторы остаются неизменными. Сами точки никогда не удаляются.
POST /collections/{collection_name}/points/vectors/delete
{
"points": [0, 3, 100],
"vectors": ["text", "image"]
}
client.delete_vectors(
collection_name="{collection_name}",
points=[0, 3, 100],
vectors=["text", "image"],
)
client.deleteVectors("{collection_name}", {
points: [0, 3, 10],
vector: ["text", "image"],
});
use qdrant_client::qdrant::{
DeletePointVectorsBuilder, PointsIdsList,
};
client
.delete_vectors(
DeletePointVectorsBuilder::new("{collection_name}")
.points_selector(PointsIdsList {
ids: vec![0.into(), 3.into(), 10.into()],
})
.vectors(vec!["text".into(), "image".into()])
.wait(true),
)
.await?;
import java.util.List;
import static io.qdrant.client.PointIdFactory.id;
client
.deleteVectorsAsync(
"{collection_name}", List.of("text", "image"), List.of(id(0), id(3), id(10)))
.get();
await client.DeleteVectorsAsync("{collection_name}", ["text", "image"], [0, 3, 10]);
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
client.DeleteVectors(context.Background(), &qdrant.DeletePointVectors{
CollectionName: "{collection_name}",
PointsSelector: qdrant.NewPointsSelector(
qdrant.NewIDNum(0), qdrant.NewIDNum(3), qdrant.NewIDNum(10)),
Vectors: &qdrant.VectorsSelector{
Names: []string{"text", "image"},
},
})
Чтобы удалить целые точки, ознакомьтесь с разделом Удаление точек.
Обновление полезной нагрузки#
Алгоритм изменения полезной нагрузки точки подробно описан в разделе Обновление полезной нагрузки.
Удаление точек#
Удаление точек:
POST /collections/{collection_name}/points/delete
{
"points": [0, 3, 100]
}
client.delete(
collection_name="{collection_name}",
points_selector=models.PointIdsList(
points=[0, 3, 100],
),
)
client.delete("{collection_name}", {
points: [0, 3, 100],
});
use qdrant_client::qdrant::{DeletePointsBuilder, PointsIdsList};
client
.delete_points(
DeletePointsBuilder::new("{collection_name}")
.points(PointsIdsList {
ids: vec![0.into(), 3.into(), 100.into()],
})
.wait(true),
)
.await?;
import java.util.List;
import static io.qdrant.client.PointIdFactory.id;
client.deleteAsync("{collection_name}", List.of(id(0), id(3), id(100)));
using Qdrant.Client;
var client = new QdrantClient("localhost", 6334);
await client.DeleteAsync(collectionName: "{collection_name}", ids: [0, 3, 100]);
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
client, err := qdrant.NewClient(&qdrant.Config{
Host: "localhost",
Port: 6334,
})
client.Delete(context.Background(), &qdrant.DeletePoints{
CollectionName: "{collection_name}",
Points: qdrant.NewPointsSelector(
qdrant.NewIDNum(0), qdrant.NewIDNum(3), qdrant.NewIDNum(100),
),
})
Альтернативный способ указать, какие точки следует удалить, заключается в использовании фильтра:
POST /collections/{collection_name}/points/delete
{
"filter": {
"must": [
{
"key": "color",
"match": {
"value": "red"
}
}
]
}
}
client.delete(
collection_name="{collection_name}",
points_selector=models.FilterSelector(
filter=models.Filter(
must=[
models.FieldCondition(
key="color",
match=models.MatchValue(value="red"),
),
],
)
),
)
client.delete("{collection_name}", {
filter: {
must: [
{
key: "color",
match: {
value: "red",
},
},
],
},
});
use qdrant_client::qdrant::{Condition, DeletePointsBuilder, Filter};
client
.delete_points(
DeletePointsBuilder::new("{collection_name}")
.points(Filter::must([Condition::matches(
"color",
"red".to_string(),
)]))
.wait(true),
)
.await?;
import static io.qdrant.client.ConditionFactory.matchKeyword;
import io.qdrant.client.grpc.Points.Filter;
client
.deleteAsync(
"{collection_name}",
Filter.newBuilder().addMust(matchKeyword("color", "red")).build())
.get();
using Qdrant.Client;
using static Qdrant.Client.Grpc.Conditions;
var client = new QdrantClient("localhost", 6334);
await client.DeleteAsync(collectionName: "{collection_name}", filter: MatchKeyword("color", "red"));
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
client, err := qdrant.NewClient(&qdrant.Config{
Host: "localhost",
Port: 6334,
})
client.Delete(context.Background(), &qdrant.DeletePoints{
CollectionName: "{collection_name}",
Points: qdrant.NewPointsSelectorFilter(
&qdrant.Filter{
Must: []*qdrant.Condition{
qdrant.NewMatch("color", "red"),
},
},
),
})
Этот пример удаляет все точки с { "color": "red" } из коллекции.
Получение точек#
Существует метод получения точек по их идентификаторам:
POST /collections/{collection_name}/points
{
"ids": [0, 3, 100]
}
client.retrieve(
collection_name="{collection_name}",
ids=[0, 3, 100],
)
client.retrieve("{collection_name}", {
ids: [0, 3, 100],
});
use qdrant_client::qdrant::GetPointsBuilder;
client
.get_points(GetPointsBuilder::new(
"{collection_name}",
vec![0.into(), 30.into(), 100.into()],
))
.await?;
import java.util.List;
import static io.qdrant.client.PointIdFactory.id;
client
.retrieveAsync("{collection_name}", List.of(id(0), id(30), id(100)), false, false, null)
.get();
using Qdrant.Client;
var client = new QdrantClient("localhost", 6334);
await client.RetrieveAsync(
collectionName: "{collection_name}",
ids: [0, 30, 100],
withPayload: false,
withVectors: false
);
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
client, err := qdrant.NewClient(&qdrant.Config{
Host: "localhost",
Port: 6334,
})
client.Get(context.Background(), &qdrant.GetPoints{
CollectionName: "{collection_name}",
Ids: []*qdrant.PointId{
qdrant.NewIDNum(0), qdrant.NewIDNum(3), qdrant.NewIDNum(100),
},
})
Этот метод имеет дополнительные параметры with_vectors и with_payload. Используя эти параметры, можно выбрать части точки, которые хотите получить в результате. Исключение помогает не тратить трафик на передачу бесполезных данных.
Отдельную точку также можно получить через API:
GET /collections/{collection_name}/points/{point_id}
Прокрутка точек#
Иногда необходимо получить все сохраненные точки без знания идентификаторов или пройти по точкам, соответствующим фильтру:
POST /collections/{collection_name}/points/scroll
{
"filter": {
"must": [
{
"key": "color",
"match": {
"value": "red"
}
}
]
},
"limit": 1,
"with_payload": true,
"with_vector": false
}
client.scroll(
collection_name="{collection_name}",
scroll_filter=models.Filter(
must=[
models.FieldCondition(key="color", match=models.MatchValue(value="red")),
]
),
limit=1,
with_payload=True,
with_vectors=False,
)
client.scroll("{collection_name}", {
filter: {
must: [
{
key: "color",
match: {
value: "red",
},
},
],
},
limit: 1,
with_payload: true,
with_vector: false,
});
use qdrant_client::qdrant::{Condition, Filter, ScrollPointsBuilder};
client
.scroll(
ScrollPointsBuilder::new("{collection_name}")
.filter(Filter::must([Condition::matches(
"color",
"red".to_string(),
)]))
.limit(1)
.with_payload(true)
.with_vectors(false),
)
.await?;
import static io.qdrant.client.ConditionFactory.matchKeyword;
import static io.qdrant.client.WithPayloadSelectorFactory.enable;
import io.qdrant.client.grpc.Points.Filter;
import io.qdrant.client.grpc.Points.ScrollPoints;
client
.scrollAsync(
ScrollPoints.newBuilder()
.setCollectionName("{collection_name}")
.setFilter(Filter.newBuilder().addMust(matchKeyword("color", "red")).build())
.setLimit(1)
.setWithPayload(enable(true))
.build())
.get();
using Qdrant.Client;
using static Qdrant.Client.Grpc.Conditions;
var client = new QdrantClient("localhost", 6334);
await client.ScrollAsync(
collectionName: "{collection_name}",
filter: MatchKeyword("color", "red"),
limit: 1,
payloadSelector: true
);
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
client, err := qdrant.NewClient(&qdrant.Config{
Host: "localhost",
Port: 6334,
})
client.Scroll(context.Background(), &qdrant.ScrollPoints{
CollectionName: "{collection_name}",
Filter: &qdrant.Filter{
Must: []*qdrant.Condition{
qdrant.NewMatch("color", "red"),
},
},
Limit: qdrant.PtrOf(uint32(1)),
WithPayload: qdrant.NewWithPayload(true),
})
Данный пример возвращает все точки с color = red.
{
"result": {
"next_page_offset": 1,
"points": [
{
"id": 0,
"payload": {
"color": "red"
}
}
]
},
"status": "ok",
"time": 0.0001
}
Интерфейс прокрутки возвращает все точки, соответствующие фильтру, постранично.
Все результирующие точки сортируются по идентификатору. Для запроса следующей страницы необходимо указать наибольший видимый идентификатор в поле offset. Для удобства этот идентификатор также возвращается в поле next_page_offset. Если значение поля next_page_offset равно null, достигнута последняя страница.
Сортировка точек по ключу полезной нагрузки#
Используя интерфейс API scroll, можно сортировать результаты по ключу полезной нагрузки. Например, можно получать точки в хронологическом порядке, если полезные нагрузки содержат поле timestamp:
Информация
Без соответствующего индекса упорядочивание на основе полезной нагрузки создавало бы слишком большую нагрузку на систему при каждом запросе. Поэтому Qdrant требует наличия индекса полезной нагрузки, поддерживающего условия диапазонного фильтрования для поля, используемого в order_by.
POST /collections/{collection_name}/points/scroll
{
"limit": 15,
"order_by": "timestamp", // <-- this!
}
client.scroll(
collection_name="{collection_name}",
limit=15,
order_by="timestamp", # <-- this!
)
client.scroll("{collection_name}", {
limit: 15,
order_by: "timestamp", // <-- this!
});
use qdrant_client::qdrant::{OrderByBuilder, ScrollPointsBuilder};
client
.scroll(
ScrollPointsBuilder::new("{collection_name}")
.limit(15)
.order_by(OrderByBuilder::new("timestamp")),
)
.await?;
import io.qdrant.client.grpc.Points.OrderBy;
import io.qdrant.client.grpc.Points.ScrollPoints;
client.scrollAsync(ScrollPoints.newBuilder()
.setCollectionName("{collection_name}")
.setLimit(15)
.setOrderBy(OrderBy.newBuilder().setKey("timestamp").build())
.build()).get();
await client.ScrollAsync("{collection_name}", limit: 15, orderBy: "timestamp");
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
client, err := qdrant.NewClient(&qdrant.Config{
Host: "localhost",
Port: 6334,
})
client.Scroll(context.Background(), &qdrant.ScrollPoints{
CollectionName: "{collection_name}",
Limit: qdrant.PtrOf(uint32(15)),
OrderBy: &qdrant.OrderBy{
Key: "timestamp",
},
})
Необходимо использовать параметр order_by key, чтобы задать ключ полезной нагрузки. Затем можно добавить другие поля для управления порядком сортировки, такие как direction и start_from:
"order_by": {
"key": "timestamp",
"direction": "desc" // default is "asc"
"start_from": 123, // start from this value
}
order_by=models.OrderBy(
key="timestamp",
direction="desc", # default is "asc"
start_from=123, # start from this value
)
order_by: {
key: "timestamp",
direction: "desc", // default is "asc"
start_from: 123, // start from this value
}
use qdrant_client::qdrant::{start_from::Value, Direction, OrderByBuilder};
OrderByBuilder::new("timestamp")
.direction(Direction::Desc.into())
.start_from(Value::Integer(123))
.build();
import io.qdrant.client.grpc.Points.Direction;
import io.qdrant.client.grpc.Points.OrderBy;
import io.qdrant.client.grpc.Points.StartFrom;
OrderBy.newBuilder()
.setKey("timestamp")
.setDirection(Direction.Desc)
.setStartFrom(StartFrom.newBuilder()
.setInteger(123)
.build())
.build();
using Qdrant.Client.Grpc;
new OrderBy
{
Key = "timestamp",
Direction = Direction.Desc,
StartFrom = 123
};
import "github.com/qdrant/go-client/qdrant"
qdrant.OrderBy{
Key: "timestamp",
Direction: qdrant.Direction_Desc.Enum(),
StartFrom: qdrant.NewStartFromInt(123),
}
Важно
Когда используется параметр order_by, пагинация отключена.
Если сортировка основана на неуникальном значении, невозможно положиться на смещение идентификатора. Таким образом, в ответе не возвращается next_page_offset. Однако все еще можно выполнять пагинацию, комбинируя "order_by": { "start_from": ... } с фильтром { "must_not": [{ "has_id": [...] }] }.
Подсчет точек#
Бывает полезным знать, сколько точек соответствует условиям фильтра, не выполняя реальный поиск.
Среди прочего, например, можно выделить следующие сценарии:
оценка размера результатов для фасетированного поиска;
определение количества страниц для пагинации;
отладка скорости выполнения запросов.
POST /collections/{collection_name}/points/count
{
"filter": {
"must": [
{
"key": "color",
"match": {
"value": "red"
}
}
]
},
"exact": true
}
client.count(
collection_name="{collection_name}",
count_filter=models.Filter(
must=[
models.FieldCondition(key="color", match=models.MatchValue(value="red")),
]
),
exact=True,
)
client.count("{collection_name}", {
filter: {
must: [
{
key: "color",
match: {
value: "red",
},
},
],
},
exact: true,
});
use qdrant_client::qdrant::{Condition, CountPointsBuilder, Filter};
client
.count(
CountPointsBuilder::new("{collection_name}")
.filter(Filter::must([Condition::matches(
"color",
"red".to_string(),
)]))
.exact(true),
)
.await?;
import static io.qdrant.client.ConditionFactory.matchKeyword;
import io.qdrant.client.grpc.Points.Filter;
client
.countAsync(
"{collection_name}",
Filter.newBuilder().addMust(matchKeyword("color", "red")).build(),
true)
.get();
using Qdrant.Client;
using static Qdrant.Client.Grpc.Conditions;
var client = new QdrantClient("localhost", 6334);
await client.CountAsync(
collectionName: "{collection_name}",
filter: MatchKeyword("color", "red"),
exact: true
);
import (
"context"
"github.com/qdrant/go-client/qdrant"
)
client, err := qdrant.NewClient(&qdrant.Config{
Host: "localhost",
Port: 6334,
})
client.Count(context.Background(), &qdrant.CountPoints{
CollectionName: "midlib",
Filter: &qdrant.Filter{
Must: []*qdrant.Condition{
qdrant.NewMatch("color", "red"),
},
},
})
Данный пример возвращает количество подсчитанных значений, соответствующих указанным условиям фильтрации:
{
"count": 3811
}
Пакетное обновление#
Можно выполнить пакет нескольких операций обновления точек. В пакет входят операции вставки, обновления и удаления точек, векторов и полезной нагрузки.
Запрос пакетного обновления состоит из списка операций. Они выполняются последовательно. Операции могут объединяться следующим образом:
Добавление/изменение точки:
upsertилиUpsertOperation;Удаление точки:
delete_pointsилиDeleteOperation;Обновление векторов:
update_vectorsилиUpdateVectorsOperation;Удаление векторов:
delete_vectorsилиDeleteVectorsOperation;Установка полезной нагрузки:
set_payloadилиSetPayloadOperation;Перезапись полезной нагрузки:
overwrite_payloadилиOverwritePayload;Удаление полезной нагрузки:
delete_payloadилиDeletePayloadOperation;Очистка полезной нагрузки:
clear_payloadилиClearPayloadOperation.
Следующий фрагмент демонстрирует использование всех этих операций:
POST /collections/{collection_name}/points/batch
{
"operations": [
{
"upsert": {
"points": [
{
"id": 1,
"vector": [1.0, 2.0, 3.0, 4.0],
"payload": {}
}
]
}
},
{
"update_vectors": {
"points": [
{
"id": 1,
"vector": [1.0, 2.0, 3.0, 4.0]
}
]
}
},
{
"delete_vectors": {
"points": [1],
"vector": [""]
}
},
{
"overwrite_payload": {
"payload": {
"test_payload": "1"
},
"points": [1]
}
},
{
"set_payload": {
"payload": {
"test_payload_2": "2",
"test_payload_3": "3"
},
"points": [1]
}
},
{
"delete_payload": {
"keys": ["test_payload_2"],
"points": [1]
}
},
{
"clear_payload": {
"points": [1]
}
},
{"delete": {"points": [1]}}
]
}
client.batch_update_points(
collection_name="{collection_name}",
update_operations=[
models.UpsertOperation(
upsert=models.PointsList(
points=[
models.PointStruct(
id=1,
vector=[1.0, 2.0, 3.0, 4.0],
payload={},
),
]
)
),
models.UpdateVectorsOperation(
update_vectors=models.UpdateVectors(
points=[
models.PointVectors(
id=1,
vector=[1.0, 2.0, 3.0, 4.0],
)
]
)
),
models.DeleteVectorsOperation(
delete_vectors=models.DeleteVectors(points=[1], vector=[""])
),
models.OverwritePayloadOperation(
overwrite_payload=models.SetPayload(
payload={"test_payload": 1},
points=[1],
)
),
models.SetPayloadOperation(
set_payload=models.SetPayload(
payload={
"test_payload_2": 2,
"test_payload_3": 3,
},
points=[1],
)
),
models.DeletePayloadOperation(
delete_payload=models.DeletePayload(keys=["test_payload_2"], points=[1])
),
models.ClearPayloadOperation(clear_payload=models.PointIdsList(points=[1])),
models.DeleteOperation(delete=models.PointIdsList(points=[1])),
],
)
client.batchUpdate("{collection_name}", {
operations: [
{
upsert: {
points: [
{
id: 1,
vector: [1.0, 2.0, 3.0, 4.0],
payload: {},
},
],
},
},
{
update_vectors: {
points: [
{
id: 1,
vector: [1.0, 2.0, 3.0, 4.0],
},
],
},
},
{
delete_vectors: {
points: [1],
vector: [""],
},
},
{
overwrite_payload: {
payload: {
test_payload: 1,
},
points: [1],
},
},
{
set_payload: {
payload: {
test_payload_2: 2,
test_payload_3: 3,
},
points: [1],
},
},
{
delete_payload: {
keys: ["test_payload_2"],
points: [1],
},
},
{
clear_payload: {
points: [1],
},
},
{
delete: {
points: [1],
},
},
],
});
use std::collections::HashMap;
use qdrant_client::qdrant::{
points_update_operation::{
ClearPayload, DeletePayload, DeletePoints, DeleteVectors, Operation, OverwritePayload,
PointStructList, SetPayload, UpdateVectors,
},
PointStruct, PointVectors, PointsUpdateOperation, UpdateBatchPointsBuilder, VectorsSelector,
};
use qdrant_client::Payload;
client
.update_points_batch(
UpdateBatchPointsBuilder::new(
"{collection_name}",
vec![
PointsUpdateOperation {
operation: Some(Operation::Upsert(PointStructList {
points: vec![PointStruct::new(
1,
vec![1.0, 2.0, 3.0, 4.0],
Payload::default(),
)],
..Default::default()
})),
},
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()),
}],
..Default::default()
})),
},
PointsUpdateOperation {
operation: Some(Operation::DeleteVectors(DeleteVectors {
points_selector: Some(vec![1.into()].into()),
vectors: Some(VectorsSelector {
names: vec!["".into()],
}),
..Default::default()
})),
},
PointsUpdateOperation {
operation: Some(Operation::OverwritePayload(OverwritePayload {
points_selector: Some(vec![1.into()].into()),
payload: HashMap::from([("test_payload".to_string(), 1.into())]),
..Default::default()
})),
},
PointsUpdateOperation {
operation: Some(Operation::SetPayload(SetPayload {
points_selector: Some(vec![1.into()].into()),
payload: HashMap::from([
("test_payload_2".to_string(), 2.into()),
("test_payload_3".to_string(), 3.into()),
]),
..Default::default()
})),
},
PointsUpdateOperation {
operation: Some(Operation::DeletePayload(DeletePayload {
points_selector: Some(vec![1.into()].into()),
keys: vec!["test_payload_2".to_string()],
..Default::default()
})),
},
PointsUpdateOperation {
operation: Some(Operation::ClearPayload(ClearPayload {
points: Some(vec![1.into()].into()),
..Default::default()
})),
},
PointsUpdateOperation {
operation: Some(Operation::DeletePoints(DeletePoints {
points: Some(vec![1.into()].into()),
..Default::default()
})),
},
],
)
.wait(true),
)
.await?;
import java.util.List;
import java.util.Map;
import static io.qdrant.client.PointIdFactory.id;
import static io.qdrant.client.ValueFactory.value;
import static io.qdrant.client.VectorsFactory.vectors;
import io.qdrant.client.grpc.Points.PointStruct;
import io.qdrant.client.grpc.Points.PointVectors;
import io.qdrant.client.grpc.Points.PointsIdsList;
import io.qdrant.client.grpc.Points.PointsSelector;
import io.qdrant.client.grpc.Points.PointsUpdateOperation;
import io.qdrant.client.grpc.Points.PointsUpdateOperation.ClearPayload;
import io.qdrant.client.grpc.Points.PointsUpdateOperation.DeletePayload;
import io.qdrant.client.grpc.Points.PointsUpdateOperation.DeletePoints;
import io.qdrant.client.grpc.Points.PointsUpdateOperation.DeleteVectors;
import io.qdrant.client.grpc.Points.PointsUpdateOperation.PointStructList;
import io.qdrant.client.grpc.Points.PointsUpdateOperation.SetPayload;
import io.qdrant.client.grpc.Points.PointsUpdateOperation.UpdateVectors;
import io.qdrant.client.grpc.Points.VectorsSelector;
client
.batchUpdateAsync(
"{collection_name}",
List.of(
PointsUpdateOperation.newBuilder()
.setUpsert(
PointStructList.newBuilder()
.addPoints(
PointStruct.newBuilder()
.setId(id(1))
.setVectors(vectors(1.0f, 2.0f, 3.0f, 4.0f))
.build())
.build())
.build(),
PointsUpdateOperation.newBuilder()
.setUpdateVectors(
UpdateVectors.newBuilder()
.addPoints(
PointVectors.newBuilder()
.setId(id(1))
.setVectors(vectors(1.0f, 2.0f, 3.0f, 4.0f))
.build())
.build())
.build(),
PointsUpdateOperation.newBuilder()
.setDeleteVectors(
DeleteVectors.newBuilder()
.setPointsSelector(
PointsSelector.newBuilder()
.setPoints(PointsIdsList.newBuilder().addIds(id(1)).build())
.build())
.setVectors(VectorsSelector.newBuilder().addNames("").build())
.build())
.build(),
PointsUpdateOperation.newBuilder()
.setOverwritePayload(
SetPayload.newBuilder()
.setPointsSelector(
PointsSelector.newBuilder()
.setPoints(PointsIdsList.newBuilder().addIds(id(1)).build())
.build())
.putAllPayload(Map.of("test_payload", value(1)))
.build())
.build(),
PointsUpdateOperation.newBuilder()
.setSetPayload(
SetPayload.newBuilder()
.setPointsSelector(
PointsSelector.newBuilder()
.setPoints(PointsIdsList.newBuilder().addIds(id(1)).build())
.build())
.putAllPayload(
Map.of("test_payload_2", value(2), "test_payload_3", value(3)))
.build())
.build(),
PointsUpdateOperation.newBuilder()
.setDeletePayload(
DeletePayload.newBuilder()
.setPointsSelector(
PointsSelector.newBuilder()
.setPoints(PointsIdsList.newBuilder().addIds(id(1)).build())
.build())
.addKeys("test_payload_2")
.build())
.build(),
PointsUpdateOperation.newBuilder()
.setClearPayload(
ClearPayload.newBuilder()
.setPoints(
PointsSelector.newBuilder()
.setPoints(PointsIdsList.newBuilder().addIds(id(1)).build())
.build())
.build())
.build(),
PointsUpdateOperation.newBuilder()
.setDeletePoints(
DeletePoints.newBuilder()
.setPoints(
PointsSelector.newBuilder()
.setPoints(PointsIdsList.newBuilder().addIds(id(1)).build())
.build())
.build())
.build()))
.get();
Чтобы объединить множество точек с операцией одного типа, используйте функциональность пакетирования напрямую в этой операции.
Ожидание результата#
Если вызов API осуществляется с параметром &wait=false, либо он явно не указан, клиенту будет отправлено подтверждение приема данных:
{
"result": {
"operation_id": 123,
"status": "acknowledged"
},
"status": "ok",
"time": 0.000206061
}
Этот ответ не означает, что данные уже доступны для извлечения. Используется модель окончательной согласованности. Может потребоваться некоторое время до фактической обработки, поскольку обновление коллекции происходит в фоновом режиме. Фактически возможно, что такой запрос в конечном итоге завершится неудачей. Если вставляется большое количество векторов, настоятельно рекомендуется использовать асинхронные запросы, чтобы воспользоваться преимуществами конвейеризации.
Если логика приложения требует гарантии того, что вектор будет доступен для поиска немедленно после ответа API, используйте флаг &wait=true. В этом случае API вернет результат только после завершения операции:
{
"result": {
"operation_id": 0,
"status": "completed"
},
"status": "ok",
"time": 0.000206061
}