From a5f168d042bc8bfd7425468dbfaba439706a31e7 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 25 Jun 2025 05:43:55 +0000
Subject: [PATCH 1/7] Initial plan
From 5f1985c06727f15013b4a00e599adfc566b9104d Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 25 Jun 2025 05:50:48 +0000
Subject: [PATCH 2/7] Add AddKeyedOllamaApiClient overloads with custom service
key support
Co-authored-by: aaronpowell <434140+aaronpowell@users.noreply.github.com>
---
.../AspireOllamaApiClientBuilder.cs | 4 +-
.../AspireOllamaChatClientExtensions.cs | 2 +-
...spireOllamaEmbeddingGeneratorExtensions.cs | 2 +-
.../AspireOllamaSharpExtensions.cs | 43 ++++++++++++++--
.../OllamaApiClientTests.cs | 49 +++++++++++++++++++
.../OllamaSharpIChatClientTests.cs | 23 +++++++++
6 files changed, 115 insertions(+), 8 deletions(-)
diff --git a/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaApiClientBuilder.cs b/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaApiClientBuilder.cs
index fa6609e7..2580ab33 100644
--- a/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaApiClientBuilder.cs
+++ b/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaApiClientBuilder.cs
@@ -8,7 +8,7 @@ namespace Microsoft.Extensions.Hosting;
/// The with which services are being registered.
/// The service key used to register the service, if any.
/// A flag to indicate whether tracing should be disabled.
-public class AspireOllamaApiClientBuilder(IHostApplicationBuilder hostBuilder, string serviceKey, bool disableTracing)
+public class AspireOllamaApiClientBuilder(IHostApplicationBuilder hostBuilder, object? serviceKey, bool disableTracing)
{
///
/// The host application builder used to configure the application.
@@ -18,7 +18,7 @@ public class AspireOllamaApiClientBuilder(IHostApplicationBuilder hostBuilder, s
///
/// Gets the service key used to register the service, if any.
///
- public string ServiceKey { get; } = serviceKey;
+ public object? ServiceKey { get; } = serviceKey;
///
/// Gets a flag indicating whether tracing should be disabled.
diff --git a/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaChatClientExtensions.cs b/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaChatClientExtensions.cs
index ea7a8b53..559af318 100644
--- a/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaChatClientExtensions.cs
+++ b/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaChatClientExtensions.cs
@@ -32,7 +32,7 @@ public static ChatClientBuilder AddKeyedChatClient(
this AspireOllamaApiClientBuilder builder)
{
ArgumentNullException.ThrowIfNull(builder, nameof(builder));
- ArgumentException.ThrowIfNullOrEmpty(builder.ServiceKey, nameof(builder.ServiceKey));
+ ArgumentNullException.ThrowIfNull(builder.ServiceKey, nameof(builder.ServiceKey));
return builder.HostBuilder.Services.AddKeyedChatClient(
builder.ServiceKey,
diff --git a/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaEmbeddingGeneratorExtensions.cs b/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaEmbeddingGeneratorExtensions.cs
index d7b11ee7..5e308371 100644
--- a/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaEmbeddingGeneratorExtensions.cs
+++ b/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaEmbeddingGeneratorExtensions.cs
@@ -31,7 +31,7 @@ public static EmbeddingGeneratorBuilder> AddKeyedEmbedd
this AspireOllamaApiClientBuilder builder)
{
ArgumentNullException.ThrowIfNull(builder, nameof(builder));
- ArgumentException.ThrowIfNullOrEmpty(builder.ServiceKey, nameof(builder.ServiceKey));
+ ArgumentNullException.ThrowIfNull(builder.ServiceKey, nameof(builder.ServiceKey));
return builder.HostBuilder.Services.AddKeyedEmbeddingGenerator(
builder.ServiceKey,
diff --git a/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaSharpExtensions.cs b/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaSharpExtensions.cs
index 88da797c..7b7960c8 100644
--- a/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaSharpExtensions.cs
+++ b/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaSharpExtensions.cs
@@ -43,6 +43,37 @@ public static AspireOllamaApiClientBuilder AddKeyedOllamaApiClient(this IHostApp
return AddOllamaClientInternal(builder, $"{DefaultConfigSectionName}:{connectionName}", connectionName, serviceKey: connectionName, configureSettings: configureSettings);
}
+ ///
+ /// Adds services to the container using the specified .
+ ///
+ /// The to read config from and add services to.
+ /// A unique key that identifies this instance of the Ollama client service.
+ /// A name used to retrieve the connection string from the ConnectionStrings configuration section.
+ /// An optional delegate that can be used for customizing options. It's invoked after the settings are read from the configuration.
+ /// Thrown when no Ollama endpoint is provided.
+ public static AspireOllamaApiClientBuilder AddKeyedOllamaApiClient(this IHostApplicationBuilder builder, object serviceKey, string connectionName, Action? configureSettings = null)
+ {
+ ArgumentNullException.ThrowIfNull(serviceKey, nameof(serviceKey));
+ ArgumentException.ThrowIfNullOrWhiteSpace(connectionName, nameof(connectionName));
+ ArgumentNullException.ThrowIfNull(builder, nameof(builder));
+ return AddOllamaClientInternal(builder, $"{DefaultConfigSectionName}:{connectionName}", connectionName, serviceKey: serviceKey, configureSettings: configureSettings);
+ }
+
+ ///
+ /// Adds services to the container using the specified .
+ ///
+ /// The to read config from and add services to.
+ /// A unique key that identifies this instance of the Ollama client service.
+ /// The settings required to configure the .
+ /// Thrown when no Ollama endpoint is provided.
+ public static AspireOllamaApiClientBuilder AddKeyedOllamaApiClient(this IHostApplicationBuilder builder, object serviceKey, OllamaSharpSettings settings)
+ {
+ ArgumentNullException.ThrowIfNull(serviceKey, nameof(serviceKey));
+ ArgumentNullException.ThrowIfNull(builder, nameof(builder));
+ ArgumentNullException.ThrowIfNull(settings, nameof(settings));
+ return AddOllamaClientInternal(builder, DefaultConfigSectionName, serviceKey.ToString() ?? "default", serviceKey: serviceKey, configureSettings: null, settings: settings);
+ }
+
///
/// Adds and services to the container.
///
@@ -105,11 +136,15 @@ private static AspireOllamaApiClientBuilder AddOllamaClientInternal(
IHostApplicationBuilder builder,
string configurationSectionName,
string connectionName,
- string? serviceKey = null,
- Action? configureSettings = null)
+ object? serviceKey = null,
+ Action? configureSettings = null,
+ OllamaSharpSettings? settings = null)
{
- OllamaSharpSettings settings = new();
- builder.Configuration.GetSection(configurationSectionName).Bind(settings);
+ settings ??= new();
+ if (string.IsNullOrEmpty(settings.Endpoint?.ToString()))
+ {
+ builder.Configuration.GetSection(configurationSectionName).Bind(settings);
+ }
if (builder.Configuration.GetConnectionString(connectionName) is string connectionString)
{
diff --git a/tests/CommunityToolkit.Aspire.OllamaSharp.Tests/OllamaApiClientTests.cs b/tests/CommunityToolkit.Aspire.OllamaSharp.Tests/OllamaApiClientTests.cs
index bcb143e9..0c10cab6 100644
--- a/tests/CommunityToolkit.Aspire.OllamaSharp.Tests/OllamaApiClientTests.cs
+++ b/tests/CommunityToolkit.Aspire.OllamaSharp.Tests/OllamaApiClientTests.cs
@@ -124,6 +124,55 @@ public void CanSetMultipleKeyedClients()
Assert.NotEqual(client, client3);
}
+ [Fact]
+ public void CanSetMultipleKeyedClientsWithCustomServiceKeys()
+ {
+ var builder = Host.CreateEmptyApplicationBuilder(null);
+ builder.Configuration.AddInMemoryCollection([
+ new KeyValuePair("ConnectionStrings:Ollama", $"Endpoint={Endpoint}"),
+ new KeyValuePair("ConnectionStrings:Ollama2", "Endpoint=https://localhost:5002/"),
+ new KeyValuePair("ConnectionStrings:Ollama3", "Endpoint=https://localhost:5003/")
+ ]);
+
+ // Use custom service keys instead of connection names
+ builder.AddKeyedOllamaApiClient("ChatModel", "Ollama");
+ builder.AddKeyedOllamaApiClient("VisionModel", "Ollama2");
+ builder.AddKeyedOllamaApiClient("EmbeddingModel", "Ollama3");
+
+ using var host = builder.Build();
+ var chatClient = host.Services.GetRequiredKeyedService("ChatModel");
+ var visionClient = host.Services.GetRequiredKeyedService("VisionModel");
+ var embeddingClient = host.Services.GetRequiredKeyedService("EmbeddingModel");
+
+ Assert.Equal(Endpoint, chatClient.Uri);
+ Assert.Equal("https://localhost:5002/", visionClient.Uri?.ToString());
+ Assert.Equal("https://localhost:5003/", embeddingClient.Uri?.ToString());
+
+ Assert.NotEqual(chatClient, visionClient);
+ Assert.NotEqual(chatClient, embeddingClient);
+ Assert.NotEqual(visionClient, embeddingClient);
+ }
+
+ [Fact]
+ public void CanSetKeyedClientWithSettingsOverload()
+ {
+ var builder = Host.CreateEmptyApplicationBuilder(null);
+
+ var settings = new OllamaSharpSettings
+ {
+ Endpoint = Endpoint,
+ SelectedModel = "testmodel"
+ };
+
+ builder.AddKeyedOllamaApiClient("TestService", settings);
+
+ using var host = builder.Build();
+ var client = host.Services.GetRequiredKeyedService("TestService");
+
+ Assert.Equal(Endpoint, client.Uri);
+ Assert.Equal("testmodel", client.SelectedModel);
+ }
+
[Fact]
public void RegisteringChatClientAndEmbeddingGeneratorReturnsCorrectModelForServices()
{
diff --git a/tests/CommunityToolkit.Aspire.OllamaSharp.Tests/OllamaSharpIChatClientTests.cs b/tests/CommunityToolkit.Aspire.OllamaSharp.Tests/OllamaSharpIChatClientTests.cs
index c2900960..2d092014 100644
--- a/tests/CommunityToolkit.Aspire.OllamaSharp.Tests/OllamaSharpIChatClientTests.cs
+++ b/tests/CommunityToolkit.Aspire.OllamaSharp.Tests/OllamaSharpIChatClientTests.cs
@@ -150,6 +150,29 @@ public void CanChainUseMethodsCorrectly()
Assert.IsType(GetInnerClient(otelClient), exactMatch: false);
}
+ [Fact]
+ public void CanSetMultipleKeyedChatClientsWithCustomServiceKeys()
+ {
+ var builder = Host.CreateEmptyApplicationBuilder(null);
+ builder.Configuration.AddInMemoryCollection([
+ new KeyValuePair("ConnectionStrings:Ollama", $"Endpoint={Endpoint}"),
+ new KeyValuePair("ConnectionStrings:Ollama2", "Endpoint=https://localhost:5002/")
+ ]);
+
+ // Use custom service keys for different chat clients
+ builder.AddKeyedOllamaApiClient("ChatModel", "Ollama").AddKeyedChatClient();
+ builder.AddKeyedOllamaApiClient("VisionModel", "Ollama2").AddKeyedChatClient();
+
+ using var host = builder.Build();
+ var chatClient = host.Services.GetRequiredKeyedService("ChatModel");
+ var visionClient = host.Services.GetRequiredKeyedService("VisionModel");
+
+ Assert.Equal(Endpoint, chatClient.GetService()?.ProviderUri);
+ Assert.Equal("https://localhost:5002/", visionClient.GetService()?.ProviderUri?.ToString());
+
+ Assert.NotEqual(chatClient, visionClient);
+ }
+
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "get_InnerClient")]
private static extern IChatClient GetInnerClient(DelegatingChatClient client);
}
From c084865ffc69230700abe5a5002f63e28e5d3a27 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 25 Jun 2025 05:51:51 +0000
Subject: [PATCH 3/7] Update API surface and add usage example for custom
service keys
Co-authored-by: aaronpowell <434140+aaronpowell@users.noreply.github.com>
---
.../api/CommunityToolkit.Aspire.OllamaSharp.cs | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/src/CommunityToolkit.Aspire.OllamaSharp/api/CommunityToolkit.Aspire.OllamaSharp.cs b/src/CommunityToolkit.Aspire.OllamaSharp/api/CommunityToolkit.Aspire.OllamaSharp.cs
index 78af13a3..ec7311d3 100644
--- a/src/CommunityToolkit.Aspire.OllamaSharp/api/CommunityToolkit.Aspire.OllamaSharp.cs
+++ b/src/CommunityToolkit.Aspire.OllamaSharp/api/CommunityToolkit.Aspire.OllamaSharp.cs
@@ -26,13 +26,13 @@ namespace Microsoft.Extensions.Hosting
{
public partial class AspireOllamaApiClientBuilder
{
- public AspireOllamaApiClientBuilder(IHostApplicationBuilder hostBuilder, string serviceKey, bool disableTracing) { }
+ public AspireOllamaApiClientBuilder(IHostApplicationBuilder hostBuilder, object? serviceKey, bool disableTracing) { }
public bool DisableTracing { get { throw null; } }
public IHostApplicationBuilder HostBuilder { get { throw null; } }
- public string ServiceKey { get { throw null; } }
+ public object? ServiceKey { get { throw null; } }
}
public static partial class AspireOllamaChatClientExtensions
@@ -53,6 +53,10 @@ public static partial class AspireOllamaSharpExtensions
{
public static AspireOllamaApiClientBuilder AddKeyedOllamaApiClient(this IHostApplicationBuilder builder, string connectionName, System.Action? configureSettings = null) { throw null; }
+ public static AspireOllamaApiClientBuilder AddKeyedOllamaApiClient(this IHostApplicationBuilder builder, object serviceKey, string connectionName, System.Action? configureSettings = null) { throw null; }
+
+ public static AspireOllamaApiClientBuilder AddKeyedOllamaApiClient(this IHostApplicationBuilder builder, object serviceKey, CommunityToolkit.Aspire.OllamaSharp.OllamaSharpSettings settings) { throw null; }
+
[System.Obsolete("This approach to registering IChatClient is deprecated, use AddKeyedOllamaApiClient().AddChatClient() instead.")]
public static void AddKeyedOllamaSharpChatClient(this IHostApplicationBuilder builder, string connectionName, System.Action? configureSettings = null) { }
From b28d706b9faedab7822bacf17712b3da3452ca7a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 25 Jun 2025 05:53:33 +0000
Subject: [PATCH 4/7] Add comprehensive test demonstrating the exact issue
resolution
Co-authored-by: aaronpowell <434140+aaronpowell@users.noreply.github.com>
---
.../OllamaApiClientTests.cs | 37 +++++++++++++++++++
1 file changed, 37 insertions(+)
diff --git a/tests/CommunityToolkit.Aspire.OllamaSharp.Tests/OllamaApiClientTests.cs b/tests/CommunityToolkit.Aspire.OllamaSharp.Tests/OllamaApiClientTests.cs
index 0c10cab6..291de588 100644
--- a/tests/CommunityToolkit.Aspire.OllamaSharp.Tests/OllamaApiClientTests.cs
+++ b/tests/CommunityToolkit.Aspire.OllamaSharp.Tests/OllamaApiClientTests.cs
@@ -173,6 +173,43 @@ public void CanSetKeyedClientWithSettingsOverload()
Assert.Equal("testmodel", client.SelectedModel);
}
+ [Fact]
+ public void CanUseSameConnectionWithDifferentServiceKeys()
+ {
+ // This test demonstrates the main use case from the issue:
+ // Using the same connection but different service keys for different models
+ var builder = Host.CreateEmptyApplicationBuilder(null);
+ builder.Configuration.AddInMemoryCollection([
+ new KeyValuePair("ConnectionStrings:LocalAI", $"Endpoint={Endpoint}")
+ ]);
+
+ // Same connection, different service keys and models
+ builder.AddKeyedOllamaApiClient("ChatModel", "LocalAI", settings =>
+ {
+ settings.SelectedModel = "llama3.2";
+ });
+
+ builder.AddKeyedOllamaApiClient("VisionModel", "LocalAI", settings =>
+ {
+ settings.SelectedModel = "llava";
+ });
+
+ using var host = builder.Build();
+ var chatClient = host.Services.GetRequiredKeyedService("ChatModel");
+ var visionClient = host.Services.GetRequiredKeyedService("VisionModel");
+
+ // Both use the same endpoint
+ Assert.Equal(Endpoint, chatClient.Uri);
+ Assert.Equal(Endpoint, visionClient.Uri);
+
+ // But have different models
+ Assert.Equal("llama3.2", chatClient.SelectedModel);
+ Assert.Equal("llava", visionClient.SelectedModel);
+
+ // And are different instances
+ Assert.NotEqual(chatClient, visionClient);
+ }
+
[Fact]
public void RegisteringChatClientAndEmbeddingGeneratorReturnsCorrectModelForServices()
{
From 2c95517c1e4479b65289d2b9af2a54360da459e7 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 26 Jun 2025 02:28:13 +0000
Subject: [PATCH 5/7] Add custom service key support to AddKeyedChatClient and
AddKeyedEmbeddingGenerator
Co-authored-by: aaronpowell <434140+aaronpowell@users.noreply.github.com>
---
.../AspireOllamaChatClientExtensions.cs | 18 ++++++++
...spireOllamaEmbeddingGeneratorExtensions.cs | 18 ++++++++
.../CommunityToolkit.Aspire.OllamaSharp.cs | 4 ++
.../OllamaSharpIChatClientTests.cs | 46 +++++++++++++++++++
.../OllamaSharpIEmbeddingGeneratorTests.cs | 46 +++++++++++++++++++
5 files changed, 132 insertions(+)
diff --git a/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaChatClientExtensions.cs b/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaChatClientExtensions.cs
index 559af318..a5abb7d2 100644
--- a/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaChatClientExtensions.cs
+++ b/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaChatClientExtensions.cs
@@ -39,6 +39,24 @@ public static ChatClientBuilder AddKeyedChatClient(
services => CreateInnerChatClient(services, builder));
}
+ ///
+ /// Registers a keyed singleton in the services provided by the using the specified service key.
+ ///
+ /// An .
+ /// The service key to use for registering the .
+ /// A that can be used to build a pipeline around the inner .
+ public static ChatClientBuilder AddKeyedChatClient(
+ this AspireOllamaApiClientBuilder builder,
+ object serviceKey)
+ {
+ ArgumentNullException.ThrowIfNull(builder, nameof(builder));
+ ArgumentNullException.ThrowIfNull(serviceKey, nameof(serviceKey));
+
+ return builder.HostBuilder.Services.AddKeyedChatClient(
+ serviceKey,
+ services => CreateInnerChatClient(services, builder));
+ }
+
///
/// Wrap the in a telemetry client if tracing is enabled.
/// Note that this doesn't use ".UseOpenTelemetry()" because the order of the clients would be incorrect.
diff --git a/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaEmbeddingGeneratorExtensions.cs b/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaEmbeddingGeneratorExtensions.cs
index 5e308371..9d2f9d43 100644
--- a/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaEmbeddingGeneratorExtensions.cs
+++ b/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaEmbeddingGeneratorExtensions.cs
@@ -38,6 +38,24 @@ public static EmbeddingGeneratorBuilder> AddKeyedEmbedd
services => CreateInnerEmbeddingGenerator(services, builder));
}
+ ///
+ /// Registers a keyed singleton in the services provided by the using the specified service key.
+ ///
+ /// An .
+ /// The service key to use for registering the .
+ /// A that can be used to build a pipeline around the inner .
+ public static EmbeddingGeneratorBuilder> AddKeyedEmbeddingGenerator(
+ this AspireOllamaApiClientBuilder builder,
+ object serviceKey)
+ {
+ ArgumentNullException.ThrowIfNull(builder, nameof(builder));
+ ArgumentNullException.ThrowIfNull(serviceKey, nameof(serviceKey));
+
+ return builder.HostBuilder.Services.AddKeyedEmbeddingGenerator(
+ serviceKey,
+ services => CreateInnerEmbeddingGenerator(services, builder));
+ }
+
///
/// Wrap the in a telemetry client if tracing is enabled.
/// Note that this doesn't use ".UseOpenTelemetry()" because the order of the clients would be incorrect.
diff --git a/src/CommunityToolkit.Aspire.OllamaSharp/api/CommunityToolkit.Aspire.OllamaSharp.cs b/src/CommunityToolkit.Aspire.OllamaSharp/api/CommunityToolkit.Aspire.OllamaSharp.cs
index ec7311d3..2f851fbc 100644
--- a/src/CommunityToolkit.Aspire.OllamaSharp/api/CommunityToolkit.Aspire.OllamaSharp.cs
+++ b/src/CommunityToolkit.Aspire.OllamaSharp/api/CommunityToolkit.Aspire.OllamaSharp.cs
@@ -40,6 +40,8 @@ public static partial class AspireOllamaChatClientExtensions
public static AI.ChatClientBuilder AddChatClient(this AspireOllamaApiClientBuilder builder) { throw null; }
public static AI.ChatClientBuilder AddKeyedChatClient(this AspireOllamaApiClientBuilder builder) { throw null; }
+
+ public static AI.ChatClientBuilder AddKeyedChatClient(this AspireOllamaApiClientBuilder builder, object serviceKey) { throw null; }
}
public static partial class AspireOllamaEmbeddingGeneratorExtensions
@@ -47,6 +49,8 @@ public static partial class AspireOllamaEmbeddingGeneratorExtensions
public static AI.EmbeddingGeneratorBuilder> AddEmbeddingGenerator(this AspireOllamaApiClientBuilder builder) { throw null; }
public static AI.EmbeddingGeneratorBuilder> AddKeyedEmbeddingGenerator(this AspireOllamaApiClientBuilder builder) { throw null; }
+
+ public static AI.EmbeddingGeneratorBuilder> AddKeyedEmbeddingGenerator(this AspireOllamaApiClientBuilder builder, object serviceKey) { throw null; }
}
public static partial class AspireOllamaSharpExtensions
diff --git a/tests/CommunityToolkit.Aspire.OllamaSharp.Tests/OllamaSharpIChatClientTests.cs b/tests/CommunityToolkit.Aspire.OllamaSharp.Tests/OllamaSharpIChatClientTests.cs
index 2d092014..dc6c0d06 100644
--- a/tests/CommunityToolkit.Aspire.OllamaSharp.Tests/OllamaSharpIChatClientTests.cs
+++ b/tests/CommunityToolkit.Aspire.OllamaSharp.Tests/OllamaSharpIChatClientTests.cs
@@ -173,6 +173,52 @@ public void CanSetMultipleKeyedChatClientsWithCustomServiceKeys()
Assert.NotEqual(chatClient, visionClient);
}
+ [Fact]
+ public void CanSetMultipleChatClientsWithDifferentServiceKeys()
+ {
+ var builder = Host.CreateEmptyApplicationBuilder(null);
+ builder.Configuration.AddInMemoryCollection([
+ new KeyValuePair("ConnectionStrings:Ollama", $"Endpoint={Endpoint}")
+ ]);
+
+ // Use one Ollama API client with multiple chat clients using different service keys
+ builder.AddKeyedOllamaApiClient("OllamaKey", "Ollama")
+ .AddKeyedChatClient("ChatKey1")
+ .AddKeyedChatClient("ChatKey2");
+
+ using var host = builder.Build();
+ var chatClient1 = host.Services.GetRequiredKeyedService("ChatKey1");
+ var chatClient2 = host.Services.GetRequiredKeyedService("ChatKey2");
+
+ Assert.Equal(Endpoint, chatClient1.GetService()?.ProviderUri);
+ Assert.Equal(Endpoint, chatClient2.GetService()?.ProviderUri);
+
+ Assert.NotEqual(chatClient1, chatClient2);
+ }
+
+ [Fact]
+ public void CanMixChatClientsAndEmbeddingGeneratorsWithCustomServiceKeys()
+ {
+ var builder = Host.CreateEmptyApplicationBuilder(null);
+ builder.Configuration.AddInMemoryCollection([
+ new KeyValuePair("ConnectionStrings:Ollama", $"Endpoint={Endpoint}")
+ ]);
+
+ // Use one Ollama API client with both chat clients and embedding generators using different service keys
+ builder.AddKeyedOllamaApiClient("OllamaKey", "Ollama")
+ .AddKeyedChatClient("ChatKey1")
+ .AddKeyedChatClient("ChatKey2");
+
+ using var host = builder.Build();
+ var chatClient1 = host.Services.GetRequiredKeyedService("ChatKey1");
+ var chatClient2 = host.Services.GetRequiredKeyedService("ChatKey2");
+
+ Assert.Equal(Endpoint, chatClient1.GetService()?.ProviderUri);
+ Assert.Equal(Endpoint, chatClient2.GetService()?.ProviderUri);
+
+ Assert.NotEqual(chatClient1, chatClient2);
+ }
+
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "get_InnerClient")]
private static extern IChatClient GetInnerClient(DelegatingChatClient client);
}
diff --git a/tests/CommunityToolkit.Aspire.OllamaSharp.Tests/OllamaSharpIEmbeddingGeneratorTests.cs b/tests/CommunityToolkit.Aspire.OllamaSharp.Tests/OllamaSharpIEmbeddingGeneratorTests.cs
index 97c931ed..e3c6ff12 100644
--- a/tests/CommunityToolkit.Aspire.OllamaSharp.Tests/OllamaSharpIEmbeddingGeneratorTests.cs
+++ b/tests/CommunityToolkit.Aspire.OllamaSharp.Tests/OllamaSharpIEmbeddingGeneratorTests.cs
@@ -149,6 +149,52 @@ public void CanChainUseMethodsCorrectly()
Assert.IsType(GetInnerGenerator(otelClient), exactMatch: false);
}
+ [Fact]
+ public void CanSetMultipleEmbeddingGeneratorsWithDifferentServiceKeys()
+ {
+ var builder = Host.CreateEmptyApplicationBuilder(null);
+ builder.Configuration.AddInMemoryCollection([
+ new KeyValuePair("ConnectionStrings:Ollama", $"Endpoint={Endpoint}")
+ ]);
+
+ // Use one Ollama API client with multiple embedding generators using different service keys
+ builder.AddKeyedOllamaApiClient("OllamaKey", "Ollama")
+ .AddKeyedEmbeddingGenerator("EmbedKey1")
+ .AddKeyedEmbeddingGenerator("EmbedKey2");
+
+ using var host = builder.Build();
+ var embedGenerator1 = host.Services.GetRequiredKeyedService>>("EmbedKey1");
+ var embedGenerator2 = host.Services.GetRequiredKeyedService>>("EmbedKey2");
+
+ Assert.Equal(Endpoint, embedGenerator1.GetService()?.ProviderUri);
+ Assert.Equal(Endpoint, embedGenerator2.GetService()?.ProviderUri);
+
+ Assert.NotEqual(embedGenerator1, embedGenerator2);
+ }
+
+ [Fact]
+ public void CanMixChatClientsAndEmbeddingGeneratorsWithCustomServiceKeys()
+ {
+ var builder = Host.CreateEmptyApplicationBuilder(null);
+ builder.Configuration.AddInMemoryCollection([
+ new KeyValuePair("ConnectionStrings:Ollama", $"Endpoint={Endpoint}")
+ ]);
+
+ // Use one Ollama API client with both chat clients and embedding generators using different service keys
+ var ollamaBuilder = builder.AddKeyedOllamaApiClient("OllamaKey", "Ollama");
+ ollamaBuilder.AddKeyedChatClient("ChatKey");
+ ollamaBuilder.AddKeyedEmbeddingGenerator("EmbedKey");
+
+ using var host = builder.Build();
+ var chatClient = host.Services.GetRequiredKeyedService("ChatKey");
+ var embedGenerator = host.Services.GetRequiredKeyedService>>("EmbedKey");
+
+ Assert.Equal(Endpoint, chatClient.GetService()?.ProviderUri);
+ Assert.Equal(Endpoint, embedGenerator.GetService()?.ProviderUri);
+
+ Assert.NotEqual(chatClient, embedGenerator);
+ }
+
private static IEmbeddingGenerator GetInnerGenerator(DelegatingEmbeddingGenerator generator)
where TEmbedding : Embedding =>
(IEmbeddingGenerator)(generator.GetType()
From e5f1e83d44069b5772f3279721bd9b9d72f38937 Mon Sep 17 00:00:00 2001
From: Aaron Powell
Date: Fri, 27 Jun 2025 03:59:18 +0000
Subject: [PATCH 6/7] Bit of cleanup to how I'd prefer this done
---
.../AspireOllamaApiClientBuilder.cs | 4 +--
.../AspireOllamaChatClientExtensions.cs | 5 +--
...spireOllamaEmbeddingGeneratorExtensions.cs | 6 +---
.../AspireOllamaSharpExtensions.cs | 1 -
.../OllamaSharpIChatClientTests.cs | 22 ++++++-------
.../OllamaSharpIEmbeddingGeneratorTests.cs | 31 +++----------------
6 files changed, 19 insertions(+), 50 deletions(-)
diff --git a/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaApiClientBuilder.cs b/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaApiClientBuilder.cs
index 2580ab33..8d0f6e82 100644
--- a/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaApiClientBuilder.cs
+++ b/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaApiClientBuilder.cs
@@ -8,7 +8,7 @@ namespace Microsoft.Extensions.Hosting;
/// The with which services are being registered.
/// The service key used to register the service, if any.
/// A flag to indicate whether tracing should be disabled.
-public class AspireOllamaApiClientBuilder(IHostApplicationBuilder hostBuilder, object? serviceKey, bool disableTracing)
+public class AspireOllamaApiClientBuilder(IHostApplicationBuilder hostBuilder, object serviceKey, bool disableTracing)
{
///
/// The host application builder used to configure the application.
@@ -18,7 +18,7 @@ public class AspireOllamaApiClientBuilder(IHostApplicationBuilder hostBuilder, o
///
/// Gets the service key used to register the service, if any.
///
- public object? ServiceKey { get; } = serviceKey;
+ public object ServiceKey { get; } = serviceKey;
///
/// Gets a flag indicating whether tracing should be disabled.
diff --git a/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaChatClientExtensions.cs b/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaChatClientExtensions.cs
index a5abb7d2..adccd168 100644
--- a/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaChatClientExtensions.cs
+++ b/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaChatClientExtensions.cs
@@ -32,11 +32,8 @@ public static ChatClientBuilder AddKeyedChatClient(
this AspireOllamaApiClientBuilder builder)
{
ArgumentNullException.ThrowIfNull(builder, nameof(builder));
- ArgumentNullException.ThrowIfNull(builder.ServiceKey, nameof(builder.ServiceKey));
- return builder.HostBuilder.Services.AddKeyedChatClient(
- builder.ServiceKey,
- services => CreateInnerChatClient(services, builder));
+ return builder.AddKeyedChatClient(builder.ServiceKey);
}
///
diff --git a/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaEmbeddingGeneratorExtensions.cs b/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaEmbeddingGeneratorExtensions.cs
index 9d2f9d43..b68bf33e 100644
--- a/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaEmbeddingGeneratorExtensions.cs
+++ b/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaEmbeddingGeneratorExtensions.cs
@@ -31,11 +31,7 @@ public static EmbeddingGeneratorBuilder> AddKeyedEmbedd
this AspireOllamaApiClientBuilder builder)
{
ArgumentNullException.ThrowIfNull(builder, nameof(builder));
- ArgumentNullException.ThrowIfNull(builder.ServiceKey, nameof(builder.ServiceKey));
-
- return builder.HostBuilder.Services.AddKeyedEmbeddingGenerator(
- builder.ServiceKey,
- services => CreateInnerEmbeddingGenerator(services, builder));
+ return builder.AddKeyedEmbeddingGenerator(builder.ServiceKey);
}
///
diff --git a/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaSharpExtensions.cs b/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaSharpExtensions.cs
index 7b7960c8..6287f94c 100644
--- a/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaSharpExtensions.cs
+++ b/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaSharpExtensions.cs
@@ -2,7 +2,6 @@
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.DependencyInjection.Extensions;
using OllamaSharp;
using System.Data.Common;
diff --git a/tests/CommunityToolkit.Aspire.OllamaSharp.Tests/OllamaSharpIChatClientTests.cs b/tests/CommunityToolkit.Aspire.OllamaSharp.Tests/OllamaSharpIChatClientTests.cs
index dc6c0d06..b409e907 100644
--- a/tests/CommunityToolkit.Aspire.OllamaSharp.Tests/OllamaSharpIChatClientTests.cs
+++ b/tests/CommunityToolkit.Aspire.OllamaSharp.Tests/OllamaSharpIChatClientTests.cs
@@ -142,11 +142,11 @@ public void CanChainUseMethodsCorrectly()
using var host = builder.Build();
var client = host.Services.GetRequiredService();
-
+
var distributedCacheClient = Assert.IsType(client);
var functionInvocationClient = Assert.IsType(GetInnerClient(distributedCacheClient));
var otelClient = Assert.IsType(GetInnerClient(functionInvocationClient));
-
+
Assert.IsType(GetInnerClient(otelClient), exactMatch: false);
}
@@ -182,9 +182,9 @@ public void CanSetMultipleChatClientsWithDifferentServiceKeys()
]);
// Use one Ollama API client with multiple chat clients using different service keys
- builder.AddKeyedOllamaApiClient("OllamaKey", "Ollama")
- .AddKeyedChatClient("ChatKey1")
- .AddKeyedChatClient("ChatKey2");
+ var cb = builder.AddKeyedOllamaApiClient("OllamaKey", "Ollama");
+ cb.AddKeyedChatClient("ChatKey1");
+ cb.AddKeyedChatClient("ChatKey2");
using var host = builder.Build();
var chatClient1 = host.Services.GetRequiredKeyedService("ChatKey1");
@@ -205,18 +205,18 @@ public void CanMixChatClientsAndEmbeddingGeneratorsWithCustomServiceKeys()
]);
// Use one Ollama API client with both chat clients and embedding generators using different service keys
- builder.AddKeyedOllamaApiClient("OllamaKey", "Ollama")
- .AddKeyedChatClient("ChatKey1")
- .AddKeyedChatClient("ChatKey2");
+ var cb = builder.AddKeyedOllamaApiClient("OllamaKey", "Ollama");
+ cb.AddKeyedChatClient("ChatKey1");
+ cb.AddKeyedEmbeddingGenerator("EmbeddingKey1");
using var host = builder.Build();
var chatClient1 = host.Services.GetRequiredKeyedService("ChatKey1");
- var chatClient2 = host.Services.GetRequiredKeyedService("ChatKey2");
+ var embeddingGenerator = host.Services.GetRequiredKeyedService>>("EmbeddingKey1");
Assert.Equal(Endpoint, chatClient1.GetService()?.ProviderUri);
- Assert.Equal(Endpoint, chatClient2.GetService()?.ProviderUri);
+ Assert.Equal(Endpoint, embeddingGenerator.GetService()?.ProviderUri);
- Assert.NotEqual(chatClient1, chatClient2);
+ Assert.Equal(chatClient1 as IOllamaApiClient, embeddingGenerator as IOllamaApiClient);
}
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "get_InnerClient")]
diff --git a/tests/CommunityToolkit.Aspire.OllamaSharp.Tests/OllamaSharpIEmbeddingGeneratorTests.cs b/tests/CommunityToolkit.Aspire.OllamaSharp.Tests/OllamaSharpIEmbeddingGeneratorTests.cs
index e3c6ff12..43341d0d 100644
--- a/tests/CommunityToolkit.Aspire.OllamaSharp.Tests/OllamaSharpIEmbeddingGeneratorTests.cs
+++ b/tests/CommunityToolkit.Aspire.OllamaSharp.Tests/OllamaSharpIEmbeddingGeneratorTests.cs
@@ -158,9 +158,9 @@ public void CanSetMultipleEmbeddingGeneratorsWithDifferentServiceKeys()
]);
// Use one Ollama API client with multiple embedding generators using different service keys
- builder.AddKeyedOllamaApiClient("OllamaKey", "Ollama")
- .AddKeyedEmbeddingGenerator("EmbedKey1")
- .AddKeyedEmbeddingGenerator("EmbedKey2");
+ var cb = builder.AddKeyedOllamaApiClient("OllamaKey", "Ollama");
+ cb.AddKeyedEmbeddingGenerator("EmbedKey1");
+ cb.AddKeyedEmbeddingGenerator("EmbedKey2");
using var host = builder.Build();
var embedGenerator1 = host.Services.GetRequiredKeyedService>>("EmbedKey1");
@@ -172,32 +172,9 @@ public void CanSetMultipleEmbeddingGeneratorsWithDifferentServiceKeys()
Assert.NotEqual(embedGenerator1, embedGenerator2);
}
- [Fact]
- public void CanMixChatClientsAndEmbeddingGeneratorsWithCustomServiceKeys()
- {
- var builder = Host.CreateEmptyApplicationBuilder(null);
- builder.Configuration.AddInMemoryCollection([
- new KeyValuePair("ConnectionStrings:Ollama", $"Endpoint={Endpoint}")
- ]);
-
- // Use one Ollama API client with both chat clients and embedding generators using different service keys
- var ollamaBuilder = builder.AddKeyedOllamaApiClient("OllamaKey", "Ollama");
- ollamaBuilder.AddKeyedChatClient("ChatKey");
- ollamaBuilder.AddKeyedEmbeddingGenerator("EmbedKey");
-
- using var host = builder.Build();
- var chatClient = host.Services.GetRequiredKeyedService("ChatKey");
- var embedGenerator = host.Services.GetRequiredKeyedService>>("EmbedKey");
-
- Assert.Equal(Endpoint, chatClient.GetService()?.ProviderUri);
- Assert.Equal(Endpoint, embedGenerator.GetService()?.ProviderUri);
-
- Assert.NotEqual(chatClient, embedGenerator);
- }
-
private static IEmbeddingGenerator GetInnerGenerator(DelegatingEmbeddingGenerator generator)
where TEmbedding : Embedding =>
- (IEmbeddingGenerator)(generator.GetType()
+ (IEmbeddingGenerator)(generator.GetType()
.GetProperty("InnerGenerator", BindingFlags.Instance | BindingFlags.NonPublic)?
.GetValue(generator, null) ?? throw new InvalidOperationException());
}
From 63729e3ab30ede360ca316f788ed42c6eb6122fe Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 30 Jun 2025 01:47:21 +0000
Subject: [PATCH 7/7] Revert API surface file changes - should be
auto-generated
Co-authored-by: aaronpowell <434140+aaronpowell@users.noreply.github.com>
---
.../api/CommunityToolkit.Aspire.OllamaSharp.cs | 12 ++----------
1 file changed, 2 insertions(+), 10 deletions(-)
diff --git a/src/CommunityToolkit.Aspire.OllamaSharp/api/CommunityToolkit.Aspire.OllamaSharp.cs b/src/CommunityToolkit.Aspire.OllamaSharp/api/CommunityToolkit.Aspire.OllamaSharp.cs
index 2f851fbc..78af13a3 100644
--- a/src/CommunityToolkit.Aspire.OllamaSharp/api/CommunityToolkit.Aspire.OllamaSharp.cs
+++ b/src/CommunityToolkit.Aspire.OllamaSharp/api/CommunityToolkit.Aspire.OllamaSharp.cs
@@ -26,13 +26,13 @@ namespace Microsoft.Extensions.Hosting
{
public partial class AspireOllamaApiClientBuilder
{
- public AspireOllamaApiClientBuilder(IHostApplicationBuilder hostBuilder, object? serviceKey, bool disableTracing) { }
+ public AspireOllamaApiClientBuilder(IHostApplicationBuilder hostBuilder, string serviceKey, bool disableTracing) { }
public bool DisableTracing { get { throw null; } }
public IHostApplicationBuilder HostBuilder { get { throw null; } }
- public object? ServiceKey { get { throw null; } }
+ public string ServiceKey { get { throw null; } }
}
public static partial class AspireOllamaChatClientExtensions
@@ -40,8 +40,6 @@ public static partial class AspireOllamaChatClientExtensions
public static AI.ChatClientBuilder AddChatClient(this AspireOllamaApiClientBuilder builder) { throw null; }
public static AI.ChatClientBuilder AddKeyedChatClient(this AspireOllamaApiClientBuilder builder) { throw null; }
-
- public static AI.ChatClientBuilder AddKeyedChatClient(this AspireOllamaApiClientBuilder builder, object serviceKey) { throw null; }
}
public static partial class AspireOllamaEmbeddingGeneratorExtensions
@@ -49,18 +47,12 @@ public static partial class AspireOllamaEmbeddingGeneratorExtensions
public static AI.EmbeddingGeneratorBuilder> AddEmbeddingGenerator(this AspireOllamaApiClientBuilder builder) { throw null; }
public static AI.EmbeddingGeneratorBuilder> AddKeyedEmbeddingGenerator(this AspireOllamaApiClientBuilder builder) { throw null; }
-
- public static AI.EmbeddingGeneratorBuilder> AddKeyedEmbeddingGenerator(this AspireOllamaApiClientBuilder builder, object serviceKey) { throw null; }
}
public static partial class AspireOllamaSharpExtensions
{
public static AspireOllamaApiClientBuilder AddKeyedOllamaApiClient(this IHostApplicationBuilder builder, string connectionName, System.Action? configureSettings = null) { throw null; }
- public static AspireOllamaApiClientBuilder AddKeyedOllamaApiClient(this IHostApplicationBuilder builder, object serviceKey, string connectionName, System.Action? configureSettings = null) { throw null; }
-
- public static AspireOllamaApiClientBuilder AddKeyedOllamaApiClient(this IHostApplicationBuilder builder, object serviceKey, CommunityToolkit.Aspire.OllamaSharp.OllamaSharpSettings settings) { throw null; }
-
[System.Obsolete("This approach to registering IChatClient is deprecated, use AddKeyedOllamaApiClient().AddChatClient() instead.")]
public static void AddKeyedOllamaSharpChatClient(this IHostApplicationBuilder builder, string connectionName, System.Action? configureSettings = null) { }