Skip to content

Commit 74d9d42

Browse files
Use a unique provider name and model id for ContentSafetyChatClient
We also embed the service configuration parameters into the provider name to ensure that the cached responses will no longer be used if the parameters change.
1 parent a449606 commit 74d9d42

File tree

5 files changed

+98
-52
lines changed

5 files changed

+98
-52
lines changed

src/Libraries/Microsoft.Extensions.AI.Evaluation.Reporting/CSharp/ReportingConfiguration.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ await ResponseCacheProvider.GetCacheAsync(
263263

264264
private static IEnumerable<string> GetCachingKeysForChatClient(IChatClient chatClient)
265265
{
266-
var metadata = chatClient.GetService<ChatClientMetadata>();
266+
ChatClientMetadata? metadata = chatClient.GetService<ChatClientMetadata>();
267267

268268
string? providerName = metadata?.ProviderName;
269269
if (!string.IsNullOrWhiteSpace(providerName))

src/Libraries/Microsoft.Extensions.AI.Evaluation.Safety/ContentSafetyChatClient.NoOpChatClient.cs

Lines changed: 0 additions & 37 deletions
This file was deleted.

src/Libraries/Microsoft.Extensions.AI.Evaluation.Safety/ContentSafetyChatClient.cs

Lines changed: 86 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
// We disable this warning because it is a false positive arising from the analyzer's lack of support for C#'s primary
77
// constructor syntax.
88

9+
using System;
910
using System.Collections.Generic;
1011
using System.Diagnostics;
1112
using System.Linq;
@@ -15,13 +16,47 @@
1516

1617
namespace Microsoft.Extensions.AI.Evaluation.Safety;
1718

18-
internal sealed partial class ContentSafetyChatClient(
19-
ContentSafetyServiceConfiguration contentSafetyServiceConfiguration,
20-
IChatClient? originalChatClient = null) : DelegatingChatClient(originalChatClient ?? NoOpChatClient.Instance)
19+
internal sealed class ContentSafetyChatClient : IChatClient
2120
{
22-
private readonly ContentSafetyService _service = new ContentSafetyService(contentSafetyServiceConfiguration);
21+
private const string Moniker = "Azure AI Content Safety";
2322

24-
public override async Task<ChatResponse> GetResponseAsync(
23+
private readonly ContentSafetyService _service;
24+
private readonly IChatClient? _originalChatClient;
25+
private readonly ChatClientMetadata _metadata;
26+
27+
public ContentSafetyChatClient(
28+
ContentSafetyServiceConfiguration contentSafetyServiceConfiguration,
29+
IChatClient? originalChatClient = null)
30+
{
31+
_service = new ContentSafetyService(contentSafetyServiceConfiguration);
32+
_originalChatClient = originalChatClient;
33+
34+
ChatClientMetadata? originalMetadata = _originalChatClient?.GetService<ChatClientMetadata>();
35+
36+
string providerName =
37+
$"{Moniker} (" +
38+
$"Subscription: {contentSafetyServiceConfiguration.SubscriptionId}, " +
39+
$"Resource Group: {contentSafetyServiceConfiguration.ResourceGroupName}, " +
40+
$"Project: {contentSafetyServiceConfiguration.ProjectName})";
41+
42+
if (originalMetadata?.ProviderName is string originalProviderName &&
43+
!string.IsNullOrWhiteSpace(originalProviderName))
44+
{
45+
providerName = $"{originalProviderName}; {providerName}";
46+
}
47+
48+
string modelId = Moniker;
49+
50+
if (originalMetadata?.DefaultModelId is string originalModelId &&
51+
!string.IsNullOrWhiteSpace(originalModelId))
52+
{
53+
modelId = $"{originalModelId}; {modelId}";
54+
}
55+
56+
_metadata = new ChatClientMetadata(providerName, originalMetadata?.ProviderUri, modelId);
57+
}
58+
59+
public async Task<ChatResponse> GetResponseAsync(
2560
IEnumerable<ChatMessage> messages,
2661
ChatOptions? options = null,
2762
CancellationToken cancellationToken = default)
@@ -38,15 +73,25 @@ await _service.AnnotateAsync(
3873
contentSafetyChatOptions.EvaluatorName,
3974
cancellationToken).ConfigureAwait(false);
4075

41-
return new ChatResponse(new ChatMessage(ChatRole.Assistant, annotationResult));
76+
return new ChatResponse(new ChatMessage(ChatRole.Assistant, annotationResult))
77+
{
78+
ModelId = Moniker
79+
};
80+
}
81+
else if (_originalChatClient is not null)
82+
{
83+
return await _originalChatClient.GetResponseAsync(
84+
messages,
85+
options,
86+
cancellationToken).ConfigureAwait(false);
4287
}
4388
else
4489
{
45-
return await base.GetResponseAsync(messages, options, cancellationToken).ConfigureAwait(false);
90+
throw new NotSupportedException();
4691
}
4792
}
4893

49-
public override async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
94+
public async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
5095
IEnumerable<ChatMessage> messages,
5196
ChatOptions? options = null,
5297
[EnumeratorCancellation] CancellationToken cancellationToken = default)
@@ -63,15 +108,45 @@ await _service.AnnotateAsync(
63108
contentSafetyChatOptions.EvaluatorName,
64109
cancellationToken).ConfigureAwait(false);
65110

66-
yield return new ChatResponseUpdate(ChatRole.Assistant, annotationResult);
111+
yield return new ChatResponseUpdate(ChatRole.Assistant, annotationResult)
112+
{
113+
ModelId = Moniker
114+
};
67115
}
68-
else
116+
else if (_originalChatClient is not null)
69117
{
70118
await foreach (var update in
71-
base.GetStreamingResponseAsync(messages, options, cancellationToken).ConfigureAwait(false))
119+
_originalChatClient.GetStreamingResponseAsync(
120+
messages,
121+
options,
122+
cancellationToken).ConfigureAwait(false))
72123
{
73124
yield return update;
74125
}
75126
}
127+
else
128+
{
129+
throw new NotSupportedException();
130+
}
131+
}
132+
133+
public object? GetService(Type serviceType, object? serviceKey = null)
134+
{
135+
if (serviceKey is null)
136+
{
137+
if (serviceType == typeof(ChatClientMetadata))
138+
{
139+
return _metadata;
140+
}
141+
else if (serviceType == typeof(ContentSafetyChatClient))
142+
{
143+
return this;
144+
}
145+
}
146+
147+
return _originalChatClient?.GetService(serviceType, serviceKey);
76148
}
149+
150+
public void Dispose()
151+
=> _originalChatClient?.Dispose();
77152
}

