Skip to content

Commit 6798c58

Browse files
Add support for new Azure AI Foundry project type for Safety evals
Fixes #6592
1 parent ed4aeac commit 6798c58

File tree

7 files changed

+325
-78
lines changed

7 files changed

+325
-78
lines changed

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

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,27 +35,38 @@ public ContentSafetyChatClient(
3535

3636
ChatClientMetadata? originalMetadata = _originalChatClient?.GetService<ChatClientMetadata>();
3737

38-
string providerName =
39-
$"{Moniker} (" +
40-
$"Subscription: {contentSafetyServiceConfiguration.SubscriptionId}, " +
41-
$"Resource Group: {contentSafetyServiceConfiguration.ResourceGroupName}, " +
42-
$"Project: {contentSafetyServiceConfiguration.ProjectName})";
38+
string providerName;
39+
Uri? providerUri = originalMetadata?.ProviderUri;
40+
41+
if (contentSafetyServiceConfiguration.IsHubBasedProject)
42+
{
43+
providerName =
44+
$"{Moniker} (" +
45+
$"Subscription: {contentSafetyServiceConfiguration.SubscriptionId}, " +
46+
$"Resource Group: {contentSafetyServiceConfiguration.ResourceGroupName}, " +
47+
$"Project: {contentSafetyServiceConfiguration.ProjectName})";
48+
}
49+
else
50+
{
51+
providerName = $"{Moniker} (Endpoint: {contentSafetyServiceConfiguration.Endpoint})";
52+
providerUri = contentSafetyServiceConfiguration.Endpoint;
53+
}
4354

4455
if (originalMetadata?.ProviderName is string originalProviderName &&
4556
!string.IsNullOrWhiteSpace(originalProviderName))
4657
{
47-
providerName = $"{originalProviderName}; {providerName}";
58+
providerName = $"{providerName}; {originalProviderName}";
4859
}
4960

5061
string modelId = Moniker;
5162

5263
if (originalMetadata?.DefaultModelId is string originalModelId &&
5364
!string.IsNullOrWhiteSpace(originalModelId))
5465
{
55-
modelId = $"{originalModelId}; {modelId}";
66+
modelId = $"{modelId}; {originalModelId}";
5667
}
5768

58-
_metadata = new ChatClientMetadata(providerName, originalMetadata?.ProviderUri, modelId);
69+
_metadata = new ChatClientMetadata(providerName, providerUri, modelId);
5970
}
6071

6172
public async Task<ChatResponse> GetResponseAsync(

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,14 @@ public bool Equals(UrlCacheKey? other)
2626
}
2727
else
2828
{
29+
#pragma warning disable S1067 // Expressions should not be too complex
2930
return
3031
other.Configuration.SubscriptionId == Configuration.SubscriptionId &&
3132
other.Configuration.ResourceGroupName == Configuration.ResourceGroupName &&
3233
other.Configuration.ProjectName == Configuration.ProjectName &&
34+
other.Configuration.Endpoint == Configuration.Endpoint &&
3335
other.AnnotationTask == AnnotationTask;
36+
#pragma warning restore S1067
3437
}
3538
}
3639

@@ -42,6 +45,7 @@ public override int GetHashCode() =>
4245
Configuration.SubscriptionId,
4346
Configuration.ResourceGroupName,
4447
Configuration.ProjectName,
48+
Configuration.Endpoint,
4549
AnnotationTask);
4650
}
4751
}

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

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ namespace Microsoft.Extensions.AI.Evaluation.Safety;
2222

