You are viewing the documentation for a prerelease version.

Working with Vector Search

      +
      Use Vector Search with Full Text Search and Query.

      To configure a project to use vector search, follow the installation instructions to add the Vector Search extension.

      You must install Couchbase Lite to use the Vector Search extension.

      Create a Vector Index

      This method shows how you can create a vector index using the Couchbase Lite Vector Search extension.

      • Java

      • Kotlin

        // create the configuration for a vector index named "vector"
        // with 300 dimensions and 20 centroids
        VectorIndexConfiguration config = new VectorIndexConfiguration("vector", 300L, 20L);

      First, initialize the config variable with the VectorIndexConfiguration() method with the following parameters:

      • The width or dimensions of the vector index is set to 300.

      • The amount of centroids is set to 20. This means that there will be 20 buckets with a single centroid each that gathers together similar vectors.

      There are also some default values that we did not set with the VectorIndexConfiguration() method, these are:

      • The encoding type - 8 bit Scalar Quantization.

      • The distance metric - Squared Euclidean.

      • The minimum and maximum number of vectors for training the index.

      You can also alter some optional config settings such as encoding.

        // create the configuration for a vector index named "vector"
        // with 300 dimensions, 20 centroids, max training size 200, min training size 100
        // no vector encoding and using COSINE distance measurement
        VectorIndexConfiguration config = new VectorIndexConfiguration("vector", 300L, 20L)
            .setEncoding(VectorEncoding.none())
            .setMetric(VectorIndexConfiguration.DistanceMetric.COSINE)
            .setMinTrainingSize(100L)
            .setMaxTrainingSize(200L);
        // create the configuration for a vector index named "vector"
        // with 300 dimensions and 20 centroids
        val config = VectorIndexConfiguration("vector", 300L, 20L)

      First, initialize the config variable with the VectorIndexConfiguration() method with the following parameters:

      • The width or dimensions of the vector index is set to 300.

      • The amount of centroids is set to 20. This means that there will be 20 buckets with a single centroid each that gathers together similar vectors.

      There are also some default values that we did not set with the VectorIndexConfiguration() method, these are:

      • The encoding type - 8 bit Scalar Quantization.

      • The distance metric - Squared Euclidean.

      • The minimum and maximum number of vectors for training the index.

      You can also alter some optional config settings such as encoding.

        // create the configuration for a vector index named "vector"
        // with 300 dimensions, 20 centroids, max training size 200, min training size 100
        // no vector encoding and using COSINE distance measurement
        val config = VectorIndexConfiguration("vector", 300L, 20L)
            .setEncoding(VectorEncoding.none())
            .setMetric(VectorIndexConfiguration.DistanceMetric.COSINE)
            .setMinTrainingSize(100L)
            .setMaxTrainingSize(200L)
      The number of vectors, the width or dimensions of the vectors and the training size can incur high CPU and memory costs as the size of each variable increases. This is because the training vectors have to be resident on the machine.

      Vector Index Configuration

      The table below displays the different configurations you can modify within your VectorIndexConfiguration() function. For more information on specific configurations, see Vector Search.

      The beta release currently only supports the format of vectors as an array of numbers.
      Table 1. Vector Index Configuration Options
      Configuration Name Is Required Default Configuration Further Information

      Expression

      Required

      No default

      A SQL++ expression indicating where to get the vectors. A document property for embedded vectors prediction() to call a registered Predictive model.

      Number of Dimensions

      Required

      No default

      2-2048

      Number of Centroids

      Required

      No default

      1-64000. The general guideline is an approximate square root of the number of documents

      Distance Metric

      Optional

      Euclidean Distance

      You can also configure Cosine Distance as your Distance Metric

      Encoding

      Optional

      Scalar Quantizer(SQ) or SQ-8 bits

      There are three possible configurations:

      • None No compression, No data loss

      • Scalar Quantizer (SQ) or SQ-8 bits (Default) Reduces the number of bits per dimension

      • Product Quantizer (PQ) Reduces the number of dimensions and bits per dimension

      Training Size

      Optional

      There are defaults for both the minimum and maximum training size for training the vectors

      • The minimum training size is set to 25x the number of Centroids

      • The maximum training size is set to 256x the number of Centroids

      Altering the default training sizes could be detrimental to the accuracy of returned results produced by the model and total computation time.

      Generating Vectors

      You can use two methods to generate vectors in Couchbase Lite:

      1. You can call a Machine Learning(ML) model, and embed the generated vectors inside the documents.

      2. You can use the prediction() function to generate vectors to be indexed for each document at the indexing time.

      Below are example configurations of the previously mentioned methods.

      Create a Vector Index with Embeddings

      This method shows you how to create a Vector Index with embeddings.

      • Java

      • Kotlin

        // create a vector index named "words_index"
        // in the collection "_default.words"
        db.getCollection("words").createIndex("word_index", new VectorIndexConfiguration("vector", 300L, 20L));

      The snippet demonstrates the creation of the vector index, word_index, in the collection _default.words, demonstrating how you can create a vector index with a word embedding for use in queries related to written language.

        // create a vector index named "words_index"
        // in the collection "_default.words"
        db.getCollection("words")!!.createIndex("word_index", VectorIndexConfiguration("vector", 300L, 20L))

      The snippet demonstrates the creation of the vector index, word_index, in the collection _default.words, demonstrating how you can create a vector index with a word embedding for use in queries related to written language.

      Create Vector Index Embeddings from a Predictive Model

      This method generates vectors to be indexed for each document at the index time by using the prediction() function.

      • Java

      • Kotlin

        // create a vector index with a simple predictive model
        final Collection collection = db.getCollection("words");
        Database.prediction.registerModel(
            "WordEmbedding",
            input -> {
                String word = input.getString("word");
                if (Utils.isEmpty(word)) {
                    Logger.log("Input word is empty");
                    return null;
                }
                try (ResultSet rs = db.createQuery(
                        "SELECT vector"
                            + " FROM " + collection.getFullName()
                            + " WHERE word = '" + word + " '")
                    .execute()) {
                    List<Result> results = rs.allResults();
                    if (results.isEmpty()) { return null; }
      
                    Array result = results.get(0).getArray(0);
                    if (result != null) {
                        MutableDictionary dict = new MutableDictionary();
                        dict.setValue("vector", result.toList());
                        return dict;
                   }
      
                    Logger.log("Unexpected result: " + result);
                }
                catch (CouchbaseLiteException e) {
                    Logger.log("Prediction query failed", e);
                }
                return null;
            });
      
        collection.createIndex(
            "words_pred_index",
            new VectorIndexConfiguration("prediction(WordEmbedding, {'word': word}).vector", 300L, 8L));
                // create a vector index with a simple predictive model
                val collection = db.getCollection("words")!!
                Database.prediction.registerModel("WordEmbedding") {
                    val word: String? = it.getString("word")
                    if (TextUtils.isEmpty(word)) {
                        Logger.log("Input word is empty")
                        return@registerModel null
                    }
                    try {
                        db.createQuery(
                            "SELECT vector"
                                    + " FROM ${collection}"
                                    + " WHERE word = '${word}'"
                        ).execute().use { rs ->
                            val results = rs.allResults()
                            if (results.isEmpty()) {
                                return@registerModel null
                            }
      
                            results[0].getArray(0)?.let { result ->
                                val dict = MutableDictionary()
                                dict.setValue("vector", result.toList())
                                return@registerModel dict
                            }
      
                            Logger.log("Prediction result is not an array")
                        }
                    } catch (e: CouchbaseLiteException) {
                        Logger.log("Prediction query failed", e)
                    }
                    return@registerModel null
                }
      
                collection.createIndex(
                    "words_pred_index",
                    VectorIndexConfiguration("prediction(WordEmbedding, {'word': word}).vector", 300L, 8L)
                )
                // end::vs-create-predictive- index[]
            }
      
            @Throws(CouchbaseLiteException::class)
            fun useVectorMatch(db: Database, hugeListOfFloats: List<Any?>?) {
                // use the vector_match function in a query
                db.getCollection("words")!!.createIndex("word_index", VectorIndexConfiguration("vector", 300L, 8L))
                val query = db.createQuery(
                    "SELECT meta().id, word"
                            + " FROM _default.words"
                            + " WHERE vector_match(words_index, \$vectorParam, 20)"
                )
                val params = Parameters()
                params.setArray("vectorParam", MutableArray((hugeListOfFloats)!!))
                query.parameters = params
                query.execute().use { rs ->
                    // process results
                }
            }
      
      
            @Throws(CouchbaseLiteException::class)
            fun useVectorDistance(db: Database, hugeListOfFloats: List<Any?>?) {
                // use the vector_distance function in a query
                db.getCollection("words")!!.createIndex("word_index", VectorIndexConfiguration("vector", 300L, 8L))
                val query = db.createQuery(
                    ("SELECT meta().id, word,vector_distance(words_index)"
                            + " FROM _default.words"
                            + " WHERE vector_match(words_index, \$dinner, 20)")
                )
                val params = Parameters()
                params.setArray("vectorParam", MutableArray((hugeListOfFloats)!!))
                query.parameters = params
                query.execute().use { rs ->
                    // process results
                }
            }
      
        }
      You can use less storage by using the prediction() function as the encoded vectors will only be stored in the index. However, the index time will be longer as vector embedding generation is occurring at run time.

      Vector SQL++ Functions

      Couchbase Lite currently supports two SQL++ functions, vector_match() and vector_distance().

      vector_match(vectorIndexIdentifier, targetVectorExpr, [limit = 3])

      Parameter Is Required Description

      vectorIndexIdentifier

      Required

      The name of the vector index to perform the vector search on.

      targetVectorExpr

      Required

      The target vector expression that returns a vector in the form of an array of numbers.

      limit

      Optional

      The limit number of the returned matched results. The maximum number allowed is 10000. An error will be returned when creating a query with a limit greater than 10000.

      The default value for the limit parameter is 3.

      Use vector_match()

      • Java

      • Kotlin

        // use the vector_match function in a query
        db.getCollection("words").createIndex("word_index", new VectorIndexConfiguration("vector", 300L, 8L));
      
        Query query = db.createQuery(
            "SELECT meta().id, word"
                + " FROM _default.words"
                + " WHERE vector_match(words_index, $vectorParam, 20)");
        Parameters params = new Parameters();
        params.setArray("vectorParam", new MutableArray(hugeListOfFloats));
        query.setParameters(params);
      
        try (ResultSet rs = query.execute()) {
            // process results
        }
        // use the vector_match function in a query
        db.getCollection("words")!!.createIndex("word_index", VectorIndexConfiguration("vector", 300L, 8L))
        val query = db.createQuery(
            "SELECT meta().id, word"
                    + " FROM _default.words"
                    + " WHERE vector_match(words_index, \$vectorParam, 20)"
        )
        val params = Parameters()
        params.setArray("vectorParam", MutableArray((hugeListOfFloats)!!))
        query.parameters = params
        query.execute().use { rs ->
            // process results
        }

      This function performs vector search against a specific vector index identifier for the specified vector expression. If the specified index does not exist, an error will occur on creation of the query. The matched vectors will be returned up to the specified limit number, if the limit is not specified then the default value will be used. The returned vectors are sorted by their distance values in ascending order by default.

      Similar to the Full Text Search match() function, vector_match() can only be called alone or at the top level AND expression.

      vector_distance(vectorIndexIdentifier)

      Parameter Is Required Description

      vectorIndexIdentifier

      Required

      The name of the vector index.

      Use vector_distance()

      • Java

      • Kotlin

        // use the vector_distance function in a query
        db.getCollection("words").createIndex("word_index", new VectorIndexConfiguration("vector", 300L, 8L));
      
        Query query = db.createQuery(
            "SELECT meta().id, word,vector_distance(words_index)"
                + " FROM _default.words"
                + " WHERE vector_match(words_index, $dinner, 20)");
        Parameters params = new Parameters();
        params.setArray("vectorParam", new MutableArray(hugeListOfFloats));
        query.setParameters(params);
      
        try (ResultSet rs = query.execute()) {
            // process results
        }
        // use the vector_distance function in a query
        db.getCollection("words")!!.createIndex("word_index", VectorIndexConfiguration("vector", 300L, 8L))
        val query = db.createQuery(
            ("SELECT meta().id, word,vector_distance(words_index)"
                    + " FROM _default.words"
                    + " WHERE vector_match(words_index, \$dinner, 20)")
        )
        val params = Parameters()
        params.setArray("vectorParam", MutableArray((hugeListOfFloats)!!))
        query.parameters = params
        query.execute().use { rs ->
            // process results
        }

      This function returns the distance between the target vector specified in the vector_match() function and the matched vector in the specified vector index based on the distance metric set in the index configuration.