diff --git a/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaChatClientExtensions.cs b/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaChatClientExtensions.cs
index adccd1686..128e70713 100644
--- a/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaChatClientExtensions.cs
+++ b/src/CommunityToolkit.Aspire.OllamaSharp/AspireOllamaChatClientExtensions.cs
@@ -2,6 +2,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using OllamaSharp;
+using OpenTelemetry;
namespace Microsoft.Extensions.Hosting;
@@ -10,6 +11,8 @@ namespace Microsoft.Extensions.Hosting;
///
public static class AspireOllamaChatClientExtensions
{
+ private const string MeaiTelemetrySourceName = "Experimental.Microsoft.Extensions.AI";
+
///
/// Registers a singleton in the services provided by the .
///
@@ -19,8 +22,25 @@ public static ChatClientBuilder AddChatClient(this AspireOllamaApiClientBuilder
{
ArgumentNullException.ThrowIfNull(builder, nameof(builder));
+ return builder.AddChatClient(configureOpenTelemetry: null);
+ }
+
+ ///
+ /// Registers a singleton in the services provided by the .
+ ///
+ /// An .
+ /// An optional delegate that can be used for customizing the OpenTelemetry chat client.
+ /// A that can be used to build a pipeline around the inner .
+ public static ChatClientBuilder AddChatClient(
+ this AspireOllamaApiClientBuilder builder,
+ Action? configureOpenTelemetry)
+ {
+ ArgumentNullException.ThrowIfNull(builder, nameof(builder));
+
+ AddTelemetrySource(builder.HostBuilder);
+
return builder.HostBuilder.Services.AddChatClient(
- services => CreateInnerChatClient(services, builder));
+ services => CreateInnerChatClient(services, builder, configureOpenTelemetry));
}
///
@@ -33,7 +53,22 @@ public static ChatClientBuilder AddKeyedChatClient(
{
ArgumentNullException.ThrowIfNull(builder, nameof(builder));
- return builder.AddKeyedChatClient(builder.ServiceKey);
+ return builder.AddKeyedChatClient(builder.ServiceKey, configureOpenTelemetry: null);
+ }
+
+ ///
+ /// Registers a keyed singleton in the services provided by the .
+ ///
+ /// An .
+ /// An optional delegate that can be used for customizing the OpenTelemetry chat client.
+ /// A that can be used to build a pipeline around the inner .
+ public static ChatClientBuilder AddKeyedChatClient(
+ this AspireOllamaApiClientBuilder builder,
+ Action? configureOpenTelemetry)
+ {
+ ArgumentNullException.ThrowIfNull(builder, nameof(builder));
+
+ return builder.AddKeyedChatClient(builder.ServiceKey, configureOpenTelemetry);
}
///
@@ -49,9 +84,29 @@ public static ChatClientBuilder AddKeyedChatClient(
ArgumentNullException.ThrowIfNull(builder, nameof(builder));
ArgumentNullException.ThrowIfNull(serviceKey, nameof(serviceKey));
+ return builder.AddKeyedChatClient(serviceKey, configureOpenTelemetry: null);
+ }
+
+ ///
+ /// Registers a keyed singleton in the services provided by the using the specified service key.
+ ///
+ /// An .
+ /// The service key to use for registering the .
+ /// An optional delegate that can be used for customizing the OpenTelemetry chat client.
+ /// A that can be used to build a pipeline around the inner .
+ public static ChatClientBuilder AddKeyedChatClient(
+ this AspireOllamaApiClientBuilder builder,
+ object serviceKey,
+ Action? configureOpenTelemetry)
+ {
+ ArgumentNullException.ThrowIfNull(builder, nameof(builder));
+ ArgumentNullException.ThrowIfNull(serviceKey, nameof(serviceKey));
+
+ AddTelemetrySource(builder.HostBuilder);
+
return builder.HostBuilder.Services.AddKeyedChatClient(
serviceKey,
- services => CreateInnerChatClient(services, builder));
+ services => CreateInnerChatClient(services, builder, configureOpenTelemetry));
}
///
@@ -61,7 +116,8 @@ public static ChatClientBuilder AddKeyedChatClient(
///
private static IChatClient CreateInnerChatClient(
IServiceProvider services,
- AspireOllamaApiClientBuilder builder)
+ AspireOllamaApiClientBuilder builder,
+ Action? configureOpenTelemetry)
{
var ollamaApiClient = services.GetRequiredKeyedService(builder.ServiceKey);
@@ -73,6 +129,22 @@ private static IChatClient CreateInnerChatClient(
}
var loggerFactory = services.GetService();
- return new OpenTelemetryChatClient(result, loggerFactory?.CreateLogger(typeof(OpenTelemetryChatClient)));
+ var otelChatClient = new OpenTelemetryChatClient(result, loggerFactory?.CreateLogger(typeof(OpenTelemetryChatClient)), MeaiTelemetrySourceName);
+
+ configureOpenTelemetry?.Invoke(otelChatClient);
+
+ return otelChatClient;
+ }
+
+ ///
+ /// Add the MEAI telemetry source to OpenTelemetry tracing.
+ ///
+ private static void AddTelemetrySource(IHostApplicationBuilder hostBuilder)
+ {
+ hostBuilder.Services.AddOpenTelemetry()
+ .WithTracing(tracing =>
+ {
+ tracing.AddSource(MeaiTelemetrySourceName);
+ });
}
}
diff --git a/src/CommunityToolkit.Aspire.OllamaSharp/CommunityToolkit.Aspire.OllamaSharp.csproj b/src/CommunityToolkit.Aspire.OllamaSharp/CommunityToolkit.Aspire.OllamaSharp.csproj
index 2170bccbb..02b64ccbd 100644
--- a/src/CommunityToolkit.Aspire.OllamaSharp/CommunityToolkit.Aspire.OllamaSharp.csproj
+++ b/src/CommunityToolkit.Aspire.OllamaSharp/CommunityToolkit.Aspire.OllamaSharp.csproj
@@ -12,6 +12,7 @@
+
diff --git a/src/CommunityToolkit.Aspire.OllamaSharp/OllamaSharpSettings.cs b/src/CommunityToolkit.Aspire.OllamaSharp/OllamaSharpSettings.cs
index c8bd59083..13b116ba6 100644
--- a/src/CommunityToolkit.Aspire.OllamaSharp/OllamaSharpSettings.cs
+++ b/src/CommunityToolkit.Aspire.OllamaSharp/OllamaSharpSettings.cs
@@ -37,6 +37,6 @@ public sealed class OllamaSharpSettings
/// Gets or sets a boolean value that indicates whether tracing is disabled or not.
///
/// Currently, the OllamaSharp SDK does not support tracing, but this is here for future use.
- internal bool DisableTracing { get; set; }
+ public bool DisableTracing { get; set; }
}
diff --git a/src/CommunityToolkit.Aspire.OllamaSharp/README.md b/src/CommunityToolkit.Aspire.OllamaSharp/README.md
index c08469fdd..4d51e4188 100644
--- a/src/CommunityToolkit.Aspire.OllamaSharp/README.md
+++ b/src/CommunityToolkit.Aspire.OllamaSharp/README.md
@@ -37,6 +37,17 @@ public class MyService(IOllamaApiClient ollamaApiClient)
To use the integration with Microsoft.Extensions.AI, call the `AddOllamaSharpChatClient` or `AddOllamaSharpEmbeddingGenerator` extension method in the _Program.cs_ file of your project. These methods take the connection name as a parameter, just as `AddOllamaApiClient` does, and will register the `IOllamaApiClient`, as well as the `IChatClient` or `IEmbeddingGenerator` in the DI container. The `IEmbeddingsGenerator` is registered with the generic arguments of `>`.
+#### Configuring OpenTelemetry
+
+When using the chat client integration, you can optionally configure the OpenTelemetry chat client to control telemetry behavior such as enabling sensitive data:
+
+```csharp
+builder.AddOllamaApiClient("ollama")
+ .AddChatClient(otel => otel.EnableSensitiveData = true);
+```
+
+The integration automatically registers the Microsoft.Extensions.AI telemetry source (`Experimental.Microsoft.Extensions.AI`) with OpenTelemetry for distributed tracing.
+
## Additional documentation
- https://github.com/awaescher/OllamaSharp
diff --git a/tests/CommunityToolkit.Aspire.OllamaSharp.Tests/OllamaSharpIChatClientTests.cs b/tests/CommunityToolkit.Aspire.OllamaSharp.Tests/OllamaSharpIChatClientTests.cs
index b409e9072..1dfd4f2fb 100644
--- a/tests/CommunityToolkit.Aspire.OllamaSharp.Tests/OllamaSharpIChatClientTests.cs
+++ b/tests/CommunityToolkit.Aspire.OllamaSharp.Tests/OllamaSharpIChatClientTests.cs
@@ -219,6 +219,83 @@ public void CanMixChatClientsAndEmbeddingGeneratorsWithCustomServiceKeys()
Assert.Equal(chatClient1 as IOllamaApiClient, embeddingGenerator as IOllamaApiClient);
}
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void CanConfigureOpenTelemetrySensitiveData(bool useKeyed)
+ {
+ var builder = Host.CreateEmptyApplicationBuilder(null);
+ builder.Configuration.AddInMemoryCollection([
+ new KeyValuePair("ConnectionStrings:Ollama", $"Endpoint={Endpoint}")
+ ]);
+
+ if (useKeyed)
+ {
+ builder.AddKeyedOllamaApiClient("Ollama").AddKeyedChatClient(otel => otel.EnableSensitiveData = true);
+ }
+ else
+ {
+ builder.AddOllamaApiClient("Ollama").AddChatClient(otel => otel.EnableSensitiveData = true);
+ }
+
+ using var host = builder.Build();
+ var client = useKeyed ?
+ host.Services.GetRequiredKeyedService("Ollama") :
+ host.Services.GetRequiredService();
+
+ // Navigate through the client chain to find the OpenTelemetryChatClient
+ var otelClient = Assert.IsType(client);
+ Assert.True(otelClient.EnableSensitiveData);
+ }
+
+ [Fact]
+ public void CanConfigureOpenTelemetrySensitiveDataWithCustomServiceKey()
+ {
+ var builder = Host.CreateEmptyApplicationBuilder(null);
+ builder.Configuration.AddInMemoryCollection([
+ new KeyValuePair("ConnectionStrings:Ollama", $"Endpoint={Endpoint}")
+ ]);
+
+ builder.AddKeyedOllamaApiClient("OllamaKey", "Ollama")
+ .AddKeyedChatClient("ChatKey", otel => otel.EnableSensitiveData = true);
+
+ using var host = builder.Build();
+ var client = host.Services.GetRequiredKeyedService("ChatKey");
+
+ var otelClient = Assert.IsType(client);
+ Assert.True(otelClient.EnableSensitiveData);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void OpenTelemetryConfigurationIsOptional(bool useKeyed)
+ {
+ var builder = Host.CreateEmptyApplicationBuilder(null);
+ builder.Configuration.AddInMemoryCollection([
+ new KeyValuePair("ConnectionStrings:Ollama", $"Endpoint={Endpoint}")
+ ]);
+
+ // Test that we can still call the methods without configuration
+ if (useKeyed)
+ {
+ builder.AddKeyedOllamaApiClient("Ollama").AddKeyedChatClient(configureOpenTelemetry: null);
+ }
+ else
+ {
+ builder.AddOllamaApiClient("Ollama").AddChatClient(configureOpenTelemetry: null);
+ }
+
+ using var host = builder.Build();
+ var client = useKeyed ?
+ host.Services.GetRequiredKeyedService("Ollama") :
+ host.Services.GetRequiredService();
+
+ var otelClient = Assert.IsType(client);
+ // EnableSensitiveData should be false by default
+ Assert.False(otelClient.EnableSensitiveData);
+ }
+
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "get_InnerClient")]
private static extern IChatClient GetInnerClient(DelegatingChatClient client);
}