diff --git a/docs/core_docs/docs/integrations/document_loaders/web_loaders/couchbase.mdx b/docs/core_docs/docs/integrations/document_loaders/web_loaders/couchbase.mdx index 950a01fa354f..901a81de4b56 100644 --- a/docs/core_docs/docs/integrations/document_loaders/web_loaders/couchbase.mdx +++ b/docs/core_docs/docs/integrations/document_loaders/web_loaders/couchbase.mdx @@ -77,7 +77,6 @@ for await (const doc of this.lazyLoad()) { ``` ### Specifying Fields with Content and Metadata - The fields that are part of the Document content can be specified using the `pageContentFields` parameter. The metadata fields for the Document can be specified using the `metadataFields` parameter. 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..588169f00195 --- /dev/null +++ b/docs/core_docs/docs/integrations/vectorstores/couchbase.mdx @@ -0,0 +1,347 @@ +--- +hide_table_of_contents: true +sidebar_class_name: node-only +--- + +import CodeBlock from "@theme/CodeBlock"; + +# Couchbase + +:::tip Compatibility +Only available on Node.js. +::: + +[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 Service](https://docs.couchbase.com/server/current/learn/services-and-indexes/services/search-service.html)(FTS) 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"; // valid couchbase connection string +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 Vector Index + +Currently, the vector index needs to be created from the Couchbase Capella or Server UI or using the REST interface. + +Let us define a vector index with the name `vector-index` on the testing bucket + +For this example, let us use the Import Index feature on the Full Text Search on the UI. +We are defining an index on the `testing` bucket's `_default` scope on the `_default` collection with the vector field set to `embedding` and text field set to `text`. +We are also indexing and storing all the fields under `metadata` in the document dynamically. The similarity metric is set to `dot_product`. + +How to Import an Index to the Full Text Search service? + +- Couchbase Server: Click on Search -> Add Index -> Import + - Copy the following Index definition in the Import screen +- [Couchbase Capella](https://docs.couchbase.com/cloud/search/import-search-index.html) + - Copy the following index definition to a new file `index.json` and import that file in Capella using the instructions in the documentation. + +### 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 an FTS 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. + +```typescript +const couchbaseConfig: CouchbaseVectorStoreArgs = { + cluster: couchbaseClient, + bucketName: "testing", + scopeName: "_default", + collectionName: "_default", + indexName: "vector-index", + textKey: "text", + embeddingKey: "embedding", +}; +``` + +## Similarity Search + +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 RecursiveCharacterTextSplitter, create langchain documents from the chunks and send to couchbase vector store. +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". +This example at the end, 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, "Jae" is close (fuzziness of 1) to "Jane". + +```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: "2022-12-31", + end: "2023-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 `search_options` 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 FTS index before creating the CouchbaseVectorStore object? + +Yes, currently you need to create the FTS 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 FTS index. Please ensure that the field that you are trying to access in the search results is part of the index. + +One way to handle this is to store a document's fields dynamically in the index. To do that, you need to select `Store Dynamic Fields` in the Advanced Settings of the FTS index. + +Similarly, if you want to search on dynamic fields, you must index those fields by selecting the option `Index Dynamic Fields` in the FTS index settings. + +Note that these options will increase the size of the index. + +## 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 by the Couchbase FTS index. In order to index the `metadata` field in the document, you need to add it to the index as a mapping. + +If you select to map all the fields in the mapping, you will be able to search by all metadata fields. Alternatively, you can select the specific fields inside `metadata` object to be indexed. You can refer to the docs to learn more about indexing child mappings. + +- [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..9aa874c52a3f 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.2.11", "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..1e6596582a0c --- /dev/null +++ b/examples/src/indexes/vector_stores/couchbase/.env.example @@ -0,0 +1,13 @@ +# Couchbase connection params +DB_CONN_STR= +DB_USERNAME= +DB_PASSWORD= + +# Couchbase vector store args +DB_BUCKET_NAME= +DB_SCOPE_NAME= +DB_COLLECTION_NAME= +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..ca34556d9733 --- /dev/null +++ b/examples/src/indexes/vector_stores/couchbase/similaritySearch.ts @@ -0,0 +1,60 @@ +import { OpenAIEmbeddings } from "@langchain/openai"; +import { + CouchbaseVectorStoreArgs, + CouchbaseVectorStore, +} from "@langchain/community/vectorstores/couchbase"; +import { Cluster } from "couchbase"; +import { readFileSync } from "fs"; +import { RecursiveCharacterTextSplitter } from "langchain/text_splitter"; + +const connectionString = process.env.DB_CONN_STR ?? "couchbase://localhost"; +const databaseUsername = process.env.DB_USERNAME ?? "Administrator"; +const databasePassword = process.env.DB_PASSWORD ?? "Password"; + +const text = readFileSync("state_of_the_union.txt", "utf8"); +const docs = await new RecursiveCharacterTextSplitter().createDocuments([text]); + +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 4bd712b0a6e0..5e40bf1b9323 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.2.11", "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.2.11", "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 27ea0134a2e8..ca303c5592af 100644 --- a/libs/langchain-community/.gitignore +++ b/libs/langchain-community/.gitignore @@ -302,6 +302,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 bacd744e9db8..21c70401224b 100644 --- a/libs/langchain-community/langchain.config.js +++ b/libs/langchain-community/langchain.config.js @@ -106,6 +106,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", @@ -268,6 +269,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 d32b4aebc305..d392843a20b1 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.2.11", "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.2.11", "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 }, @@ -1203,6 +1208,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", @@ -2438,6 +2452,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..c893b5bfbabc --- /dev/null +++ b/libs/langchain-community/src/vectorstores/couchbase.ts @@ -0,0 +1,606 @@ +/* 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. If not provided, defaults to undefined. + * @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 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 = "text"; + + private embeddingKey: string; + + 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.textKey}_embedding`; + } + + 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..76f7e8ad266e --- /dev/null +++ b/libs/langchain-community/src/vectorstores/tests/couchbase.int.test.ts @@ -0,0 +1,240 @@ +/* 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.DB_CONN_STR ?? "couchbase://localhost"; + const databaseUsername = process.env.DB_USERNAME ?? "Administrator"; + const databasePassword = process.env.DB_PASSWORD ?? "Password"; + const bucketName = process.env.DB_BUCKET_NAME ?? "testing"; + const scopeName = process.env.DB_SCOPE_NAME ?? "_default"; + const collectionName = process.env.DB_COLLECTION_NAME ?? "_default"; + const indexName = process.env.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 e71c060f8991..c48c7461b2d9 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.2.11": + version: 4.2.11 + resolution: "@couchbase/couchbase-darwin-arm64-napi@npm:4.2.11" 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.2.11": + version: 4.2.11 + resolution: "@couchbase/couchbase-darwin-x64-napi@npm:4.2.11" 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.2.11": + version: 4.2.11 + resolution: "@couchbase/couchbase-linux-arm64-napi@npm:4.2.11" 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.2.11": + version: 4.2.11 + resolution: "@couchbase/couchbase-linux-x64-napi@npm:4.2.11" 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.2.11": + version: 4.2.11 + resolution: "@couchbase/couchbase-linuxmusl-x64-napi@npm:4.2.11" 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.2.11": + version: 4.2.11 + resolution: "@couchbase/couchbase-win32-x64-napi@npm:4.2.11" 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.2.11 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.2.11 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: @@ -18770,16 +18774,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.2.11": + version: 4.2.11 + resolution: "couchbase@npm:4.2.11" + dependencies: + "@couchbase/couchbase-darwin-arm64-napi": 4.2.11 + "@couchbase/couchbase-darwin-x64-napi": 4.2.11 + "@couchbase/couchbase-linux-arm64-napi": 4.2.11 + "@couchbase/couchbase-linux-x64-napi": 4.2.11 + "@couchbase/couchbase-linuxmusl-x64-napi": 4.2.11 + "@couchbase/couchbase-win32-x64-napi": 4.2.11 cmake-js: ^7.2.1 node-addon-api: ^7.0.0 dependenciesMeta: @@ -18795,7 +18799,7 @@ __metadata: optional: true "@couchbase/couchbase-win32-x64-napi": optional: true - checksum: 1cc4725c5f16c3173691a9e4f702e479df545473deac694f7a8627f58a63a92718824d018730b51a7d4d6a0a8e125b0ef5f3f81cf995a831b8a3adfa05e9ecc7 + checksum: f1987b8cbe1763d2f30fcd5a51ab5fb1c6c3828ab3e7fa744bc4911e091f3aac89c918960f4664d539e57ff4b2b30d1a7fe5a69c44dc02f60062794c5d45c6de languageName: node linkType: hard @@ -21426,6 +21430,7 @@ __metadata: axios: ^0.26.0 chromadb: ^1.5.3 convex: ^1.3.1 + couchbase: ^4.2.11 date-fns: ^3.3.1 dotenv: ^16.0.3 eslint: ^8.33.0 @@ -26122,7 +26127,7 @@ __metadata: cheerio: ^1.0.0-rc.12 chromadb: ^1.5.3 convex: ^1.3.1 - couchbase: ^4.2.10 + couchbase: ^4.2.11 d3-dsv: ^2.0.0 dotenv: ^16.0.3 dpdm: ^3.12.0 @@ -26204,7 +26209,7 @@ __metadata: cheerio: ^1.0.0-rc.12 chromadb: "*" convex: ^1.3.1 - couchbase: ^4.2.10 + couchbase: ^4.2.11 d3-dsv: ^2.0.0 epub2: ^3.0.1 fast-xml-parser: "*"