
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: productNameIndex, applyCutoff: false))
.Except(ctx.Subjects) // never recommend the seed itself
.AsUIDEnumerable()))
// 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))
.AsUIDEnumerable()))
// Combine the rankings.
.Fuse(f => f.UsingReciprocalRankFusion())
.ExecuteAsync(ct);
| Piece | What it does |
|---|---|
| Signal | A candidate source returning ranked UIDs. Text, graph, external — as many as you need |
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).