Files
Abdon Pijpelink 434a451c2b Update Hybrid Search with Reranking tutorial (#2274)
* Update for Cloud Inference and data ingestion

* Fix link

* Review feedback

* Make code snippets testable

* Add C# code snippets

* Add Go code snippets

* Add Java code snippets

* Add Rust code snippets

* Add TS code snippets

* Move CSV streaming/parsing to separate function
2026-04-22 11:03:05 +02:00

198 lines
5.3 KiB
C#

using System.Net.Http;
using Microsoft.VisualBasic.FileIO;
using Qdrant.Client;
using Qdrant.Client.Grpc;
public class Snippet
{
public static async Task Run()
{
// @hide-start
string QDRANT_URL = "xyz-example.eu-central.aws.cloud.qdrant.io";
string QDRANT_API_KEY = "<your-api-key>";
// @hide-end
// @block-start client-connection
var client = new QdrantClient(
host: QDRANT_URL,
https: true,
apiKey: QDRANT_API_KEY
);
// @block-end client-connection
// @block-start define-models
string denseEmbeddingModel = "sentence-transformers/all-MiniLM-L6-v2";
string sparseEmbeddingModel = "qdrant/bm25";
string lateInteractionEmbeddingModel = "answerdotai/answerai-colbert-small-v1";
// @block-end define-models
// @block-start create-collection
string collectionName = "hybrid-search";
if (await client.CollectionExistsAsync(collectionName))
await client.DeleteCollectionAsync(collectionName);
await client.CreateCollectionAsync(
collectionName: collectionName,
vectorsConfig: new VectorParamsMap
{
Map =
{
["dense"] = new VectorParams
{
Size = 384,
Distance = Distance.Cosine,
},
["multi"] = new VectorParams
{
Size = 96,
Distance = Distance.Cosine,
MultivectorConfig = new() { Comparator = MultiVectorComparator.MaxSim },
HnswConfig = new HnswConfigDiff { M = 0 }, // Disable HNSW for reranking
},
}
},
sparseVectorsConfig: new SparseVectorConfig
{
Map =
{
["sparse"] = new SparseVectorParams { Modifier = Modifier.Idf }
}
}
);
// @block-end create-collection
// @block-start parse-csv
async IAsyncEnumerable<(string title, string author, string description)> ParseCsv(string url)
{
using var httpClient = new HttpClient();
using var stream = await httpClient.GetStreamAsync(url);
using var parser = new TextFieldParser(new StreamReader(stream));
parser.TextFieldType = Microsoft.VisualBasic.FileIO.FieldType.Delimited;
parser.SetDelimiters(",");
string[]? headers = parser.ReadFields();
int titleIdx = Array.IndexOf(headers!, "Title");
int authorIdx = Array.IndexOf(headers!, "Author");
int descriptionIdx = Array.IndexOf(headers!, "Description");
while (!parser.EndOfData)
{
var fields = parser.ReadFields()!;
yield return (fields[titleIdx], fields[authorIdx], fields[descriptionIdx]);
}
}
// @block-end parse-csv
// @block-start ingest-data
string csvUrl = "https://raw.githubusercontent.com/qdrant/examples/refs/heads/master/sci-fi-books/top_100_scifi_books_full.csv";
int batchSize = 25;
ulong idx = 0;
var buffer = new List<PointStruct>();
await foreach (var (title, author, description) in ParseCsv(csvUrl))
{
buffer.Add(new PointStruct
{
Id = idx++,
Vectors = new Dictionary<string, Vector>
{
["dense"] = new Document { Text = description, Model = denseEmbeddingModel },
["sparse"] = new Document { Text = description, Model = sparseEmbeddingModel },
["multi"] = new Document { Text = description, Model = lateInteractionEmbeddingModel },
},
Payload = { ["title"] = title, ["author"] = author, ["description"] = description }
});
if (buffer.Count >= batchSize)
{
await client.UpsertAsync(collectionName: collectionName, points: buffer);
buffer.Clear();
}
}
if (buffer.Count > 0)
await client.UpsertAsync(collectionName: collectionName, points: buffer);
// @block-end ingest-data
// @block-start dense-retrieval
string query = "time travel";
var results = await client.QueryAsync(
collectionName: collectionName,
query: new Document { Text = query, Model = denseEmbeddingModel },
usingVector: "dense",
limit: 10
);
foreach (var result in results)
Console.WriteLine(result);
// @block-end dense-retrieval
// @block-start sparse-retrieval
results = await client.QueryAsync(
collectionName: collectionName,
query: new Document { Text = query, Model = sparseEmbeddingModel },
usingVector: "sparse",
limit: 10
);
foreach (var result in results)
Console.WriteLine(result);
// @block-end sparse-retrieval
// @block-start hybrid-search
results = await client.QueryAsync(
collectionName: collectionName,
prefetch: new List<PrefetchQuery>
{
new()
{
Query = new Document { Text = query, Model = denseEmbeddingModel },
Using = "dense",
Limit = 20,
},
new()
{
Query = new Document { Text = query, Model = sparseEmbeddingModel },
Using = "sparse",
Limit = 20,
},
},
query: Fusion.Rrf,
payloadSelector: true,
limit: 10
);
foreach (var result in results)
Console.WriteLine(result);
// @block-end hybrid-search
// @block-start rerank
results = await client.QueryAsync(
collectionName: collectionName,
prefetch: new List<PrefetchQuery>
{
new()
{
Query = new Document { Text = query, Model = denseEmbeddingModel },
Using = "dense",
Limit = 20,
},
new()
{
Query = new Document { Text = query, Model = sparseEmbeddingModel },
Using = "sparse",
Limit = 20,
},
},
query: new Document { Text = query, Model = lateInteractionEmbeddingModel },
usingVector: "multi",
payloadSelector: true,
limit: 10
);
foreach (var result in results)
Console.WriteLine(result);
// @block-end rerank
}
}