From 3c63344a9280eac3c2a35606b8d1d04140a9ebaa Mon Sep 17 00:00:00 2001 From: Lokesh Goel <113521973+lokesh-couchbase@users.noreply.github.com> Date: Sat, 16 Mar 2024 03:37:32 +0530 Subject: [PATCH] community[minor]: Add support for Couchbase Vector Store (#4737) * Couchbase vector store (#1) * added couchbase document loader * fixed loader to use stringify * add doc file * updated tests * update types as per new requirement * update comments for typedoc * fix formatting issues and remove print in tests * add support for couchbase vector search using sdk * improved the params of couchbase * bump couchbase sdk version * remove rest implementation * add tsdoc * use initialize to create instance of class * improved tsdocs * add tests * add similarity search in documentation * add hybrid search in documentation and tests * remove unwanted files * fix formatting * make docs better * bump couchbase version * Prefix env vars --------- Co-authored-by: jacoblee93 --- .../integrations/vectorstores/couchbase.mdx | 369 +++++++++++ examples/package.json | 1 + .../vector_stores/couchbase/.env.example | 13 + .../couchbase/similaritySearch.ts | 67 ++ langchain/package.json | 4 +- libs/langchain-community/.gitignore | 4 + libs/langchain-community/langchain.config.js | 2 + libs/langchain-community/package.json | 18 + .../src/load/import_constants.ts | 1 + .../src/vectorstores/couchbase.ts | 608 ++++++++++++++++++ .../vectorstores/tests/couchbase.int.test.ts | 241 +++++++ yarn.lock | 67 +- 12 files changed, 1362 insertions(+), 33 deletions(-) create mode 100644 docs/core_docs/docs/integrations/vectorstores/couchbase.mdx create mode 100644 examples/src/indexes/vector_stores/couchbase/.env.example create mode 100644 examples/src/indexes/vector_stores/couchbase/similaritySearch.ts create mode 100644 libs/langchain-community/src/vectorstores/couchbase.ts create mode 100644 libs/langchain-community/src/vectorstores/tests/couchbase.int.test.ts diff --git a/docs/core_docs/docs/integrations/vectorstores/couchbase.mdx b/docs/core_docs/docs/integrations/vectorstores/couchbase.mdx new file mode 100644 index 000000000000..d7a722dd4bac --- /dev/null +++ b/docs/core_docs/docs/integrations/vectorstores/couchbase.mdx @@ -0,0 +1,369 @@ +--- +hide_table_of_contents: true +sidebar_class_name: node-only +--- + +import CodeBlock from "@theme/CodeBlock"; + +# Couchbase + +[Couchbase](http://couchbase.com/) is an award-winning distributed NoSQL cloud database that delivers unmatched versatility, performance, scalability, and financial value for all of your cloud, mobile, +AI, and edge computing applications. Couchbase embraces AI with coding assistance for developers and vector search for their applications. + +Vector Search is a part of the [Full Text Search Service](https://docs.couchbase.com/server/current/learn/services-and-indexes/services/search-service.html) (Search Service) in Couchbase. + +This tutorial explains how to use Vector Search in Couchbase. You can work with both [Couchbase Capella](https://www.couchbase.com/products/capella/) and your self-managed Couchbase Server. + +## Installation + +You will need couchbase and langchain community to use couchbase vector store. For this tutorial, we will use OpenAI embeddings + +```bash npm2yarn +npm install couchbase @langchain/openai @langchain/community +``` + +## Create Couchbase Connection Object + +We create a connection to the Couchbase cluster initially and then pass the cluster object to the Vector Store. Here, we are connecting using the username and password. +You can also connect using any other supported way to your cluster. + +For more information on connecting to the Couchbase cluster, please check the [Node SDK documentation](https://docs.couchbase.com/nodejs-sdk/current/hello-world/start-using-sdk.html#connect). + +```typescript +import { Cluster } from "couchbase"; + +const connectionString = "couchbase://localhost"; // or couchbases://localhost if you are using TLS +const dbUsername = "Administrator"; // valid database user with read access to the bucket being queried +const dbPassword = "Password"; // password for the database user + +const couchbaseClient = await Cluster.connect(connectionString, { + username: dbUsername, + password: dbPassword, + configProfile: "wanDevelopment", +}); +``` + +## Create the Search Index + +Currently, the Search index needs to be created from the Couchbase Capella or Server UI or using the REST interface. + +For this example, let us use the Import Index feature on the Search Service on the UI. + +Let us define a Search index with the name `vector-index` on the testing bucket. +We are defining an index on the `testing` bucket's `_default` scope on the `_default` collection with the vector field set to `embedding` with 1536 dimensions and the text field set to `text`. +We are also indexing and storing all the fields under `metadata` in the document as a dynamic mapping to account for varying document structures. The similarity metric is set to `dot_product`. + +### How to Import an Index to the Full Text Search service? + +- [Couchbase Server](https://docs.couchbase.com/server/current/search/import-search-index.html) + - Click on Search -> Add Index -> Import + - Copy the following Index definition in the Import screen + - Click on Create Index to create the index. +- [Couchbase Capella](https://docs.couchbase.com/cloud/search/import-search-index.html) + - Copy the fllowing index definition to a new file `index.json` + - Import the file in Capella using the instructions in the documentation. + - Click on Create Index to create the index. + +### Index Definition + +```json +{ + "name": "vector-index", + "type": "fulltext-index", + "params": { + "doc_config": { + "docid_prefix_delim": "", + "docid_regexp": "", + "mode": "type_field", + "type_field": "type" + }, + "mapping": { + "default_analyzer": "standard", + "default_datetime_parser": "dateTimeOptional", + "default_field": "_all", + "default_mapping": { + "dynamic": true, + "enabled": true, + "properties": { + "metadata": { + "dynamic": true, + "enabled": true + }, + "embedding": { + "enabled": true, + "dynamic": false, + "fields": [ + { + "dims": 1536, + "index": true, + "name": "embedding", + "similarity": "dot_product", + "type": "vector", + "vector_index_optimized_for": "recall" + } + ] + }, + "text": { + "enabled": true, + "dynamic": false, + "fields": [ + { + "index": true, + "name": "text", + "store": true, + "type": "text" + } + ] + } + } + }, + "default_type": "_default", + "docvalues_dynamic": false, + "index_dynamic": true, + "store_dynamic": true, + "type_field": "_type" + }, + "store": { + "indexType": "scorch", + "segmentVersion": 16 + } + }, + "sourceType": "gocbcore", + "sourceName": "testing", + "sourceParams": {}, + "planParams": { + "maxPartitionsPerPIndex": 103, + "indexPartitions": 10, + "numReplicas": 0 + } +} +``` + +For more details on how to create a search index with support for Vector fields, please refer to the documentation: + +- [Couchbase Capella](https://docs.couchbase.com/cloud/search/create-search-indexes.html) +- [Couchbase Server](https://docs.couchbase.com/server/current/search/create-search-indexes.html) + +For using this vector store, CouchbaseVectorStoreArgs needs to be configured. +textKey and embeddingKey are optional fields, required if you want to use specific keys + +```typescript +const couchbaseConfig: CouchbaseVectorStoreArgs = { + cluster: couchbaseClient, + bucketName: "testing", + scopeName: "_default", + collectionName: "_default", + indexName: "vector-index", + textKey: "text", + embeddingKey: "embedding", +}; +``` + +## Create Vector Store + +We create the vector store object with the cluster information and the search index name. + +```typescript +const store = await CouchbaseVectorStore.initialize( + embeddings, // embeddings object to create embeddings from text + couchbaseConfig +); +``` + +## Basic Vector Search Example + +The following example showcases how to use couchbase vector search and perform similarity search. +For this example, we are going to load the "state_of_the_union.txt" file via the TextLoader, +chunk the text into 500 character chunks with no overlaps and index all these chunks into Couchbase. + +After the data is indexed, we perform a simple query to find the top 4 chunks that are similar to the +query "What did president say about Ketanji Brown Jackson". +At the emd, also shows how to get similarity score + +import SimilaritySearch from "@examples/indexes/vector_stores/couchbase/similaritySearch.ts"; + +{SimilaritySearch} + +## Specifying Fields to Return + +You can specify the fields to return from the document using `fields` parameter in the filter during searches. +These fields are returned as part of the `metadata` object. You can fetch any field that is stored in the index. +The `textKey` of the document is returned as part of the document's `pageContent`. + +If you do not specify any fields to be fetched, all the fields stored in the index are returned. + +If you want to fetch one of the fields in the metadata, you need to specify it using `.` +For example, to fetch the `source` field in the metadata, you need to use `metadata.source`. + +```typescript +const result = await store.similaritySearch(query, 1, { + fields: ["metadata.source"], +}); +console.log(result[0]); +``` + +## Hybrid Search + +Couchbase allows you to do hybrid searches by combining vector search results with searches on non-vector fields of the document like the `metadata` object. + +The results will be based on the combination of the results from both vector search and the searches supported by full text search service. +The scores of each of the component searches are added up to get the total score of the result. + +To perform hybrid searches, there is an optional key, `searchOptions` in `fields` parameter that can be passed to all the similarity searches. +The different search/query possibilities for the `searchOptions` can be found [here](https://docs.couchbase.com/server/current/search/search-request-params.html#query-object). + +### Create Diverse Metadata for Hybrid Search + +In order to simulate hybrid search, let us create some random metadata from the existing documents. +We uniformly add three fields to the metadata, `date` between 2010 & 2020, `rating` between 1 & 5 and `author` set to either John Doe or Jane Doe. +We will also declare few sample queries. + +```typescript +for (let i = 0; i < docs.length; i += 1) { + docs[i].metadata.date = `${2010 + (i % 10)}-01-01`; + docs[i].metadata.rating = 1 + (i % 5); + docs[i].metadata.author = ["John Doe", "Jane Doe"][i % 2]; +} + +const store = await CouchbaseVectorStore.fromDocuments( + docs, + embeddings, + couchbaseConfig +); + +const query = "What did the president say about Ketanji Brown Jackson"; +const independenceQuery = "Any mention about independence?"; +``` + +### Example: Search by Exact Value + +We can search for exact matches on a textual field like the author in the `metadata` object. + +```typescript +const exactValueResult = await store.similaritySearch(query, 4, { + fields: ["metadata.author"], + searchOptions: { + query: { field: "metadata.author", match: "John Doe" }, + }, +}); +console.log(exactValueResult[0]); +``` + +### Example: Search by Partial Match + +We can search for partial matches by specifying a fuzziness for the search. This is useful when you want to search for slight variations or misspellings of a search query. + +Here, "Johny" is close (fuzziness of 1) to "John Doe". + +```typescript +const partialMatchResult = await store.similaritySearch(query, 4, { + fields: ["metadata.author"], + searchOptions: { + query: { field: "metadata.author", match: "Johny", fuzziness: 1 }, + }, +}); +console.log(partialMatchResult[0]); +``` + +### Example: Search by Date Range Query + +We can search for documents that are within a date range query on a date field like `metadata.date`. + +```typescript +const dateRangeResult = await store.similaritySearch(independenceQuery, 4, { + fields: ["metadata.date", "metadata.author"], + searchOptions: { + query: { + start: "2016-12-31", + end: "2017-01-02", + inclusiveStart: true, + inclusiveEnd: false, + field: "metadata.date", + }, + }, +}); +console.log(dateRangeResult[0]); +``` + +### Example: Search by Numeric Range Query + +We can search for documents that are within a range for a numeric field like `metadata.rating`. + +```typescript +const ratingRangeResult = await store.similaritySearch(independenceQuery, 4, { + fields: ["metadata.rating"], + searchOptions: { + query: { + min: 3, + max: 5, + inclusiveMin: false, + inclusiveMax: true, + field: "metadata.rating", + }, + }, +}); +console.log(ratingRangeResult[0]); +``` + +### Example: Combining Multiple Search Conditions + +Different queries can by combined using AND (conjuncts) or OR (disjuncts) operators. + +In this example, we are checking for documents with a rating between 3 & 4 and dated between 2015 & 2018. + +```typescript +const multipleConditionsResult = await store.similaritySearch(texts[0], 4, { + fields: ["metadata.rating", "metadata.date"], + searchOptions: { + query: { + conjuncts: [ + { min: 3, max: 4, inclusive_max: true, field: "metadata.rating" }, + { start: "2016-12-31", end: "2017-01-02", field: "metadata.date" }, + ], + }, + }, +}); +console.log(multipleConditionsResult[0]); +``` + +### Other Queries + +Similarly, you can use any of the supported Query methods like Geo Distance, Polygon Search, Wildcard, Regular Expressions, etc in the `searchOptions` Key of `filter` parameter. +Please refer to the documentation for more details on the available query methods and their syntax. + +- [Couchbase Capella](https://docs.couchbase.com/cloud/search/search-request-params.html#query-object) +- [Couchbase Server](https://docs.couchbase.com/server/current/search/search-request-params.html#query-object) + +
+
+ +# Frequently Asked Questions + +## Question: Should I create the Search index before creating the CouchbaseVectorStore object? + +Yes, currently you need to create the Search index before creating the `CouchbaseVectoreStore` object. + +## Question: I am not seeing all the fields that I specified in my search results. + +In Couchbase, we can only return the fields stored in the Search index. Please ensure that the field that you are trying to access in the search results is part of the Search index. + +One way to handle this is to index and store a document's fields dynamically in the index. + +- In Capella, you need to go to "Advanced Mode" then under the chevron "General Settings" you can check "[X] Store Dynamic Fields" or "[X] Index Dynamic Fields" +- In Couchbase Server, in the Index Editor (not Quick Editor) under the chevron "Advanced" you can check "[X] Store Dynamic Fields" or "[X] Index Dynamic Fields" + +Note that these options will increase the size of the index. + +For more details on dynamic mappings, please refer to the [documentation](https://docs.couchbase.com/cloud/search/customize-index.html). + +## Question: I am unable to see the metadata object in my search results. + +This is most likely due to the `metadata` field in the document not being indexed and/or stored by the Couchbase Search index. In order to index the `metadata` field in the document, you need to add it to the index as a child mapping. + +If you select to map all the fields in the mapping, you will be able to search by all metadata fields. Alternatively, to optimize the index, you can select the specific fields inside `metadata` object to be indexed. +You can refer to the [docs](https://docs.couchbase.com/cloud/search/customize-index.html) to learn more about indexing child mappings. + +To create Child Mappings, you can refer to the following docs - + +- [Couchbase Capella](https://docs.couchbase.com/cloud/search/create-child-mapping.html) +- [Couchbase Server](https://docs.couchbase.com/server/current/fts/fts-creating-index-from-UI-classic-editor-dynamic.html) diff --git a/examples/package.json b/examples/package.json index 5e778c67cc97..def41784ab8b 100644 --- a/examples/package.json +++ b/examples/package.json @@ -64,6 +64,7 @@ "axios": "^0.26.0", "chromadb": "^1.5.3", "convex": "^1.3.1", + "couchbase": "^4.3.0", "date-fns": "^3.3.1", "exa-js": "^1.0.12", "faiss-node": "^0.5.1", diff --git a/examples/src/indexes/vector_stores/couchbase/.env.example b/examples/src/indexes/vector_stores/couchbase/.env.example new file mode 100644 index 000000000000..27644fdbaff5 --- /dev/null +++ b/examples/src/indexes/vector_stores/couchbase/.env.example @@ -0,0 +1,13 @@ +# Couchbase connection params +COUCHBASE_DB_CONN_STR= +COUCHBASE_DB_USERNAME= +COUCHBASE_DB_PASSWORD= + +# Couchbase vector store args +COUCHBASE_DB_BUCKET_NAME= +COUCHBASE_DB_SCOPE_NAME= +COUCHBASE_DB_COLLECTION_NAME= +COUCHBASE_DB_INDEX_NAME= + +# Open AI Key for embeddings +OPENAI_API_KEY= \ No newline at end of file diff --git a/examples/src/indexes/vector_stores/couchbase/similaritySearch.ts b/examples/src/indexes/vector_stores/couchbase/similaritySearch.ts new file mode 100644 index 000000000000..fe063bfe8c53 --- /dev/null +++ b/examples/src/indexes/vector_stores/couchbase/similaritySearch.ts @@ -0,0 +1,67 @@ +import { OpenAIEmbeddings } from "@langchain/openai"; +import { + CouchbaseVectorStoreArgs, + CouchbaseVectorStore, +} from "@langchain/community/vectorstores/couchbase"; +import { Cluster } from "couchbase"; +import { TextLoader } from "langchain/document_loaders/fs/text"; +import { CharacterTextSplitter } from "langchain/text_splitter"; + +const connectionString = + process.env.COUCHBASE_DB_CONN_STR ?? "couchbase://localhost"; +const databaseUsername = process.env.COUCHBASE_DB_USERNAME ?? "Administrator"; +const databasePassword = process.env.COUCHBASE_DB_PASSWORD ?? "Password"; + +// Load documents from file +const loader = new TextLoader("./state_of_the_union.txt"); +const rawDocuments = await loader.load(); +const splitter = new CharacterTextSplitter({ + chunkSize: 500, + chunkOverlap: 0, +}); +const docs = await splitter.splitDocuments(rawDocuments); + +const couchbaseClient = await Cluster.connect(connectionString, { + username: databaseUsername, + password: databasePassword, + configProfile: "wanDevelopment", +}); + +// Open AI API Key is required to use OpenAIEmbeddings, some other embeddings may also be used +const embeddings = new OpenAIEmbeddings({ + openAIApiKey: process.env.OPENAI_API_KEY, +}); + +const couchbaseConfig: CouchbaseVectorStoreArgs = { + cluster: couchbaseClient, + bucketName: "testing", + scopeName: "_default", + collectionName: "_default", + indexName: "vector-index", + textKey: "text", + embeddingKey: "embedding", +}; + +const store = await CouchbaseVectorStore.fromDocuments( + docs, + embeddings, + couchbaseConfig +); + +const query = "What did president say about Ketanji Brown Jackson"; + +const resultsSimilaritySearch = await store.similaritySearch(query); +console.log("resulting documents: ", resultsSimilaritySearch[0]); + +// Similarity Search With Score +const resultsSimilaritySearchWithScore = await store.similaritySearchWithScore( + query, + 1 +); +console.log("resulting documents: ", resultsSimilaritySearchWithScore[0][0]); +console.log("resulting scores: ", resultsSimilaritySearchWithScore[0][1]); + +const result = await store.similaritySearch(query, 1, { + fields: ["metadata.source"], +}); +console.log(result[0]); diff --git a/langchain/package.json b/langchain/package.json index 577f98915367..e2690f32a5aa 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -1255,7 +1255,7 @@ "cheerio": "^1.0.0-rc.12", "chromadb": "^1.5.3", "convex": "^1.3.1", - "couchbase": "^4.2.10", + "couchbase": "^4.3.0", "d3-dsv": "^2.0.0", "dotenv": "^16.0.3", "dpdm": "^3.12.0", @@ -1325,7 +1325,7 @@ "cheerio": "^1.0.0-rc.12", "chromadb": "*", "convex": "^1.3.1", - "couchbase": "^4.2.10", + "couchbase": "^4.3.0", "d3-dsv": "^2.0.0", "epub2": "^3.0.1", "fast-xml-parser": "*", diff --git a/libs/langchain-community/.gitignore b/libs/langchain-community/.gitignore index 5ab8bb3ca296..9bb18ab4b539 100644 --- a/libs/langchain-community/.gitignore +++ b/libs/langchain-community/.gitignore @@ -306,6 +306,10 @@ vectorstores/convex.cjs vectorstores/convex.js vectorstores/convex.d.ts vectorstores/convex.d.cts +vectorstores/couchbase.cjs +vectorstores/couchbase.js +vectorstores/couchbase.d.ts +vectorstores/couchbase.d.cts vectorstores/elasticsearch.cjs vectorstores/elasticsearch.js vectorstores/elasticsearch.d.ts diff --git a/libs/langchain-community/langchain.config.js b/libs/langchain-community/langchain.config.js index d8646afa5278..e29bf5397c32 100644 --- a/libs/langchain-community/langchain.config.js +++ b/libs/langchain-community/langchain.config.js @@ -107,6 +107,7 @@ export const config = { "vectorstores/closevector/web": "vectorstores/closevector/web", "vectorstores/cloudflare_vectorize": "vectorstores/cloudflare_vectorize", "vectorstores/convex": "vectorstores/convex", + "vectorstores/couchbase": "vectorstores/couchbase", "vectorstores/elasticsearch": "vectorstores/elasticsearch", "vectorstores/faiss": "vectorstores/faiss", "vectorstores/googlevertexai": "vectorstores/googlevertexai", @@ -270,6 +271,7 @@ export const config = { "vectorstores/closevector/web", "vectorstores/cloudflare_vectorize", "vectorstores/convex", + "vectorstores/couchbase", "vectorstores/elasticsearch", "vectorstores/faiss", "vectorstores/googlevertexai", diff --git a/libs/langchain-community/package.json b/libs/langchain-community/package.json index 8eecd12880ab..220d66b87e54 100644 --- a/libs/langchain-community/package.json +++ b/libs/langchain-community/package.json @@ -122,6 +122,7 @@ "closevector-web": "0.1.6", "cohere-ai": ">=6.0.0", "convex": "^1.3.1", + "couchbase": "^4.3.0", "discord.js": "^14.14.1", "dotenv": "^16.0.3", "dpdm": "^3.12.0", @@ -225,6 +226,7 @@ "closevector-web": "0.1.6", "cohere-ai": "*", "convex": "^1.3.1", + "couchbase": "^4.3.0", "discord.js": "^14.14.1", "dria": "^0.0.3", "faiss-node": "^0.5.1", @@ -418,6 +420,9 @@ "convex": { "optional": true }, + "couchbase": { + "optional": true + }, "discord.js": { "optional": true }, @@ -1212,6 +1217,15 @@ "import": "./vectorstores/convex.js", "require": "./vectorstores/convex.cjs" }, + "./vectorstores/couchbase": { + "types": { + "import": "./vectorstores/couchbase.d.ts", + "require": "./vectorstores/couchbase.d.cts", + "default": "./vectorstores/couchbase.d.ts" + }, + "import": "./vectorstores/couchbase.js", + "require": "./vectorstores/couchbase.cjs" + }, "./vectorstores/elasticsearch": { "types": { "import": "./vectorstores/elasticsearch.d.ts", @@ -2451,6 +2465,10 @@ "vectorstores/convex.js", "vectorstores/convex.d.ts", "vectorstores/convex.d.cts", + "vectorstores/couchbase.cjs", + "vectorstores/couchbase.js", + "vectorstores/couchbase.d.ts", + "vectorstores/couchbase.d.cts", "vectorstores/elasticsearch.cjs", "vectorstores/elasticsearch.js", "vectorstores/elasticsearch.d.ts", diff --git a/libs/langchain-community/src/load/import_constants.ts b/libs/langchain-community/src/load/import_constants.ts index bf5526878cb5..996fdebec21a 100644 --- a/libs/langchain-community/src/load/import_constants.ts +++ b/libs/langchain-community/src/load/import_constants.ts @@ -43,6 +43,7 @@ export const optionalImportEntrypoints: string[] = [ "langchain_community/vectorstores/closevector/web", "langchain_community/vectorstores/cloudflare_vectorize", "langchain_community/vectorstores/convex", + "langchain_community/vectorstores/couchbase", "langchain_community/vectorstores/elasticsearch", "langchain_community/vectorstores/faiss", "langchain_community/vectorstores/googlevertexai", diff --git a/libs/langchain-community/src/vectorstores/couchbase.ts b/libs/langchain-community/src/vectorstores/couchbase.ts new file mode 100644 index 000000000000..a582bc4a3c66 --- /dev/null +++ b/libs/langchain-community/src/vectorstores/couchbase.ts @@ -0,0 +1,608 @@ +/* eslint-disable no-param-reassign */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { EmbeddingsInterface } from "@langchain/core/embeddings"; +import { VectorStore } from "@langchain/core/vectorstores"; +import { + Bucket, + Cluster, + Collection, + Scope, + SearchRequest, + VectorQuery, + VectorSearch, +} from "couchbase"; +import { Document } from "@langchain/core/documents"; +import { v4 as uuid } from "uuid"; + +/** + * This interface define the optional fields for adding vector + * - `ids` - vector of ids for each document. If undefined, then uuid will be used + * - `metadata` - vector of metadata object for each document + */ +export interface AddVectorOptions { + ids?: string[]; + metadata?: Record[]; +} + +/** + * This interface defines the fields required to initialize a vector store + * These are the fields part of config: + * @property {Cluster} cluster - The Couchbase cluster that the store will interact with. + * @property {string} bucketName - The name of the bucket in the Couchbase cluster. + * @property {string} scopeName - The name of the scope within the bucket. + * @property {string} collectionName - The name of the collection within the scope. + * @property {string} indexName - The name of the index to be used for vector search. + * @property {string} textKey - The key to be used for text in the documents. Defaults to "text". + * @property {string} embeddingKey - The key to be used for embeddings in the documents. Defaults to "embedding". + * @property {boolean} scopedIndex - Whether to use a scoped index for vector search. Defaults to true. + * @property {AddVectorOptions} addVectorOptions - Options for adding vectors with specific id/metadata + */ +export interface CouchbaseVectorStoreArgs { + cluster: Cluster; + bucketName: string; + scopeName: string; + collectionName: string; + indexName: string; + textKey?: string; + embeddingKey?: string; + scopedIndex?: boolean; + addVectorOptions?: AddVectorOptions; +} + +/** + * This type defines the search filters used in couchbase vector search + * - `fields`: Optional list of fields to include in the + * metadata of results. Note that these need to be stored in the index. + * If nothing is specified, defaults to all the fields stored in the index. + * - `searchOptions`: Optional search options that are passed to Couchbase search. Defaults to empty object. + */ +type CouchbaseVectorStoreFilter = { + fields?: any; + searchOptions?: any; +}; + +/** + * Class for interacting with the Couchbase database. It extends the + * VectorStore class and provides methods for adding vectors and + * documents, and searching for similar vectors. + * Initiate the class using initialize() method. + */ +export class CouchbaseVectorStore extends VectorStore { + declare FilterType: CouchbaseVectorStoreFilter; + + private metadataKey = "metadata"; + + private readonly defaultTextKey = "text"; + + private readonly defaultScopedIndex = true; + + private readonly defaultEmbeddingKey = "embedding"; + + private cluster: Cluster; + + private _bucket: Bucket; + + private _scope: Scope; + + private _collection: Collection; + + private bucketName: string; + + private scopeName: string; + + private collectionName: string; + + private indexName: string; + + private textKey = this.defaultTextKey; + + private embeddingKey = this.defaultEmbeddingKey; + + private scopedIndex: boolean; + + /** + * The private constructor used to provide embedding to parent class. + * Initialize the class using static initialize() method + * @param embedding - object to generate embedding + * @param config - the fields required to initialize a vector store + */ + private constructor( + embedding: EmbeddingsInterface, + config: CouchbaseVectorStoreArgs + ) { + super(embedding, config); + } + + /** + * initialize class for interacting with the Couchbase database. + * It extends the VectorStore class and provides methods + * for adding vectors and documents, and searching for similar vectors. + * This also verifies the params + * + * @param embeddings - object to generate embedding + * @param config - the fields required to initialize a vector store + */ + static async initialize( + embeddings: EmbeddingsInterface, + config: CouchbaseVectorStoreArgs + ) { + const store = new CouchbaseVectorStore(embeddings, config); + + const { + cluster, + bucketName, + scopeName, + collectionName, + indexName, + textKey, + embeddingKey, + scopedIndex, + } = config; + + store.cluster = cluster; + store.bucketName = bucketName; + store.scopeName = scopeName; + store.collectionName = collectionName; + store.indexName = indexName; + if (textKey) { + store.textKey = textKey; + } else { + store.textKey = store.defaultTextKey; + } + + if (embeddingKey) { + store.embeddingKey = embeddingKey; + } else { + store.embeddingKey = store.defaultEmbeddingKey; + } + + if (scopedIndex !== undefined) { + store.scopedIndex = scopedIndex; + } else { + store.scopedIndex = store.defaultScopedIndex; + } + + try { + store._bucket = store.cluster.bucket(store.bucketName); + store._scope = store._bucket.scope(store.scopeName); + store._collection = store._scope.collection(store.collectionName); + } catch (err) { + throw new Error( + "Error connecting to couchbase, Please check connection and credentials" + ); + } + + try { + if ( + !(await store.checkBucketExists()) || + !(await store.checkIndexExists()) || + !(await store.checkScopeAndCollectionExists()) + ) { + throw new Error("Error while initializing vector store"); + } + } catch (err) { + throw new Error(`Error while initializing vector store: ${err}`); + } + return store; + } + + /** + * An asynchrononous method to verify the search indexes. + * It retrieves all indexes and checks if specified index is present. + * + * @throws - If the specified index does not exist in the database. + * + * @returns - returns promise true if no error is found + */ + private async checkIndexExists(): Promise { + if (this.scopedIndex) { + const allIndexes = await this._scope.searchIndexes().getAllIndexes(); + const indexNames = allIndexes.map((index) => index.name); + if (!indexNames.includes(this.indexName)) { + throw new Error( + `Index ${this.indexName} does not exist. Please create the index before searching.` + ); + } + } else { + const allIndexes = await this.cluster.searchIndexes().getAllIndexes(); + const indexNames = allIndexes.map((index) => index.name); + if (!indexNames.includes(this.indexName)) { + throw new Error( + `Index ${this.indexName} does not exist. Please create the index before searching.` + ); + } + } + return true; + } + + /** + * An asynchronous method to verify the existence of a bucket. + * It retrieves the bucket using the bucket manager and checks if the specified bucket is present. + * + * @throws - If the specified bucket does not exist in the database. + * + * @returns - Returns a promise that resolves to true if no error is found, indicating the bucket exists. + */ + private async checkBucketExists(): Promise { + const bucketManager = this.cluster.buckets(); + try { + await bucketManager.getBucket(this.bucketName); + return true; + } catch (error) { + throw new Error( + `Bucket ${this.bucketName} does not exist. Please create the bucket before searching.` + ); + } + } + + /** + * An asynchronous method to verify the existence of a scope and a collection within that scope. + * It retrieves all scopes and collections in the bucket, and checks if the specified scope and collection are present. + * + * @throws - If the specified scope does not exist in the bucket, or if the specified collection does not exist in the scope. + * + * @returns - Returns a promise that resolves to true if no error is found, indicating the scope and collection exist. + */ + private async checkScopeAndCollectionExists(): Promise { + const scopeCollectionMap: Record = {}; + + // Get a list of all scopes in the bucket + const scopes = await this._bucket.collections().getAllScopes(); + for (const scope of scopes) { + scopeCollectionMap[scope.name] = []; + + // Get a list of all the collections in the scope + for (const collection of scope.collections) { + scopeCollectionMap[scope.name].push(collection.name); + } + } + + // Check if the scope exists + if (!Object.keys(scopeCollectionMap).includes(this.scopeName)) { + throw new Error( + `Scope ${this.scopeName} not found in Couchbase bucket ${this.bucketName}` + ); + } + + // Check if the collection exists in the scope + if (!scopeCollectionMap[this.scopeName].includes(this.collectionName)) { + throw new Error( + `Collection ${this.collectionName} not found in scope ${this.scopeName} in Couchbase bucket ${this.bucketName}` + ); + } + + return true; + } + + _vectorstoreType(): string { + return "couchbase"; + } + + /** + * Formats couchbase metadata by removing `metadata.` from initials + * @param fields - all the fields of row + * @returns - formatted metadata fields + */ + private formatMetadata = (fields: any) => { + delete fields[this.textKey]; + const metadataFields: { [key: string]: any } = {}; + // eslint-disable-next-line guard-for-in + for (const key in fields) { + const newKey = key.replace(`${this.metadataKey}.`, ""); + metadataFields[newKey] = fields[key]; + } + return metadataFields; + }; + + /** + * Performs a similarity search on the vectors in the Couchbase database and returns the documents and their corresponding scores. + * + * @param queryEmbeddings - Embedding vector to look up documents similar to. + * @param k - Number of documents to return. Defaults to 4. + * @param filter - Optional search filter that are passed to Couchbase search. Defaults to empty object. + * - `fields`: Optional list of fields to include in the + * metadata of results. Note that these need to be stored in the index. + * If nothing is specified, defaults to all the fields stored in the index. + * - `searchOptions`: Optional search options that are passed to Couchbase search. Defaults to empty object. + * + * @returns - Promise of list of [document, score] that are the most similar to the query vector. + * + * @throws If the search operation fails. + */ + async similaritySearchVectorWithScore( + queryEmbeddings: number[], + k = 4, + filter: CouchbaseVectorStoreFilter = {} + ): Promise<[Document, number][]> { + let { fields } = filter; + const { searchOptions } = filter; + + if (!fields) { + fields = ["*"]; + } + if ( + !(fields.length === 1 && fields[0] === "*") && + !fields.includes(this.textKey) + ) { + fields.push(this.textKey); + } + + const searchRequest = new SearchRequest( + VectorSearch.fromVectorQuery( + new VectorQuery(this.embeddingKey, queryEmbeddings).numCandidates(k) + ) + ); + + let searchIterator; + const docsWithScore: [Document, number][] = []; + try { + if (this.scopedIndex) { + searchIterator = this._scope.search(this.indexName, searchRequest, { + limit: k, + fields, + raw: searchOptions, + }); + } else { + searchIterator = this.cluster.search(this.indexName, searchRequest, { + limit: k, + fields, + raw: searchOptions, + }); + } + + const searchRows = (await searchIterator).rows; + for (const row of searchRows) { + const text = row.fields[this.textKey]; + const metadataFields = this.formatMetadata(row.fields); + const searchScore = row.score; + const doc = new Document({ + pageContent: text, + metadata: metadataFields, + }); + docsWithScore.push([doc, searchScore]); + } + } catch (err) { + console.log("error received"); + throw new Error(`Search failed with error: ${err}`); + } + return docsWithScore; + } + + /** + * Return documents that are most similar to the vector embedding. + * + * @param queryEmbeddings - Embedding to look up documents similar to. + * @param k - The number of similar documents to return. Defaults to 4. + * @param filter - Optional search filter that are passed to Couchbase search. Defaults to empty object. + * - `fields`: Optional list of fields to include in the + * metadata of results. Note that these need to be stored in the index. + * If nothing is specified, defaults to all the fields stored in the index. + * - `searchOptions`: Optional search options that are passed to Couchbase search. Defaults to empty object. + * + * @returns - A promise that resolves to an array of documents that match the similarity search. + */ + async similaritySearchByVector( + queryEmbeddings: number[], + k = 4, + filter: CouchbaseVectorStoreFilter = {} + ): Promise { + const docsWithScore = await this.similaritySearchVectorWithScore( + queryEmbeddings, + k, + filter + ); + const docs = []; + for (const doc of docsWithScore) { + docs.push(doc[0]); + } + return docs; + } + + /** + * Return documents that are most similar to the query. + * + * @param query - Query to look up for similar documents + * @param k - The number of similar documents to return. Defaults to 4. + * @param filter - Optional search filter that are passed to Couchbase search. Defaults to empty object. + * - `fields`: Optional list of fields to include in the + * metadata of results. Note that these need to be stored in the index. + * If nothing is specified, defaults to all the fields stored in the index. + * - `searchOptions`: Optional search options that are passed to Couchbase search. Defaults to empty object. + * + * @returns - Promise of list of documents that are most similar to the query. + */ + async similaritySearch( + query: string, + k = 4, + filter: CouchbaseVectorStoreFilter = {} + ): Promise { + const queryEmbeddings = await this.embeddings.embedQuery(query); + const docsWithScore = await this.similaritySearchVectorWithScore( + queryEmbeddings, + k, + filter + ); + const docs = []; + for (const doc of docsWithScore) { + docs.push(doc[0]); + } + return docs; + } + + /** + * Return documents that are most similar to the query with their scores. + * + * @param query - Query to look up for similar documents + * @param k - The number of similar documents to return. Defaults to 4. + * @param filter - Optional search filter that are passed to Couchbase search. Defaults to empty object. + * - `fields`: Optional list of fields to include in the + * metadata of results. Note that these need to be stored in the index. + * If nothing is specified, defaults to all the fields stored in the index. + * - `searchOptions`: Optional search options that are passed to Couchbase search. Defaults to empty object. + * + * @returns - Promise of list of documents that are most similar to the query. + */ + async similaritySearchWithScore( + query: string, + k = 4, + filter: CouchbaseVectorStoreFilter = {} + ): Promise<[Document, number][]> { + const queryEmbeddings = await this.embeddings.embedQuery(query); + const docsWithScore = await this.similaritySearchVectorWithScore( + queryEmbeddings, + k, + filter + ); + return docsWithScore; + } + + /** + * Add vectors and corresponding documents to a couchbase collection + * If the document IDs are passed, the existing documents (if any) will be + * overwritten with the new ones. + * @param vectors - The vectors to be added to the collection. + * @param documents - The corresponding documents to be added to the collection. + * @param options - Optional parameters for adding vectors. + * This may include the IDs and metadata of the documents to be added. Defaults to an empty object. + * + * @returns - A promise that resolves to an array of document IDs that were added to the collection. + */ + public async addVectors( + vectors: number[][], + documents: Document[], + options: AddVectorOptions = {} + ): Promise { + // Get document ids. if ids are not available then use UUIDs for each document + let ids: string[] | undefined = options ? options.ids : undefined; + if (ids === undefined) { + ids = Array.from({ length: documents.length }, () => uuid()); + } + + // Get metadata for each document. if metadata is not available, use empty object for each document + let metadata: any = options ? options.metadata : undefined; + if (metadata === undefined) { + metadata = Array.from({ length: documents.length }, () => ({})); + } + + const documentsToInsert = ids.map((id: string, index: number) => ({ + [id]: { + [this.textKey]: documents[index].pageContent, + [this.embeddingKey]: vectors[index], + [this.metadataKey]: metadata[index], + }, + })); + + const docIds: string[] = []; + for (const document of documentsToInsert) { + try { + const currentDocumentKey = Object.keys(document)[0]; + await this._collection.upsert( + currentDocumentKey, + document[currentDocumentKey] + ); + docIds.push(currentDocumentKey); + } catch (e) { + console.log("error received while upserting document", e); + } + } + + return docIds; + } + + /** + * Run texts through the embeddings and persist in vectorstore. + * If the document IDs are passed, the existing documents (if any) will be + * overwritten with the new ones. + * @param documents - The corresponding documents to be added to the collection. + * @param options - Optional parameters for adding documents. + * This may include the IDs and metadata of the documents to be added. Defaults to an empty object. + * + * @returns - A promise that resolves to an array of document IDs that were added to the collection. + */ + public async addDocuments( + documents: Document[], + options: AddVectorOptions = {} + ) { + const texts = documents.map(({ pageContent }) => pageContent); + const metadatas = documents.map((doc) => doc.metadata); + if (!options.metadata) { + options.metadata = metadatas; + } + return this.addVectors( + await this.embeddings.embedDocuments(texts), + documents, + options + ); + } + + /** + * Create a new CouchbaseVectorStore from a set of documents. + * This function will initialize a new store, add the documents to it, and then return the store. + * @param documents - The documents to be added to the new store. + * @param embeddings - The embeddings to be used for the documents. + * @param config - The configuration for the new CouchbaseVectorStore. This includes the options for adding vectors. + * + * @returns - A promise that resolves to the new CouchbaseVectorStore that contains the added documents. + */ + static async fromDocuments( + documents: Document[], + embeddings: EmbeddingsInterface, + config: CouchbaseVectorStoreArgs + ): Promise { + const store = await this.initialize(embeddings, config); + await store.addDocuments(documents, config.addVectorOptions); + return store; + } + + /** + * Create a new CouchbaseVectorStore from a set of texts. + * This function will convert each text and its corresponding metadata into a Document, + * initialize a new store, add the documents to it, and then return the store. + * @param texts - The texts to be converted into Documents and added to the new store. + * @param metadatas - The metadata for each text. If an array is passed, each text will have its corresponding metadata. + * If not, all texts will have the same metadata. + * @param embeddings - The embeddings to be used for the documents. + * @param config - The configuration for the new CouchbaseVectorStore. This includes the options for adding vectors. + * + * @returns - A promise that resolves to the new CouchbaseVectorStore that contains the added documents. + */ + static async fromTexts( + texts: string[], + metadatas: any, + embeddings: EmbeddingsInterface, + config: CouchbaseVectorStoreArgs + ): Promise { + const docs = []; + + for (let i = 0; i < texts.length; i += 1) { + const metadata = Array.isArray(metadatas) ? metadatas[i] : metadatas; + const newDoc = new Document({ + pageContent: texts[i], + metadata, + }); + docs.push(newDoc); + } + return await this.fromDocuments(docs, embeddings, config); + } + + /** + * Delete documents from the collection. + * This function will attempt to remove each document in the provided list of IDs from the collection. + * If an error occurs during the deletion of a document, an error will be thrown with the ID of the document and the error message. + * @param ids - An array of document IDs to be deleted from the collection. + * + * @returns - A promise that resolves when all documents have been attempted to be deleted. If a document could not be deleted, an error is thrown. + */ + public async delete(ids: string[]): Promise { + for (let i = 0; i < ids.length; i += 1) { + const removeId = ids[i]; + try { + await this._collection.remove(removeId); + } catch (err) { + throw new Error( + `Error while deleting document - Document Id: ${ids[i]}, Error: ${err}` + ); + } + } + } +} diff --git a/libs/langchain-community/src/vectorstores/tests/couchbase.int.test.ts b/libs/langchain-community/src/vectorstores/tests/couchbase.int.test.ts new file mode 100644 index 000000000000..2c92347495e9 --- /dev/null +++ b/libs/langchain-community/src/vectorstores/tests/couchbase.int.test.ts @@ -0,0 +1,241 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable no-process-env */ +import { describe, test } from "@jest/globals"; +import { Cluster } from "couchbase"; +import { OpenAIEmbeddings } from "@langchain/openai"; +import { Document } from "@langchain/core/documents"; +import { + CouchbaseVectorStore, + CouchbaseVectorStoreArgs, +} from "../couchbase.js"; + +describe.skip("Couchbase vector store", () => { + const connectionString = + process.env.COUCHBASE_DB_CONN_STR ?? "couchbase://localhost"; + const databaseUsername = process.env.COUCHBASE_DB_USERNAME ?? "Administrator"; + const databasePassword = process.env.COUCHBASE_DB_PASSWORD ?? "Password"; + const bucketName = process.env.COUCHBASE_DB_BUCKET_NAME ?? "testing"; + const scopeName = process.env.COUCHBASE_DB_SCOPE_NAME ?? "_default"; + const collectionName = process.env.COUCHBASE_DB_COLLECTION_NAME ?? "_default"; + const indexName = process.env.COUCHBASE_DB_INDEX_NAME ?? "vector-index"; + const textFieldKey = "text"; + const embeddingFieldKey = "embedding"; + const isScopedIndex = true; + let couchbaseClient: Cluster; + let embeddings: OpenAIEmbeddings; + + const texts = [ + "Couchbase, built on a key-value store, offers efficient data operations.", + "As a NoSQL database, Couchbase provides scalability and flexibility to handle diverse data types.", + "Couchbase supports N1QL, a SQL-like language, easing the transition for developers familiar with SQL.", + "Couchbase ensures high availability with built-in fault tolerance and automatic multi-master replication.", + "With its memory-first architecture, Couchbase delivers high performance and low latency data access.", + ]; + + const metadata = [ + { id: "101", name: "Efficient Operator" }, + { id: "102", name: "Flexible Storer" }, + { id: "103", name: "Quick Performer" }, + { id: "104", name: "Reliable Guardian" }, + { id: "105", name: "Adaptable Navigator" }, + ]; + + beforeEach(async () => { + couchbaseClient = await Cluster.connect(connectionString, { + username: databaseUsername, + password: databasePassword, + configProfile: "wanDevelopment", + }); + + embeddings = new OpenAIEmbeddings({ + openAIApiKey: process.env.OPENAI_API_KEY, + }); + }); + + test("from Texts to vector store", async () => { + const couchbaseConfig: CouchbaseVectorStoreArgs = { + cluster: couchbaseClient, + bucketName, + scopeName, + collectionName, + indexName, + textKey: textFieldKey, + embeddingKey: embeddingFieldKey, + scopedIndex: isScopedIndex, + }; + + const store = await CouchbaseVectorStore.fromTexts( + texts, + metadata, + embeddings, + couchbaseConfig + ); + const results = await store.similaritySearchWithScore(texts[0], 1); + + expect(results.length).toEqual(1); + expect(results[0][0].pageContent).toEqual(texts[0]); + expect(results[0][0].metadata.name).toEqual(metadata[0].name); + expect(results[0][0].metadata.id).toEqual(metadata[0].id); + }); + + test("Add and delete Documents to vector store", async () => { + const couchbaseConfig: CouchbaseVectorStoreArgs = { + cluster: couchbaseClient, + bucketName, + scopeName, + collectionName, + indexName, + textKey: textFieldKey, + embeddingKey: embeddingFieldKey, + scopedIndex: isScopedIndex, + }; + + const documents: Document[] = []; + for (let i = 0; i < texts.length; i += 1) { + documents.push({ + pageContent: texts[i], + metadata: {}, + }); + } + + const store = await CouchbaseVectorStore.initialize( + embeddings, + couchbaseConfig + ); + const ids = await store.addDocuments(documents, { + ids: metadata.map((val) => val.id), + metadata: metadata.map((val) => { + const metadataObj = { + name: val.name, + }; + return metadataObj; + }), + }); + + expect(ids.length).toEqual(texts.length); + for (let i = 0; i < ids.length; i += 1) { + expect(ids[i]).toEqual(metadata[i].id); + } + + const results = await store.similaritySearch(texts[1], 1); + + expect(results.length).toEqual(1); + expect(results[0].pageContent).toEqual(texts[1]); + expect(results[0].metadata.name).toEqual(metadata[1].name); + + await store.delete(ids); + const cbCollection = couchbaseClient + .bucket(bucketName) + .scope(scopeName) + .collection(collectionName); + expect((await cbCollection.exists(ids[0])).exists).toBe(false); + expect((await cbCollection.exists(ids[4])).exists).toBe(false); + + const resultsDeleted = await store.similaritySearch(texts[1], 1); + expect(resultsDeleted.length).not.toEqual(1); + }); + + test("hybrid search", async () => { + const couchbaseConfig: CouchbaseVectorStoreArgs = { + cluster: couchbaseClient, + bucketName, + scopeName, + collectionName, + indexName, + textKey: textFieldKey, + embeddingKey: embeddingFieldKey, + scopedIndex: isScopedIndex, + }; + + const query = `Couchbase offers impressive memory-first performance for your important applications.`; + + const hybridSearchMetadata: { [key: string]: any }[] = []; + + // Add More Metadata + for (let i = 0; i < texts.length; i += 1) { + const doc: { [key: string]: any } = {}; + doc.date = `${2020 + (i % 10)}-01-01`; + doc.rating = 1 + (i % 5); + doc.author = ["John Doe", "Jane Doe"][(i + 1) % 2]; + doc.id = (i + 100).toString(); + hybridSearchMetadata.push(doc); + } + const store = await CouchbaseVectorStore.fromTexts( + texts, + hybridSearchMetadata, + embeddings, + couchbaseConfig + ); + + const resultsSimilaritySearch = await store.similaritySearch(query, 1); + expect(resultsSimilaritySearch.length).toEqual(1); + expect(resultsSimilaritySearch[0].metadata.date).not.toEqual(undefined); + + // search by exact value in metadata + const exactValueResult = await store.similaritySearch(query, 4, { + fields: ["metadata.author"], + searchOptions: { + query: { field: "metadata.author", match: "John Doe" }, + }, + }); + + expect(exactValueResult.length).toEqual(4); + expect(exactValueResult[0].metadata.author).toEqual("John Doe"); + + // search by partial match in metadata + const partialMatchResult = await store.similaritySearch(query, 4, { + fields: ["metadata.author"], + searchOptions: { + query: { field: "metadata.author", match: "Johny", fuzziness: 1 }, + }, + }); + + expect(partialMatchResult.length).toEqual(4); + expect(partialMatchResult[0].metadata.author).toEqual("John Doe"); + + // search by date range + const dateRangeResult = await store.similaritySearch(query, 4, { + fields: ["metadata.date", "metadata.author"], + searchOptions: { + query: { + start: "2022-12-31", + end: "2023-01-02", + inclusiveStart: true, + inclusiveEnd: false, + field: "metadata.date", + }, + }, + }); + + expect(dateRangeResult.length).toEqual(4); + + // search by rating range + const ratingRangeResult = await store.similaritySearch(texts[0], 4, { + fields: ["metadata.rating"], + searchOptions: { + query: { + min: 3, + max: 5, + inclusiveMin: false, + inclusiveMax: true, + field: "metadata.rating", + }, + }, + }); + expect(ratingRangeResult.length).toEqual(4); + + // multiple search conditions + const multipleConditionsResult = await store.similaritySearch(texts[0], 4, { + fields: ["metadata.rating", "metadata.date"], + searchOptions: { + query: { + conjuncts: [ + { min: 3, max: 4, inclusive_max: true, field: "metadata.rating" }, + { start: "2022-12-31", end: "2023-01-02", field: "metadata.date" }, + ], + }, + }, + }); + expect(multipleConditionsResult.length).toEqual(4); + }); +}); diff --git a/yarn.lock b/yarn.lock index d6e4c379a788..8f90746f150e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6645,44 +6645,44 @@ __metadata: languageName: node linkType: hard -"@couchbase/couchbase-darwin-arm64-napi@npm:4.2.10": - version: 4.2.10 - resolution: "@couchbase/couchbase-darwin-arm64-napi@npm:4.2.10" +"@couchbase/couchbase-darwin-arm64-napi@npm:4.3.0": + version: 4.3.0 + resolution: "@couchbase/couchbase-darwin-arm64-napi@npm:4.3.0" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@couchbase/couchbase-darwin-x64-napi@npm:4.2.10": - version: 4.2.10 - resolution: "@couchbase/couchbase-darwin-x64-napi@npm:4.2.10" +"@couchbase/couchbase-darwin-x64-napi@npm:4.3.0": + version: 4.3.0 + resolution: "@couchbase/couchbase-darwin-x64-napi@npm:4.3.0" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@couchbase/couchbase-linux-arm64-napi@npm:4.2.10": - version: 4.2.10 - resolution: "@couchbase/couchbase-linux-arm64-napi@npm:4.2.10" +"@couchbase/couchbase-linux-arm64-napi@npm:4.3.0": + version: 4.3.0 + resolution: "@couchbase/couchbase-linux-arm64-napi@npm:4.3.0" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"@couchbase/couchbase-linux-x64-napi@npm:4.2.10": - version: 4.2.10 - resolution: "@couchbase/couchbase-linux-x64-napi@npm:4.2.10" +"@couchbase/couchbase-linux-x64-napi@npm:4.3.0": + version: 4.3.0 + resolution: "@couchbase/couchbase-linux-x64-napi@npm:4.3.0" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@couchbase/couchbase-linuxmusl-x64-napi@npm:4.2.10": - version: 4.2.10 - resolution: "@couchbase/couchbase-linuxmusl-x64-napi@npm:4.2.10" +"@couchbase/couchbase-linuxmusl-x64-napi@npm:4.3.0": + version: 4.3.0 + resolution: "@couchbase/couchbase-linuxmusl-x64-napi@npm:4.3.0" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@couchbase/couchbase-win32-x64-napi@npm:4.2.10": - version: 4.2.10 - resolution: "@couchbase/couchbase-win32-x64-napi@npm:4.2.10" +"@couchbase/couchbase-win32-x64-napi@npm:4.3.0": + version: 4.3.0 + resolution: "@couchbase/couchbase-win32-x64-napi@npm:4.3.0" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -8999,6 +8999,7 @@ __metadata: closevector-web: 0.1.6 cohere-ai: ">=6.0.0" convex: ^1.3.1 + couchbase: ^4.3.0 discord.js: ^14.14.1 dotenv: ^16.0.3 dpdm: ^3.12.0 @@ -9105,6 +9106,7 @@ __metadata: closevector-web: 0.1.6 cohere-ai: "*" convex: ^1.3.1 + couchbase: ^4.3.0 discord.js: ^14.14.1 dria: ^0.0.3 faiss-node: ^0.5.1 @@ -9244,6 +9246,8 @@ __metadata: optional: true convex: optional: true + couchbase: + optional: true discord.js: optional: true dria: @@ -18775,16 +18779,16 @@ __metadata: languageName: node linkType: hard -"couchbase@npm:^4.2.10": - version: 4.2.10 - resolution: "couchbase@npm:4.2.10" - dependencies: - "@couchbase/couchbase-darwin-arm64-napi": 4.2.10 - "@couchbase/couchbase-darwin-x64-napi": 4.2.10 - "@couchbase/couchbase-linux-arm64-napi": 4.2.10 - "@couchbase/couchbase-linux-x64-napi": 4.2.10 - "@couchbase/couchbase-linuxmusl-x64-napi": 4.2.10 - "@couchbase/couchbase-win32-x64-napi": 4.2.10 +"couchbase@npm:^4.3.0": + version: 4.3.0 + resolution: "couchbase@npm:4.3.0" + dependencies: + "@couchbase/couchbase-darwin-arm64-napi": 4.3.0 + "@couchbase/couchbase-darwin-x64-napi": 4.3.0 + "@couchbase/couchbase-linux-arm64-napi": 4.3.0 + "@couchbase/couchbase-linux-x64-napi": 4.3.0 + "@couchbase/couchbase-linuxmusl-x64-napi": 4.3.0 + "@couchbase/couchbase-win32-x64-napi": 4.3.0 cmake-js: ^7.2.1 node-addon-api: ^7.0.0 dependenciesMeta: @@ -18800,7 +18804,7 @@ __metadata: optional: true "@couchbase/couchbase-win32-x64-napi": optional: true - checksum: 1cc4725c5f16c3173691a9e4f702e479df545473deac694f7a8627f58a63a92718824d018730b51a7d4d6a0a8e125b0ef5f3f81cf995a831b8a3adfa05e9ecc7 + checksum: 99c24663cd7ab524668f26f151f92a78ecf850ddeca919b71f26eebe1134c492218639f9f491ff95e749628bd108164f78f13884e85a1de27959152247f8ecab languageName: node linkType: hard @@ -21431,6 +21435,7 @@ __metadata: axios: ^0.26.0 chromadb: ^1.5.3 convex: ^1.3.1 + couchbase: ^4.3.0 date-fns: ^3.3.1 dotenv: ^16.0.3 eslint: ^8.33.0 @@ -26127,7 +26132,7 @@ __metadata: cheerio: ^1.0.0-rc.12 chromadb: ^1.5.3 convex: ^1.3.1 - couchbase: ^4.2.10 + couchbase: ^4.3.0 d3-dsv: ^2.0.0 dotenv: ^16.0.3 dpdm: ^3.12.0 @@ -26209,7 +26214,7 @@ __metadata: cheerio: ^1.0.0-rc.12 chromadb: "*" convex: ^1.3.1 - couchbase: ^4.2.10 + couchbase: ^4.3.0 d3-dsv: ^2.0.0 epub2: ^3.0.1 fast-xml-parser: "*"