In a previous post I showed you how to use Semantic Kernel, Ollama and Qdrant to generate and store vector embeddings. Doing this doesn't make much sense if we don't look at a way to expose and use this data. In this post I'll show you how to query the vector store to search for results.
Remark: If you missed the previous post, go check it out first.
Letās dive inā¦
We start by having a look again at the model we were using:
internal class EmailText | |
{ | |
/// <summary>A unique key for the email paragraph.</summary> | |
[VectorStoreRecordKey] | |
public required Guid Key { get; init; } | |
/// <summary>The text of the paragraph.</summary> | |
[VectorStoreRecordData(IsFullTextSearchable = true, StoragePropertyName = "email_text")] | |
public required string Text { get; init; } | |
[VectorStoreRecordVector(768, StoragePropertyName = "email_text_embedding")] | |
public ReadOnlyMemory<float> TextEmbedding { get; set; } | |
} |
First important thing to notice is that we can decided which properties should be used when querying the vector store. This is useful when we have multiple vectors stored in the data model. If we donāt specify the vector property, the first vector found will be used when querying. (In our example we only have one vector property so in theory we could ignore this).
Letās do a first search:
var collection = vectorStore.GetCollection<Guid, EmailText>("emails"); | |
var sampleEmail = "Hi there, welcome at CloudBrew! I hope you enjoy the conference."; | |
var searchVector = await textEmbeddingGenerationService.GenerateEmbeddingAsync(sampleEmail); | |
var vectorSearchOptions = new VectorSearchOptions | |
{ | |
VectorPropertyName = nameof(EmailText.TextEmbedding), | |
Top=2 | |
}; | |
var searchResult = await collection.VectorizedSearchAsync(searchVector, vectorSearchOptions); | |
await foreach (var record in searchResult.Results) | |
{ | |
Console.WriteLine("Email key " + record.Record.Key); | |
Console.WriteLine("Email text: " + record.Record.Text); | |
} |
Remark: Notice that we need to use the Property name not the name configured in the store or attribute.
Most vector stores also support the concept of filtering. This allows us to provide a filter that will filter the records before applying the vector search. Using filters helps to reduce latency and processing costs.
Letās add an extra property āTagā to our model and set the IsFilterable
Property to true
on the VectorStoreRecordData
attribute:
internal class EmailText | |
{ | |
/// <summary>A unique key for the email paragraph.</summary> | |
[VectorStoreRecordKey] | |
public required Guid Key { get; init; } | |
/// <summary>The text of the paragraph.</summary> | |
[VectorStoreRecordData(IsFullTextSearchable = true, StoragePropertyName = "email_text")] | |
public required string Text { get; init; } | |
[VectorStoreRecordData(IsFilterable = true)] | |
public required string Tag { get; init; } | |
[VectorStoreRecordVector(768, StoragePropertyName = "email_text_embedding")] | |
public ReadOnlyMemory<float> TextEmbedding { get; set; } | |
} |
Now we use this property to filter the results first before applying the vector search:
var vectorStore = new QdrantVectorStore(new QdrantClient("localhost")); | |
var collection = vectorStore.GetCollection<Guid, EmailText>("emails"); | |
var sampleEmail = "Hi there, welcome at CloudBrew! I hope you enjoy the conference."; | |
var searchVector = await textEmbeddingGenerationService.GenerateEmbeddingAsync(sampleEmail); | |
var filter = new VectorSearchFilter() | |
.EqualTo(nameof(EmailText.Tag), "CloudBrew"); | |
var vectorSearchOptions = new VectorSearchOptions | |
{ | |
VectorPropertyName = nameof(EmailText.TextEmbedding), | |
Filter = filter, | |
Top =2 | |
}; | |
var searchResult = await collection.VectorizedSearchAsync(searchVector, vectorSearchOptions); | |
await foreach (var record in searchResult.Results) | |
{ | |
Console.WriteLine("Email key " + record.Record.Key); | |
Console.WriteLine("Email text: " + record.Record.Text); | |
} |
Great! In a next post, we first take a deeper looker look at Qdrant before we continue with a look at the text search feature of Semantic Kernel.
More information
Generate text embeddings with Semantic Kernel and Ollama
Vector search using Semantic Kernel Vector Store connectors (Preview) | Microsoft Learn