Skip to content

Conversation

@mgravell
Copy link
Collaborator

@mgravell mgravell commented Nov 12, 2025

tasks:

primary API is new HybridSearchResult ISearchCommands.HybridSearch((string indexName, HybridSearchQuery query, IReadOnlyDictionary<string, object>? parameters) API (and Async twin).

Note that because FT.HYBRID supports parameterization in both the queries and the filters, I have deviated from the pattern for FT.SEARCH, in that the parameters are specified separately to the query. This means that a single query instance can be created and stored, then reused repeatedly with different values. To avoid concurrency concerns, "popsicle immutability" is used in the query object, becoming automatically frozen when issued. For convenience, a secondary type-based API is presented to allow parameters from, for example, anonymous types.

HybridSearchQuery acts as a builder, so:

var query = new HybridSearchQuery().Search("text part").VectorSearch("@vectorField", vectorData);
var result = ft.HybridSearch("myIndex", query);

However, a full API for the supported server features is available, for example:

var query = new HybridSearchQuery()
            .Search(new("foo", Scorer.BM25StdTanh(5), "text_score_alias"))
            .VectorSearch(new HybridSearchQuery.VectorSearchConfig("bar", new float[] { 1, 2, 3 },
                    VectorSearchMethod.NearestNeighbour(10, 100, "vector_distance_alias"))
                .WithFilter("@foo:bar").WithScoreAlias("vector_score_alias"))
            .Combine(HybridSearchQuery.Combiner.ReciprocalRankFusion(10, 0.5), "my_combined_alias")
            .ReturnFields("field1", "field2")
            .GroupBy("field1").Reduce(Reducers.Quantile("@field3", 0.5).As("reducer_alias"))
            .Apply(new("@field1 + @field2", "apply_alias"))
            .SortBy(SortedField.Asc("field1"), SortedField.Desc("field2"))
            .Filter("@field1:bar")
            .Limit(12, 54)
            .ExplainScore()
            .Timeout()

var args = Parameters.From(new { x = 12, y = "abc" });
var results = ft.HybridSearch(query, args);

To complement this, we introduce some additional additional types; the types specific to hybrid-search are mostly inside NRedisStack.Search.HybridSearchQuery; the types that are also conceptually wider to all of search are in NRedisStack.Search.

  • NRedisStack.Search.ApplyExpression - immutable readonly struct tuple of string expression and string? alias - NRedisStack.Search.Scorer - immutable class, private subtypes for TFIDF, BM25STD, etc from https://redis.io/docs/latest/develop/ai/search-and-query/advanced-concepts/scoring/
  • NRedisStack.Search.VectorData - abstracts over ways of representing vector data; readonly struct
  • NRedisStack.Search.HybridSearchQuery - the new builder API
  • NRedisStack.Search.HybridSearchQuery - the new result API
  • NRedisStack.Search.HybridSearchQuery.Combiner - immutable class, private subtypes for RRD and linear combiners
  • NRedisStack.Search.HybridSearchQuery.SearchConfig - immutable readonly struct describing a text search with scorer and alias
  • NRedisStack.Search.HybridSearchQuery.VectorSearchConfig - immutable readonly struct describing a vector search with method, filter, alias
  • NRedisStack.Search.VectorSearchMethod - immutable class, private subtypes for "nearest neighbour", "range", etc
  • NRedisStack.Search.Parameters - allows objects to be used as parameter sources

At the moment VectorData only supports ROM<float> (float[]), in line with SE.Redis VSIM support, but we can extend this as necessary.


The new APIs are marked [Experimental], pointing people to (when merged) a portal with the contents from https://github.com/redis/NRedisStack/blob/9548d539ff72ac370c7c0706e8e9c53b8492f1b3/docs/exp/NRS001.md


API note: the significance of With* in the VectorSearchConfig and SearchConfig is that these are "withers", i.e. while it is a fluent API, the underlying type is immutable, so this returns a different instance (in this case, of a value-type). This contrasts with the HybridSearchQuery which pairs with SearchQuery in exposing a mutable fluent API, where each method ends return this.


Note that this also includes a fix for #453

@mgravell mgravell marked this pull request as draft November 12, 2025 14:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants