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,