Skip to content

Commit f8a22b8

Browse files
.Net: Migrate Azure Chat Completion Service to AzureOpenAI SDK v2 (#6984)
### Motivation and Context This PR is the next step in a series of follow-up PRs to migrate AzureOpenAIConnector to Azure AI SDK v2. It updates all code related to AzureOpenAI ChatCompletionService to use the Azure AI SDK v2. One of the goals of the PR is to update the code with a minimal number of changes to make the code review as easy as possible, so almost all methods keep their names as they were even though they might not be relevant anymore. This will be fixed in one of the follow-up PRs. ### Description This PR does the following: 1. Migrates AzureOpenAIChatCompletionService, ClientCore, and other model classes both use, to Azure AI SDK v2. 2. Updates ToolCallBehavior classes to return a list of functions and function choice. This change is required because the new SDK model requires both of those for the CompletionsOptions class creation and does not allow setting them after the class is already created, as it used to allow. 3. Adapts related unit tests to the API changes. ### Next steps 1. Add integration tests. 2. Rename internal/private methods that were intentionally left with old, irrelevant names to minimize the code review delta. ### Out of scope: * #6991 ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [x] The code builds clean without any errors or warnings - [x] 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 - [x] All unit tests pass, and I have added new tests where possible - [x] I didn't break anyone 😄
1 parent c8d9ade commit f8a22b8

25 files changed

+714
-969
lines changed

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

+1-5
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,11 @@ public void ItCreatesOpenAIExecutionSettingsWithCorrectDefaults()
2626
Assert.Equal(1, executionSettings.TopP);
2727
Assert.Equal(0, executionSettings.FrequencyPenalty);
2828
Assert.Equal(0, executionSettings.PresencePenalty);
29-
Assert.Equal(1, executionSettings.ResultsPerPrompt);
3029
Assert.Null(executionSettings.StopSequences);
3130
Assert.Null(executionSettings.TokenSelectionBiases);
3231
Assert.Null(executionSettings.TopLogprobs);
3332
Assert.Null(executionSettings.Logprobs);
34-
Assert.Null(executionSettings.AzureChatExtensionsOptions);
33+
Assert.Null(executionSettings.AzureChatDataSource);
3534
Assert.Equal(128, executionSettings.MaxTokens);
3635
}
3736

@@ -45,7 +44,6 @@ public void ItUsesExistingOpenAIExecutionSettings()
4544
TopP = 0.7,
4645
FrequencyPenalty = 0.7,
4746
PresencePenalty = 0.7,
48-
ResultsPerPrompt = 2,
4947
StopSequences = new string[] { "foo", "bar" },
5048
ChatSystemPrompt = "chat system prompt",
5149
MaxTokens = 128,
@@ -231,7 +229,6 @@ public void PromptExecutionSettingsFreezeWorksAsExpected()
231229
// Assert
232230
Assert.True(executionSettings.IsFrozen);
233231
Assert.Throws<InvalidOperationException>(() => executionSettings.ModelId = "gpt-4");
234-
Assert.Throws<InvalidOperationException>(() => executionSettings.ResultsPerPrompt = 2);
235232
Assert.Throws<InvalidOperationException>(() => executionSettings.Temperature = 1);
236233
Assert.Throws<InvalidOperationException>(() => executionSettings.TopP = 1);
237234
Assert.Throws<NotSupportedException>(() => executionSettings.StopSequences?.Add("STOP"));
@@ -262,7 +259,6 @@ private static void AssertExecutionSettings(AzureOpenAIPromptExecutionSettings e
262259
Assert.Equal(0.7, executionSettings.TopP);
263260
Assert.Equal(0.7, executionSettings.FrequencyPenalty);
264261
Assert.Equal(0.7, executionSettings.PresencePenalty);
265-
Assert.Equal(2, executionSettings.ResultsPerPrompt);
266262
Assert.Equal(new string[] { "foo", "bar" }, executionSettings.StopSequences);
267263
Assert.Equal("chat system prompt", executionSettings.ChatSystemPrompt);
268264
Assert.Equal(new Dictionary<int, int>() { { 1, 2 }, { 3, 4 } }, executionSettings.TokenSelectionBiases);

dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/AzureOpenAITestHelper.cs

+10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft. All rights reserved.
22

33
using System.IO;
4+
using System.Net.Http;
45

56
namespace SemanticKernel.Connectors.AzureOpenAI.UnitTests;
67

@@ -17,4 +18,13 @@ internal static string GetTestResponse(string fileName)
1718
{
1819
return File.ReadAllText($"./TestData/{fileName}");
1920
}
21+
22+
/// <summary>
23+
/// Reads test response from file and create <see cref="StreamContent"/>.
24+
/// </summary>
25+
/// <param name="fileName">Name of the file with test response.</param>
26+
internal static StreamContent GetTestResponseAsStream(string fileName)
27+
{
28+
return new StreamContent(File.OpenRead($"./TestData/{fileName}"));
29+
}
2030
}

dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/AzureToolCallBehaviorTests.cs renamed to dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/AzureOpenAIToolCallBehaviorTests.cs

+36-46
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,23 @@
22

33
using System.Collections.Generic;
44
using System.Linq;
5-
using Azure.AI.OpenAI;
65
using Microsoft.SemanticKernel;
76
using Microsoft.SemanticKernel.Connectors.AzureOpenAI;
8-
using static Microsoft.SemanticKernel.Connectors.AzureOpenAI.AzureToolCallBehavior;
7+
using OpenAI.Chat;
8+
using static Microsoft.SemanticKernel.Connectors.AzureOpenAI.AzureOpenAIToolCallBehavior;
99

1010
namespace SemanticKernel.Connectors.AzureOpenAI.UnitTests;
1111

1212
/// <summary>
13-
/// Unit tests for <see cref="AzureToolCallBehavior"/>
13+
/// Unit tests for <see cref="AzureOpenAIToolCallBehavior"/>
1414
/// </summary>
15-
public sealed class AzureToolCallBehaviorTests
15+
public sealed class AzureOpenAIToolCallBehaviorTests
1616
{
1717
[Fact]
1818
public void EnableKernelFunctionsReturnsCorrectKernelFunctionsInstance()
1919
{
2020
// Arrange & Act
21-
var behavior = AzureToolCallBehavior.EnableKernelFunctions;
21+
var behavior = AzureOpenAIToolCallBehavior.EnableKernelFunctions;
2222

2323
// Assert
2424
Assert.IsType<KernelFunctions>(behavior);
@@ -30,7 +30,7 @@ public void AutoInvokeKernelFunctionsReturnsCorrectKernelFunctionsInstance()
3030
{
3131
// Arrange & Act
3232
const int DefaultMaximumAutoInvokeAttempts = 128;
33-
var behavior = AzureToolCallBehavior.AutoInvokeKernelFunctions;
33+
var behavior = AzureOpenAIToolCallBehavior.AutoInvokeKernelFunctions;
3434

3535
// Assert
3636
Assert.IsType<KernelFunctions>(behavior);
@@ -42,7 +42,7 @@ public void EnableFunctionsReturnsEnabledFunctionsInstance()
4242
{
4343
// Arrange & Act
4444
List<AzureOpenAIFunction> functions = [new("Plugin", "Function", "description", [], null)];
45-
var behavior = AzureToolCallBehavior.EnableFunctions(functions);
45+
var behavior = AzureOpenAIToolCallBehavior.EnableFunctions(functions);
4646

4747
// Assert
4848
Assert.IsType<EnabledFunctions>(behavior);
@@ -52,7 +52,7 @@ public void EnableFunctionsReturnsEnabledFunctionsInstance()
5252
public void RequireFunctionReturnsRequiredFunctionInstance()
5353
{
5454
// Arrange & Act
55-
var behavior = AzureToolCallBehavior.RequireFunction(new("Plugin", "Function", "description", [], null));
55+
var behavior = AzureOpenAIToolCallBehavior.RequireFunction(new("Plugin", "Function", "description", [], null));
5656

5757
// Assert
5858
Assert.IsType<RequiredFunction>(behavior);
@@ -63,65 +63,62 @@ public void KernelFunctionsConfigureOptionsWithNullKernelDoesNotAddTools()
6363
{
6464
// Arrange
6565
var kernelFunctions = new KernelFunctions(autoInvoke: false);
66-
var chatCompletionsOptions = new ChatCompletionsOptions();
6766

6867
// Act
69-
kernelFunctions.ConfigureOptions(null, chatCompletionsOptions);
68+
var options = kernelFunctions.ConfigureOptions(null);
7069

7170
// Assert
72-
Assert.Empty(chatCompletionsOptions.Tools);
71+
Assert.Null(options.Choice);
72+
Assert.Null(options.Tools);
7373
}
7474

7575
[Fact]
7676
public void KernelFunctionsConfigureOptionsWithoutFunctionsDoesNotAddTools()
7777
{
7878
// Arrange
7979
var kernelFunctions = new KernelFunctions(autoInvoke: false);
80-
var chatCompletionsOptions = new ChatCompletionsOptions();
8180
var kernel = Kernel.CreateBuilder().Build();
8281

8382
// Act
84-
kernelFunctions.ConfigureOptions(kernel, chatCompletionsOptions);
83+
var options = kernelFunctions.ConfigureOptions(kernel);
8584

8685
// Assert
87-
Assert.Null(chatCompletionsOptions.ToolChoice);
88-
Assert.Empty(chatCompletionsOptions.Tools);
86+
Assert.Null(options.Choice);
87+
Assert.Null(options.Tools);
8988
}
9089

9190
[Fact]
9291
public void KernelFunctionsConfigureOptionsWithFunctionsAddsTools()
9392
{
9493
// Arrange
9594
var kernelFunctions = new KernelFunctions(autoInvoke: false);
96-
var chatCompletionsOptions = new ChatCompletionsOptions();
9795
var kernel = Kernel.CreateBuilder().Build();
9896

9997
var plugin = this.GetTestPlugin();
10098

10199
kernel.Plugins.Add(plugin);
102100

103101
// Act
104-
kernelFunctions.ConfigureOptions(kernel, chatCompletionsOptions);
102+
var options = kernelFunctions.ConfigureOptions(kernel);
105103

106104
// Assert
107-
Assert.Equal(ChatCompletionsToolChoice.Auto, chatCompletionsOptions.ToolChoice);
105+
Assert.Equal(ChatToolChoice.Auto, options.Choice);
108106

109-
this.AssertTools(chatCompletionsOptions);
107+
this.AssertTools(options.Tools);
110108
}
111109

112110
[Fact]
113111
public void EnabledFunctionsConfigureOptionsWithoutFunctionsDoesNotAddTools()
114112
{
115113
// Arrange
116114
var enabledFunctions = new EnabledFunctions([], autoInvoke: false);
117-
var chatCompletionsOptions = new ChatCompletionsOptions();
118115

119116
// Act
120-
enabledFunctions.ConfigureOptions(null, chatCompletionsOptions);
117+
var options = enabledFunctions.ConfigureOptions(null);
121118

122119
// Assert
123-
Assert.Null(chatCompletionsOptions.ToolChoice);
124-
Assert.Empty(chatCompletionsOptions.Tools);
120+
Assert.Null(options.Choice);
121+
Assert.Null(options.Tools);
125122
}
126123

127124
[Fact]
@@ -130,10 +127,9 @@ public void EnabledFunctionsConfigureOptionsWithAutoInvokeAndNullKernelThrowsExc
130127
// Arrange
131128
var functions = this.GetTestPlugin().GetFunctionsMetadata().Select(function => function.ToAzureOpenAIFunction());
132129
var enabledFunctions = new EnabledFunctions(functions, autoInvoke: true);
133-
var chatCompletionsOptions = new ChatCompletionsOptions();
134130

135131
// Act & Assert
136-
var exception = Assert.Throws<KernelException>(() => enabledFunctions.ConfigureOptions(null, chatCompletionsOptions));
132+
var exception = Assert.Throws<KernelException>(() => enabledFunctions.ConfigureOptions(null));
137133
Assert.Equal($"Auto-invocation with {nameof(EnabledFunctions)} is not supported when no kernel is provided.", exception.Message);
138134
}
139135

@@ -143,11 +139,10 @@ public void EnabledFunctionsConfigureOptionsWithAutoInvokeAndEmptyKernelThrowsEx
143139
// Arrange
144140
var functions = this.GetTestPlugin().GetFunctionsMetadata().Select(function => function.ToAzureOpenAIFunction());
145141
var enabledFunctions = new EnabledFunctions(functions, autoInvoke: true);
146-
var chatCompletionsOptions = new ChatCompletionsOptions();
147142
var kernel = Kernel.CreateBuilder().Build();
148143

149144
// Act & Assert
150-
var exception = Assert.Throws<KernelException>(() => enabledFunctions.ConfigureOptions(kernel, chatCompletionsOptions));
145+
var exception = Assert.Throws<KernelException>(() => enabledFunctions.ConfigureOptions(kernel));
151146
Assert.Equal($"The specified {nameof(EnabledFunctions)} function MyPlugin-MyFunction is not available in the kernel.", exception.Message);
152147
}
153148

@@ -160,18 +155,17 @@ public void EnabledFunctionsConfigureOptionsWithKernelAndPluginsAddsTools(bool a
160155
var plugin = this.GetTestPlugin();
161156
var functions = plugin.GetFunctionsMetadata().Select(function => function.ToAzureOpenAIFunction());
162157
var enabledFunctions = new EnabledFunctions(functions, autoInvoke);
163-
var chatCompletionsOptions = new ChatCompletionsOptions();
164158
var kernel = Kernel.CreateBuilder().Build();
165159

166160
kernel.Plugins.Add(plugin);
167161

168162
// Act
169-
enabledFunctions.ConfigureOptions(kernel, chatCompletionsOptions);
163+
var options = enabledFunctions.ConfigureOptions(kernel);
170164

171165
// Assert
172-
Assert.Equal(ChatCompletionsToolChoice.Auto, chatCompletionsOptions.ToolChoice);
166+
Assert.Equal(ChatToolChoice.Auto, options.Choice);
173167

174-
this.AssertTools(chatCompletionsOptions);
168+
this.AssertTools(options.Tools);
175169
}
176170

177171
[Fact]
@@ -180,10 +174,9 @@ public void RequiredFunctionsConfigureOptionsWithAutoInvokeAndNullKernelThrowsEx
180174
// Arrange
181175
var function = this.GetTestPlugin().GetFunctionsMetadata().Select(function => function.ToAzureOpenAIFunction()).First();
182176
var requiredFunction = new RequiredFunction(function, autoInvoke: true);
183-
var chatCompletionsOptions = new ChatCompletionsOptions();
184177

185178
// Act & Assert
186-
var exception = Assert.Throws<KernelException>(() => requiredFunction.ConfigureOptions(null, chatCompletionsOptions));
179+
var exception = Assert.Throws<KernelException>(() => requiredFunction.ConfigureOptions(null));
187180
Assert.Equal($"Auto-invocation with {nameof(RequiredFunction)} is not supported when no kernel is provided.", exception.Message);
188181
}
189182

@@ -193,11 +186,10 @@ public void RequiredFunctionsConfigureOptionsWithAutoInvokeAndEmptyKernelThrowsE
193186
// Arrange
194187
var function = this.GetTestPlugin().GetFunctionsMetadata().Select(function => function.ToAzureOpenAIFunction()).First();
195188
var requiredFunction = new RequiredFunction(function, autoInvoke: true);
196-
var chatCompletionsOptions = new ChatCompletionsOptions();
197189
var kernel = Kernel.CreateBuilder().Build();
198190

199191
// Act & Assert
200-
var exception = Assert.Throws<KernelException>(() => requiredFunction.ConfigureOptions(kernel, chatCompletionsOptions));
192+
var exception = Assert.Throws<KernelException>(() => requiredFunction.ConfigureOptions(kernel));
201193
Assert.Equal($"The specified {nameof(RequiredFunction)} function MyPlugin-MyFunction is not available in the kernel.", exception.Message);
202194
}
203195

@@ -207,18 +199,17 @@ public void RequiredFunctionConfigureOptionsAddsTools()
207199
// Arrange
208200
var plugin = this.GetTestPlugin();
209201
var function = plugin.GetFunctionsMetadata()[0].ToAzureOpenAIFunction();
210-
var chatCompletionsOptions = new ChatCompletionsOptions();
211202
var requiredFunction = new RequiredFunction(function, autoInvoke: true);
212203
var kernel = new Kernel();
213204
kernel.Plugins.Add(plugin);
214205

215206
// Act
216-
requiredFunction.ConfigureOptions(kernel, chatCompletionsOptions);
207+
var options = requiredFunction.ConfigureOptions(kernel);
217208

218209
// Assert
219-
Assert.NotNull(chatCompletionsOptions.ToolChoice);
210+
Assert.NotNull(options.Choice);
220211

221-
this.AssertTools(chatCompletionsOptions);
212+
this.AssertTools(options.Tools);
222213
}
223214

