Vector Search using a Dynamic Query



  • Vector search is a method for finding documents based on their contextual similarity to the search item provided in a given query.

  • Your data is converted into vectors, known as embeddings, and stored in a multidimensional space.
    Unlike traditional keyword-based searches, which rely on exact matches, vector search identifies vectors closest to your query vector and retrieves the corresponding documents.

Dynamic vector search query - Overview

Overview


  • A dynamic vector search query can be performed on:

    • Raw text stored in your documents.
    • Pre-made embeddings that you created yourself and stored using these Data types.
    • Pre-made embeddings that are automatically generated from your document content
      by RavenDB's Embeddings generation tasks using external service providers.
  • Note: Vector search queries cannot be used with Subscription queries.

  • When executing a dynamic vector search query, RavenDB creates a Corax Auto-Index to process the query,
    and the results are retrieved from that index.

  • To make a dynamic vector search query:

    • From the Client API - use method VectorSearch()
    • In RQL - use method vector.search()
    • Examples are provided below

Creating embeddings for the Auto-index


  • Creating embeddings from TEXTUAL content:

    • Pre-made embeddings via tasks:
      Embeddings can be created from textual content in your documents by defining Tasks that generate embeddings.
      When performing a dynamic vector search query over textual data and explicitly specifying the task, results will be retrieved by comparing your search term against the embeddings previously generated by that task.
      A query example is available in: Querying pre-made embeddings generated by tasks.

    • Default embeddings generation:
      When querying textual data without specifying a task, RavenDB generates an embedding vector for the specified document field in each document of the queried collection, using the built-in bge-micro-v2 sentence-transformer model. A query example is available in: Querying raw text.

  • Creating embeddings from NUMERICAL arrays:
    When querying over pre-made numerical arrays that are already in vector format,
    RavenDB will index them without transformation (unless further quantization is applied).
    A query example is available in: Vector search on numerical content.

    To avoid index errors, ensure that the dimensionality of these numerical arrays (i.e., their length)
    is consistent across all your source documents for the field you are querying.
    If you wish to enforce such consistency -
    perform a vector search using a Static-index instead of a dynamic query.

  • Quantizing the embeddings:
    The embeddings are quantized based on the parameters specified in the query.
    Learn more about quantization in Quantization options.

  • Indexing the embeddings:
    RavenDB indexes the embeddings on the server using the HNSW algorithm.
    This algorithm organizes embeddings into a high-dimensional graph structure,
    enabling efficient retrieval of Approximate Nearest Neighbors (ANN) during queries.

Retrieving results


  • Processing the query:
    To ensure consistent comparisons, the search term is transformed into an embedding vector using the same method as the document fields. The server will search for the most similar vectors in the indexed vector space, taking into account all the query parameters described below.
    The documents that correspond to the resulting vectors are then returned to the client.

  • Search results:
    By default, the resulting documents will be ordered by their score. You can modify this behavior using the Indexing.Corax.VectorSearch.OrderByScoreAutomatically configuration key.
    In addition, you can apply any of the 'order by' methods to your query, as explained in sort query results.

The dynamic query parameters


  • Source data format
    RavenDB supports performing vector search on TEXTUAL values or NUMERICAL arrays.
    the source data can be formatted as Text, Single, Int8, or Binary.

  • Target quantization
    You can specify the quantization encoding for the embeddings that will be created from source data.
    Learn more about quantization in Quantization options.

  • Minimum similarity
    You can specify the minimum similarity to use when searching for related vectors.
    The value can be between 0.0f and 1.0f.

    • A value closer to 1.0f requires higher similarity between vectors,
      while a value closer to 0.0f allows for less similarity.
    • Important: To filter out less relevant results when performing vector search queries,
      it is recommended to explicitly specify the minimum similarity level at query time.

    If not specified, the default value is taken from the following configuration key: Indexing.Corax.VectorSearch.DefaultMinimumSimilarity.

  • Number of candidates
    You can specify the maximum number of vectors that RavenDB will return from a graph search.
    The number of the resulting documents that correspond to these vectors may be:

    • lower than the number of candidates - when multiple vectors originated from the same document.
    • higher than the number of candidates - when the same vector is shared between multiple documents.

    If not specified, the default value is taken from the following configuration key: Indexing.Corax.VectorSearch.DefaultNumberOfCandidatesForQuerying.

  • Search method

    • Approximate Nearest-Neighbor search (Default):
      Search for related vectors in an approximate manner, providing faster results.
    • Exact search:
      Perform a thorough scan of the vectors to find the actual closest vectors,
      offering better accuracy but at a higher computational cost.
      Learn more in Exact search.

Corax auto-indexes


  • Only Corax indexes support vector search.

  • Even if your default auto-index engine is set to Lucene (via Indexing.Auto.SearchEngineType),
    performing a vector search using a dynamic query will create a new auto-index based on Corax.

  • Normally, new dynamic queries extend existing auto-indexes if they require additional fields.
    However, a dynamic query with a vector search will not extend an existing Lucene-based auto-index.

    For example, suppose you have an existing Lucene-based auto-index on the Employees collection: e.g.:
    Auto/Employees/ByFirstName.

    Now, you run a query that:

    • searches for Employees by LastName (a regular text search)
    • and performs a vector search over the Notes field.

    The following new Corax-based auto-index will be created:
    Auto/Employees/ByLastNameAndVector.search(embedding.text(Notes)),
    and the existing Lucene index on Employees will not be deleted or extended.

