Update OpenAI to 2.9.1, Azure.AI.OpenAI to 2.9.0-beta.1, Azure.AI.Projects to 2.0.0-beta.2, Microsoft.Extensions.AI* to 10.4.0 (#13668)

### Motivation and Context

Bumps core AI SDK dependencies to their latest versions to pick up new
features and fixes.

### Description

Updates the following packages in `Directory.Packages.props`:

- **OpenAI** 2.8.0 → 2.9.1
- **Azure.AI.OpenAI** 2.8.0-beta.1 → 2.9.0-beta.1
- **Azure.AI.Projects** 1.1.0 → 2.0.0-beta.2
- **Azure.Identity** 1.17.1 → 1.19.0 (cascading dependency from
Azure.AI.Projects 2.0.0-beta.2)
- **Microsoft.Extensions.AI\*** 10.3.0 → 10.4.0
- **System.Text.Json, System.Numerics.Tensors,
Microsoft.Extensions.Logging.Abstractions,
Microsoft.Bcl.AsyncInterfaces, System.Threading.Channels** → 10.0.4

Addresses the following breaking changes introduced by OpenAI 2.9.1:

- `CreateInputImagePart` BinaryData overload removed (replaced with data
URI approach)
- Realtime API type renames (`ConversationTool` → `RealtimeTool`,
`ConversationSessionOptions` → `RealtimeConversationSessionOptions`,
etc.)
- `ResponsesClient` constructor no longer accepts a model parameter;
model is now specified on `CreateResponseOptions.Model`
- `ResponseResult.Instructions` is now read-only

Code changes to adapt:

- Added `ModelId` property to `OpenAIResponseAgent` and propagated it
through `ResponseCreationOptionsFactory`
- Updated unit tests (`BaseOpenAIResponseClientTest`,
`OpenAIResponseAgentExtensionsTests`, `OpenAIResponseExtensionsTests`)
- Updated integration tests (`OpenAIResponseAgentTests`,
`OpenAIResponseAgentFixture`)
- Updated `BaseResponsesAgentTest` and `GettingStartedWithAgents`
samples
- Updated `AgentFrameworkMigration` samples (`GetResponsesClient`,
`AsIChatClient`, `CreateAIAgent`, modelId propagation)
- Rewrote `OpenAIRealtime` demo for the restructured Realtime API event
model

### Contribution Checklist

- [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
- [ ] I didn't break anyone 😄

<!-- START COPILOT CODING AGENT TIPS -->
---

📍 Connect Copilot coding agent with [Jira](https://gh.io/cca-jira-docs),
[Azure Boards](https://gh.io/cca-azure-boards-docs) or
[Linear](https://gh.io/cca-linear-docs) to delegate work to Copilot in
one click without leaving your project management tool.

---------

Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: stephentoub <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Roger Barreto <[email protected]>
This commit is contained in:
Copilot
2026-03-19 15:41:43 +00:00
committed by GitHub
co-authored by stephentoub Copilot Roger Barreto
parent 3c1b688dcf
commit 30aec16700
24 changed files with 208 additions and 168 deletions
+12 -12
View File
@@ -22,9 +22,9 @@
<PackageVersion Include="AWSSDK.SecurityToken" Version="4.0.5.6" />
<PackageVersion Include="Azure.AI.Agents.Persistent" Version="1.1.0" />
<PackageVersion Include="Azure.AI.ContentSafety" Version="1.0.0" />
<PackageVersion Include="Azure.AI.OpenAI" Version="2.8.0-beta.1" />
<PackageVersion Include="Azure.AI.Projects" Version="1.1.0" />
<PackageVersion Include="Azure.Identity" Version="1.17.1" />
<PackageVersion Include="Azure.AI.OpenAI" Version="2.9.0-beta.1" />
<PackageVersion Include="Azure.AI.Projects" Version="2.0.0-beta.2" />
<PackageVersion Include="Azure.Identity" Version="1.19.0" />
<PackageVersion Include="Azure.Monitor.OpenTelemetry.Exporter" Version="1.5.0" />
<PackageVersion Include="Azure.Search.Documents" Version="11.7.0" />
<PackageVersion Include="Community.OData.Linq" Version="2.1.0" />
@@ -92,7 +92,7 @@
<PackageVersion Include="Npgsql" Version="8.0.7" />
<PackageVersion Include="OData2Linq" Version="2.2.0" />
<PackageVersion Include="OllamaSharp" Version="5.4.12" />
<PackageVersion Include="OpenAI" Version="2.8.0" />
<PackageVersion Include="OpenAI" Version="2.9.1" />
<PackageVersion Include="OpenTelemetry.Exporter.Console" Version="1.14.0" />
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.14.0" />
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.14.0" />
@@ -109,8 +109,8 @@
<PackageVersion Include="System.Linq.AsyncEnumerable" Version="10.0.4" />
<PackageVersion Include="System.Memory.Data" Version="10.0.2" />
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
<PackageVersion Include="System.Numerics.Tensors" Version="10.0.3" />
<PackageVersion Include="System.Text.Json" Version="10.0.3" />
<PackageVersion Include="System.Numerics.Tensors" Version="10.0.4" />
<PackageVersion Include="System.Text.Json" Version="10.0.4" />
<PackageVersion Include="System.ValueTuple" Version="4.6.1" />
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.6.3" />
<PackageVersion Include="A2A" Version="0.3.1-preview" />
@@ -119,10 +119,10 @@
<!-- Tokenizers -->
<PackageVersion Include="Microsoft.ML.Tokenizers" Version="2.0.0" />
<!-- Microsoft.Extensions.* -->
<PackageVersion Include="Microsoft.Extensions.AI" Version="10.3.0" />
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="10.3.0" />
<PackageVersion Include="Microsoft.Extensions.AI" Version="10.4.0" />
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="10.4.0" />
<PackageVersion Include="Microsoft.Extensions.AI.AzureAIInference" Version="10.0.0-preview.1.25559.3" />
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="10.3.0" />
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="10.4.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.2" />
@@ -131,14 +131,14 @@
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.3" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.4" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.Testing" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="10.2.0" />
<PackageVersion Include="Microsoft.Extensions.ServiceDiscovery" Version="10.2.0" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.3" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.4" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Logging.Debug" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Options.DataAnnotations" Version="10.0.2" />
@@ -151,7 +151,7 @@
<PackageVersion Include="Moq" Version="[4.18.4]" />
<PackageVersion Include="FluentAssertions" Version="8.2.0" />
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageVersion Include="System.Threading.Channels" Version="10.0.3" />
<PackageVersion Include="System.Threading.Channels" Version="10.0.4" />
<PackageVersion Include="System.Threading.Tasks.Dataflow" Version="10.0.2" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.abstractions" Version="2.0.3" />
@@ -3,6 +3,7 @@
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.SemanticKernel.Agents.OpenAI;
using OpenAI;
@@ -24,8 +25,8 @@ async Task SKAgentAsync()
Console.WriteLine("\n=== SK Agent ===\n");
var responseClient = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
.GetResponsesClient(deploymentName);
OpenAIResponseAgent agent = new(responseClient)
.GetResponsesClient();
OpenAIResponseAgent agent = new(responseClient, deploymentName)
{
Name = "Joker",
Instructions = "You are good at telling jokes.",
@@ -54,9 +55,9 @@ async Task SKAgent_As_AFAgentAsync()
Console.WriteLine("\n=== SK Agent Converted as an AF Agent ===\n");
var responseClient = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
.GetResponsesClient(deploymentName);
.GetResponsesClient();
OpenAIResponseAgent skAgent = new(responseClient)
OpenAIResponseAgent skAgent = new(responseClient, deploymentName)
{
Name = "Joker",
Instructions = "You are good at telling jokes.",
@@ -83,7 +84,7 @@ async Task AFAgentAsync()
Console.WriteLine("\n=== AF Agent ===\n");
var agent = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
.GetResponsesClient(deploymentName)
.GetResponsesClient().AsIChatClient(deploymentName)
.CreateAIAgent(name: "Joker", instructions: "You are good at telling jokes.");
var thread = agent.GetNewThread();
@@ -51,8 +51,8 @@ async Task SKAgentAsync()
Console.WriteLine("\n=== SK Agent ===\n");
var responseClient = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
.GetResponsesClient(deploymentName);
OpenAIResponseAgent agent = new(responseClient)
.GetResponsesClient();
OpenAIResponseAgent agent = new(responseClient, deploymentName)
{
Name = "Thinker",
Instructions = "You are good at thinking hard before answering.",
@@ -119,11 +119,11 @@ async Task SKAgent_As_AFAgentAsync()
Console.WriteLine("\n=== SK Agent Converted as an AF Agent ===\n");
var responseClient = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
.GetResponsesClient(deploymentName);
.GetResponsesClient();
#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
OpenAIResponseAgent skAgent = new(responseClient)
OpenAIResponseAgent skAgent = new(responseClient, deploymentName)
{
Name = "Thinker",
Instructions = "You are good at thinking hard before answering.",
@@ -180,7 +180,7 @@ async Task AFAgentAsync()
Console.WriteLine("\n=== AF Agent ===\n");
var agent = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
.GetResponsesClient(deploymentName)
.GetResponsesClient().AsIChatClient(deploymentName)
.CreateAIAgent(name: "Thinker", instructions: "You are good at thinking hard before answering.");
var thread = agent.GetNewThread();
@@ -29,7 +29,7 @@ await AFAgentAsync();
async Task SKAgentAsync()
{
OpenAIResponseAgent agent = new(new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
.GetResponsesClient(deploymentName))
.GetResponsesClient(), deploymentName)
{ StoreEnabled = true };
// Initialize plugin and add to the agent's Kernel (same as direct Kernel usage).
@@ -49,7 +49,7 @@ async Task SKAgentAsync()
async Task SKAgent_As_AFAgentAsync()
{
OpenAIResponseAgent skAgent = new(new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
.GetResponsesClient(deploymentName))
.GetResponsesClient(), deploymentName)
{ StoreEnabled = true };
// Initialize plugin and add to the agent's Kernel (same as direct Kernel usage).
@@ -66,7 +66,7 @@ async Task SKAgent_As_AFAgentAsync()
async Task AFAgentAsync()
{
var agent = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
.GetResponsesClient(deploymentName)
.GetResponsesClient().AsIChatClient(deploymentName)
.CreateAIAgent(instructions: "You are a helpful assistant", tools: [AIFunctionFactory.Create(GetWeather)]);
Console.WriteLine("\n=== AF Agent Response ===\n");
@@ -28,7 +28,7 @@ async Task SKAgentAsync()
var serviceCollection = new ServiceCollection();
serviceCollection.AddTransient<Microsoft.SemanticKernel.Agents.Agent>((sp)
=> new OpenAIResponseAgent(new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
.GetResponsesClient(deploymentName))
.GetResponsesClient(), deploymentName)
{
Name = "Joker",
Instructions = "You are good at telling jokes.",
@@ -49,7 +49,7 @@ async Task SKAgent_As_AFAgentAsync()
var serviceCollection = new ServiceCollection();
serviceCollection.AddTransient<Microsoft.SemanticKernel.Agents.Agent>((sp)
=> new OpenAIResponseAgent(new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
.GetResponsesClient(deploymentName))
.GetResponsesClient(), deploymentName)
{
Name = "Joker",
Instructions = "You are good at telling jokes.",
@@ -71,7 +71,7 @@ async Task AFAgentAsync()
var serviceCollection = new ServiceCollection();
serviceCollection.AddTransient<AIAgent>((sp) => new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
.GetResponsesClient(deploymentName)
.GetResponsesClient().AsIChatClient(deploymentName)
.CreateAIAgent(name: "Joker", instructions: "You are good at telling jokes."));
await using ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.SemanticKernel.Agents.OpenAI;
using OpenAI;
@@ -21,7 +22,7 @@ async Task SKAgentAsync()
{
Console.WriteLine("\n=== SK Agent ===\n");
var responseClient = new OpenAIClient(apiKey).GetResponsesClient(model);
var responseClient = new OpenAIClient(apiKey).GetResponsesClient();
OpenAIResponseAgent agent = new(responseClient)
{
Name = "Joker",
@@ -51,7 +52,7 @@ async Task SKAgent_As_AFAgentAsync()
{
Console.WriteLine("\n=== SK Agent Converted as an AF Agent ===\n");
var responseClient = new OpenAIClient(apiKey).GetResponsesClient(model);
var responseClient = new OpenAIClient(apiKey).GetResponsesClient();
OpenAIResponseAgent skAgent = new(responseClient)
{
@@ -79,7 +80,7 @@ async Task AFAgentAsync()
{
Console.WriteLine("\n=== AF Agent ===\n");
var agent = new OpenAIClient(apiKey).GetResponsesClient(model)
var agent = new OpenAIClient(apiKey).GetResponsesClient().AsIChatClient(model)
.CreateAIAgent(name: "Joker", instructions: "You are good at telling jokes.");
var thread = agent.GetNewThread();
@@ -48,7 +48,7 @@ async Task SKAgentAsync()
{
Console.WriteLine("\n=== SK Agent ===\n");
var responseClient = new OpenAIClient(apiKey).GetResponsesClient(model);
var responseClient = new OpenAIClient(apiKey).GetResponsesClient();
OpenAIResponseAgent agent = new(responseClient)
{
Name = "Thinker",
@@ -116,7 +116,7 @@ async Task SKAgent_As_AFAgentAsync()
{
Console.WriteLine("\n=== SK Agent Converted as an AF Agent ===\n");
var responseClient = new OpenAIClient(apiKey).GetResponsesClient(model);
var responseClient = new OpenAIClient(apiKey).GetResponsesClient();
#pragma warning disable SKEXP0110 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
@@ -176,7 +176,7 @@ async Task AFAgentAsync()
{
Console.WriteLine("\n=== AF Agent ===\n");
var agent = new OpenAIClient(apiKey).GetResponsesClient(model)
var agent = new OpenAIClient(apiKey).GetResponsesClient().AsIChatClient(model)
.CreateAIAgent(name: "Thinker", instructions: "You are at thinking hard before answering.");
var thread = agent.GetNewThread();
@@ -28,7 +28,7 @@ async Task SKAgentAsync()
{
var builder = Kernel.CreateBuilder().AddOpenAIChatClient(model, apiKey);
OpenAIResponseAgent agent = new(new OpenAIClient(apiKey).GetResponsesClient(model)) { StoreEnabled = true };
OpenAIResponseAgent agent = new(new OpenAIClient(apiKey).GetResponsesClient(), model) { StoreEnabled = true };
// Initialize plugin and add to the agent's Kernel (same as direct Kernel usage).
agent.Kernel.Plugins.Add(KernelPluginFactory.CreateFromFunctions("KernelPluginName", [KernelFunctionFactory.CreateFromMethod(GetWeather)]));
@@ -48,7 +48,7 @@ async Task SKAgent_As_AFAgentAsync()
{
var builder = Kernel.CreateBuilder().AddOpenAIChatClient(model, apiKey);
OpenAIResponseAgent skAgent = new(new OpenAIClient(apiKey).GetResponsesClient(model)) { StoreEnabled = true };
OpenAIResponseAgent skAgent = new(new OpenAIClient(apiKey).GetResponsesClient(), model) { StoreEnabled = true };
// Initialize plugin and add to the agent's Kernel (same as direct Kernel usage).
skAgent.Kernel.Plugins.Add(KernelPluginFactory.CreateFromFunctions("KernelPluginName", [KernelFunctionFactory.CreateFromMethod(GetWeather)]));
@@ -63,7 +63,7 @@ async Task SKAgent_As_AFAgentAsync()
async Task AFAgentAsync()
{
var agent = new OpenAIClient(apiKey).GetResponsesClient(model).CreateAIAgent(
var agent = new OpenAIClient(apiKey).GetResponsesClient().AsIChatClient(model).CreateAIAgent(
instructions: "You are a helpful assistant",
tools: [AIFunctionFactory.Create(GetWeather)]);
@@ -25,7 +25,7 @@ async Task SKAgentAsync()
var serviceCollection = new ServiceCollection();
serviceCollection.AddTransient<Microsoft.SemanticKernel.Agents.Agent>((sp)
=> new OpenAIResponseAgent(new OpenAIClient(apiKey).GetResponsesClient(model))
=> new OpenAIResponseAgent(new OpenAIClient(apiKey).GetResponsesClient(), model)
{
Name = "Joker",
Instructions = "You are good at telling jokes."
@@ -44,7 +44,7 @@ async Task SKAgent_As_AFAgentAsync()
var serviceCollection = new ServiceCollection();
serviceCollection.AddTransient<Microsoft.SemanticKernel.Agents.Agent>((sp)
=> new OpenAIResponseAgent(new OpenAIClient(apiKey).GetResponsesClient(model))
=> new OpenAIResponseAgent(new OpenAIClient(apiKey).GetResponsesClient(), model)
{
Name = "Joker",
Instructions = "You are good at telling jokes."
@@ -65,7 +65,7 @@ async Task AFAgentAsync()
var serviceCollection = new ServiceCollection();
serviceCollection.AddTransient((sp) => new OpenAIClient(apiKey)
.GetResponsesClient(model)
.GetResponsesClient().AsIChatClient(model)
.CreateAIAgent(name: "Joker", instructions: "You are good at telling jokes."));
await using ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
+81 -61
View File
@@ -33,19 +33,22 @@ internal sealed class Program
kernel.ImportPluginFromType<WeatherPlugin>();
// Start a new conversation session.
using RealtimeSession session = await realtimeConversationClient.StartConversationSessionAsync("gpt-4o-realtime-preview");
using RealtimeSessionClient session = await realtimeConversationClient.StartConversationSessionAsync("gpt-4o-realtime-preview");
// Initialize session options.
// Session options control connection-wide behavior shared across all conversations,
// including audio input format and voice activity detection settings.
ConversationSessionOptions sessionOptions = new()
RealtimeConversationSessionOptions sessionOptions = new()
{
Voice = ConversationVoice.Alloy,
InputAudioFormat = RealtimeAudioFormat.Pcm16,
OutputAudioFormat = RealtimeAudioFormat.Pcm16,
InputTranscriptionOptions = new()
AudioOptions = new()
{
Model = "whisper-1",
InputAudioOptions = new()
{
AudioTranscriptionOptions = new()
{
Model = "whisper-1",
},
},
},
};
@@ -58,7 +61,7 @@ internal sealed class Program
// If any tools are available, set tool choice to "auto".
if (sessionOptions.Tools.Count > 0)
{
sessionOptions.ToolChoice = ConversationToolChoice.CreateAutoToolChoice();
sessionOptions.ToolChoice = RealtimeDefaultToolChoice.Auto;
}
// Configure session with defined options.
@@ -66,8 +69,8 @@ internal sealed class Program
// Items such as user, assistant, or system messages, as well as input audio, can be sent to the session.
// An example of sending user message to the session.
// ConversationItem can be constructed from Microsoft.SemanticKernel.ChatMessageContent if needed by mapping the relevant fields.
await session.AddItemAsync(RealtimeItem.CreateUserMessage(["I'm trying to decide what to wear on my trip."]));
// RealtimeItem can be constructed from Microsoft.SemanticKernel.ChatMessageContent if needed by mapping the relevant fields.
await session.AddItemAsync(RealtimeItem.CreateUserMessageItem("I'm trying to decide what to wear on my trip."));
// Use audio file that contains a recorded question: "What's the weather like in San Francisco, California?"
string inputAudioPath = FindFile("Assets\\realtime_whats_the_weather_pcm16_24khz_mono.wav");
@@ -81,115 +84,132 @@ internal sealed class Program
Dictionary<string, StringBuilder> functionArgumentBuildersById = [];
// Define a loop to receive conversation updates in the session.
await foreach (RealtimeUpdate update in session.ReceiveUpdatesAsync())
await foreach (RealtimeServerUpdate update in session.ReceiveUpdatesAsync())
{
// Notification indicating the start of the conversation session.
if (update is ConversationSessionStartedUpdate sessionStartedUpdate)
if (update is RealtimeServerUpdateSessionCreated sessionStartedUpdate)
{
Console.WriteLine($"<<< Session started. ID: {sessionStartedUpdate.SessionId}");
Console.WriteLine($"<<< Session started. ID: {sessionStartedUpdate.EventId}");
Console.WriteLine();
}
// Notification indicating the start of detected voice activity.
if (update is InputAudioSpeechStartedUpdate speechStartedUpdate)
if (update is RealtimeServerUpdateInputAudioBufferSpeechStarted speechStartedUpdate)
{
Console.WriteLine(
$" -- Voice activity detection started at {speechStartedUpdate.AudioStartTime}");
}
// Notification indicating the end of detected voice activity.
if (update is InputAudioSpeechFinishedUpdate speechFinishedUpdate)
if (update is RealtimeServerUpdateInputAudioBufferSpeechStopped speechFinishedUpdate)
{
Console.WriteLine(
$" -- Voice activity detection ended at {speechFinishedUpdate.AudioEndTime}");
}
// Notification indicating the start of item streaming, such as a function call or response message.
if (update is OutputStreamingStartedUpdate itemStreamingStartedUpdate)
if (update is RealtimeServerUpdateResponseOutputItemAdded itemStreamingStartedUpdate)
{
Console.WriteLine(" -- Begin streaming of new item");
if (!string.IsNullOrEmpty(itemStreamingStartedUpdate.FunctionName))
if (itemStreamingStartedUpdate.Item is RealtimeFunctionCallItem funcItem)
{
Console.Write($" {itemStreamingStartedUpdate.FunctionName}: ");
Console.Write($" {funcItem.FunctionName}: ");
}
}
// Notification about item streaming delta, which may include audio transcript, audio bytes, or function arguments.
if (update is OutputDeltaUpdate deltaUpdate)
// Notification about audio transcript delta.
if (update is RealtimeServerUpdateResponseOutputAudioTranscriptDelta audioTranscriptDelta)
{
Console.Write(deltaUpdate.AudioTranscript);
Console.Write(deltaUpdate.Text);
Console.Write(deltaUpdate.FunctionArguments);
Console.Write(audioTranscriptDelta.Delta);
}
// Handle audio bytes.
if (deltaUpdate.AudioBytes is not null)
// Notification about text delta.
if (update is RealtimeServerUpdateResponseOutputTextDelta textDelta)
{
Console.Write(textDelta.Delta);
}
// Notification about audio bytes delta.
if (update is RealtimeServerUpdateResponseOutputAudioDelta audioDelta)
{
if (audioDelta.Delta is not null)
{
if (!outputAudioStreamsById.TryGetValue(deltaUpdate.ItemId, out MemoryStream? value))
if (!outputAudioStreamsById.TryGetValue(audioDelta.ItemId, out MemoryStream? value))
{
value = new MemoryStream();
outputAudioStreamsById[deltaUpdate.ItemId] = value;
outputAudioStreamsById[audioDelta.ItemId] = value;
}
value.Write(deltaUpdate.AudioBytes);
value.Write(audioDelta.Delta.ToArray());
}
}
// Notification about function call arguments delta.
if (update is RealtimeServerUpdateResponseFunctionCallArgumentsDelta funcArgsDelta)
{
if (!functionArgumentBuildersById.TryGetValue(funcArgsDelta.ItemId, out StringBuilder? arguments))
{
functionArgumentBuildersById[funcArgsDelta.ItemId] = arguments = new();
}
// Handle function arguments.
if (!functionArgumentBuildersById.TryGetValue(deltaUpdate.ItemId, out StringBuilder? arguments))
if (funcArgsDelta.Delta is not null)
{
functionArgumentBuildersById[deltaUpdate.ItemId] = arguments = new();
}
if (!string.IsNullOrWhiteSpace(deltaUpdate.FunctionArguments))
{
arguments.Append(deltaUpdate.FunctionArguments);
arguments.Append(funcArgsDelta.Delta.ToString());
}
}
// Notification indicating the end of item streaming, such as a function call or response message.
// At this point, audio transcript can be displayed on console, or a function can be called with aggregated arguments.
if (update is OutputStreamingFinishedUpdate itemStreamingFinishedUpdate)
if (update is RealtimeServerUpdateResponseOutputItemDone itemStreamingFinishedUpdate)
{
Console.WriteLine();
Console.WriteLine($" -- Item streaming finished, item_id={itemStreamingFinishedUpdate.ItemId}");
Console.WriteLine($" -- Item streaming finished, response_id={itemStreamingFinishedUpdate.ResponseId}");
// If an item is a function call, invoke a function with provided arguments.
if (itemStreamingFinishedUpdate.FunctionCallId is not null)
if (itemStreamingFinishedUpdate.Item is RealtimeFunctionCallItem functionCallItem)
{
Console.WriteLine($" + Responding to tool invoked by item: {itemStreamingFinishedUpdate.FunctionName}");
Console.WriteLine($" + Responding to tool invoked by item: {functionCallItem.FunctionName}");
// Parse function name.
var (functionName, pluginName) = ParseFunctionName(itemStreamingFinishedUpdate.FunctionName);
var (functionName, pluginName) = ParseFunctionName(functionCallItem.FunctionName);
// Deserialize arguments.
var argumentsString = functionArgumentBuildersById[itemStreamingFinishedUpdate.ItemId].ToString();
var argumentsString = functionArgumentBuildersById.TryGetValue(functionCallItem.Id, out var sb) ? sb.ToString() : "{}";
var arguments = DeserializeArguments(argumentsString);
// Create a function call content based on received data.
var functionCallContent = new FunctionCallContent(
functionName: functionName,
pluginName: pluginName,
id: itemStreamingFinishedUpdate.FunctionCallId,
id: functionCallItem.CallId,
arguments: arguments);
// Invoke a function.
var resultContent = await functionCallContent.InvokeAsync(kernel);
// Create a function call output conversation item with function call result.
RealtimeItem functionOutputItem = RealtimeItem.CreateFunctionCallOutput(
callId: itemStreamingFinishedUpdate.FunctionCallId,
output: ProcessFunctionResult(resultContent.Result));
RealtimeItem functionOutputItem = RealtimeItem.CreateFunctionCallOutputItem(
callId: functionCallItem.CallId,
functionOutput: ProcessFunctionResult(resultContent.Result));
// Send function call output conversation item to the session, so the model can use it for further processing.
await session.AddItemAsync(functionOutputItem);
}
// If an item is a response message, output it to the console.
else if (itemStreamingFinishedUpdate.MessageContentParts?.Count > 0)
else if (itemStreamingFinishedUpdate.Item is RealtimeMessageItem messageItem && messageItem.Content?.Count > 0)
{
Console.Write($" + [{itemStreamingFinishedUpdate.MessageRole}]: ");
Console.Write($" + [{messageItem.Role}]: ");
foreach (ConversationContentPart contentPart in itemStreamingFinishedUpdate.MessageContentParts)
foreach (RealtimeMessageContentPart contentPart in messageItem.Content)
{
Console.Write(contentPart.AudioTranscript);
if (contentPart is RealtimeOutputAudioMessageContentPart audioContentPart)
{
Console.Write(audioContentPart.Transcript);
}
else if (contentPart is RealtimeOutputTextMessageContentPart textContentPart)
{
Console.Write(textContentPart.Text);
}
}
Console.WriteLine();
@@ -197,7 +217,7 @@ internal sealed class Program
}
// Notification indicating the completion of transcription from input audio.
if (update is InputAudioTranscriptionFinishedUpdate transcriptionCompletedUpdate)
if (update is RealtimeServerUpdateConversationItemInputAudioTranscriptionCompleted transcriptionCompletedUpdate)
{
Console.WriteLine();
Console.WriteLine($" -- User audio transcript: {transcriptionCompletedUpdate.Transcript}");
@@ -205,13 +225,13 @@ internal sealed class Program
}
// Notification about completed model response turn.
if (update is ResponseFinishedUpdate turnFinishedUpdate)
if (update is RealtimeServerUpdateResponseDone turnFinishedUpdate)
{
Console.WriteLine($" -- Model turn generation finished. Status: {turnFinishedUpdate.Status}");
Console.WriteLine($" -- Model turn generation finished. Status: {turnFinishedUpdate.Response?.Status}");
// If the created session items contain a function name, it indicates a function call result has been provided,
// If the output items contain a function call, it indicates a function call result has been provided,
// and response updates can begin.
if (turnFinishedUpdate.CreatedItems.Any(item => item.FunctionName?.Length > 0))
if (turnFinishedUpdate.Response?.OutputItems?.Any(item => item is RealtimeFunctionCallItem) == true)
{
Console.WriteLine(" -- Ending client turn for pending tool responses");
@@ -225,10 +245,10 @@ internal sealed class Program
}
// Notification about error in conversation session.
if (update is RealtimeErrorUpdate errorUpdate)
if (update is RealtimeServerUpdateError errorUpdate)
{
Console.WriteLine();
Console.WriteLine($"ERROR: {errorUpdate.Message}");
Console.WriteLine($"ERROR: {errorUpdate.Error?.Message}");
break;
}
}
@@ -337,7 +357,7 @@ internal sealed class Program
}
/// <summary>Helper method to convert Kernel plugins/function to realtime session conversation tools.</summary>
private static IEnumerable<ConversationTool> ConvertFunctions(Kernel kernel)
private static IEnumerable<RealtimeTool> ConvertFunctions(Kernel kernel)
{
foreach (var plugin in kernel.Plugins)
{
@@ -347,10 +367,10 @@ internal sealed class Program
{
var toolDefinition = metadata.ToOpenAIFunction().ToFunctionDefinition(false);
yield return new ConversationFunctionTool(name: toolDefinition.FunctionName)
yield return new RealtimeFunctionTool(functionName: toolDefinition.FunctionName)
{
Description = toolDefinition.FunctionDescription,
Parameters = toolDefinition.FunctionParameters
FunctionDescription = toolDefinition.FunctionDescription,
FunctionParameters = toolDefinition.FunctionParameters
};
}
}
@@ -15,7 +15,7 @@ public class Step01_OpenAIResponseAgent(ITestOutputHelper output) : BaseResponse
public async Task UseOpenAIResponseAgentAsync()
{
// Define the agent
OpenAIResponseAgent agent = new(this.Client)
OpenAIResponseAgent agent = new(this.Client, this.ModelId)
{
Name = "ResponseAgent",
Instructions = "Answer all queries in English and French.",
@@ -33,7 +33,7 @@ public class Step01_OpenAIResponseAgent(ITestOutputHelper output) : BaseResponse
public async Task UseOpenAIResponseAgentStreamingAsync()
{
// Define the agent
OpenAIResponseAgent agent = new(this.Client)
OpenAIResponseAgent agent = new(this.Client, this.ModelId)
{
Name = "ResponseAgent",
Instructions = "Answer all queries in English and French.",
@@ -48,7 +48,7 @@ public class Step01_OpenAIResponseAgent(ITestOutputHelper output) : BaseResponse
public async Task UseOpenAIResponseAgentWithThreadedConversationAsync()
{
// Define the agent
OpenAIResponseAgent agent = new(this.Client)
OpenAIResponseAgent agent = new(this.Client, this.ModelId)
{
Name = "ResponseAgent",
Instructions = "Answer all queries in the users preferred language.",
@@ -84,7 +84,7 @@ public class Step01_OpenAIResponseAgent(ITestOutputHelper output) : BaseResponse
public async Task UseOpenAIResponseAgentWithThreadedConversationStreamingAsync()
{
// Define the agent
OpenAIResponseAgent agent = new(this.Client)
OpenAIResponseAgent agent = new(this.Client, this.ModelId)
{
Name = "ResponseAgent",
Instructions = "Answer all queries in the users preferred language.",
@@ -116,7 +116,7 @@ public class Step01_OpenAIResponseAgent(ITestOutputHelper output) : BaseResponse
public async Task UseOpenAIResponseAgentWithImageContentAsync()
{
// Define the agent
OpenAIResponseAgent agent = new(this.Client)
OpenAIResponseAgent agent = new(this.Client, this.ModelId)
{
Name = "ResponseAgent",
Instructions = "Provide a detailed description including the weather conditions.",
@@ -18,7 +18,7 @@ public class Step02_OpenAIResponseAgent_ConversationState(ITestOutputHelper outp
public async Task ManuallyConstructPastConversationAsync()
{
// Define the agent
OpenAIResponseAgent agent = new(this.Client)
OpenAIResponseAgent agent = new(this.Client, this.ModelId)
{
StoreEnabled = false,
};
@@ -46,7 +46,7 @@ public class Step02_OpenAIResponseAgent_ConversationState(ITestOutputHelper outp
public async Task ManuallyConstructPastConversationStreamingAsync()
{
// Define the agent
OpenAIResponseAgent agent = new(this.Client)
OpenAIResponseAgent agent = new(this.Client, this.ModelId)
{
StoreEnabled = false,
};
@@ -75,7 +75,7 @@ public class Step02_OpenAIResponseAgent_ConversationState(ITestOutputHelper outp
public async Task ManageConversationStateWithResponseIdAsync()
{
// Define the agent
OpenAIResponseAgent agent = new(this.Client)
OpenAIResponseAgent agent = new(this.Client, this.ModelId)
{
StoreEnabled = false,
};
@@ -106,7 +106,7 @@ public class Step02_OpenAIResponseAgent_ConversationState(ITestOutputHelper outp
public async Task ManageConversationStateWithResponseIdStreamingAsync()
{
// Define the agent
OpenAIResponseAgent agent = new(this.Client)
OpenAIResponseAgent agent = new(this.Client, this.ModelId)
{
StoreEnabled = false,
};
@@ -138,7 +138,7 @@ public class Step02_OpenAIResponseAgent_ConversationState(ITestOutputHelper outp
public async Task StoreConversationStateAsync()
{
// Define the agent
OpenAIResponseAgent agent = new(this.Client)
OpenAIResponseAgent agent = new(this.Client, this.ModelId)
{
StoreEnabled = true,
};
@@ -186,7 +186,7 @@ public class Step02_OpenAIResponseAgent_ConversationState(ITestOutputHelper outp
public async Task StoreConversationStateWithStreamingAsync()
{
// Define the agent
OpenAIResponseAgent agent = new(this.Client)
OpenAIResponseAgent agent = new(this.Client, this.ModelId)
{
StoreEnabled = true,
};
@@ -15,7 +15,7 @@ public class Step03_OpenAIResponseAgent_ReasoningModel(ITestOutputHelper output)
public async Task UseOpenAIResponseAgentWithAReasoningModelAsync()
{
// Define the agent
OpenAIResponseAgent agent = new(this.Client)
OpenAIResponseAgent agent = new(this.Client, this.ModelId)
{
Name = "ResponseAgent",
Instructions = "Answer all queries with a detailed response.",
@@ -33,7 +33,7 @@ public class Step03_OpenAIResponseAgent_ReasoningModel(ITestOutputHelper output)
public async Task UseOpenAIResponseAgentWithAReasoningModelAndSummariesAsync()
{
// Define the agent
OpenAIResponseAgent agent = new(this.Client);
OpenAIResponseAgent agent = new(this.Client, this.ModelId);
// ResponseCreationOptions allows you to specify tools for the agent.
OpenAIResponseAgentInvokeOptions invokeOptions = new()
@@ -86,7 +86,7 @@ public class Step03_OpenAIResponseAgent_ReasoningModel(ITestOutputHelper output)
public async Task UseOpenAIResponseAgentWithAReasoningModelAndToolsAsync()
{
// Define the agent
OpenAIResponseAgent agent = new(this.Client)
OpenAIResponseAgent agent = new(this.Client, this.ModelId)
{
Name = "ResponseAgent",
Instructions = "Answer all queries with a detailed response.",
@@ -22,7 +22,7 @@ public class Step04_OpenAIResponseAgent_Tools(ITestOutputHelper output) : BaseRe
public async Task InvokeAgentWithFunctionToolsAsync()
{
// Define the agent
OpenAIResponseAgent agent = new(this.Client)
OpenAIResponseAgent agent = new(this.Client, this.ModelId)
{
StoreEnabled = false,
};
@@ -53,7 +53,7 @@ public class Step04_OpenAIResponseAgent_Tools(ITestOutputHelper output) : BaseRe
public async Task InvokeAgentWithWebSearchAsync()
{
// Define the agent
OpenAIResponseAgent agent = new(this.Client)
OpenAIResponseAgent agent = new(this.Client, this.ModelId)
{
StoreEnabled = false,
};
@@ -89,7 +89,7 @@ public class Step04_OpenAIResponseAgent_Tools(ITestOutputHelper output) : BaseRe
});
// Define the agent
OpenAIResponseAgent agent = new(this.Client)
OpenAIResponseAgent agent = new(this.Client, this.ModelId)
{
StoreEnabled = false,
};
@@ -131,7 +131,7 @@ public class Step04_OpenAIResponseAgent_Tools(ITestOutputHelper output) : BaseRe
public async Task InvokeAgentWithMultipleToolsAsync()
{
// Define the agent
OpenAIResponseAgent agent = new(this.Client)
OpenAIResponseAgent agent = new(this.Client, this.ModelId)
{
StoreEnabled = false,
};
@@ -29,11 +29,18 @@ internal static class KernelContentExtensions
internal static ResponseContentPart ToResponseContentPart(this ImageContent content)
{
return content.Uri is not null
? ResponseContentPart.CreateInputImagePart(content.Uri)
: content.Data is not null
? ResponseContentPart.CreateInputImagePart(new BinaryData(content.Data), content.MimeType)
: throw new NotSupportedException("ImageContent cannot be converted to ResponseContentPart. Only ImageContent with a uri or binary data is supported.");
if (content.Uri is not null)
{
return ResponseContentPart.CreateInputImagePart(content.Uri);
}
if (content.Data is not null)
{
var dataUri = new Uri($"data:{content.MimeType};base64,{Convert.ToBase64String(content.Data.Value.ToArray())}");
return ResponseContentPart.CreateInputImagePart(dataUri);
}
throw new NotSupportedException("ImageContent cannot be converted to ResponseContentPart. Only ImageContent with a uri or binary data is supported.");
}
internal static ResponseContentPart ToResponseContentPart(this BinaryContent content)
@@ -23,6 +23,7 @@ internal static class ResponseCreationOptionsFactory
{
creationOptions = new CreateResponseOptions
{
Model = responseAgentInvokeOptions.ResponseCreationOptions.Model ?? agent.ModelId,
EndUserId = responseAgentInvokeOptions.ResponseCreationOptions.EndUserId ?? agent.GetDisplayName(),
Instructions = responseAgentInvokeOptions.ResponseCreationOptions.Instructions ?? instructions,
StoredOutputEnabled = responseAgentInvokeOptions.ResponseCreationOptions.StoredOutputEnabled ?? agent.StoreEnabled,
@@ -44,6 +45,7 @@ internal static class ResponseCreationOptionsFactory
{
creationOptions = new CreateResponseOptions
{
Model = agent.ModelId,
EndUserId = agent.GetDisplayName(),
Instructions = instructions,
StoredOutputEnabled = agent.StoreEnabled,
@@ -22,11 +22,13 @@ public sealed class OpenAIResponseAgent : Agent
/// Initializes a new instance of the <see cref="OpenAIResponseAgent"/> class.
/// </summary>
/// <param name="client">The OpenAI provider for accessing the Responses API service.</param>
public OpenAIResponseAgent(ResponsesClient client)
/// <param name="modelId">The model to use for generating responses.</param>
public OpenAIResponseAgent(ResponsesClient client, string? modelId = null)
{
Verify.NotNull(client);
this.Client = client;
this.ModelId = modelId;
}
/// <summary>
@@ -34,6 +36,11 @@ public sealed class OpenAIResponseAgent : Agent
/// </summary>
public ResponsesClient Client { get; }
/// <summary>
/// The model to use for generating responses.
/// </summary>
public string? ModelId { get; init; }
/// <summary>
/// Storing of messages is enabled.
/// </summary>
@@ -27,7 +27,7 @@ public class BaseOpenAIResponseClientTest : IDisposable
{
Transport = new HttpClientPipelineTransport(this.HttpClient)
};
this.Client = new ResponsesClient("model", new ApiKeyCredential("apiKey"), clientOptions);
this.Client = new ResponsesClient(new ApiKeyCredential("apiKey"), clientOptions);
}
/// <inheritdoc />
@@ -238,14 +238,13 @@ public class OpenAIResponseExtensionsTests
return result;
}
private ResponseResult CreateMockOpenAIResponse(string id, DateTimeOffset createdAt, ResponseError error, string instructions, string model, string previousResponseId, float temperature, IEnumerable<ResponseTool> tools, float topP, IDictionary<string, string> metadata, ResponseIncompleteStatusDetails incompleteStatusDetails, IEnumerable<ResponseItem> outputItems, bool parallelToolCallsEnabled, ResponseToolChoice toolChoice)
private ResponseResult CreateMockOpenAIResponse(string id, DateTimeOffset createdAt, ResponseError error, string model, string previousResponseId, float temperature, IEnumerable<ResponseTool> tools, float topP, IDictionary<string, string> metadata, ResponseIncompleteStatusDetails incompleteStatusDetails, IEnumerable<ResponseItem> outputItems, bool parallelToolCallsEnabled, ResponseToolChoice toolChoice)
{
var result = new ResponseResult
{
Id = id,
CreatedAt = createdAt,
Error = error,
Instructions = instructions,
Model = model,
PreviousResponseId = previousResponseId,
Temperature = temperature,
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.ClientModel;
using System.Text.Json;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.OpenAI;
@@ -16,7 +17,7 @@ public sealed class OpenAIResponseAgentExtensionsTests
public void AsAIAgent_WithValidOpenAIResponseAgent_ReturnsSemanticKernelAIAgent()
{
// Arrange
var responseClient = new ResponsesClient("model", "apikey");
var responseClient = new ResponsesClient(new ApiKeyCredential("apikey"));
var responseAgent = new OpenAIResponseAgent(responseClient);
// Act
@@ -41,7 +42,7 @@ public sealed class OpenAIResponseAgentExtensionsTests
public void AsAIAgent_CreatesWorkingThreadFactoryStoreTrue()
{
// Arrange
var responseClient = new ResponsesClient("model", "apikey");
var responseClient = new ResponsesClient(new ApiKeyCredential("apikey"));
var responseAgent = new OpenAIResponseAgent(responseClient)
{
StoreEnabled = true
@@ -62,7 +63,7 @@ public sealed class OpenAIResponseAgentExtensionsTests
public void AsAIAgent_CreatesWorkingThreadFactoryStoreFalse()
{
// Arrange
var responseClient = new ResponsesClient("model", "apikey");
var responseClient = new ResponsesClient(new ApiKeyCredential("apikey"));
var responseAgent = new OpenAIResponseAgent(responseClient)
{
StoreEnabled = false
@@ -83,7 +84,7 @@ public sealed class OpenAIResponseAgentExtensionsTests
public void AsAIAgent_ThreadDeserializationFactory_WithNullAgentId_CreatesNewThread()
{
// Arrange
var responseClient = new ResponsesClient("model", "apikey");
var responseClient = new ResponsesClient(new ApiKeyCredential("apikey"));
var responseAgent = new OpenAIResponseAgent(responseClient)
{
StoreEnabled = true
@@ -105,7 +106,7 @@ public sealed class OpenAIResponseAgentExtensionsTests
public void AsAIAgent_ThreadDeserializationFactory_WithValidAgentId_CreatesThreadWithId()
{
// Arrange
var responseClient = new ResponsesClient("model", "apikey");
var responseClient = new ResponsesClient(new ApiKeyCredential("apikey"));
var responseAgent = new OpenAIResponseAgent(responseClient)
{
StoreEnabled = true
@@ -129,7 +130,7 @@ public sealed class OpenAIResponseAgentExtensionsTests
public void AsAIAgent_ThreadSerializer_SerializesThreadId()
{
// Arrange
var responseClient = new ResponsesClient("model", "apikey");
var responseClient = new ResponsesClient(new ApiKeyCredential("apikey"));
var responseAgent = new OpenAIResponseAgent(responseClient)
{
StoreEnabled = true
@@ -152,7 +153,7 @@ public sealed class OpenAIResponseAgentExtensionsTests
[Fact]
public void AsAIAgent_ThreadDeserializationFactory_WithNullJson_CreatesThreadWithEmptyChatHistory()
{
var responseClient = new ResponsesClient("model", "apikey");
var responseClient = new ResponsesClient(new ApiKeyCredential("apikey"));
var responseAgent = new OpenAIResponseAgent(responseClient);
var jsonElement = JsonSerializer.SerializeToElement((string?)null);
@@ -171,7 +172,7 @@ public sealed class OpenAIResponseAgentExtensionsTests
[Fact]
public void AsAIAgent_ThreadDeserializationFactory_WithChatHistory_CreatesThreadWithChatHistory()
{
var responseClient = new ResponsesClient("model", "apikey");
var responseClient = new ResponsesClient(new ApiKeyCredential("apikey"));
var responseAgent = new OpenAIResponseAgent(responseClient);
var expectedChatHistory = new ChatHistory("mock message", AuthorRole.User);
var jsonElement = JsonSerializer.SerializeToElement(expectedChatHistory);
@@ -195,7 +196,7 @@ public sealed class OpenAIResponseAgentExtensionsTests
public void AsAIAgent_ThreadSerializer_SerializesChatHistory()
{
// Arrange
var responseClient = new ResponsesClient("model", "apikey");
var responseClient = new ResponsesClient(new ApiKeyCredential("apikey"));
var responseAgent = new OpenAIResponseAgent(responseClient);
var expectedChatHistory = new ChatHistory("mock message", AuthorRole.User);
var jsonElement = JsonSerializer.SerializeToElement(expectedChatHistory);
@@ -122,8 +122,8 @@ internal partial class ClientCore
{
"STANDARD" => GeneratedImageQuality.Standard,
"HIGH" or "HD" => GeneratedImageQuality.High,
"MEDIUM" => GeneratedImageQuality.Medium,
"LOW" => GeneratedImageQuality.Low,
"MEDIUM" => GeneratedImageQuality.MediumQuality,
"LOW" => GeneratedImageQuality.LowQuality,
"AUTO" => GeneratedImageQuality.Auto,
_ => throw new NotSupportedException($"The provided quality '{quality}' is not supported.")
};
@@ -95,12 +95,12 @@ public class OpenAIResponseAgentFixture : AgentFixture
{
OpenAIConfiguration configuration = this._configuration.GetSection("OpenAI").Get<OpenAIConfiguration>()!;
var options = new OpenAIClientOptions();
this._responseClient = new(model: configuration.ChatModelId, credential: new ApiKeyCredential(configuration.ApiKey), options: options);
this._responseClient = new(credential: new ApiKeyCredential(configuration.ApiKey), options: options);
var kernelBuilder = Kernel.CreateBuilder();
Kernel kernel = kernelBuilder.Build();
this._agent = new OpenAIResponseAgent(this._responseClient)
this._agent = new OpenAIResponseAgent(this._responseClient, configuration.ChatModelId)
{
Name = "HelpfulAssistant",
Instructions = "You are a helpful assistant.",
@@ -109,10 +109,10 @@ public class OpenAIResponseAgentFixture : AgentFixture
};
this._thread = new OpenAIResponseAgentThread(this._responseClient);
var response = await this._responseClient.CreateResponseAsync([ResponseItem.CreateUserMessageItem("Hello")]);
var response = await this._responseClient.CreateResponseAsync(new CreateResponseOptions(configuration.ChatModelId, [ResponseItem.CreateUserMessageItem("Hello")]));
this._createdThread = new OpenAIResponseAgentThread(this._responseClient, response.Value.Id);
var serviceFailingClient = new ResponsesClient(configuration.ModelId, credential: new ApiKeyCredential("FakeApiKey"), options: options);
var serviceFailingClient = new ResponsesClient(credential: new ApiKeyCredential("FakeApiKey"), options: options);
this._serviceFailingAgentThread = new OpenAIResponseAgentThread(serviceFailingClient);
this._createdServiceFailingAgentThread = new OpenAIResponseAgentThread(this._responseClient, "FakeId");
@@ -43,10 +43,11 @@ public sealed class OpenAIResponseAgentTests(ITestOutputHelper output)
[InlineData("What is the capital of France?", "Paris", false, false)]
public async Task OpenAIResponseAgentInvokeAsync(string input, string expectedAnswerContains, bool isOpenAI, bool storeEnabled)
{
ResponsesClient client = this.CreateClient(isOpenAI);
var (client, modelId) = this.CreateClient(isOpenAI);
await this.ExecuteAgentAsync(
client,
modelId,
storeEnabled,
input,
expectedAnswerContains);
@@ -63,8 +64,8 @@ public sealed class OpenAIResponseAgentTests(ITestOutputHelper output)
public async Task OpenAIResponseAgentInvokeWithThreadAsync(string input, string expectedAnswerContains, bool isOpenAI, bool storeEnabled)
{
// Arrange
ResponsesClient client = this.CreateClient(isOpenAI);
OpenAIResponseAgent agent = new(client)
var (client, modelId) = this.CreateClient(isOpenAI);
OpenAIResponseAgent agent = new(client, modelId)
{
StoreEnabled = storeEnabled,
Instructions = "Answer all queries in English and French."
@@ -112,9 +113,9 @@ public sealed class OpenAIResponseAgentTests(ITestOutputHelper output)
public async Task OpenAIResponseAgentInvokeWithFunctionCallingAsync(string input, string expectedAnswerContains, bool isOpenAI, bool storeEnabled)
{
// Arrange
ResponsesClient client = this.CreateClient(isOpenAI);
var (client, modelId) = this.CreateClient(isOpenAI);
KernelPlugin plugin = KernelPluginFactory.CreateFromType<MenuPlugin>();
OpenAIResponseAgent agent = new(client)
OpenAIResponseAgent agent = new(client, modelId)
{
StoreEnabled = storeEnabled,
Instructions = "Answer questions about the menu."
@@ -161,10 +162,11 @@ public sealed class OpenAIResponseAgentTests(ITestOutputHelper output)
[InlineData("What is the capital of France?", "Paris", false, false)]
public async Task OpenAIResponseAgentInvokeStreamingAsync(string input, string expectedAnswerContains, bool isOpenAI, bool storeEnabled)
{
ResponsesClient client = this.CreateClient(isOpenAI);
var (client, modelId) = this.CreateClient(isOpenAI);
await this.ExecuteStreamingAgentAsync(
client,
modelId,
storeEnabled,
input,
expectedAnswerContains);
@@ -181,8 +183,8 @@ public sealed class OpenAIResponseAgentTests(ITestOutputHelper output)
public async Task OpenAIResponseAgentInvokeStreamingWithThreadAsync(bool isOpenAI, bool storeEnabled)
{
// Arrange
ResponsesClient client = this.CreateClient(isOpenAI);
OpenAIResponseAgent agent = new(client)
var (client, modelId) = this.CreateClient(isOpenAI);
OpenAIResponseAgent agent = new(client, modelId)
{
StoreEnabled = storeEnabled,
Instructions = "Answer all queries in English and French."
@@ -229,9 +231,9 @@ public sealed class OpenAIResponseAgentTests(ITestOutputHelper output)
public async Task OpenAIResponseAgentInvokeStreamingWithFunctionCallingAsync(string input, string expectedAnswerContains, bool isOpenAI, bool storeEnabled)
{
// Arrange
ResponsesClient client = this.CreateClient(isOpenAI);
var (client, modelId) = this.CreateClient(isOpenAI);
KernelPlugin plugin = KernelPluginFactory.CreateFromType<MenuPlugin>();
OpenAIResponseAgent agent = new(client)
OpenAIResponseAgent agent = new(client, modelId)
{
StoreEnabled = storeEnabled,
Instructions = "Answer questions about the menu."
@@ -274,12 +276,13 @@ public sealed class OpenAIResponseAgentTests(ITestOutputHelper output)
private async Task ExecuteAgentAsync(
ResponsesClient client,
string modelId,
bool storeEnabled,
string input,
string expected)
{
// Arrange
OpenAIResponseAgent agent = new(client)
OpenAIResponseAgent agent = new(client, modelId)
{
StoreEnabled = storeEnabled,
Instructions = "Answer all queries in English and French."
@@ -306,12 +309,13 @@ public sealed class OpenAIResponseAgentTests(ITestOutputHelper output)
private async Task ExecuteStreamingAgentAsync(
ResponsesClient client,
string modelId,
bool storeEnabled,
string input,
string expected)
{
// Arrange
OpenAIResponseAgent agent = new(client)
OpenAIResponseAgent agent = new(client, modelId)
{
StoreEnabled = storeEnabled,
Instructions = "Answer all queries in English and French."
@@ -355,22 +359,17 @@ public sealed class OpenAIResponseAgentTests(ITestOutputHelper output)
return configuration;
}
private ResponsesClient CreateClient(bool isOpenAI)
private (ResponsesClient Client, string ModelId) CreateClient(bool isOpenAI)
{
ResponsesClient client;
if (isOpenAI)
{
client = this.CreateClient(this._configuration.GetSection("OpenAI").Get<OpenAIConfiguration>());
}
else
{
client = this.CreateClient(this._configuration.GetSection("AzureOpenAI").Get<AzureOpenAIConfiguration>());
return this.CreateClient(this._configuration.GetSection("OpenAI").Get<OpenAIConfiguration>());
}
return client;
return this.CreateClient(this._configuration.GetSection("AzureOpenAI").Get<AzureOpenAIConfiguration>());
}
private ResponsesClient CreateClient(OpenAIConfiguration? configuration)
private (ResponsesClient, string) CreateClient(OpenAIConfiguration? configuration)
{
Assert.NotNull(configuration);
@@ -387,10 +386,10 @@ public sealed class OpenAIResponseAgentTests(ITestOutputHelper output)
};
}
return new ResponsesClient(configuration.ChatModelId, new ApiKeyCredential(configuration.ApiKey), options);
return (new ResponsesClient(new ApiKeyCredential(configuration.ApiKey), options), configuration.ChatModelId!);
}
private ResponsesClient CreateClient(AzureOpenAIConfiguration? configuration)
private (ResponsesClient, string) CreateClient(AzureOpenAIConfiguration? configuration)
{
Assert.NotNull(configuration);
@@ -408,7 +407,7 @@ public sealed class OpenAIResponseAgentTests(ITestOutputHelper output)
}
var azureClient = new AzureOpenAIClient(new Uri(configuration.Endpoint), new AzureCliCredential(), options);
return azureClient.GetResponsesClient(configuration.ChatDeploymentName);
return (azureClient.GetResponsesClient(), configuration.ChatDeploymentName!);
}
public sealed class MenuPlugin
@@ -27,11 +27,14 @@ public abstract class BaseResponsesAgentTest : BaseAgentsTest<ResponsesClient>
});
}
this.Client = new(model: model ?? TestConfiguration.OpenAI.ChatModelId, credential: new ApiKeyCredential(TestConfiguration.OpenAI.ApiKey), options: options);
this.ModelId = model ?? TestConfiguration.OpenAI.ChatModelId;
this.Client = new(credential: new ApiKeyCredential(TestConfiguration.OpenAI.ApiKey), options: options);
this.FileClient = new OpenAIFileClient(TestConfiguration.OpenAI.ApiKey);
this.VectorStoreClient = new VectorStoreClient(TestConfiguration.OpenAI.ApiKey);
}
protected string ModelId { get; set; }
protected OpenAIFileClient FileClient { get; set; }
protected VectorStoreClient VectorStoreClient { get; set; }