From 4ecfe62ff106e63138dbc938027eec8d825e52a3 Mon Sep 17 00:00:00 2001 From: Luca Schiavone <16616566+Overvolted@users.noreply.github.com> Date: Tue, 3 Nov 2026 09:04:32 +0100 Subject: [PATCH] samples: add Milvus vector store examples --- dotnet/Directory.Packages.props | 1 + dotnet/samples/Concepts/Concepts.csproj | 1 + .../VectorStoreFixtures/VectorStoreInfra.cs | 38 ++++++++ .../VectorStoreMilvusContainerFixture.cs | 62 +++++++++++++ ...e_VectorSearch_MultiStore_AzureAISearch.cs | 1 + ...torStore_VectorSearch_MultiStore_Common.cs | 1 + ...rStore_VectorSearch_MultiStore_InMemory.cs | 1 + ...torStore_VectorSearch_MultiStore_Milvus.cs | 86 +++++++++++++++++++ ...rStore_VectorSearch_MultiStore_Postgres.cs | 1 + ...torStore_VectorSearch_MultiStore_Qdrant.cs | 1 + ...ctorStore_VectorSearch_MultiStore_Redis.cs | 1 + .../Options/ApplicationConfig.cs | 6 ++ .../VectorStoreRAG/Options/MilvusConfig.cs | 24 ++++++ .../samples/Demos/VectorStoreRAG/Program.cs | 22 +++++ dotnet/samples/Demos/VectorStoreRAG/README.md | 21 +++++ .../VectorStoreRAG/VectorStoreRAG.csproj | 1 + .../Demos/VectorStoreRAG/appsettings.json | 7 ++ 17 files changed, 275 insertions(+) create mode 100644 dotnet/samples/Concepts/Memory/VectorStoreFixtures/VectorStoreMilvusContainerFixture.cs create mode 100644 dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_Milvus.cs create mode 100644 dotnet/samples/Demos/VectorStoreRAG/Options/MilvusConfig.cs diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props index 19688b4333d7..aa3ed6481845 100644 --- a/dotnet/Directory.Packages.props +++ b/dotnet/Directory.Packages.props @@ -188,6 +188,7 @@ + diff --git a/dotnet/samples/Concepts/Concepts.csproj b/dotnet/samples/Concepts/Concepts.csproj index 9633d7893ff9..589f0d069292 100644 --- a/dotnet/samples/Concepts/Concepts.csproj +++ b/dotnet/samples/Concepts/Concepts.csproj @@ -54,6 +54,7 @@ + diff --git a/dotnet/samples/Concepts/Memory/VectorStoreFixtures/VectorStoreInfra.cs b/dotnet/samples/Concepts/Memory/VectorStoreFixtures/VectorStoreInfra.cs index 77c5e8a55db9..d1742de25395 100644 --- a/dotnet/samples/Concepts/Memory/VectorStoreFixtures/VectorStoreInfra.cs +++ b/dotnet/samples/Concepts/Memory/VectorStoreFixtures/VectorStoreInfra.cs @@ -10,6 +10,44 @@ namespace Memory.VectorStoreFixtures; /// internal static class VectorStoreInfra { + /// + /// Set up a standalone Milvus container with embedded etcd and local storage. + /// + /// The docker client to create the container with. + /// The id of the container. + public static async Task SetupMilvusContainerAsync(DockerClient client) + { + await client.Images.CreateImageAsync( + new ImagesCreateParameters + { + FromImage = "milvusdb/milvus", + Tag = "v2.6.25", + }, + null, + new Progress()); + + var container = await client.Containers.CreateContainerAsync(new CreateContainerParameters() + { + Image = "milvusdb/milvus:v2.6.25", + Cmd = ["milvus", "run", "standalone"], + Env = ["DEPLOY_MODE=STANDALONE", "ETCD_USE_EMBED=true", "COMMON_STORAGETYPE=local"], + HostConfig = new HostConfig() + { + PortBindings = new Dictionary> + { + { "19530", new List { new() { HostPort = "19530" } } }, + }, + }, + ExposedPorts = new Dictionary + { + { "19530", default }, + }, + }); + + await client.Containers.StartContainerAsync(container.ID, new ContainerStartParameters()); + return container.ID; + } + /// /// Setup the postgres pgvector container by pulling the image and running it. /// diff --git a/dotnet/samples/Concepts/Memory/VectorStoreFixtures/VectorStoreMilvusContainerFixture.cs b/dotnet/samples/Concepts/Memory/VectorStoreFixtures/VectorStoreMilvusContainerFixture.cs new file mode 100644 index 000000000000..47b0cc06d25a --- /dev/null +++ b/dotnet/samples/Concepts/Memory/VectorStoreFixtures/VectorStoreMilvusContainerFixture.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Docker.DotNet; +using Grpc.Core; +using Milvus.Client; + +namespace Memory.VectorStoreFixtures; + +/// +/// Fixture that starts a Milvus container for the samples and deletes it afterward. +/// +public class VectorStoreMilvusContainerFixture : IAsyncLifetime +{ + private DockerClient? _dockerClient; + private string? _milvusContainerId; + + public Task InitializeAsync() => Task.CompletedTask; + + public async Task ManualInitializeAsync() + { + if (this._milvusContainerId is null) + { + using var dockerClientConfiguration = new DockerClientConfiguration(); + this._dockerClient = dockerClientConfiguration.CreateClient(); + this._milvusContainerId = await VectorStoreInfra.SetupMilvusContainerAsync(this._dockerClient); + } + + // Milvus must finish starting before the sample creates collections. + using var milvusClient = new MilvusClient("localhost"); + using var timeout = new CancellationTokenSource(TimeSpan.FromMinutes(2)); + while (true) + { + try + { + await milvusClient.ListCollectionsAsync(cancellationToken: timeout.Token); + return; + } + catch (RpcException) when (!timeout.IsCancellationRequested) + { + await Task.Delay(1000, timeout.Token); + } + } + } + + public async Task DisposeAsync() + { + if (this._dockerClient is not null) + { + try + { + if (this._milvusContainerId is not null) + { + await VectorStoreInfra.DeleteContainerAsync(this._dockerClient, this._milvusContainerId); + } + } + finally + { + this._dockerClient.Dispose(); + } + } + } +} diff --git a/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_AzureAISearch.cs b/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_AzureAISearch.cs index 81cd303017d6..b7136ec60182 100644 --- a/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_AzureAISearch.cs +++ b/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_AzureAISearch.cs @@ -22,6 +22,7 @@ namespace Memory; /// /// /// +/// /// /// To run this sample, you need an already existing Azure AI Search instance. /// To set your secrets use: diff --git a/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_Common.cs b/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_Common.cs index cf92aa577e3d..8dcd8b64c25c 100644 --- a/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_Common.cs +++ b/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_Common.cs @@ -16,6 +16,7 @@ namespace Memory; /// /// /// +/// /// /// The vector store to ingest data into. /// The service to use for generating embeddings. diff --git a/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_InMemory.cs b/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_InMemory.cs index f88f893c489d..d86773cc1028 100644 --- a/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_InMemory.cs +++ b/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_InMemory.cs @@ -20,6 +20,7 @@ namespace Memory; /// /// /// +/// /// public class VectorStore_VectorSearch_MultiStore_InMemory(ITestOutputHelper output) : BaseTest(output) { diff --git a/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_Milvus.cs b/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_Milvus.cs new file mode 100644 index 000000000000..4e2cf1ee0deb --- /dev/null +++ b/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_Milvus.cs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Azure.AI.OpenAI; +using Azure.Identity; +using CommunityToolkit.VectorData.Milvus; +using Memory.VectorStoreFixtures; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.SemanticKernel; +using Milvus.Client; + +namespace Memory; + +/// +/// An example showing how to use common code, that can work with any vector database, with a Milvus database. +/// The common code is in the class. +/// The common code ingests data into the vector store and then searches over that data. +/// This example is part of a set of examples each showing a different vector database. +/// +/// For other databases, see the following classes: +/// +/// +/// +/// +/// +/// +/// To run this sample, you need a local instance of Docker running, since the associated fixture will try and start a Milvus container in the local docker instance. +/// +public class VectorStore_VectorSearch_MultiStore_Milvus(ITestOutputHelper output, VectorStoreMilvusContainerFixture milvusFixture) : BaseTest(output), IClassFixture +{ + [Fact] + public async Task ExampleWithDIAsync() + { + // Use the kernel for DI purposes. + var kernelBuilder = Kernel + .CreateBuilder(); + + // Register an embedding generation service with the DI container. + kernelBuilder.AddAzureOpenAIEmbeddingGenerator( + deploymentName: TestConfiguration.AzureOpenAIEmbeddings.DeploymentName, + endpoint: TestConfiguration.AzureOpenAIEmbeddings.Endpoint, + credential: new AzureCliCredential(), + dimensions: 1536); + + // Initialize the Milvus docker container via the fixtures and register the Milvus VectorStore. + await milvusFixture.ManualInitializeAsync(); + kernelBuilder.Services.AddMilvusVectorStore("localhost", https: false); + + // Register the test output helper common processor with the DI container. + kernelBuilder.Services.AddSingleton(this.Output); + kernelBuilder.Services.AddTransient(); + + // Build the kernel. + var kernel = kernelBuilder.Build(); + + // Build a common processor object using the DI container. + var processor = kernel.GetRequiredService(); + + // Run the process and pass a key generator function to it, to generate unique record keys. + // The key generator function is required, since different vector stores may require different key types. + // E.g. Milvus supports Guid, long and string keys, but others may support strings only. + await processor.IngestDataAndSearchAsync("skglossaryWithDI", () => Guid.NewGuid()); + } + + [Fact] + public async Task ExampleWithoutDIAsync() + { + // Create an embedding generation service. + var embeddingGenerator = new AzureOpenAIClient(new Uri(TestConfiguration.AzureOpenAIEmbeddings.Endpoint), new AzureCliCredential()) + .GetEmbeddingClient(TestConfiguration.AzureOpenAIEmbeddings.DeploymentName) + .AsIEmbeddingGenerator(1536); + + // Initialize the Milvus docker container via the fixtures and construct the Milvus VectorStore. + await milvusFixture.ManualInitializeAsync(); + var milvusClient = new MilvusClient("localhost"); + using var vectorStore = new MilvusVectorStore(milvusClient, ownsClient: true); + + // Create the common processor that works for any vector store. + var processor = new VectorStore_VectorSearch_MultiStore_Common(vectorStore, embeddingGenerator, this.Output); + + // Run the process and pass a key generator function to it, to generate unique record keys. + // The key generator function is required, since different vector stores may require different key types. + // E.g. Milvus supports Guid, long and string keys, but others may support strings only. + await processor.IngestDataAndSearchAsync("skglossaryWithoutDI", () => Guid.NewGuid()); + } +} diff --git a/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_Postgres.cs b/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_Postgres.cs index 3995ba486c87..164af5965e78 100644 --- a/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_Postgres.cs +++ b/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_Postgres.cs @@ -21,6 +21,7 @@ namespace Memory; /// /// /// +/// /// /// To run this sample, you need a local instance of Docker running, since the associated fixture will try and start a Postgres container in the local docker instance. /// diff --git a/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_Qdrant.cs b/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_Qdrant.cs index 3640c4ba7330..24952b22b86a 100644 --- a/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_Qdrant.cs +++ b/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_Qdrant.cs @@ -22,6 +22,7 @@ namespace Memory; /// /// /// +/// /// /// To run this sample, you need a local instance of Docker running, since the associated fixture will try and start a Qdrant container in the local docker instance. /// diff --git a/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_Redis.cs b/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_Redis.cs index ae8e2e1d2bab..5a15dc1fe247 100644 --- a/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_Redis.cs +++ b/dotnet/samples/Concepts/Memory/VectorStore_VectorSearch_MultiStore_Redis.cs @@ -22,6 +22,7 @@ namespace Memory; /// /// /// +/// /// /// Redis supports two record storage types: Json and HashSet. /// Note the use of the enum to specify the preferred storage type. diff --git a/dotnet/samples/Demos/VectorStoreRAG/Options/ApplicationConfig.cs b/dotnet/samples/Demos/VectorStoreRAG/Options/ApplicationConfig.cs index 3b9658419ecd..96fef043d7c0 100644 --- a/dotnet/samples/Demos/VectorStoreRAG/Options/ApplicationConfig.cs +++ b/dotnet/samples/Demos/VectorStoreRAG/Options/ApplicationConfig.cs @@ -17,6 +17,7 @@ internal sealed class ApplicationConfig private readonly AzureAISearchConfig _azureAISearchConfig = new(); private readonly CosmosConfig _azureDocumentDBConfig = new(); private readonly CosmosConfig _cosmosNoSqlConfig = new(); + private readonly MilvusConfig _milvusConfig = new(); private readonly QdrantConfig _qdrantConfig = new(); private readonly RedisConfig _redisConfig = new(); private readonly WeaviateConfig _weaviateConfig = new(); @@ -48,6 +49,9 @@ public ApplicationConfig(ConfigurationManager configurationManager) configurationManager .GetRequiredSection($"VectorStores:{CosmosConfig.NoSqlConfigSectionName}") .Bind(this._cosmosNoSqlConfig); + configurationManager + .GetRequiredSection($"VectorStores:{MilvusConfig.ConfigSectionName}") + .Bind(this._milvusConfig); configurationManager .GetRequiredSection($"VectorStores:{QdrantConfig.ConfigSectionName}") .Bind(this._qdrantConfig); @@ -75,6 +79,8 @@ public ApplicationConfig(ConfigurationManager configurationManager) public CosmosConfig CosmosNoSqlConfig => this._cosmosNoSqlConfig; + public MilvusConfig MilvusConfig => this._milvusConfig; + public QdrantConfig QdrantConfig => this._qdrantConfig; public RedisConfig RedisConfig => this._redisConfig; diff --git a/dotnet/samples/Demos/VectorStoreRAG/Options/MilvusConfig.cs b/dotnet/samples/Demos/VectorStoreRAG/Options/MilvusConfig.cs new file mode 100644 index 000000000000..c9c993396086 --- /dev/null +++ b/dotnet/samples/Demos/VectorStoreRAG/Options/MilvusConfig.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.ComponentModel.DataAnnotations; + +namespace VectorStoreRAG.Options; + +/// +/// Milvus service settings. +/// +internal sealed class MilvusConfig +{ + public const string ConfigSectionName = "Milvus"; + + [Required] + public string Host { get; set; } = string.Empty; + + public int Port { get; set; } = 19530; + + public bool Https { get; set; } = true; + + public string? ApiKey { get; set; } + + public string? Database { get; set; } +} diff --git a/dotnet/samples/Demos/VectorStoreRAG/Program.cs b/dotnet/samples/Demos/VectorStoreRAG/Program.cs index e7382eb9e71e..4bfd3d0aaa6d 100644 --- a/dotnet/samples/Demos/VectorStoreRAG/Program.cs +++ b/dotnet/samples/Demos/VectorStoreRAG/Program.cs @@ -92,6 +92,27 @@ kernelBuilder.Services.AddInMemoryVectorStoreRecordCollection>( appConfig.RagConfig.CollectionName); break; + case "Milvus": + if (string.IsNullOrWhiteSpace(appConfig.MilvusConfig.ApiKey)) + { + kernelBuilder.Services.AddMilvusCollection>( + appConfig.RagConfig.CollectionName, + appConfig.MilvusConfig.Host, + port: appConfig.MilvusConfig.Port, + https: appConfig.MilvusConfig.Https, + database: appConfig.MilvusConfig.Database); + } + else + { + kernelBuilder.Services.AddMilvusCollection>( + appConfig.RagConfig.CollectionName, + appConfig.MilvusConfig.Host, + apiKey: appConfig.MilvusConfig.ApiKey, + port: appConfig.MilvusConfig.Port, + https: appConfig.MilvusConfig.Https, + database: appConfig.MilvusConfig.Database); + } + break; case "Qdrant": kernelBuilder.Services.AddQdrantCollection>( appConfig.RagConfig.CollectionName, @@ -123,6 +144,7 @@ case "AzureDocumentDB": case "CosmosNoSql": case "InMemory": + case "Milvus": case "Redis": RegisterServices(builder, kernelBuilder, appConfig); break; diff --git a/dotnet/samples/Demos/VectorStoreRAG/README.md b/dotnet/samples/Demos/VectorStoreRAG/README.md index 95f4b1909318..6ab7f6ece953 100644 --- a/dotnet/samples/Demos/VectorStoreRAG/README.md +++ b/dotnet/samples/Demos/VectorStoreRAG/README.md @@ -12,6 +12,7 @@ The sample can be configured in various ways: 1. AzureDocumentDB 1. CosmosNoSql 1. InMemory + 1. Milvus 1. Qdrant 1. Redis 1. Weaviate @@ -130,6 +131,26 @@ dotnet user-secrets set "VectorStores:CosmosNoSql:ConnectionString" "" ``` +### Milvus + +To use Milvus, set `Rag:VectorStoreType` to `Milvus`. The provider requires Milvus 2.6 or later. + +Start a local standalone instance with embedded etcd and local storage using Docker: + +```cli +docker run -d --name milvus -p 19530:19530 -e DEPLOY_MODE=STANDALONE -e ETCD_USE_EMBED=true -e COMMON_STORAGETYPE=local milvusdb/milvus:v2.6.25 milvus run standalone +``` + +The default sample settings connect to this local instance without TLS or authentication. For a remote TLS endpoint, configure the host, port, database, and API key as needed: + +```cli +dotnet user-secrets set "VectorStores:Milvus:Host" "" +dotnet user-secrets set "VectorStores:Milvus:Port" "19530" +dotnet user-secrets set "VectorStores:Milvus:Https" "true" +dotnet user-secrets set "VectorStores:Milvus:Database" "default" +dotnet user-secrets set "VectorStores:Milvus:ApiKey" "" +``` + ### Qdrant If you want to use Qdrant as your vector store, you will need to have an instance of Qdrant available. diff --git a/dotnet/samples/Demos/VectorStoreRAG/VectorStoreRAG.csproj b/dotnet/samples/Demos/VectorStoreRAG/VectorStoreRAG.csproj index 6e9d9f62fc7b..5a5864691216 100644 --- a/dotnet/samples/Demos/VectorStoreRAG/VectorStoreRAG.csproj +++ b/dotnet/samples/Demos/VectorStoreRAG/VectorStoreRAG.csproj @@ -21,6 +21,7 @@ + diff --git a/dotnet/samples/Demos/VectorStoreRAG/appsettings.json b/dotnet/samples/Demos/VectorStoreRAG/appsettings.json index 8ba1a6952153..292dba325f1e 100644 --- a/dotnet/samples/Demos/VectorStoreRAG/appsettings.json +++ b/dotnet/samples/Demos/VectorStoreRAG/appsettings.json @@ -37,6 +37,13 @@ "ConnectionString": "", "DatabaseName": "" }, + "Milvus": { + "Host": "localhost", + "Port": 19530, + "Https": false, + "ApiKey": null, + "Database": "default" + }, "Qdrant": { "Host": "localhost", "Port": 6334,