Vector search on TEXT

Querying raw text

  • The following example searches for Product documents where the 'Name' field is similar to the search term "italian food".

  • Since the query does Not specify an Embeddings generation task, RavenDB dynamically generates embedding vectors for the 'Name' field of each document in the queried collection using the built-in
    bge-micro-v2 text-embedding model. The generated embeddings are indexed within the auto-index.
    Unlike embeddings pre-made by tasks, this process does not create dedicated collections for storing embeddings.

  • Since this query does not specify a target quantization format, the generated embedding vectors will be encoded in the default Single format (single-precision floating-point).
    Refer to Quantization options for examples that specify the destination quantization.

    var similarProducts = session.Query<Product>()
         // Perform a vector search
         // Call the 'VectorSearch' method
        .VectorSearch(
            // Call 'WithText'
            // Specify the document field in which to search for similar values
            field => field.WithText(x => x.Name),
            // Call 'ByText' 
            // Provide the term for the similarity comparison
            searchTerm => searchTerm.ByText("italian food"),
            // It is recommended to specify the minimum similarity level
            0.82f,
            // Optionally, specify the number of candidates for the search
            20)
         // Waiting for not-stale results is not mandatory
         // but will assure results are not stale
        .Customize(x => x.WaitForNonStaleResults())
        .ToList();
    var similarProducts = await asyncSession.Query<Product>()
        .VectorSearch(
            field => field.WithText(x => x.Name), 
            searchTerm => searchTerm.ByText("italian food"),
            0.82f,
            20)
        .Customize(x => x.WaitForNonStaleResults())
        .ToListAsync();
    var similarProducts = session.Advanced
        .DocumentQuery<Product>()
        .VectorSearch(
            field => field.WithText(x => x.Name),
            searchTerm => searchTerm.ByText("italian food"),
            0.82f,
            20)
        .WaitForNonStaleResults()
        .ToList();
    var similarProducts = await asyncSession.Advanced
        .AsyncDocumentQuery<Product>()
        .VectorSearch(
            field => field.WithText(x => x.Name),
            searchTerm => searchTerm.ByText("italian food"),
            0.82f,
            20)
        .WaitForNonStaleResults()
        .ToListAsync();
    var similarProducts = session.Advanced
        .RawQuery<Product>(@"
            from 'Products'
            // Wrap the document field 'Name' with 'embedding.text' to indicate the source data type
            where vector.search(embedding.text(Name), $searchTerm, 0.82, 20)")
        .AddParameter("searchTerm", "italian food")
        .WaitForNonStaleResults()
        .ToList();
    var similarProducts = await asyncSession.Advanced
        .AsyncRawQuery<Product>(@"
            from 'Products'
            // Wrap the document field 'Name' with 'embedding.text' to indicate the source data type
            where vector.search(embedding.text(Name), $searchTerm, 0.82, 20)")
        .AddParameter("searchTerm", "italian food")
        .WaitForNonStaleResults()
        .ToListAsync();
    // Query the Products collection
    from "Products"
    // Call 'vector.search'
    // Wrap the document field 'Name' with 'embedding.text' to indicate the source data type
    where vector.search(embedding.text(Name), "italian food", 0.82, 20)
  • Executing the above query on the RavenDB sample data will create the following auto-index:
    Auto/Products/ByVector.search(embedding.text(Name))

    Search for italian food 1

    Products with a name similar to 'italian food' - high similarity

  • Running the same query at a lower similarity level will return more results related to "Italian food" but they may be less similar:

    Search for italian food 2

    Products with a name similar to 'italian food' - with lower similarity


Querying pre-made embeddings generated by tasks

  • The following example searches for Category documents where the 'Name' field is similar to the search term "candy".

  • The query explicitly specifies the identifier of the embeddings generation task that was defined in
    this example. Results are retrieved by comparing the search term against the pre-made embeddings generated by this task, which are stored in the Embedding collections.

  • To ensure consistent comparisons, the search term is transformed into an embedding using the same embeddings generation task.

var similarCategories = session.Query<Category>()
    .VectorSearch(
        field => field
             // Call 'WithText'
             // Specify the document field in which to search for similar values
            .WithText(x => x.Name)
             // Call 'UsingTask'
             // Specify the identifier of the task that generated
             // the embeddings for the Name field
            .UsingTask("id-for-task-open-ai"),
        // Call 'ByText' 
        // Provide the search term for the similarity comparison
        searchTerm => searchTerm.ByText("candy"),
        // It is recommended to specify the minimum similarity level
        0.75f)
    .Customize(x => x.WaitForNonStaleResults())
    .ToList();
var similarCategories = await asyncSession.Query<Category>()
    .VectorSearch(
        field => field
            .WithText(x => x.Name)
            .UsingTask("id-for-task-open-ai"),
        searchTerm => searchTerm.ByText("candy"),
        0.75f)
    .Customize(x => x.WaitForNonStaleResults())
    .ToListAsync();
var similarCategories = session.Advanced
    .DocumentQuery<Category>()
    .VectorSearch(
        field => field
            .WithText(x => x.Name)
            .UsingTask("id-for-task-open-ai"),
        searchTerm => searchTerm.ByText("candy"),
        0.75f)
    .WaitForNonStaleResults()
    .ToList();
var similarCategories = await asyncSession.Advanced
    .AsyncDocumentQuery<Category>()
    .VectorSearch(
        field => field
            .WithText(x => x.Name)
            .UsingTask("id-for-task-open-ai"),
        searchTerm => searchTerm.ByText("candy"),
        0.75f)
    .WaitForNonStaleResults()
    .ToListAsync();
var similarCategories = session.Advanced
    .RawQuery<Category>(@"
        from 'Categories'
        // Specify the identifier of the task that generated the embeddings inside 'ai.task'
        where vector.search(embedding.text(Name, ai.task('id-for-task-open-ai')), $searchTerm, 0.75)")
    .AddParameter("searchTerm", "candy")
    .WaitForNonStaleResults()
    .ToList();
var similarCategories = await asyncSession.Advanced
    .AsyncRawQuery<Category>(@"
        from 'Categories'
        // Specify the identifier of the task that generated the embeddings inside 'ai.task'
        where vector.search(embedding.text(Name, ai.task('id-for-task-open-ai')), $searchTerm, 0.75)")
    .AddParameter("searchTerm", "candy")
    .WaitForNonStaleResults()
    .ToListAsync();
// Query the Categories collection
from "Categories"
// Call 'vector.search'
// Specify the identifier of the task that generated the embeddings inside the 'ai.task' method
where vector.search(embedding.text(Name, ai.task('id-for-task-open-ai')), $searchTerm, 0.75)
{"searchTerm": "candy"}
  • Executing the above query on the RavenDB sample data will create the following auto-index:
    Auto/Categories/ByVector.search(embedding.text(Name|ai.task('id-for-task-open-ai')))

Vector search on NUMERICAL content

  • The following examples will use the sample data shown below.
    The Movie class includes various formats of numerical vector data.
    Note: This sample data is minimal to keep the examples simple.

  • Note the usage of RavenDB's dedicated data type, RavenVector, which is highly optimized for reading and writing arrays to disk. Learn more about the source data types suitable for vector search in Data types for vector search.

  • Unlike vector searches on text, where RavenDB transforms the raw text into an embedding vector,
    numerical vector searches require your source data to already be in an embedding vector format.

  • If your raw data is in a float format, you can request further quantization of the embeddings that will be indexed in the auto-index. See an example of this in: Quantization options.

  • Raw data that is already formatted as Int8 or Binary cannot be quantized to lower-form (e.g. Int8 -> Int1).
    When storing data in these formats in your documents, you should use RavenDB’s vectorQuantizer methods.


Sample data:

// Sample class representing a document with various formats of numerical vectors
public class Movie
{
    public string Id { get; set; }
    public string Title { get; set; }
    
    // This field will hold numerical vector data - Not quantized
    public RavenVector<float> TagsEmbeddedAsSingle { get; set; }
    
    // This field will hold numerical vector data - Quantized to Int8
    public sbyte[][] TagsEmbeddedAsInt8 { get; set; }
    
    // This field will hold numerical vector data - Encoded in Base64 format
    public List<string> TagsEmbeddedAsBase64 { get; set; }
}
using (var session = store.OpenSession())
{
    var movie1 = new Movie()
    {
        Title = "Hidden Figures",
        Id = "movies/1",
    
        // Embedded vector represented as float values
        TagsEmbeddedAsSingle = new RavenVector<float>(new float[]
        {
            6.599999904632568f, 7.699999809265137f
        }),
        
        // Embedded vectors encoded in Base64 format
        TagsEmbeddedAsBase64 = new List<string>()
        {
            "zczMPc3MTD6amZk+", "mpmZPs3MzD4AAAA/"
        },
        
        // Array of embedded vectors quantized to Int8
        TagsEmbeddedAsInt8 = new sbyte[][]
        {
            // Use RavenDB's quantization methods to convert float vectors to Int8
            VectorQuantizer.ToInt8(new float[] { 0.1f, 0.2f }),
            VectorQuantizer.ToInt8(new float[] { 0.3f, 0.4f })
        },
    };

    var movie2 = new Movie()
    {
        Title = "The Shawshank Redemption",
        Id = "movies/2",
    
        TagsEmbeddedAsSingle =new RavenVector<float>(new float[]
        {
            8.800000190734863f, 9.899999618530273f
        }),
        TagsEmbeddedAsBase64 = new List<string>() {"zcxMPs3MTD9mZmY/", "zcxMPpqZmT4zMzM/"},
        TagsEmbeddedAsInt8 = new sbyte[][]
        {
            VectorQuantizer.ToInt8(new float[] { 0.5f, 0.6f }),
            VectorQuantizer.ToInt8(new float[] { 0.7f, 0.8f })
        }
    };

    session.Store(movie1);
    session.Store(movie2);
    session.SaveChanges();
}
{
    "Title": "Hidden Figures",
    
    "TagsEmbeddedAsSingle": {
        "@vector": [
            6.599999904632568,
            7.699999809265137
        ]
    },
    
    "TagsEmbeddedAsInt8": [
        [
            64,
            127,
            -51,
            -52,
            76,
            62
        ],
        [
            95,
            127,
            -51,
            -52,
            -52,
            62
        ]
    ],
    
    "TagsEmbeddedAsBase64": [
        "zczMPc3MTD6amZk+",
        "mpmZPs3MzD4AAAA/"
    ],
    
    "@metadata": {
        "@collection": "Movies"
    }
}

Examples:

These examples search for Movie documents with vectors similar to the one provided in the query.

  • Search on the TagsEmbeddedAsSingle field,
    which contains numerical data in floating-point format.

var similarMovies = session.Query<Movie>()
     // Perform a vector search
     // Call the 'VectorSearch' method
    .VectorSearch(
        // Call 'WithEmbedding', specify:
        // * The source field that contains the embedding in the document
        // * The source embedding type
        field => field.WithEmbedding(
            x => x.TagsEmbeddedAsSingle, VectorEmbeddingType.Single),
        // Call 'ByEmbedding'
        // Provide the vector for the similarity comparison
        queryVector => queryVector.ByEmbedding(
            new RavenVector<float>(new float[] { 6.599999904632568f, 7.699999809265137f })),
        // It is recommended to specify the minimum similarity level
        0.85f,
        // Optionally, specify the number of candidates for the search
        10)
    .Customize(x => x.WaitForNonStaleResults())
    .ToList();
var similarMovies = await asyncSession.Query<Movie>()
    .VectorSearch(
        field => field.WithEmbedding(
            x => x.TagsEmbeddedAsSingle, VectorEmbeddingType.Single),
        queryVector => queryVector.ByEmbedding(
            new RavenVector<float>(new float[] { 6.599999904632568f, 7.699999809265137f })),
        0.85f,
        10)
    .Customize(x => x.WaitForNonStaleResults())
    .ToListAsync();
var similarMovies = session.Advanced
    .DocumentQuery<Movie>()
    .VectorSearch(
        field => field.WithEmbedding(
            x => x.TagsEmbeddedAsSingle, VectorEmbeddingType.Single),
        queryVector => queryVector.ByEmbedding(
            new RavenVector<float>(new float[] { 6.599999904632568f, 7.699999809265137f })),
        0.85f,
        10)
    .WaitForNonStaleResults()
    .ToList();
var similarMovies = await asyncSession.Advanced
    .AsyncDocumentQuery<Movie>()
    .VectorSearch(
        field => field.WithEmbedding(
            x => x.TagsEmbeddedAsSingle, VectorEmbeddingType.Single),
        queryVector => queryVector.ByEmbedding(
            new RavenVector<float>(new float[] { 6.599999904632568f, 7.699999809265137f })),
        0.85f,
        10)
    .WaitForNonStaleResults()
    .ToListAsync();
var similarProducts = session.Advanced
    .RawQuery<Movie>(@"
        from 'Movies' 
        where vector.search(TagsEmbeddedAsSingle, $queryVector, 0.85, 10)")
    .AddParameter("queryVector", new RavenVector<float>(new float[]
        {
            6.599999904632568f, 7.699999809265137f
        }))
    .WaitForNonStaleResults()
    .ToList();
var similarProducts = await asyncSession.Advanced
    .AsyncRawQuery<Movie>(@"
        from 'Movies' 
        where vector.search(TagsEmbeddedAsSingle, $queryVector, 0.85, 10)")
    .AddParameter("queryVector", new RavenVector<float>(new float[]
        {
            6.599999904632568f, 7.699999809265137f
        }))
    .WaitForNonStaleResults()
    .ToListAsync();
from "Movies"
// The source document field type is interpreted as 'Single' by default
where vector.search(TagsEmbeddedAsSingle, $queryVector, 0.85, 10)
{ "queryVector" : { "@vector" : [6.599999904632568, 7.699999809265137] }}

  • Search on the TagsEmbeddedAsInt8 field,
    which contains numerical data that is already quantized in Int8 format.

var similarMovies = session.Query<Movie>()
    .VectorSearch(
        // Call 'WithEmbedding', specify:
        // * The source field that contains the embeddings in the document
        // * The source embedding type
        field => field.WithEmbedding(
            x => x.TagsEmbeddedAsInt8, VectorEmbeddingType.Int8),
        // Call 'ByEmbedding'
        // Provide the vector for the similarity comparison
        // (provide a single vector from the vector list in the TagsEmbeddedAsInt8 field)
        queryVector => queryVector.ByEmbedding(
            // The provided vector MUST be in the same format as was stored in your document
            // Call 'VectorQuantizer.ToInt8' to transform the raw data to the Int8 format  
            VectorQuantizer.ToInt8(new float[] { 0.1f, 0.2f })))
    .Customize(x => x.WaitForNonStaleResults())
    .ToList();
from "Movies"
// Wrap the source document field name with 'embedding.i8' to indicate the source data type
where vector.search(embedding.i8(TagsEmbeddedAsInt8), $queryVector)
{ "queryVector" : [64, 127, -51, -52, 76, 62] }

  • Search on the TagsEmbeddedAsBase64 field,
    which contains numerical data represented in Base64 format.

var similarMovies = session.Query<Movie>()
    .VectorSearch(
        // Call 'WithBase64', specify:
        // * The source field that contains the embeddings in the document
        // * The source embedding type
        //   (the type from which the Base64 string was constructed)
        field => field.WithBase64(x => x.TagsEmbeddedAsBase64, VectorEmbeddingType.Single),
        // Call 'ByBase64'
        // Provide the Base64 string that represents the vector to query against
        queryVector => queryVector.ByBase64("zczMPc3MTD6amZk+"))
    .Customize(x => x.WaitForNonStaleResults())
    .ToList();
from "Movies"
// * Wrap the source document field name using 'embedding.<format>' to specify
//   the source data type from which the Base64 string was generated.
// * If the document field is Not wrapped, 'single' is assumed as the default source type. 
where vector.search(TagsEmbeddedAsBase64, $queryVectorBase64)
{ "queryVectorBase64" : "zczMPc3MTD6amZk+" }

  • When performing a dynamic vector search query, you can specify whether to perform an exact search to find the closest similar vectors in the vector space:

    • A thorough scan will be performed to find the actual closest vectors.
    • This ensures better accuracy but comes at a higher computational cost.
  • If exact is Not specified, the search defaults to the Approximate Nearest-Neighbor (ANN) method,
    which finds related vectors in an approximate manner, offering faster results.

  • The following example demonstrates how to specify the exact method in the query.
    Setting the param is similar for both text and numerical content searches.

    var similarProducts = session.Query<Product>()
        .VectorSearch(
            field => field.WithText(x => x.Name),
            searchTerm => searchTerm.ByText("italian food"),
            // Optionally, set the 'isExact' param to true to perform an Exact search
            isExact: true)
        .Customize(x => x.WaitForNonStaleResults())
        .ToList();
    var similarProducts = await asyncSession.Query<Product>()
        .VectorSearch(
            field => field.WithText(x => x.Name), 
            searchTerm => searchTerm.ByText("italian food"),
            isExact: true)
        .Customize(x => x.WaitForNonStaleResults())
        .ToListAsync();
    var similarProducts = session.Advanced
        .DocumentQuery<Product>()
        .VectorSearch(
            field => field.WithText(x => x.Name),
            searchTerm => searchTerm.ByText("italian food"),
            isExact: true)
        .WaitForNonStaleResults()
        .ToList();
    var similarProducts = await asyncSession.Advanced
        .AsyncDocumentQuery<Product>()
        .VectorSearch(
            field => field.WithText(x => x.Name),
            searchTerm => searchTerm.ByText("italian food"),
            isExact: true)
        .WaitForNonStaleResults()
        .ToListAsync();
    var similarProducts = session.Advanced
        .RawQuery<Product>(@"
            from 'Products'
            // Wrap the query with the 'exact()' method
            where exact(vector.search(embedding.text(Name), $searchTerm))")
        .AddParameter("searchTerm", "italian food")
        .WaitForNonStaleResults()
        .ToList();
    var similarProducts = await asyncSession.Advanced
        .AsyncRawQuery<Product>(@"
            from 'Products'
            // Wrap the query with the 'exact()' method
            where exact(vector.search(embedding.text(Name), $searchTerm))")
        .AddParameter("searchTerm", "italian food")
        .WaitForNonStaleResults()
        .ToListAsync();
    from "Products"
    // Wrap the vector.search query with the 'exact()' method
    where exact(vector.search(embedding.text(Name), "italian food"))

Quantization options

What is quantization:

Quantization is a technique that reduces the precision of numerical data. It converts high-precision values, such as 32-bit floating-point numbers, into lower-precision formats like 8-bit integers or binary representations.

The quantization process, applied to each dimension (or item) in the numerical array, serves as a form of compression by reducing the number of bits used to represent each value in the vector. For example, transitioning from 32-bit floats to 8-bit integers significantly reduces data size while preserving the vector's essential structure.

Although it introduces some precision loss, quantization minimizes storage requirements and optimizes memory usage. It also reduces computational overhead, making operations like similarity searches faster and more efficient.

Quantization in RavenDB:

For non-quantized raw 32-bit data or text stored in your documents, RavenDB allows you to choose the quantization format for the generated embeddings stored in the index.
The selected quantization type determines the similarity search technique that will be applied.

If no target quantization format is specified, the Single option will be used as the default.

The available quantization options are:

  • Single (a 32-bit floating point value per dimension):
    Provides precise vector representations.
    The Cosine similarity method will be used for searching and matching.

  • Int8 (an 8-bit integer value per dimension):
    Reduces storage requirements while maintaining good performance.
    Saves up to 75% storage compared to 32-bit floating-point values.
    The Cosine similarity method will be used for searching and matching.

  • Binary (1-bit per dimension):
    Minimizes storage usage, suitable for use cases where binary representation suffices.
    Saves approximately 96% storage compared to 32-bit floating-point values.
    The Hamming distance method will be used for searching and matching.

    If your documents contain data that is already quantized,
    it cannot be re-quantized to a lower precision format (e.g., Int8 cannot be converted to Binary).


Examples

  • In this example:
    • The source data consists of text.
    • The generated embeddings will use the Int8 format.

var similarProducts = session.Query<Product>()
    .VectorSearch(
        field => field
            // Specify the source text field for the embeddings
            .WithText(x => x.Name)
            // Set the quantization type for the generated embeddings
            .TargetQuantization(VectorEmbeddingType.Int8),
        searchTerm => searchTerm
            // Provide the search term for comparison
            .ByText("italian food"))
    .Customize(x => x.WaitForNonStaleResults())
    .ToList();
var similarProducts = await asyncSession.Query<Product>()
    .VectorSearch(
        field => field
            .WithText(x => x.Name)
            .TargetQuantization(VectorEmbeddingType.Int8),
        searchTerm => searchTerm
            .ByText("italian food"))
    .Customize(x => x.WaitForNonStaleResults())
    .ToListAsync();
var similarProducts = session.Advanced
    .DocumentQuery<Product>()
    .VectorSearch(
        field => field
            .WithText(x => x.Name)
            .TargetQuantization(VectorEmbeddingType.Int8), 
        searchTerm => searchTerm
            .ByText("italian food"))
    .WaitForNonStaleResults()
    .ToList();
var similarProducts = await asyncSession.Advanced
    .AsyncDocumentQuery<Product>()
    .VectorSearch(
        field => field
            .WithText(x => x.Name)
            .TargetQuantization(VectorEmbeddingType.Int8),
        searchTerm => searchTerm
            .ByText("italian food"))
    .WaitForNonStaleResults()
    .ToListAsync();
var similarProducts = session.Advanced
    .RawQuery<Product>(@"
        from 'Products'
        // Wrap the 'Name' field with 'embedding.text_i8'
        where vector.search(embedding.text_i8(Name), $searchTerm)")
    .AddParameter("searchTerm", "italian food")
    .WaitForNonStaleResults()
    .ToList();
var similarProducts = await asyncSession.Advanced
    .AsyncRawQuery<Product>(@"
        from 'Products'
        // Wrap the 'Name' field with 'embedding.text_i8'
        where vector.search(embedding.text_i8(Name), $searchTerm)")
    .AddParameter("searchTerm", "italian food")
    .WaitForNonStaleResults()
    .ToListAsync();
from "Products"
// Wrap the 'Name' field with 'embedding.text_i8'
where vector.search(embedding.text_i8(Name), $searchTerm)
{ "searchTerm" : "italian food" }

  • In this example:
    • The source data is an array of 32-bit floats.
    • The generated embeddings will use the Binary format.

var similarMovies = session.Query<Movie>()
    .VectorSearch(
        field => field
            // Specify the source field and its type   
            .WithEmbedding(x => x.TagsEmbeddedAsSingle, VectorEmbeddingType.Single)
            // Set the quantization type for the generated embeddings
            .TargetQuantization(VectorEmbeddingType.Binary),
        queryVector => queryVector
            // Provide the vector to use for comparison
            .ByEmbedding(new RavenVector<float>(new float[]
            {
                6.599999904632568f, 7.699999809265137f
            })))
    .Customize(x => x.WaitForNonStaleResults())
    .ToList();
var similarMovies = await asyncSession.Query<Movie>()
    .VectorSearch(
        field => field
            .WithEmbedding(x => x.TagsEmbeddedAsSingle, VectorEmbeddingType.Single)
            .TargetQuantization(VectorEmbeddingType.Binary),
        queryVector => queryVector
            .ByEmbedding(new RavenVector<float>(new float[]
             {
                 6.599999904632568f, 7.699999809265137f
             })))
    .Customize(x => x.WaitForNonStaleResults())
    .ToListAsync();
var similarProducts = session.Advanced
    .DocumentQuery<Movie>()
    .VectorSearch(
        field => field
            .WithEmbedding(x => x.TagsEmbeddedAsSingle, VectorEmbeddingType.Single)
            .TargetQuantization(VectorEmbeddingType.Binary),
        queryVector => queryVector
            .ByEmbedding(new RavenVector<float>(new float[]
             {
                 6.599999904632568f, 7.699999809265137f
             })))
    .WaitForNonStaleResults()
    .ToList();
var similarProducts = await asyncSession.Advanced
    .AsyncDocumentQuery<Movie>()
    .VectorSearch(
        field => field
            .WithEmbedding(x => x.TagsEmbeddedAsSingle, VectorEmbeddingType.Single)
            .TargetQuantization(VectorEmbeddingType.Binary),
        queryVector => queryVector
            .ByEmbedding(new RavenVector<float>(new float[]
             {
                 6.599999904632568f, 7.699999809265137f
             })))
    .WaitForNonStaleResults()
    .ToListAsync();
var similarMovies = session.Advanced
    .RawQuery<Movie>(@"
        from 'Movies'
        // Wrap the 'TagsEmbeddedAsSingle' field with 'embedding.f32_i1'
        where vector.search(embedding.f32_i1(TagsEmbeddedAsSingle), $queryVector)")
    .AddParameter("queryVector", new RavenVector<float>(new float[]
        {
            6.599999904632568f, 7.699999809265137f
        }))
    .WaitForNonStaleResults()
    .ToList();
var similarMovies = await asyncSession.Advanced
    .AsyncRawQuery<Movie>(@"
        from 'Movies'
        // Wrap the 'TagsEmbeddedAsSingle' field with 'embedding.f32_i1'
        where vector.search(embedding.f32_i1(TagsEmbeddedAsSingle), $queryVector)")
    .AddParameter("queryVector", new RavenVector<float>(new float[]
        {
            6.599999904632568f, 7.699999809265137f
        }))
    .WaitForNonStaleResults()
    .ToListAsync();
from "Movies"
// Wrap the 'TagsEmbeddedAsSingle' field with 'embedding.f32_i1'
where vector.search(embedding.f32_i1(TagsEmbeddedAsSingle), $queryVector)
{ "queryVector" : { "@vector" : [6.599999904632568,7.699999809265137] }}


Field configuration methods in RQL:

The following methods are available for performing a vector search via RQL:

  • embedding.text:
    Generates embeddings from text as multi-dimensional vectors with 32-bit floating-point values,
    without applying quantization.

  • embedding.text_i8:
    Generates embeddings from text as multi-dimensional vectors with 8-bit integer values.

  • embedding.text_i1:
    Generates embeddings from text as multi-dimensional vectors in a binary format.


  • embedding.f32_i8:
    Converts multi-dimensional vectors with 32-bit floating-point values into vectors with 8-bit integer values.

  • embedding.f32_i1:
    Converts multi-dimensional vectors with 32-bit floating-point values into vectors in a binary format.


  • embedding.i8:
    Indicates that the source data is already quantized as Int8 (cannot be further quantized).

  • embedding.i1:
    Indicates that the source data is already quantized as binary (cannot be further quantized).

Wrap the field name using any of the relevant methods listed above, based on your requirements.
For example, the following RQL encodes text to Int8:

from "Products"
// Wrap the document field with 'embedding.text_i8'
where vector.search(embedding.text_i8(Name), "italian food", 0.82, 20)

When the field name is Not wrapped in any method, the underlying values are treated as numerical values in the form of 32-bit floating-point (Single) precision. For example, the following RQL will use the floating-point values as they are, without applying further quantization:

from "Movies"
// No wrapping
where vector.search(TagsEmbeddedAsSingle, $queryVector, 0.85, 10)
{"queryVector" : { "@vector" : [6.599999904632568, 7.699999809265137] }}

Querying vector fields and regular data in the same query

  • You can perform a vector search and a regular search in the same query.
    A single auto-index will be created for both search predicates.

  • In the following example, results will include Product documents with content similar to "Italian food" in their Name field and a PricePerUnit above 20. The following auto-index will be generated:
    Auto/Products/ByPricePerUnitAndVector.search(embedding.text(Name)).

var similarProducts = session.Query<Product>()
     // Perform a filtering condition:
    .Where(x => x.PricePerUnit > 35)
     // Perform a vector search:
    .VectorSearch(
        field => field.WithText(x => x.Name),
        searchTerm => searchTerm.ByText("italian food"),
        0.75f, 16)
    .Customize(x => x.WaitForNonStaleResults())
    .ToList();
var similarProducts = await asyncSession.Query<Product>()
    .Where(x => x.PricePerUnit > 35)
    .VectorSearch(
        field => field.WithText(x => x.Name),
        searchTerm => searchTerm.ByText("italian food"),
        0.75f, 16)
    .Customize(x => x.WaitForNonStaleResults())
    .ToListAsync();
var similarProducts = session.Advanced
    .DocumentQuery<Product>()
    .VectorSearch(
        field => field.WithText(x => x.Name),
        searchTerm => searchTerm.ByText("italian food"),
        0.75f, 16)
    .WhereGreaterThan(x => x.PricePerUnit, 35)
    .WaitForNonStaleResults()
    .ToList();
var similarProducts = await asyncSession.Advanced
    .AsyncDocumentQuery<Product>()
    .VectorSearch(
        field => field.WithText(x => x.Name),
        searchTerm => searchTerm.ByText("italian food"),
        0.75f, 16)
    .WhereGreaterThan(x => x.PricePerUnit, 35)
    .WaitForNonStaleResults()
    .ToListAsync();
var similarProducts = session.Advanced
    .RawQuery<Product>(@"
        from 'Products'
        where (PricePerUnit > $minPrice) and (vector.search(embedding.text(Name), $searchTerm, 0.75, 16))")
    .AddParameter("minPrice", 35.0)
    .AddParameter("searchTerm", "italian food")
    .WaitForNonStaleResults()
    .ToList();
var similarProducts = await asyncSession.Advanced
    .AsyncRawQuery<Product>(@"
        from 'Products'
        where (PricePerUnit > $minPrice) and (vector.search(embedding.text(Name), $searchTerm, 0.75, 16))")
    .AddParameter("minPrice", 35.0)
    .AddParameter("searchTerm", "italian food")
    .WaitForNonStaleResults()
    .ToListAsync();
from "Products"
// The filtering condition:
where (PricePerUnit > $minPrice)
and (vector.search(embedding.text(Name), $searchTerm, 0.75, 16))
{ "minPrice" : 35.0, "searchTerm" : "italian food" }

Impact of NumberOfCandidates on query results:

  • When combining a vector search with a filtering condition, the filter applies only to the documents retrieved within the NumberOfCandidates param limit. Increasing or decreasing NumberOfCandidates can affect the query results. A larger NumberOfCandidates increases the pool of documents considered, improving the chances of finding results that match both the vector search and the filter condition.

  • For example, in the above query, the vector search executes with: Similarity 0.75f and NumberOfCandidates 16. Running this query on RavenDB's sample data returns 2 documents.

  • However, if you increase NumberOfCandidates, the query will retrieve more candidate documents before applying the filtering condition. If you run the following query:

    from "Products"
    where (PricePerUnit > $minPrice)
    // Run vector search with similarity 0.75 and NumberOfCandidates 25
    and (vector.search(embedding.text(Name), $searchTerm, 0.75, 25))
    { "minPrice" : 35.0, "searchTerm" : "italian food" }

    now the query returns 4 documents instead of 2.

Syntax

VectorSearch:

public IRavenQueryable<T> VectorSearch<T>(
    Func<IVectorFieldFactory<T>, IVectorEmbeddingTextField> textFieldFactory,
    Action<IVectorEmbeddingTextFieldValueFactory> textValueFactory,
    float? minimumSimilarity = null,
    int? numberOfCandidates = null,
    bool isExact = false);

public IRavenQueryable<T> VectorSearch<T>(
    Func<IVectorFieldFactory<T>, IVectorEmbeddingField> embeddingFieldFactory,
    Action<IVectorEmbeddingFieldValueFactory> embeddingValueFactory,
    float? minimumSimilarity = null,
    int? numberOfCandidates = null,
    bool isExact = false);

public IRavenQueryable<T> VectorSearch<T>(
    Func<IVectorFieldFactory<T>, IVectorField> embeddingFieldFactory,
    Action<IVectorFieldValueFactory> embeddingValueFactory,
    float? minimumSimilarity = null,
    int? numberOfCandidates = null,
    bool isExact = false);
Parameter Type Description
embeddingFieldFactory Func<IVectorFieldFactory<T>, IVectorEmbeddingTextField>

Func<IVectorFieldFactory<T>, IVectorEmbeddingField>
Factory creating embedding vector field for indexing purposes.
embeddingFieldFactory Func<IVectorFieldFactory<T>, IVectorField> Factory using existing, already indexed vector field.
embeddingValueFactory Action<IVectorEmbeddingTextFieldValueFactory>
Action<IVectorEmbeddingFieldValueFactory>
Action<IVectorFieldValueFactory>
Factory preparing queried data to be used in vector search.
minimumSimilarity float? Minimum similarity between the queried value and the indexed value for the vector search to match.
numberOfCandidates int? Number of candidate nodes for the HNSW algorithm.
Higher values improve accuracy but require more computation.
isExact bool false - vector search will be performed in an approximate manner.
true - vector search will be performed in an exact manner.

The default value for minimumSimilarity is defined by this configuration key:
Indexing.Corax.VectorSearch.DefaultMinimumSimilarity .

The default value for numberOfCandidates is defined by this configuration key:
Indexing.Corax.VectorSearch.DefaultNumberOfCandidatesForQuerying.


IVectorFieldFactory:

public interface IVectorFieldFactory<T>
{
    // Methods for the dynamic query:
    public IVectorEmbeddingTextField WithText(string documentFieldName);
    public IVectorEmbeddingTextField WithText(Expression<Func<T, object>> propertySelector);
    public IVectorEmbeddingField WithEmbedding(string documentFieldName, 
        VectorEmbeddingType storedEmbeddingQuantization = VectorEmbeddingType.Single);
    public IVectorEmbeddingField WithEmbedding(Expression<Func<T, object>> propertySelector,
        VectorEmbeddingType storedEmbeddingQuantization = VectorEmbeddingType.Single);
    public IVectorEmbeddingField WithBase64(string documentFieldName,
        VectorEmbeddingType storedEmbeddingQuantization = VectorEmbeddingType.Single);
    public IVectorEmbeddingField WithBase64(Expression<Func<T, object>> propertySelector,
        VectorEmbeddingType storedEmbeddingQuantization = VectorEmbeddingType.Single);
    
    // Methods for querying a static index:
    public IVectorField WithField(string indexFieldName);
    public IVectorField WithField(Expression<Func<T, object>> indexPropertySelector);
}
Parameter Type Description
documentFieldName string The name of the document field containing
text / embedding / base64 encoded data.
indexFieldName string The name of the index-field that vector search will be performed on.
propertySelector Expression<Func<T, object>> Path to the document field containing
text / embedding /base64 encoded data.
indexPropertySelector Expression<Func<T, object>> Path to the index-field containing indexed data.
storedEmbeddingQuantization VectorEmbeddingType Quantization format of the stored embeddings.
Default: VectorEmbeddingType.Single

IVectorEmbeddingTextField:

public interface IVectorEmbeddingTextField
{
    public IVectorEmbeddingTextField TargetQuantization(
        VectorEmbeddingType targetEmbeddingQuantization);
    
    public IVectorEmbeddingTextField UsingTask(
        string embeddingsGenerationTaskIdentifier);
}

public interface IVectorEmbeddingField
{
    public IVectorEmbeddingField TargetQuantization(
        VectorEmbeddingType targetEmbeddingQuantization);
}
Parameter Type Description
targetEmbeddingQuantization VectorEmbeddingType The desired target quantization format.
embeddingsGenerationTaskIdentifier string The identifier of an embeddings generation task.
Used to locate the embeddings generated by the task in the Embedding collections.

public enum VectorEmbeddingType
{
    Single,
    Int8,
    Binary,
    Text
}

IVectorEmbeddingTextFieldValueFactory:

public interface IVectorEmbeddingTextFieldValueFactory
{
    // Defines the queried text
    public void ByText(string text);
}
public interface IVectorEmbeddingFieldValueFactory
{
    // Define the queried embedding:
    // =============================
    
    // 'embeddings' is an Enumerable containing embedding values.
    public void ByEmbedding<T>(IEnumerable<T> embedding) where T : unmanaged, INumber<T>;
    
    // 'embeddings' is an array containing embedding values.
    public void ByEmbedding<T>(T[] embedding) where T : unmanaged, INumber<T>;
    
    // Defines queried embedding in base64 format.
    // 'base64Embedding' is encoded as base64 string.
    public void ByBase64(string base64Embedding);
    
    // 'embedding` is a `RavenVector` containing embedding values.
    public void ByEmbedding<T>(RavenVector<T> embedding) where T : unmanaged, INumber<T>;
}

RavenVector:

RavenVector is RavenDB's dedicated data type for storing and querying numerical embeddings.
Learn more in RavenVector

public class RavenVector<T>()
{
    public T[] Embedding { get; set; }
} 

VectorQuanitzer:

RavenDB provides the following quantizer methods.
Use them to transform your raw data to the desired format.
Other quantizers may not be compatible.

public static class VectorQuantizer
{
    public static sbyte[] ToInt8(float[] rawEmbedding);
    public static byte[] ToInt1(ReadOnlySpan<float> rawEmbedding);
}