import { GetResponse, QueryResponse, AddResponse, CollectionMetadata, ConfigOptions, GetParams, AddParams, UpsertParams, ModifyCollectionParams, UpdateParams, QueryParams, PeekParams, DeleteParams } from "./types"; import { IEmbeddingFunction } from './embeddings/IEmbeddingFunction'; import { ApiApi as DefaultApi } from "./generated"; import { handleError, handleSuccess } from "./utils"; import { toArray, toArrayOfArrays } from "./utils"; export class Collection { public name: string; public id: string; public metadata: CollectionMetadata | undefined; /** * @ignore */ private api: DefaultApi & ConfigOptions; /** * @ignore */ public embeddingFunction: IEmbeddingFunction | undefined; /** * @ignore */ constructor( name: string, id: string, api: DefaultApi, metadata?: CollectionMetadata, embeddingFunction?: IEmbeddingFunction ) { this.name = name; this.id = id; this.metadata = metadata; this.api = api; if (embeddingFunction !== undefined) this.embeddingFunction = embeddingFunction; } /** * @ignore */ private setName(name: string): void { this.name = name; } /** * @ignore */ private setMetadata(metadata: CollectionMetadata | undefined): void { this.metadata = metadata; } /** * @ignore */ private async validate( require_embeddings_or_documents: boolean, // set to false in the case of Update ids: string | string[], embeddings: number[] | number[][] | undefined, metadatas?: object | object[], documents?: string | string[], ) { if (require_embeddings_or_documents) { if ((embeddings === undefined) && (documents === undefined)) { throw new Error( "embeddings and documents cannot both be undefined", ); } } if ((embeddings === undefined) && (documents !== undefined)) { const documentsArray = toArray(documents); if (this.embeddingFunction !== undefined) { embeddings = await this.embeddingFunction.generate(documentsArray); } else { throw new Error( "embeddingFunction is undefined. Please configure an embedding function" ); } } if (embeddings === undefined) throw new Error("embeddings is undefined but shouldnt be"); const idsArray = toArray(ids); const embeddingsArray: number[][] = toArrayOfArrays(embeddings); let metadatasArray: object[] | undefined; if (metadatas === undefined) { metadatasArray = undefined; } else { metadatasArray = toArray(metadatas); } let documentsArray: (string | undefined)[] | undefined; if (documents === undefined) { documentsArray = undefined; } else { documentsArray = toArray(documents); } // validate all ids are strings for (let i = 0; i < idsArray.length; i += 1) { if (typeof idsArray[i] !== "string") { throw new Error( `Expected ids to be strings, found ${typeof idsArray[i]} at index ${i}` ); } } if ( (embeddingsArray !== undefined && idsArray.length !== embeddingsArray.length) || (metadatasArray !== undefined && idsArray.length !== metadatasArray.length) || (documentsArray !== undefined && idsArray.length !== documentsArray.length) ) { throw new Error( "ids, embeddings, metadatas, and documents must all be the same length" ); } const uniqueIds = new Set(idsArray); if (uniqueIds.size !== idsArray.length) { const duplicateIds = idsArray.filter((item, index) => idsArray.indexOf(item) !== index); throw new Error( `Expected IDs to be unique, found duplicates for: ${duplicateIds}`, ); } return [idsArray, embeddingsArray, metadatasArray, documentsArray] } /** * Add items to the collection * @param {Object} params - The parameters for the query. * @param {ID | IDs} [params.ids] - IDs of the items to add. * @param {Embedding | Embeddings} [params.embeddings] - Optional embeddings of the items to add. * @param {Metadata | Metadatas} [params.metadatas] - Optional metadata of the items to add. * @param {Document | Documents} [params.documents] - Optional documents of the items to add. * @returns {Promise} - The response from the API. True if successful. * * @example * ```typescript * const response = await collection.add({ * ids: ["id1", "id2"], * embeddings: [[1, 2, 3], [4, 5, 6]], * metadatas: [{ "key": "value" }, { "key": "value" }], * documents: ["document1", "document2"] * }); * ``` */ public async add({ ids, embeddings, metadatas, documents, }: AddParams): Promise { const [idsArray, embeddingsArray, metadatasArray, documentsArray] = await this.validate( true, ids, embeddings, metadatas, documents ) const response = await this.api.add(this.id, { // @ts-ignore ids: idsArray, embeddings: embeddingsArray as number[][], // We know this is defined because of the validate function // @ts-ignore documents: documentsArray, // @ts-ignore metadatas: metadatasArray, }, this.api.options) .then(handleSuccess) .catch(handleError); return response } /** * Upsert items to the collection * @param {Object} params - The parameters for the query. * @param {ID | IDs} [params.ids] - IDs of the items to add. * @param {Embedding | Embeddings} [params.embeddings] - Optional embeddings of the items to add. * @param {Metadata | Metadatas} [params.metadatas] - Optional metadata of the items to add. * @param {Document | Documents} [params.documents] - Optional documents of the items to add. * @returns {Promise} - The response from the API. True if successful. * * @example * ```typescript * const response = await collection.upsert({ * ids: ["id1", "id2"], * embeddings: [[1, 2, 3], [4, 5, 6]], * metadatas: [{ "key": "value" }, { "key": "value" }], * documents: ["document1", "document2"], * }); * ``` */ public async upsert({ ids, embeddings, metadatas, documents, }: UpsertParams): Promise { const [idsArray, embeddingsArray, metadatasArray, documentsArray] = await this.validate( true, ids, embeddings, metadatas, documents ) const response = await this.api.upsert(this.id, { //@ts-ignore ids: idsArray, embeddings: embeddingsArray as number[][], // We know this is defined because of the validate function //@ts-ignore documents: documentsArray, //@ts-ignore metadatas: metadatasArray, }, this.api.options ) .then(handleSuccess) .catch(handleError); return response } /** * Count the number of items in the collection * @returns {Promise} - The response from the API. * * @example * ```typescript * const response = await collection.count(); * ``` */ public async count(): Promise { const response = await this.api.count(this.id, this.api.options); return handleSuccess(response); } /** * Modify the collection name or metadata * @param {Object} params - The parameters for the query. * @param {string} [params.name] - Optional new name for the collection. * @param {CollectionMetadata} [params.metadata] - Optional new metadata for the collection. * @returns {Promise} - The response from the API. * * @example * ```typescript * const response = await collection.modify({ * name: "new name", * metadata: { "key": "value" }, * }); * ``` */ public async modify({ name, metadata }: ModifyCollectionParams = {}): Promise { const response = await this.api .updateCollection( this.id, { new_name: name, new_metadata: metadata, }, this.api.options ) .then(handleSuccess) .catch(handleError); this.setName(name || this.name); this.setMetadata(metadata || this.metadata); return response; } /** * Get items from the collection * @param {Object} params - The parameters for the query. * @param {ID | IDs} [params.ids] - Optional IDs of the items to get. * @param {Where} [params.where] - Optional where clause to filter items by. * @param {PositiveInteger} [params.limit] - Optional limit on the number of items to get. * @param {PositiveInteger} [params.offset] - Optional offset on the items to get. * @param {IncludeEnum[]} [params.include] - Optional list of items to include in the response. * @param {WhereDocument} [params.whereDocument] - Optional where clause to filter items by. * @returns {Promise} - The response from the server. * * @example * ```typescript * const response = await collection.get({ * ids: ["id1", "id2"], * where: { "key": "value" }, * limit: 10, * offset: 0, * include: ["embeddings", "metadatas", "documents"], * whereDocument: { $contains: "value" }, * }); * ``` */ public async get({ ids, where, limit, offset, include, whereDocument, }: GetParams = {}): Promise { let idsArray = undefined; if (ids !== undefined) idsArray = toArray(ids); return await this.api .aGet(this.id, { ids: idsArray, where, limit, offset, //@ts-ignore include, where_document: whereDocument, }, this.api.options) .then(handleSuccess) .catch(handleError); } /** * Update the embeddings, documents, and/or metadatas of existing items * @param {Object} params - The parameters for the query. * @param {ID | IDs} [params.ids] - The IDs of the items to update. * @param {Embedding | Embeddings} [params.embeddings] - Optional embeddings to update. * @param {Metadata | Metadatas} [params.metadatas] - Optional metadatas to update. * @param {Document | Documents} [params.documents] - Optional documents to update. * @returns {Promise} - The API Response. True if successful. Else, error. * * @example * ```typescript * const response = await collection.update({ * ids: ["id1", "id2"], * embeddings: [[1, 2, 3], [4, 5, 6]], * metadatas: [{ "key": "value" }, { "key": "value" }], * documents: ["new document 1", "new document 2"], * }); * ``` */ public async update({ ids, embeddings, metadatas, documents, }: UpdateParams): Promise { if ( embeddings === undefined && documents === undefined && metadatas === undefined ) { throw new Error( "embeddings, documents, and metadatas cannot all be undefined" ); } else if (embeddings === undefined && documents !== undefined) { const documentsArray = toArray(documents); if (this.embeddingFunction !== undefined) { embeddings = await this.embeddingFunction.generate(documentsArray); } else { throw new Error( "embeddingFunction is undefined. Please configure an embedding function" ); } } // backend expects None if metadatas is undefined if (metadatas !== undefined) metadatas = toArray(metadatas); if (documents !== undefined) documents = toArray(documents); var resp = await this.api .update( this.id, { ids: toArray(ids), embeddings: embeddings ? toArrayOfArrays(embeddings) : undefined, documents: documents, metadatas: metadatas }, this.api.options ) .then(handleSuccess) .catch(handleError); return resp; } /** * Performs a query on the collection using the specified parameters. * * @param {Object} params - The parameters for the query. * @param {Embedding | Embeddings} [params.queryEmbeddings] - Optional query embeddings to use for the search. * @param {PositiveInteger} [params.nResults] - Optional number of results to return (default is 10). * @param {Where} [params.where] - Optional query condition to filter results based on metadata values. * @param {string | string[]} [params.queryTexts] - Optional query text(s) to search for in the collection. * @param {WhereDocument} [params.whereDocument] - Optional query condition to filter results based on document content. * @param {IncludeEnum[]} [params.include] - Optional array of fields to include in the result, such as "metadata" and "document". * * @returns {Promise} A promise that resolves to the query results. * @throws {Error} If there is an issue executing the query. * @example * // Query the collection using embeddings * const results = await collection.query({ * queryEmbeddings: [[0.1, 0.2, ...], ...], * nResults: 10, * where: {"name": {"$eq": "John Doe"}}, * include: ["metadata", "document"] * }); * @example * ```js * // Query the collection using query text * const results = await collection.query({ * queryTexts: "some text", * nResults: 10, * where: {"name": {"$eq": "John Doe"}}, * include: ["metadata", "document"] * }); * ``` * */ public async query({ queryEmbeddings, nResults, where, queryTexts, whereDocument, include, }: QueryParams): Promise { if (nResults === undefined) nResults = 10 if (queryEmbeddings === undefined && queryTexts === undefined) { throw new Error( "queryEmbeddings and queryTexts cannot both be undefined" ); } else if (queryEmbeddings === undefined && queryTexts !== undefined) { const queryTextsArray = toArray(queryTexts); if (this.embeddingFunction !== undefined) { queryEmbeddings = await this.embeddingFunction.generate(queryTextsArray); } else { throw new Error( "embeddingFunction is undefined. Please configure an embedding function" ); } } if (queryEmbeddings === undefined) throw new Error("embeddings is undefined but shouldnt be"); const query_embeddingsArray = toArrayOfArrays(queryEmbeddings); return await this.api .getNearestNeighbors(this.id, { query_embeddings: query_embeddingsArray, where, n_results: nResults, where_document: whereDocument, //@ts-ignore include: include, }, this.api.options) .then(handleSuccess) .catch(handleError); } /** * Peek inside the collection * @param {Object} params - The parameters for the query. * @param {PositiveInteger} [params.limit] - Optional number of results to return (default is 10). * @returns {Promise} A promise that resolves to the query results. * @throws {Error} If there is an issue executing the query. * * @example * ```typescript * const results = await collection.peek({ * limit: 10 * }); * ``` */ public async peek({ limit }: PeekParams = {}): Promise { if (limit === undefined) limit = 10; const response = await this.api.aGet(this.id, { limit: limit, }, this.api.options); return handleSuccess(response); } /** * Deletes items from the collection. * @param {Object} params - The parameters for deleting items from the collection. * @param {ID | IDs} [params.ids] - Optional ID or array of IDs of items to delete. * @param {Where} [params.where] - Optional query condition to filter items to delete based on metadata values. * @param {WhereDocument} [params.whereDocument] - Optional query condition to filter items to delete based on document content. * @returns {Promise} A promise that resolves to the IDs of the deleted items. * @throws {Error} If there is an issue deleting items from the collection. * * @example * ```typescript * const results = await collection.delete({ * ids: "some_id", * where: {"name": {"$eq": "John Doe"}}, * whereDocument: {"$contains":"search_string"} * }); * ``` */ public async delete({ ids, where, whereDocument }: DeleteParams = {}): Promise { let idsArray = undefined; if (ids !== undefined) idsArray = toArray(ids); return await this.api .aDelete(this.id, { ids: idsArray, where: where, where_document: whereDocument }, this.api.options) .then(handleSuccess) .catch(handleError); } }