224215
private KernelPlugin GetTestPlugin()
@@ -233,16 +224,15 @@ private KernelPlugin GetTestPlugin()
233224
return KernelPluginFactory.CreateFromFunctions("MyPlugin", [function]);
234225
}
235226

236-
private void AssertTools(ChatCompletionsOptions chatCompletionsOptions)
227+
private void AssertTools(IList<ChatTool>? tools)
237228
{
238-
Assert.Single(chatCompletionsOptions.Tools);
239-
240-
var tool = chatCompletionsOptions.Tools[0] as ChatCompletionsFunctionToolDefinition;
229+
Assert.NotNull(tools);
230+
var tool = Assert.Single(tools);
241231

242232
Assert.NotNull(tool);
243233

244-
Assert.Equal("MyPlugin-MyFunction", tool.Name);
245-
Assert.Equal("Test Function", tool.Description);
246-
Assert.Equal("{\"type\":\"object\",\"required\":[],\"properties\":{\"parameter1\":{\"type\":\"string\"},\"parameter2\":{\"type\":\"string\"}}}", tool.Parameters.ToString());
234+
Assert.Equal("MyPlugin-MyFunction", tool.FunctionName);
235+
Assert.Equal("Test Function", tool.FunctionDescription);
236+
Assert.Equal("{\"type\":\"object\",\"required\":[],\"properties\":{\"parameter1\":{\"type\":\"string\"},\"parameter2\":{\"type\":\"string\"}}}", tool.FunctionParameters.ToString());
247237
}
248238
}

0 commit comments

Comments
 (0)