src/Libraries/Microsoft.Extensions.AI.Evaluation.Safety/ContentSafetyService.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,15 +192,15 @@ private async ValueTask<string> GetServiceDiscoveryUrlAsync(
192192
string evaluatorName,
193193
CancellationToken cancellationToken)
194194
{
195-
string requestUrl =
195+
string resourceManagerUrl =
196196
$"https://management.azure.com/subscriptions/{serviceConfiguration.SubscriptionId}" +
197197
$"/resourceGroups/{serviceConfiguration.ResourceGroupName}" +
198198
$"/providers/Microsoft.MachineLearningServices/workspaces/{serviceConfiguration.ProjectName}" +
199199
$"?api-version=2023-08-01-preview";
200200

201201
HttpResponseMessage response =
202202
await GetResponseAsync(
203-
requestUrl,
203+
resourceManagerUrl,
204204
evaluatorName,
205205
cancellationToken: cancellationToken).ConfigureAwait(false);
206206

src/Libraries/Microsoft.Extensions.AI.Evaluation.Safety/ContentSafetyServiceConfigurationExtensions.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using Microsoft.Shared.Diagnostics;
5+
46
namespace Microsoft.Extensions.AI.Evaluation.Safety;
57

68
/// <summary>
@@ -33,6 +35,8 @@ public static ChatConfiguration ToChatConfiguration(
3335
this ContentSafetyServiceConfiguration contentSafetyServiceConfiguration,
3436
ChatConfiguration? originalChatConfiguration = null)
3537
{
38+
_ = Throw.IfNull(contentSafetyServiceConfiguration);
39+
3640
#pragma warning disable CA2000 // Dispose objects before they go out of scope.
3741
// We can't dispose newChatClient here because it is returned to the caller.
3842

@@ -67,5 +71,9 @@ public static ChatConfiguration ToChatConfiguration(
6771
public static IChatClient ToIChatClient(
6872
this ContentSafetyServiceConfiguration contentSafetyServiceConfiguration,
6973
IChatClient? originalChatClient = null)
70-
=> new ContentSafetyChatClient(contentSafetyServiceConfiguration, originalChatClient);
74+
{
75+
_ = Throw.IfNull(contentSafetyServiceConfiguration);
76+
77+
return new ContentSafetyChatClient(contentSafetyServiceConfiguration, originalChatClient);
78+
}
7179
}

0 commit comments

Comments
 (0)