
Using the graph
Embedding similarity alone returns the most similar items across the entire corpus. The graph lets you scope that — either as a hard filter or as a soft ranking signal.
The graph as a filter — search within a product's ticket history:
// Get all tickets for a specific product.
var scope = Q().StartAt("Product", "MBA-2024")
.In("ForProduct")
.AsUIDEnumerable()
.ToArray();
var similar = await Q()
.StartAt(seedTicketUID)
.ToSimilarity()
.AddSignal("embedding", s => s.FromAsync(async ctx =>
(await ctx.Graph.Query().StartAtSimilarTextAsync(bodyText, 20, new[] { N.Ticket.Type }))
.AsUIDEnumerable()))
.AddRule("product-filter", r => r.Filter((ctx, candidates) =>
candidates.Where(uid => scope.Contains(uid))))
.ExecuteAsync(ct);
Without the filter, "similar tickets" might return results from unrelated products. The graph constraint scopes similarity to what's relevant — without a separate index per product. It composes with permissions too: scope can be constrained to UIDs the current user can see.
The graph as a ranking signal — boost items that are graph-neighbours of the seed. Items that are both semantically similar and closely connected rank higher.

var sharedTags = Q().StartAt(seedUID).Out("HasTag").AsUIDEnumerable().ToArray();
var results = await Graph.Query()
.StartAt(seedUID)
.ToSimilarity()
.AddSignal("embedding", s => s
.Weight(0.7f)
.FromAsync(async ctx =>
(await ctx.Graph.Query().StartAtSimilarTextAsync(bodyText, 20, new[] { N.Ticket.Type }))
.AsUIDEnumerable()))
.AddSignal("shared-tags", s => s
.Weight(0.3f)
.From(ctx => ctx.Graph.Query().StartAt(sharedTags).In("HasTag").Distinct().AsUIDEnumerable()))
.Fuse(f => f.UsingReciprocalRankFusion())
.ExecuteAsync(ct);
Filter when a constraint is hard ("only this product"); use a weighted signal when it's a preference ("prefer the same project, but don't exclude others").