2323
internal sealed partial class ContentSafetyService(ContentSafetyServiceConfiguration serviceConfiguration)
2424
{
25+
private const string APIVersionForServiceDiscoveryInHubBasedProjects = "?api-version=2023-08-01-preview";
26+
private const string APIVersionForNonHubBasedProjects = "?api-version=2025-05-15-preview";
27+
2528
private static HttpClient? _sharedHttpClient;
2629
private static HttpClient SharedHttpClient
2730
{
@@ -168,20 +171,27 @@ private async ValueTask<string> GetServiceUrlAsync(
168171
return _serviceUrl;
169172
}
170173

171-
string discoveryUrl =
172-
await GetServiceDiscoveryUrlAsync(evaluatorName, cancellationToken).ConfigureAwait(false);
173-
174-
serviceUrl =
175-
$"{discoveryUrl}/raisvc/v1.0" +
176-
$"/subscriptions/{serviceConfiguration.SubscriptionId}" +
177-
$"/resourceGroups/{serviceConfiguration.ResourceGroupName}" +
178-
$"/providers/Microsoft.MachineLearningServices/workspaces/{serviceConfiguration.ProjectName}";
174+
if (serviceConfiguration.IsHubBasedProject)
175+
{
176+
string discoveryUrl =
177+
await GetServiceDiscoveryUrlAsync(evaluatorName, cancellationToken).ConfigureAwait(false);
178+
179+
serviceUrl =
180+
$"{discoveryUrl}/raisvc/v1.0" +
181+
$"/subscriptions/{serviceConfiguration.SubscriptionId}" +
182+
$"/resourceGroups/{serviceConfiguration.ResourceGroupName}" +
183+
$"/providers/Microsoft.MachineLearningServices/workspaces/{serviceConfiguration.ProjectName}";
184+
}
185+
else
186+
{
187+
serviceUrl = $"{serviceConfiguration.Endpoint.AbsoluteUri}/evaluations";
188+
}
179189

180190
await EnsureServiceAvailabilityAsync(
181-
serviceUrl,
182-
capability: annotationTask,
183-
evaluatorName,
184-
cancellationToken).ConfigureAwait(false);
191+
serviceUrl,
192+
capability: annotationTask,
193+
evaluatorName,
194+
cancellationToken).ConfigureAwait(false);
185195

186196
_ = _serviceUrlCache.TryAdd(key, serviceUrl);
187197
_serviceUrl = serviceUrl;
@@ -196,7 +206,7 @@ private async ValueTask<string> GetServiceDiscoveryUrlAsync(
196206
$"https://management.azure.com/subscriptions/{serviceConfiguration.SubscriptionId}" +
197207
$"/resourceGroups/{serviceConfiguration.ResourceGroupName}" +
198208
$"/providers/Microsoft.MachineLearningServices/workspaces/{serviceConfiguration.ProjectName}" +
199-
$"?api-version=2023-08-01-preview";
209+
$"{APIVersionForServiceDiscoveryInHubBasedProjects}";
200210

201211
HttpResponseMessage response =
202212
await GetResponseAsync(
@@ -244,7 +254,10 @@ private async ValueTask EnsureServiceAvailabilityAsync(
244254
string evaluatorName,
245255
CancellationToken cancellationToken)
246256
{
247-
string serviceAvailabilityUrl = $"{serviceUrl}/checkannotation";
257+
string serviceAvailabilityUrl =
258+
serviceConfiguration.IsHubBasedProject
259+
? $"{serviceUrl}/checkannotation"
260+
: $"{serviceUrl}/checkannotation{APIVersionForNonHubBasedProjects}";
248261

249262
HttpResponseMessage response =
250263
await GetResponseAsync(
@@ -297,7 +310,10 @@ private async ValueTask<string> SubmitAnnotationRequestAsync(
297310
string evaluatorName,
298311
CancellationToken cancellationToken)
299312
{
300-
string annotationUrl = $"{serviceUrl}/submitannotation";
313+
string annotationUrl =
314+
serviceConfiguration.IsHubBasedProject
315+
? $"{serviceUrl}/submitannotation"
316+
: $"{serviceUrl}/submitannotation{APIVersionForNonHubBasedProjects}";
301317

302318
HttpResponseMessage response =
303319
await GetResponseAsync(
@@ -426,10 +442,13 @@ private async ValueTask AddHeadersAsync(
426442

427443
httpRequestMessage.Headers.Add("User-Agent", userAgent);
428444

445+
TokenRequestContext context =
446+
serviceConfiguration.IsHubBasedProject
447+
? new TokenRequestContext(scopes: ["https://management.azure.com/.default"])
448+
: new TokenRequestContext(scopes: ["https://ai.azure.com/.default"]);
449+
429450
AccessToken token =
430-
await serviceConfiguration.Credential.GetTokenAsync(
431-
new TokenRequestContext(scopes: ["https://management.azure.com/.default"]),
432-
cancellationToken).ConfigureAwait(false);
451+
await serviceConfiguration.Credential.GetTokenAsync(context, cancellationToken).ConfigureAwait(false);
433452

434453
httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.Token);
435454

0 commit comments

Comments
 (0)