UNPKG

@qdrant/js-client-rest

Version:

This repository contains the REST client for the [Qdrant](https://github.com/qdrant/qdrant) vector search engine.

1,238 lines (1,227 loc) 106 kB
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 majority of repli