Skip to content

Commit c664e7b

Browse files
slreznitRogerBarretomarkwallace-microsoft
authored
.Net: Add UserSecurityContext to SK dotnet AzureOpenAIPromptExecutionSettings (#11656)
### Motivation and Context Expose UserSecurityContext in SK dotnet via AzureOpenAIPromptExecutionSettings as this is available in Azure only. https://learn.microsoft.com/en-us/dotnet/api/azure.ai.openai.usersecuritycontext?view=azure-dotnet-preview <!-- Thank you for your contribution to the semantic-kernel repo! Please help reviewers and future users, providing the following information: 1. Why is this change required? 2. What problem does it solve? 3. What scenario does it contribute to? 4. If it fixes an open issue, please link to the issue here. --> ### Description <!-- Describe your changes, the overall approach, the underlying design. These notes will help understanding how your code works. Thanks! --> ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [ ] The code builds clean without any errors or warnings - [ ] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [ ] All unit tests pass, and I have added new tests where possible - [ ] I didn't break anyone 😄 --------- Co-authored-by: Roger Barreto <[email protected]> Co-authored-by: Mark Wallace <[email protected]>
1 parent cce7ee2 commit c664e7b

File tree

4 files changed

+76
-0
lines changed

4 files changed

+76
-0
lines changed

dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/Services/AzureOpenAIChatCompletionServiceTests.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,49 @@ public async Task GetChatMessageContentsHandlesMaxTokensCorrectlyAsync(bool useN
306306
Assert.Equal(123, propertyValue.GetInt32());
307307
}
308308

309+
[Fact]
310+
public async Task GetChatMessageContentsHandlesUserSecurityContextCorrectlyAsync()
311+
{
312+
// Arrange
313+
var service = new AzureOpenAIChatCompletionService("deployment", "https://endpoint", "api-key", "model-id", this._httpClient);
314+
var settings = new AzureOpenAIPromptExecutionSettings();
315+
316+
#pragma warning disable AOAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
317+
var userSecurityContext = new UserSecurityContext()
318+
{
319+
ApplicationName = "My-AI-App",
320+
SourceIP = "203.0.113.42",
321+
EndUserId = "f3b8e23c-36a1-4e47-8f12-bd77a33f29b4",
322+
EndUserTenantId = "8c946a0e-c75b-4f3a-b2e6-0d12e63c7e48"
323+
};
324+
settings.UserSecurityContext = userSecurityContext;
325+
326+
using var responseMessage = new HttpResponseMessage(HttpStatusCode.OK)
327+
{
328+
Content = new StringContent(AzureOpenAITestHelper.GetTestResponse("chat_completion_test_response.json"))
329+
};
330+
this._messageHandlerStub.ResponsesToReturn.Add(responseMessage);
331+
332+
// Act
333+
var result = await service.GetChatMessageContentsAsync(new ChatHistory("System message"), settings);
334+
335+
// Assert
336+
var requestContent = this._messageHandlerStub.RequestContents[0];
337+
338+
Assert.NotNull(requestContent);
339+
340+
var content = JsonSerializer.Deserialize<JsonElement>(Encoding.UTF8.GetString(requestContent));
341+
342+
Assert.True(content.TryGetProperty("user_security_context", out var propertyValue));
343+
344+
using JsonDocument doc = JsonDocument.Parse(propertyValue.GetRawText());
345+
Assert.Equal(userSecurityContext.ApplicationName, doc.RootElement.GetProperty("application_name").GetString());
346+
Assert.Equal(userSecurityContext.SourceIP, doc.RootElement.GetProperty("source_ip").GetString());
347+
Assert.Equal(userSecurityContext.EndUserId, doc.RootElement.GetProperty("end_user_id").GetString());
348+
Assert.Equal(userSecurityContext.EndUserTenantId, doc.RootElement.GetProperty("end_user_tenant_id").GetString());
349+
#pragma warning restore AOAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
350+
}
351+
309352
[Theory]
310353
[InlineData("stream", "true")]
311354
[InlineData("stream_options", "{\"include_usage\":true}")]

dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/Settings/AzureOpenAIPromptExecutionSettingsTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public void ItCreatesOpenAIExecutionSettingsWithCorrectDefaults()
3535
Assert.Null(executionSettings.TopLogprobs);
3636
Assert.Null(executionSettings.Logprobs);
3737
Assert.Null(executionSettings.AzureChatDataSource);
38+
Assert.Null(executionSettings.UserSecurityContext);
3839
Assert.False(executionSettings.SetNewMaxCompletionTokensEnabled);
3940
Assert.Equal(maxTokensSettings, executionSettings.MaxTokens);
4041
Assert.Null(executionSettings.Store);
@@ -263,6 +264,7 @@ public void PromptExecutionSettingsFreezeWorksAsExpected()
263264
Assert.Throws<InvalidOperationException>(() => executionSettings.Store = false);
264265
Assert.Throws<NotSupportedException>(() => executionSettings.Metadata?.Add("bar", "foo"));
265266
Assert.Throws<InvalidOperationException>(() => executionSettings.SetNewMaxCompletionTokensEnabled = true);
267+
Assert.Throws<InvalidOperationException>(() => executionSettings.UserSecurityContext = null);
266268

267269
executionSettings!.Freeze(); // idempotent
268270
Assert.True(executionSettings.IsFrozen);

dotnet/src/Connectors/Connectors.AzureOpenAI/Core/AzureClientCore.ChatCompletion.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@ protected override ChatCompletionOptions CreateChatCompletionOptions(
6161
#pragma warning restore AOAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
6262
}
6363

64+
if (azureSettings.UserSecurityContext is not null)
65+
{
66+
#pragma warning disable AOAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
67+
options.SetUserSecurityContext(azureSettings.UserSecurityContext);
68+
#pragma warning restore AOAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
69+
}
70+
6471
var responseFormat = GetResponseFormat(executionSettings);
6572
if (responseFormat is not null)
6673
{

dotnet/src/Connectors/Connectors.AzureOpenAI/Settings/AzureOpenAIPromptExecutionSettings.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Diagnostics.CodeAnalysis;
55
using System.Text.Json;
66
using System.Text.Json.Serialization;
7+
using Azure.AI.OpenAI;
78
using Azure.AI.OpenAI.Chat;
89
using Microsoft.SemanticKernel.Connectors.OpenAI;
910
using Microsoft.SemanticKernel.Text;
@@ -16,6 +17,25 @@ namespace Microsoft.SemanticKernel.Connectors.AzureOpenAI;
1617
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
1718
public sealed class AzureOpenAIPromptExecutionSettings : OpenAIPromptExecutionSettings
1819
{
20+
/// <summary>
21+
/// Get/Set the user security context which contains several parameters that describe the AI application itself, and the end user that interacts with the AI application.
22+
/// These fields assist your security operations teams to investigate and mitigate security incidents by providing a comprehensive approach to protecting your AI applications.
23+
/// <see href="https://learn.microsoft.com/en-us/azure/defender-for-cloud/gain-end-user-context-ai">Learn more</see> about protecting AI applications using Microsoft Defender for Cloud.
24+
/// </summary>
25+
[JsonIgnore]
26+
#pragma warning disable AOAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
27+
[Experimental("SKEXP0010")]
28+
public UserSecurityContext? UserSecurityContext
29+
{
30+
get => this._userSecurityContext;
31+
set
32+
{
33+
this.ThrowIfFrozen();
34+
this._userSecurityContext = value;
35+
}
36+
}
37+
#pragma warning restore AOAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
38+
1939
/// <summary>
2040
/// Enabling this property will enforce the new <c>max_completion_tokens</c> parameter to be send the Azure OpenAI API.
2141
/// </summary>
@@ -59,6 +79,7 @@ public override PromptExecutionSettings Clone()
5979
var settings = base.Clone<AzureOpenAIPromptExecutionSettings>();
6080
settings.AzureChatDataSource = this.AzureChatDataSource;
6181
settings.SetNewMaxCompletionTokensEnabled = this.SetNewMaxCompletionTokensEnabled;
82+
settings.UserSecurityContext = this.UserSecurityContext;
6283
return settings;
6384
}
6485

@@ -125,6 +146,9 @@ public static AzureOpenAIPromptExecutionSettings FromExecutionSettingsWithData(P
125146
[Experimental("SKEXP0010")]
126147
private AzureSearchChatDataSource? _azureChatDataSource;
127148
private bool _setNewMaxCompletionTokensEnabled;
149+
#pragma warning disable AOAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
150+
private UserSecurityContext? _userSecurityContext;
151+
#pragma warning restore AOAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
128152

129153
#endregion
130154
}

0 commit comments

Comments
 (0)