Kladde.Verlauf answers questions about tenders from the sections in the container knowledge. Why an answer may come only from sources that were found is shown by "AI track, Lesson T5.1" to "T5.3"; why meaning alone fails on numbers and technical terms, rank fusion (RRF) with its worked example, and the measurement of the hits among the first three are shown by "T5.4". None of that is repeated here. What is new is how Cosmos DB fuses both searches in one query, how the sections get in, and how you back up the choice of search with numbers.
Not run against Azure: all blocks are compiled with Microsoft.Azure.Cosmos 3.64.0, Newtonsoft.Json 13.0.4 and Microsoft.Extensions.AI 10.11.0. Only what needs no service was run: calculation, stand-in, test set. The figures below belong to an invented task with a toy embedding and say nothing about Cosmos DB. This lesson changes none of the facts F-01 to F-08.
The query with RRF
According to Learn, RRF combines two or more scores, such as VectorDistance and FullTextScore, into one ranking. The function goes in ORDER BY RANK; an array of numbers as the last argument weights the scores, and [2, 1] counts the vector search twice. Prerequisites and limits: the container needs a vector and a full-text policy with their indexes (Lesson 4.1), the account and the features are signed up, RRF cannot be combined with an ORDER BY on other paths and cannot be output in SELECT. So you never see the fused value, and a threshold like MinScore from T5.3 cannot be applied to it; T5.4 solves this with a second query by meaning. How Cosmos DB calculates internally, for instance with which constant, Learn does not say (open).
public static class HybridQueries
{
public static QueryDefinition Hybrid(
string officeId, float[] question, IReadOnlyList<string> words, int top, double[]? weights = null)
{
var names = words.Select((_, i) => $"@w{i}").ToArray();
var weightList = weights is null
? ""
: ", [" + string.Join(", ", weights.Select(w => w.ToString(CultureInfo.InvariantCulture))) + "]";
var query = new QueryDefinition(
"SELECT TOP @top c.id, c.tenderId, c.heading, c.text FROM c WHERE c.officeId = @officeId "
+ "ORDER BY RANK RRF(VectorDistance(c.embedding, @question), "
+ $"FullTextScore(c.text, {string.Join(", ", names)}){weightList})")
.WithParameter("@top", top)
.WithParameter("@officeId", officeId)
.WithParameter("@question", question);
for (var i = 0; i < names.Length; i++)
{
query = query.WithParameter(names[i], words[i]);
}
return query;
}
}
The words come from the code, not from the model (T5.4, Keywords), each as its own parameter; Learn advises splitting phrases into single words. An adapter CosmosKnowledgeSearch runs the queries with the office as the PartitionKey in the options. It is compiled, but not run against Azure.