本文将介绍如何在Semantic Kernel中使用Qdrant向量数据库,并演示如何在Semantic Kernel中进行向量更新和查询操作。
1. 背景
在前一篇文章《Qdrant 向量数据库的部署以及如何在 .NET 中使用 TLS 安全访问》中,我们介绍了如何使用 Docker 部署 Qdrant 向量数据库,以及其相关的安全配置,并演示了如何使用 .NET 通过 TLS 安全访问 Qdrant 向量数据库。现在,我们将在Semantic Kernel中使用Qdrant向量数据库,并演示如何进行向量更新和查询操作。
Semantic Kernel是一个开源的语义内核 SDK,它提供了一种高效的方式让用户可以在自己的应用程序中集成大语言模型 (LLM) 的强大功能。Semantic Kernel提供了多种向量数据库的连接器,可以与各种向量数据库集成,从而提供高效的向量查询和更新功能。
2. 在Semantic Kernel中使用Qdrant
在我们的大语言模型 (LLM) 应用程序中,我们通常会需要构建短期和长期记忆的方式,以赋予更智能的应用程序更大的能力。这个时候,我们就需要使用向量数据库来存储和查询向量数据。Qdrant 是一个高性能的向量数据库,它提供了高效的向量查询和更新功能,可以满足我们的需求。
2.1 安装Semantic Kernel SDK
在Semantic Kernel中使用Qdrant向量数据库,我们首先需要安装Semantic Kernel SDK,以及 Semantic Kernel 的 Memory 插件和 Qdrant 连接器:
dotnet add package Microsoft.SemanticKernel --version 1.6.3
dotnet add package Microsoft.SemanticKernel.Plugins.Memory --version 1.6.3-alpha
dotnet add package Microsoft.SemanticKernel.Connectors.Qdrant --version 1.6.3-alpha
通过上面的 alpha 标识,我们可以看到 Semantic Kernel 的 Memory 插件和 Qdrant 连接器还处于预览阶段,后续相关方法可能会有所变化,我们需要注意这一点。
在安装好 Semantic Kernel SDK 和相关插件后,我们就可以在我们的应用程序中使用 Qdrant 向量数据库了。接下来我会进行一个一个简单的代码示例,修改自 Github 的 notebook 《Building Semantic Memory with Embeddings》,这里我们更改了存储方式,将VolatileMemoryStore
改为使用 Qdrant 向量数据库的方式。
2.2 引入 Embedding 服务
完成了基础的类库安装,我们就可以引入相关的命名空间了:
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Microsoft.SemanticKernel.Connectors.Qdrant;
using Microsoft.SemanticKernel.Memory;
接下来,我们需要创建一个 MemoryBuilder 对象,这里需要注意的是,因为功能是实验性的,所以我们需要禁用一些警告:
#pragma warning disable SKEXP0001, SKEXP0010, SKEXP0050
var memoryBuilder = new MemoryBuilder();
非常重要的是,这里我们需要选择一个 Embedding 服务,用来将文本转换为向量。这里我们使用的是 Azure AI 的 text-embedding-ada-002
服务,需要在 Azure OpenAI Studio 中完成该模型的部署:
memoryBuilder.WithAzureOpenAITextEmbeddingGeneration("text-embedding-ada-002", "AZURE_ENDPOINT ", "AZURE_OPENAI_KEY");
2.3 连接 Qdrant 向量数据库
接下来我们使用 Semantic Kernel 提供的连接器,将 MemoryBuilder
与 Qdrant 向量数据库连接起来,这里使用的通讯方式不是我们上一篇文章中官方客户端使用的 GRPC,而是使用的 HTTP:
HttpClient httpClient = new HttpClient(new CustomQdrantHandler("<certificate thumbprint>", "client.pfx", "password"));
#pragma warning disable SKEXP0020
memoryBuilder.WithQdrantMemoryStore(httpClient, 1536 , "https://localhost:6333");
var memory = memoryBuilder.Build();
这里需要注意的是,因为我们从官方样例的 VolatileMemoryStore
改为了 Qdrant 向量数据库,所以这里我们需要使用 WithQdrantMemoryStore
方法,这个方法需要提供所使用的 Embedding 的维度。
另外,因为我们使用的是自签名证书,所以我们需要对 HttpClient 进行一些配置,这里我们使用了一个自定义的 CustomQdrantHandler
类,用来处理证书的验证,并提供客户端证书进行双向认证。
internal class CustomQdrantHandler : HttpClientHandler{
private string _knownHash;
private X509Certificate2 _clientCertificate;
public CustomQdrantHandler(string knownHash, string certPath, string certPassword) : base()
{
_knownHash = knownHash;
_clientCertificate = new X509Certificate2(certPath, certPassword);
this.ClientCertificates.Add(_clientCertificate);
this.ServerCertificateCustomValidationCallback = CheckServerCertificate;
}
private bool CheckServerCertificate(HttpRequestMessage httpRequestMessage, X509Certificate2 certificate, X509Chain chain, SslPolicyErrors errors)
{
using var sha256 = SHA256.Create();
var hashBytes = sha256.ComputeHash(certificate.GetPublicKey());
var hashString = BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
return hashString == _knownHash;
}
}
2.4 向量更新和查询
在完成了 MemoryBuilder 的构建后,我们就可以使用 Memory 对象进行向量的更新和查询操作了。这里我们使用一个关于“我”的简单介绍的例子,将一些文本转换为向量,并存储到 Qdrant 向量数据库中:
string MemoryCollectionName = "aboutMe";
await memory.SaveInformationAsync(MemoryCollectionName, id: "info1", text: "My name is Andrea");
await memory.SaveInformationAsync(MemoryCollectionName, id: "info2", text: "I currently work as a tourist operator");
await memory.SaveInformationAsync(MemoryCollectionName, id: "info3", text: "I currently live in Seattle and have been living there since 2005");
await memory.SaveInformationAsync(MemoryCollectionName, id: "info4", text: "I visited France and Italy five times since 2015");
await memory.SaveInformationAsync(MemoryCollectionName, id: "info5", text: "My family is from New York");
通过上面的代码,我们将这些文本信息存储到 Qdrant 向量数据库中,SaveInformationAsync
指定了集合名称、文本 ID 和文本内容。
接下来,我们可以定义下面一些问题,然后使用 Memory 对象进行查询操作:
var questions = new[]
{
"what is my name?",
"where do I live?",
"where is my family from?",
"where have I travelled?",
"what do I do for work?",
};
foreach (var q in questions)
{
var response = await memory.SearchAsync(MemoryCollectionName, q).FirstOrDefaultAsync();
Console.WriteLine("Q: " + q);
Console.WriteLine("A: " + response?.Relevance.ToString() + "\t" + response?.Metadata.Text);
}
通过上面的代码,我们搜索并打印了一些问题的答案,这里我们使用的是 SearchAsync
方法,指定了集合名称和问题文本。该方法对问题进行了一些筛选,默认只返回最相关的一个答案,并且要求相关性至少为 0.7。
在运行后,我们即可在 Qdrant 的 Web 界面上看到相关的向量数据:
3. 总结
在Semantic Kernel中使用Kernel Memory服务和Qdrant向量数据库可以极大地提高数据的存储和检索效率。通过灵活的数据处理流程和强大的查询功能,可以轻松地在大量的数据中找到最相关的信息。这对于构建高效的AI系统来说,是非常重要的。