@qdrant/js-client-rest
Version:
This repository contains the REST client for the [Qdrant](https://github.com/qdrant/qdrant) vector search engine.
1,240 lines (1,228 loc) • 106 kB
JavaScript
'use strict';
var none = {
isNone: function () {
return true;
},
orElse: function (fallback) {
return fallback;
},
orCall: function (getFallback) {
return getFallback();
},
orNull: function () {
return null;
},
orThrow: function (message) {
if (message === void 0) { message = 'Unexpected null value'; }
throw new TypeError(message);
},
map: function () {
return none;
},
get: function () {
return none;
}
};
var Some = /** @class */ (function () {
function Some(value) {
this.value = value;
}
Some.prototype.isNone = function () {
return false;
};
Some.prototype.orElse = function () {
return this.value;
};
Some.prototype.orCall = function () {
return this.value;
};
Some.prototype.orNull = function () {
return this.value;
};
Some.prototype.orThrow = function () {
return this.value;
};
Some.prototype.map = function (f) {
return maybe(f(this.value));
};
Some.prototype.get = function (key) {
return this.map(function (obj) { return obj[key]; });
};
return Some;
}());
function isMaybe(value) {
return value === none || value instanceof Some;
}
function maybe(value) {
if (isMaybe(value)) {
return value;
}
if (value == null) {
return none;
}
return some(value);
}
function some(value) {
if (value == null) {
throw new TypeError('some() does not accept null or undefined');
}
return new Some(value);
}
class ApiError extends Error {
constructor(response) {
super(response.statusText);
Object.setPrototypeOf(this, new.target.prototype);
this.headers = response.headers;
this.url = response.url;
this.status = response.status;
this.statusText = response.statusText;
this.data = response.data;
}
}
let bigintReviver;
let bigintReplacer;
if ('rawJSON' in JSON) {
bigintReviver = function (_key, val, context) {
if (Number.isInteger(val) && !Number.isSafeInteger(val)) {
try {
return BigInt(context.source);
}
catch {
return val;
}
}
return val;
};
bigintReplacer = function (_key, val) {
if (typeof val === 'bigint') {
return JSON.rawJSON(String(val));
}
return val;
};
}
const sendBody = (method) => method === 'post' ||
method === 'put' ||
method === 'patch' ||
method === 'delete';
function queryString(params) {
const qs = [];
const encode = (key, value) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`;
Object.keys(params).forEach((key) => {
const value = params[key];
if (value != null) {
if (Array.isArray(value)) {
value.forEach((value) => qs.push(encode(key, value)));
}
else {
qs.push(encode(key, value));
}
}
});
if (qs.length > 0) {
return `?${qs.join('&')}`;
}
return '';
}
function getPath(path, payload) {
return path.replace(/\{([^}]+)\}/g, (_, key) => {
const value = encodeURIComponent(payload[key]);
delete payload[key];
return value;
});
}
function getQuery(method, payload, query) {
let queryObj = {};
if (sendBody(method)) {
query.forEach((key) => {
queryObj[key] = payload[key];
delete payload[key];
});
}
else {
queryObj = { ...payload };
}
return queryString(queryObj);
}
function getHeaders(body, init) {
const headers = new Headers(init);
if (body !== undefined &&
!(body instanceof FormData) &&
!headers.has('Content-Type')) {
headers.append('Content-Type', 'application/json');
}
if (!headers.has('Accept')) {
headers.append('Accept', 'application/json');
}
return headers;
}
function getBody(method, payload) {
if (!sendBody(method)) {
return;
}
const body = payload instanceof FormData
? payload
: JSON.stringify(payload, bigintReplacer);
return method === 'delete' && body === '{}' ? undefined : body;
}
function mergeRequestInit(first, second) {
const headers = new Headers(first?.headers);
const other = new Headers(second?.headers);
for (const key of other.keys()) {
const value = other.get(key);
if (value != null) {
headers.set(key, value);
}
}
return { ...first, ...second, headers };
}
function getFetchParams(request) {
const payload = Object.assign(Array.isArray(request.payload) ? [] : {}, request.payload);
const path = getPath(request.path, payload);
const query = getQuery(request.method, payload, request.queryParams);
const body = getBody(request.method, payload);
const headers = sendBody(request.method)
? getHeaders(body, request.init?.headers)
: new Headers(request.init?.headers);
const url = request.baseUrl + path + query;
const init = {
...request.init,
method: request.method.toUpperCase(),
headers,
body,
};
return { url, init };
}
async function getResponseData(response) {
if (response.status === 204) {
return;
}
const contentType = response.headers.get('content-type');
const responseText = await response.text();
if (contentType && contentType.includes('application/json')) {
return JSON.parse(responseText, bigintReviver);
}
try {
return JSON.parse(responseText, bigintReviver);
}
catch (e) {
return responseText;
}
}
async function fetchJson(url, init) {
const response = await fetch(url, init);
const data = await getResponseData(response);
const result = {
headers: response.headers,
url: response.url,
ok: response.ok,
status: response.status,
statusText: response.statusText,
data,
};
if (result.ok) {
return result;
}
throw new ApiError(result);
}
function wrapMiddlewares(middlewares, fetch) {
const handler = async (index, url, init) => {
if (middlewares == null || index === middlewares.length) {
return fetch(url, init);
}
const current = middlewares[index];
return await current(url, init, (nextUrl, nextInit) => handler(index + 1, nextUrl, nextInit));
};
return (url, init) => handler(0, url, init);
}
async function fetchUrl(request) {
const { url, init } = getFetchParams(request);
const response = await request.fetch(url, init);
return response;
}
function createFetch(fetch) {
const fun = async (payload, init) => {
try {
return await fetch(payload, init);
}
catch (err) {
if (err instanceof ApiError) {
throw new fun.Error(err);
}
throw err;
}
};
fun.Error = class extends ApiError {
constructor(error) {
super(error);
Object.setPrototypeOf(this, new.target.prototype);
}
getActualType() {
return {
status: this.status,
data: this.data,
};
}
};
return fun;
}
function fetcher() {
let baseUrl = '';
let defaultInit = {};
const middlewares = [];
const fetch = wrapMiddlewares(middlewares, fetchJson);
return {
configure: (config) => {
baseUrl = config.baseUrl || '';
defaultInit = config.init || {};
middlewares.splice(0);
middlewares.push(...(config.use || []));
},
use: (mw) => middlewares.push(mw),
path: (path) => ({
method: (method) => ({
create: ((queryParams) => createFetch((payload, init) => fetchUrl({
baseUrl: baseUrl || '',
path: path,
method: method,
queryParams: Object.keys(queryParams || {}),
payload,
init: mergeRequestInit(defaultInit, init),
fetch,
}))),
}),
}),
};
}
const Fetcher = {
for: () => fetcher(),
};
function createClusterApi(client) {
return {
/**
* Get information about the current state and composition of the cluster
*/
clusterStatus: client.path('/cluster').method('get').create(),
/**
* Get cluster information for a collection
*/
collectionClusterInfo: client.path('/collections/{collection_name}/cluster').method('get').create(),
recoverCurrentPeer: client.path('/cluster/recover').method('post').create(),
/**
* Tries to remove peer from the cluster. Will return an error if peer has shards on it.
*/
removePeer: client.path('/cluster/peer/{peer_id}').method('delete').create({ force: true }),
updateCollectionCluster: client
.path('/collections/{collection_name}/cluster')
.method('post')
.create({ timeout: true }),
};
}
function createCollectionsApi(client) {
return {
/**
* Get cluster information for a collection
*/
collectionClusterInfo: client.path('/collections/{collection_name}/cluster').method('get').create(),
/**
* Create new collection with given parameters
*/
createCollection: client.path('/collections/{collection_name}').method('put').create({ timeout: true }),
/**
* Create index for field in collection
*/
createFieldIndex: client
.path('/collections/{collection_name}/index')
.method('put')
.create({ ordering: true, wait: true }),
/**
* Create new snapshot for a collection
*/
createSnapshot: client.path('/collections/{collection_name}/snapshots').method('post').create({ wait: true }),
/**
* Drop collection and all associated data
*/
deleteCollection: client.path('/collections/{collection_name}').method('delete').create({ timeout: true }),
/**
* Delete field index for collection
*/
deleteFieldIndex: client
.path('/collections/{collection_name}/index/{field_name}')
.method('delete')
.create({ ordering: true, wait: true }),
/**
* Delete snapshot for a collection
*/
deleteSnapshots: client
.path('/collections/{collection_name}/snapshots/{snapshot_name}')
.method('delete')
.create({ wait: true }),
/**
* Get detailed information about specified existing collection
*/
getCollection: client.path('/collections/{collection_name}').method('get').create(),
/**
* Get list of all aliases for a collection
*/
getCollectionAliases: client.path('/collections/{collection_name}/aliases').method('get').create(),
/**
* Get list name of all existing collections
*/
getCollections: client.path('/collections').method('get').create(),
/**
* Get list of all existing collections aliases
*/
getCollectionsAliases: client.path('/aliases').method('get').create(),
/**
* Check the existence of a collection
*/
collectionExists: client.path('/collections/{collection_name}/exists').method('get').create(),
/**
* Download specified snapshot from a collection as a file
* @todo Fetcher needs to handle Blob for file downloads
*/
getSnapshot: client.path('/collections/{collection_name}/snapshots/{snapshot_name}').method('get').create(),
/**
* Get list of snapshots for a collection
*/
listSnapshots: client.path('/collections/{collection_name}/snapshots').method('get').create(),
updateAliases: client.path('/collections/aliases').method('post').create({ timeout: true }),
/**
* Update parameters of the existing collection
*/
updateCollection: client.path('/collections/{collection_name}').method('patch').create({ timeout: true }),
updateCollectionCluster: client
.path('/collections/{collection_name}/cluster')
.method('post')
.create({ timeout: true }),
};
}
function createPointsApi(client) {
return {
/**
* Remove all payload for specified points
*/
clearPayload: client
.path('/collections/{collection_name}/points/payload/clear')
.method('post')
.create({ ordering: true, wait: true }),
/**
* Count points which matches given filtering condition
*/
countPoints: client.path('/collections/{collection_name}/points/count').method('post').create({ timeout: true }),
/**
* Delete specified key payload for points
*/
deletePayload: client
.path('/collections/{collection_name}/points/payload/delete')
.method('post')
.create({ wait: true, ordering: true }),
/**
* Delete points
*/
deletePoints: client
.path('/collections/{collection_name}/points/delete')
.method('post')
.create({ wait: true, ordering: true }),
/**
* Update vectors
*/
updateVectors: client
.path('/collections/{collection_name}/points/vectors')
.method('put')
.create({ wait: true, ordering: true }),
/**
* Delete vectors
*/
deleteVectors: client
.path('/collections/{collection_name}/points/vectors/delete')
.method('post')
.create({ wait: true, ordering: true }),
/**
* Retrieve full information of single point by id
*/
getPoint: client.path('/collections/{collection_name}/points/{id}').method('get').create(),
/**
* Retrieve multiple points by specified IDs
*/
getPoints: client
.path('/collections/{collection_name}/points')
.method('post')
.create({ consistency: true, timeout: true }),
/**
* Replace full payload of points with new one
*/
overwritePayload: client
.path('/collections/{collection_name}/points/payload')
.method('put')
.create({ wait: true, ordering: true }),
/**
* Look for the points which are closer to stored positive examples and at the same time further to negative examples.
*/
recommendBatchPoints: client
.path('/collections/{collection_name}/points/recommend/batch')
.method('post')
.create({ consistency: true, timeout: true }),
/**
* Look for the points which are closer to stored positive examples and at the same time further to negative examples.
*/
recommendPoints: client
.path('/collections/{collection_name}/points/recommend')
.method('post')
.create({ consistency: true, timeout: true }),
/**
* Search point groups
*/
searchPointGroups: client
.path('/collections/{collection_name}/points/search/groups')
.method('post')
.create({ consistency: true, timeout: true }),
/**
* Scroll request - paginate over all points which matches given filtering condition
*/
scrollPoints: client
.path('/collections/{collection_name}/points/scroll')
.method('post')
.create({ consistency: true, timeout: true }),
/**
* Retrieve by batch the closest points based on vector similarity and given filtering conditions
*/
searchBatchPoints: client
.path('/collections/{collection_name}/points/search/batch')
.method('post')
.create({ consistency: true, timeout: true }),
/**
* Retrieve closest points based on vector similarity and given filtering conditions
*/
searchPoints: client
.path('/collections/{collection_name}/points/search')
.method('post')
.create({ consistency: true, timeout: true }),
/**
* Set payload values for points
*/
setPayload: client
.path('/collections/{collection_name}/points/payload')
.method('post')
.create({ wait: true, ordering: true }),
/**
* Perform insert + updates on points. If point with given ID already exists - it will be overwritten.
*/
upsertPoints: client
.path('/collections/{collection_name}/points')
.method('put')
.create({ wait: true, ordering: true }),
/**
* Recommend point groups
*/
recommendPointGroups: client
.path('/collections/{collection_name}/points/recommend/groups')
.method('post')
.create({ consistency: true, timeout: true }),
/**
* Apply a series of update operations for points, vectors and payloads
*/
batchUpdate: client
.path('/collections/{collection_name}/points/batch')
.method('post')
.create({ wait: true, ordering: true }),
/**
* Discover points
*/
discoverPoints: client
.path('/collections/{collection_name}/points/discover')
.method('post')
.create({ consistency: true, timeout: true }),
/**
* Discover batch points
*/
discoverBatchPoints: client
.path('/collections/{collection_name}/points/discover/batch')
.method('post')
.create({ consistency: true, timeout: true }),
/**
* Query points
*/
queryPoints: client
.path('/collections/{collection_name}/points/query')
.method('post')
.create({ consistency: true, timeout: true }),
/**
* Query points in batch
*/
queryBatchPoints: client
.path('/collections/{collection_name}/points/query/batch')
.method('post')
.create({ consistency: true, timeout: true }),
/**
* Query points, grouped by a given payload field
*/
queryPointsGroups: client
.path('/collections/{collection_name}/points/query/groups')
.method('post')
.create({ consistency: true, timeout: true }),
};
}
function createServiceApi(client) {
return {
/**
* Get lock options. If write is locked, all write operations and collection creation are forbidden
*/
getLocks: client.path('/locks').method('get').create(),
/**
* Collect metrics data including app info, collections info, cluster info and statistics
*/
metrics: client.path('/metrics').method('get').create(),
/**
* Set lock options. If write is locked, all write operations and collection creation are forbidden. Returns previous lock options
*/
postLocks: client.path('/locks').method('post').create(),
/**
* Collect telemetry data including app info, system info, collections info, cluster info, configs and statistics
*/
telemetry: client.path('/telemetry').method('get').create(),
/**
* An endpoint for health checking used in Kubernetes.
*/
healthz: client.path('/healthz').method('get').create(),
/**
* An endpoint for health checking used in Kubernetes.
*/
livez: client.path('/livez').method('get').create(),
/**
* An endpoint for health checking used in Kubernetes.
*/
readyz: client.path('/readyz').method('get').create(),
/**
* Returns information about the running Qdrant instance.
*/
root: client.path('/').method('get').create(),
/**
* Get issues
*/
getIssues: client.path('/issues').method('get').create(),
/**
* Clear issues
*/
clearIssues: client.path('/issues').method('delete').create(),
};
}
function createSnapshotsApi(client) {
return {
/**
* Create new snapshot of the whole storage
*/
createFullSnapshot: client.path('/snapshots').method('post').create({ wait: true }),
/**
* Create new snapshot for a collection
*/
createSnapshot: client.path('/collections/{collection_name}/snapshots').method('post').create({ wait: true }),
/**
* Delete snapshot of the whole storage
*/
deleteFullSnapshot: client.path('/snapshots/{snapshot_name}').method('delete').create({ wait: true }),
/**
* Delete snapshot for a collection
*/
deleteSnapshot: client
.path('/collections/{collection_name}/snapshots/{snapshot_name}')
.method('delete')
.create({ wait: true }),
/**
* Download specified snapshot of the whole storage as a file
* @todo Fetcher needs to handle Blob for file downloads
*/
getFullSnapshot: client.path('/snapshots/{snapshot_name}').method('get').create(),
/**
* Download specified snapshot from a collection as a file
* @todo Fetcher needs to handle Blob for file downloads
*/
getSnapshot: client.path('/collections/{collection_name}/snapshots/{snapshot_name}').method('get').create(),
/**
* Get list of snapshots of the whole storage
*/
listFullSnapshots: client.path('/snapshots').method('get').create(),
/**
* Get list of snapshots for a collection
*/
listSnapshots: client.path('/collections/{collection_name}/snapshots').method('get').create(),
/**
* Recover local collection data from an uploaded snapshot. This will overwrite any data, stored on this node, for the collection. If collection does not exist - it will be created.
*/
recoverFromUploadedSnapshot: client
.path('/collections/{collection_name}/snapshots/upload')
.method('post')
.create({ wait: true, priority: true, checksum: true }),
/**
* Recover local collection data from a snapshot. This will overwrite any data, stored on this node, for the collection. If collection does not exist - it will be created
*/
recoverFromSnapshot: client
.path('/collections/{collection_name}/snapshots/recover')
.method('put')
.create({ wait: true }),
/**
* Recover shard of a local collection from an uploaded snapshot. This will overwrite any data, stored on this node, for the collection shard
*/
recoverShardFromUploadedSnapshot: client
.path('/collections/{collection_name}/shards/{shard_id}/snapshots/upload')
.method('post')
.create({ wait: true, priority: true, checksum: true }),
/**
* Recover shard of a local collection data from a snapshot. This will overwrite any data, stored in this shard, for the collection
*/
recoverShardFromSnapshot: client
.path('/collections/{collection_name}/shards/{shard_id}/snapshots/recover')
.method('put')
.create({ wait: true }),
/**
* Get list of snapshots for a shard of a collection
*/
listShardSnapshots: client
.path('/collections/{collection_name}/shards/{shard_id}/snapshots')
.method('get')
.create(),
/**
* Create new snapshot of a shard for a collection
*/
createShardSnapshot: client
.path('/collections/{collection_name}/shards/{shard_id}/snapshots')
.method('post')
.create({ wait: true }),
/**
* Download specified snapshot of a shard from a collection as a file
*/
getShardSnapshot: client
.path('/collections/{collection_name}/shards/{shard_id}/snapshots/{snapshot_name}')
.method('get')
.create(),
/**
* Delete snapshot of a shard for a collection
*/
deleteShardSnapshot: client
.path('/collections/{collection_name}/shards/{shard_id}/snapshots/{snapshot_name}')
.method('delete')
.create({ wait: true }),
};
}
function createShardsApi(client) {
return {
/**
* Create shard key
*/
createShardKey: client.path('/collections/{collection_name}/shards').method('put').create({ timeout: true }),
/**
* Delete shard key
*/
deleteShardKey: client
.path('/collections/{collection_name}/shards/delete')
.method('post')
.create({ timeout: true }),
};
}
const MAX_CONTENT = 200;
class CustomError extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
Object.setPrototypeOf(this, new.target.prototype);
}
}
class QdrantClientUnexpectedResponseError extends CustomError {
static forResponse(response) {
const statusCodeStr = `${response.status}`;
const reasonPhraseStr = !response.statusText ? '(Unrecognized Status Code)' : `(${response.statusText})`;
const statusStr = `${statusCodeStr} ${reasonPhraseStr}`.trim();
const dataStr = response.data ? JSON.stringify(response.data, null, 2) : null;
let shortContent = '';
if (dataStr) {
shortContent = dataStr.length <= MAX_CONTENT ? dataStr : dataStr.slice(0, -4) + ' ...';
}
const rawContentStr = `Raw response content:\n${shortContent}`;
return new QdrantClientUnexpectedResponseError(`Unexpected Response: ${statusStr}\n${rawContentStr}`);
}
}
class QdrantClientConfigError extends CustomError {
}
class QdrantClientTimeoutError extends CustomError {
}
function createApis(baseUrl, args) {
const client = createClient(baseUrl, args);
return {
cluster: createClusterApi(client),
collections: createCollectionsApi(client),
points: createPointsApi(client),
service: createServiceApi(client),
snapshots: createSnapshotsApi(client),
shards: createShardsApi(client),
};
}
function createClient(baseUrl, { headers, timeout, connections }) {
const use = [];
if (Number.isFinite(timeout)) {
use.push(async (url, init, next) => {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);
try {
return await next(url, Object.assign(init, { signal: controller.signal }));
}
catch (e) {
if (e instanceof Error && e.name === 'AbortError') {
throw new QdrantClientTimeoutError(e.message);
}
throw e;
}
finally {
clearTimeout(id);
}
});
}
use.push(async (url, init, next) => {
const response = await next(url, init);
if (response.status === 200 || response.status === 201) {
return response;
}
throw QdrantClientUnexpectedResponseError.forResponse(response);
});
const client = Fetcher.for();
// Configure client with 'undici' agent which is used in Node 18+
client.configure({
baseUrl,
init: {
headers,
dispatcher: undefined,
},
use,
});
return client;
}
class QdrantClient {
constructor({ url, host, apiKey, https, prefix, port = 6333, timeout = 300000, ...args } = {}) {
this._https = https ?? typeof apiKey === 'string';
this._scheme = this._https ? 'https' : 'http';
this._prefix = prefix ?? '';
if (this._prefix.length > 0 && !this._prefix.startsWith('/')) {
this._prefix = `/${this._prefix}`;
}
if (url && host) {
throw new QdrantClientConfigError(`Only one of \`url\`, \`host\` params can be set. Url is ${url}, host is ${host}`);
}
if (host && (host.startsWith('http://') || host.startsWith('https://') || /:\d+$/.test(host))) {
throw new QdrantClientConfigError('The `host` param is not expected to contain neither protocol (http:// or https://) nor port (:6333).\n' +
'Try to use the `url` parameter instead.');
}
else if (url) {
if (!(url.startsWith('http://') || url.startsWith('https://'))) {
throw new QdrantClientConfigError('The `url` param expected to contain a valid URL starting with a protocol (http:// or https://).');
}
const parsedUrl = new URL(url);
this._host = parsedUrl.hostname;
this._port = parsedUrl.port ? Number(parsedUrl.port) : port;
this._scheme = parsedUrl.protocol.replace(':', '');
if (this._prefix.length > 0 && parsedUrl.pathname !== '/') {
throw new QdrantClientConfigError('Prefix can be set either in `url` or in `prefix`.\n' +
`url is ${url}, prefix is ${parsedUrl.pathname}`);
}
}
else {
this._port = port;
this._host = host ?? '127.0.0.1';
}
const headers = new Headers([['user-agent', 'qdrant-js']]);
const metadata = args.headers ?? {};
Object.keys(metadata).forEach((field) => {
if (metadata[field]) {
headers.set(field, String(metadata[field]));
}
});
if (typeof apiKey === 'string') {
if (this._scheme === 'http') {
console.warn('Api key is used with unsecure connection.');
}
headers.set('api-key', apiKey);
}
const address = this._port ? `${this._host}:${this._port}` : this._host;
this._restUri = `${this._scheme}://${address}${this._prefix}`;
const connections = args.maxConnections;
const restArgs = { headers, timeout, connections };
this._openApiClient = createApis(this._restUri, restArgs);
}
/**
* API getter
*
* @param name Name of api
* @returns An instance of a namespaced API, generated from OpenAPI schema.
*/
api(name) {
return this._openApiClient[name];
}
/**
* Search for points in multiple collections
*
* @param collectionName Name of the collection
* @param {object} args -
* - searches: List of search requests
* - consistency: Read consistency of the search. Defines how many replicas should be queried before returning the result.
* Values:
* number - number of replicas to query, values should present in all queried replicas
* 'majority' - query all replicas, but return values present in the majority of replicas
* 'quorum' - query the majority of replicas, return values present in all of them
* 'all' - query all replicas, and return values present in all replicas
* - timeout: If set, overrides global timeout setting for this request. Unit is seconds.
* @returns List of search responses
*/
async searchBatch(collection_name, { searches, consistency, timeout, }) {
const response = await this._openApiClient.points.searchBatchPoints({
collection_name,
consistency,
timeout,
searches,
});
return maybe(response.data.result).orThrow('Search batch returned empty');
}
/**
* Search for closest vectors in collection taking into account filtering conditions
*
* @param collection_name Collection to search in
* @param {object} args -
* - shard_key: Specify in which shards to look for the points, if not specified - look in all shards
* - vector:
* Search for vectors closest to this.
* Can be either a vector itself, or a named vector, or a tuple of vector name and vector itself
* - filter:
* - Exclude vectors which doesn't fit given conditions.
* - If `None` - search among all vectors
* - params: Additional search params
* - limit: How many results return
* - offset:
* Offset of the first result to return.
* May be used to paginate results.
* Note: large offset values may cause performance issues.
* - with_payload:
* - Specify which stored payload should be attached to the result.
* - If `True` - attach all payload
* - If `False` - do not attach any payload
* - If List of string - include only specified fields
* - If `PayloadSelector` - use explicit rules
* - with_vector:
* - If `True` - Attach stored vector to the search result.
* - If `False` - Do not attach vector.
* - If List of string - include only specified fields
* - Default: `False`
* - score_threshold:
* Define a minimal score threshold for the result.
* If defined, less similar results will not be returned.
* Score of the returned result might be higher or smaller than the threshold depending
* on the Distance function used.
* E.g. for cosine similarity only higher scores will be returned.
* - consistency:
* Read consistency of the search. Defines how many replicas should be queried before returning the result.
* Values:
* - int - number of replicas to query, values should present in all queried replicas
* - 'majority' - query all replicas, but return values present in the majority of replicas
* - 'quorum' - query the majority of replicas, return values present in all of them
* - 'all' - query all replicas, and return values present in all replicas
* - timeout: If set, overrides global timeout setting for this request. Unit is seconds.
* @example
* // Search with filter
* client.search(
* "test_collection",
* {
* vector: [1.0, 0.1, 0.2, 0.7],
* filter: {
* must: [
* {
* key: 'color',
* range: {
* color: 'red'
* }
* }
* ]
* )
* }
* )
* @returns List of found close points with similarity scores.
*/
async search(collection_name, { shard_key, vector, limit = 10, offset = 0, filter, params, with_payload = true, with_vector = false, score_threshold, consistency, timeout, }) {
const response = await this._openApiClient.points.searchPoints({
collection_name,
consistency,
timeout,
shard_key,
vector,
limit,
offset,
filter,
params,
with_payload,
with_vector,
score_threshold,
});
return maybe(response.data.result).orThrow('Search returned empty');
}
/**
* Perform multiple recommend requests in batch mode
* @param collection_name Name of the collection
* @param {object} args
* - searches: List of recommend requests
* - consistency:
* Read consistency of the search. Defines how many replicas should be queried before returning the result.
* Values:
* - number - number of replicas to query, values should present in all queried replicas
* - 'majority' - query all replicas, but return values present in the majority of replicas
* - 'quorum' - query the majority of replicas, return values present in all of them
* - 'all' - query all replicas, and return values present in all replicas
* - timeout: If set, overrides global timeout setting for this request. Unit is seconds.
* @returns List of recommend responses
*/
async recommendBatch(collection_name, { searches, consistency, timeout, }) {
const response = await this._openApiClient.points.recommendBatchPoints({
collection_name,
searches,
consistency,
timeout,
});
return maybe(response.data.result).orElse([]);
}
/**
* @alias recommendBatch
*/
async recommend_batch(collection_name, { searches, consistency, timeout, }) {
const response = await this._openApiClient.points.recommendBatchPoints({
collection_name,
searches,
consistency,
timeout,
});
return maybe(response.data.result).orElse([]);
}
/**
* Recommendation request. Provides positive and negative examples of the vectors,
* which can be ids of points that are already stored in the collection, raw vectors, or even ids and vectors combined.
* Service should look for the points which are closer to positive examples and at the same time further to negative examples.
* The concrete way of how to compare negative and positive distances is up to the `strategy` chosen.
* @param collection_name Collection to search in
* @param {object} args
* - shard_key: Specify in which shards to look for the points, if not specified - look in all shards
* - positive:
* List of stored point IDs, which should be used as reference for similarity search.
* If there is only one ID provided - this request is equivalent to the regular search with vector of that point.
* If there are more than one IDs, Qdrant will attempt to search for similar to all of them.
* Recommendation for multiple vectors is experimental. Its behaviour may change in the future.
* - negative:
* List of stored point IDs, which should be dissimilar to the search result.
* Negative examples is an experimental functionality. Its behaviour may change in the future.
* - strategy:
* How to use positive and negative examples to find the results.
* - query_filter:
* - Exclude vectors which doesn't fit given conditions.
* - If `None` - search among all vectors
* - search_params: Additional search params
* - limit: How many results return
* - Default: `10`
* - offset:
* Offset of the first result to return.
* May be used to paginate results.
* Note: large offset values may cause performance issues.
* - Default: `0`
* - with_payload:
* - Specify which stored payload should be attached to the result.
* - If `True` - attach all payload
* - If `False` - do not attach any payload
* - If List of string - include only specified fields
* - If `PayloadSelector` - use explicit rules
* - Default: `true`
* - with_vector:
* - If `True` - Attach stored vector to the search result.
* - If `False` - Do not attach vector.
* - If List of string - include only specified fields
* - Default: `false`
* - score_threshold:
* Define a minimal score threshold for the result.
* If defined, less similar results will not be returned.
* Score of the returned result might be higher or smaller than the threshold depending
* on the Distance function used.
* E.g. for cosine similarity only higher scores will be returned.
* - using:
* Name of the vectors to use for recommendations.
* If `None` - use default vectors.
* - lookupFrom:
* Defines a location (collection and vector field name), used to lookup vectors for recommendations.
* If `None` - use current collection will be used.
* - consistency:
* Read consistency of the search. Defines how many replicas should be queried before returning the result.
* Values:
* - int - number of replicas to query, values should present in all queried replicas
* - 'majority' - query all replicas, but return values present in the majority of replicas
* - 'quorum' - query the majority of replicas, return values present in all of them
* - 'all' - query all replicas, and return values present in all replicas
* - timeout: If set, overrides global timeout setting for this request. Unit is seconds.
* @returns List of recommended points with similarity scores.
*/
async recommend(collection_name, { shard_key, positive, negative, strategy, filter, params, limit = 10, offset = 0, with_payload = true, with_vector = false, score_threshold, using, lookup_from, consistency, timeout, }) {
const response = await this._openApiClient.points.recommendPoints({
collection_name,
limit,
shard_key,
positive,
negative,
strategy,
filter,
params,
offset,
with_payload,
with_vector,
score_threshold,
using,
lookup_from,
consistency,
timeout,
});
return maybe(response.data.result).orThrow('Recommend points API returned empty');
}
/**
* Scroll over all (matching) points in the collection.
* @param collection_name Name of the collection
* @param {object} args
* - shard_key: Specify in which shards to look for the points, if not specified - look in all shards
* - filter: If provided - only returns points matching filtering conditions
* - limit: How many points to return
* - offset: If provided - skip points with ids less than given `offset`
* - with_payload:
* - Specify which stored payload should be attached to the result.
* - If `True` - attach all payload
* - If `False` - do not attach any payload
* - If List of string - include only specified fields
* - If `PayloadSelector` - use explicit rules
* - Default: `true`
* - with_vector:
* - If `True` - Attach stored vector to the search result.
* - If `False` - Do not attach vector.
* - If List of string - include only specified fields
* - Default: `false`
* - consistency:
* Read consistency of the search. Defines how many replicas should be queried before returning the result.
* Values:
* - int - number of replicas to query, values should present in all queried replicas
* - 'majority' - query all replicas, but return values present in the majority of replicas
* - 'quorum' - query the majority of replicas, return values present in all of them
* - 'all' - query all replicas, and return values present in all replicas
* - order_by:
* Order the records by a payload field.
* @returns
* A pair of (List of points) and (optional offset for the next scroll request).
* If next page offset is `None` - there is no more points in the collection to scroll.
*/
async scroll(collection_name, { shard_key, filter, consistency, timeout, limit = 10, offset, with_payload = true, with_vector = false, order_by, } = {}) {
const response = await this._openApiClient.points.scrollPoints({
collection_name,
shard_key,
limit,
offset,
filter,
with_payload,
with_vector,
order_by,
consistency,
timeout,
});
return maybe(response.data.result).orThrow('Scroll points API returned empty');
}
/**
* Count points in the collection.
* Count points in the collection matching the given filter.
* @param collection_name
* @param {object} args
* - shard_key: Specify in which shards to look for the points, if not specified - look in all shards
* - filter: filtering conditions
* - exact:
* If `True` - provide the exact count of points matching the filter.
* If `False` - provide the approximate count of points matching the filter. Works faster.
* Default: `true`
* @returns Amount of points in the collection matching the filter.
*/
async count(collection_name, { shard_key, filter, exact = true, timeout } = {}) {
const response = await this._openApiClient.points.countPoints({
collection_name,
shard_key,
filter,
exact,
timeout,
});
return maybe(response.data.result).orThrow('Count points returned empty');
}
/**
* Get cluster information for a collection.
* @param collection_name
* @returns Operation result
*/
async collectionClusterInfo(collection_name) {
const response = await this._openApiClient.collections.collectionClusterInfo({ collection_name });
return maybe(response.data.result).orThrow('Collection cluster info returned empty');
}
/**
* Update vectors
* @param collection_name
* @param {object} args
* - wait: Await for the results to be processed.
* - If `true`, result will be returned only when all changes are applied
* - If `false`, result will be returned immediately after the confirmation of receiving.
* - Default: `true`
* - ordering: Define strategy for ordering of the points. Possible values:
* - 'weak' - write operations may be reordered, works faster, default
* - 'medium' - write operations go through dynamically selected leader,
* may be inconsistent for a short period of time in case of leader change
* - 'strong' - Write operations go through the permanent leader,
* consistent, but may be unavailable if leader is down
* - points: Points with named vectors
* - shard_key: Specify in which shards to look for the points, if not specified - look in all shards
* @returns Operation result
*/
async updateVectors(collection_name, { wait = true, ordering, points, shard_key, }) {
const response = await this._openApiClient.points.updateVectors({
collection_name,
wait,
ordering,
points,
shard_key,
});
return maybe(response.data.result).orThrow('Update vectors returned empty');
}
/**
* Delete vectors
* @param collection_name
* @param {object} args
* - wait: Await for the results to be processed.
* - If `true`, result will be returned only when all changes are applied
* - If `false`, result will be returned immediately after the confirmation of receiving.
* - Default: `true`
* - ordering: Define strategy for ordering of the points. Possible values:
* - 'weak' - write operations may be reordered, works faster, default
* - 'medium' - write operations go through dynamically selected leader,
* may be inconsistent for a short period of time in case of leader change
* - 'strong' - Write operations go through the permanent leader,
* consistent, but may be unavailable if leader is down
* - points: Deletes values from each point in this list
* - filter: Deletes values from points that satisfy this filter condition
* - vector: Vector names
* - shard_key: Specify in which shards to look for the points, if not specified - look in all shards
* @returns Operation result
*/
async deleteVectors(collection_name, { wait = true, ordering, points, filter, vector, shard_key, }) {
const response = await this._openApiClient.points.deleteVectors({
collection_name,
wait,
ordering,
points,
filter,
vector,
shard_key,
});
return maybe(response.data.result).orThrow('Delete vectors returned empty');
}
/**
* Search point groups
* @param collection_name
* @param {object} args -
* - consistency: Read consistency of the search. Defines how many replicas should be queried before returning the result.
* Values:
* number - number of replicas to query, values should present in all queried replicas
* 'majority' - query all replicas, but return values present in the ma