
The recommendation engine
A single lookup answers "what is like this?". For "what should we recommend?", IQuery.ToSimilarity(...) combines multiple signals into one ranking with fusion, then clips or reweights it with rules.
var result = await Graph.Query()
.StartAt(seedUID) // the subject signals see in ctx.Subjects
.ToSimilarity(o => o.MaxCandidates(200))
// 1. Text similarity over product names (embedding signal).
.AddSignal("SimilarName", s => s
.Weight(1.0f)
.FromAsync(async ctx =>
(await ctx.Graph.Query().StartAtSimilarTextAsync(
seedName, count: 100, nodeTypes: new[] { N.Product.Type },
indexUID: Indexes.Product.SentenceEmbeddingsIndex_Name_ArcticXS, applyCutoff: false))
.Except(ctx.Subjects))) // returns the IQuery — its similarity scores are kept
// 2. Same manufacturer (graph traversal signal).
.AddSignal("SameManufacturer", s => s
.Weight(0.7f)
.From(ctx => ctx.Graph.Query().StartAt(ctx.Subjects)
.Out(N.Manufacturer.Type, E.ManufacturedBy)
.Out(N.Product.Type, E.Manufactures)
.Except(ctx.Graph.Query().StartAt(ctx.Subjects))))
// Combine the rankings.
.Fuse(f => f.UsingReciprocalRankFusion())
.ExecuteAsync(ct);
| Piece | What it does |
|---|---|
| Signal | A candidate source returning an IQuery. Text, graph, external — as many as you need. If the query carries scores (e.g. StartAtSimilarTextAsync), the engine uses them; otherwise it ranks by position |
Weight(...) |
Scales a signal's contribution to the fused rank |
| Fusion | Merges the rankings. ReciprocalRankFusion (default) is robust across different score scales and rewards consensus; MaxScore lets one signal dominate |
With one signal, the scenario uses its scores directly. With two or more, you must call Fuse(...) or it falls back to MaxScore (rarely what you want).
You can also start the scenario straight from a similarity search — the initial scores become the first signal:
var result = await (await Graph.Query()
.StartAtSimilarTextAsync(seedName, count: 100, nodeTypes: new[] { N.Product.Type }))
.ToSimilarity()
.ExecuteAsync(ct); // result.Signals["StartAt"] holds the seeding similarity scores