diff --git a/eng/packages/General.props b/eng/packages/General.props
index 68e004a9275..044a0cd6b7d 100644
--- a/eng/packages/General.props
+++ b/eng/packages/General.props
@@ -17,7 +17,7 @@
-
+
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIRealtimeExtensions.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIRealtimeExtensions.cs
index f54da262d22..428950fcf23 100644
--- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIRealtimeExtensions.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIRealtimeExtensions.cs
@@ -13,10 +13,10 @@ namespace OpenAI.Realtime;
[Experimental(DiagnosticIds.Experiments.AIOpenAIRealtime)]
public static class MicrosoftExtensionsAIRealtimeExtensions
{
- /// Creates an OpenAI from an .
+ /// Creates an OpenAI from an .
/// The function to convert.
- /// An OpenAI representing .
+ /// An OpenAI representing .
/// is .
- public static ConversationFunctionTool AsOpenAIConversationFunctionTool(this AIFunctionDeclaration function) =>
- OpenAIRealtimeConversationClient.ToOpenAIConversationFunctionTool(Throw.IfNull(function));
+ public static RealtimeFunctionTool AsOpenAIRealtimeFunctionTool(this AIFunctionDeclaration function) =>
+ OpenAIRealtimeConversationClient.ToOpenAIRealtimeFunctionTool(Throw.IfNull(function));
}
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIResponsesExtensions.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIResponsesExtensions.cs
index 1fbf28268cb..419b65aaecc 100644
--- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIResponsesExtensions.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIResponsesExtensions.cs
@@ -98,7 +98,6 @@ public static ResponseResult AsOpenAIResponseResult(this ChatResponse response,
ConversationOptions = OpenAIClientExtensions.IsConversationId(response.ConversationId) ? new(response.ConversationId) : null,
CreatedAt = response.CreatedAt ?? default,
Id = response.ResponseId,
- Instructions = options?.Instructions,
MaxOutputTokenCount = options?.MaxOutputTokens,
Model = response.ModelId ?? options?.ModelId,
ParallelToolCallsEnabled = options?.AllowMultipleToolCalls ?? true,
@@ -108,6 +107,11 @@ public static ResponseResult AsOpenAIResponseResult(this ChatResponse response,
Usage = OpenAIResponsesChatClient.ToResponseTokenUsage(response.Usage),
};
+ if (options?.Instructions is { Length: > 0 })
+ {
+ result.Instructions.Add(ResponseItem.CreateDeveloperMessageItem(options.Instructions));
+ }
+
foreach (var responseItem in OpenAIResponsesChatClient.ToOpenAIResponseItems(response.Messages, options))
{
result.OutputItems.Add(responseItem);
@@ -122,7 +126,7 @@ public static ResponseResult AsOpenAIResponseResult(this ChatResponse response,
///
/// does not derive from , so it cannot be added directly to a list of s.
/// Instead, this method wraps the provided in an and adds that to the list.
- /// The returned by will
+ /// The returned by will
/// be able to unwrap the when it processes the list of tools and use the provided as-is.
///
public static void Add(this IList tools, ResponseTool tool)
@@ -138,7 +142,7 @@ public static void Add(this IList tools, ResponseTool tool)
///
///
/// The returned tool is only suitable for use with the returned by
- /// (or s that delegate
+ /// (or s that delegate
/// to such an instance). It is likely to be ignored by any other implementation.
///
///
@@ -147,7 +151,7 @@ public static void Add(this IList tools, ResponseTool tool)
/// , those types should be preferred instead of this method, as they are more portable,
/// capable of being respected by any implementation. This method does not attempt to
/// map the supplied to any of those types, it simply wraps it as-is:
- /// the returned by will
+ /// the returned by will
/// be able to unwrap the when it processes the list of tools.
///
///
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIChatClient.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIChatClient.cs
index b3e03d01fa2..80ed42ffb35 100644
--- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIChatClient.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIChatClient.cs
@@ -681,7 +681,7 @@ ChatResponseFormatJson jsonFormat when OpenAIClientExtensions.StrictSchemaTransf
private static ChatReasoningEffortLevel? ToOpenAIChatReasoningEffortLevel(ReasoningEffort? effort) =>
effort switch
{
- ReasoningEffort.None => new ChatReasoningEffortLevel("none"),
+ ReasoningEffort.None => ChatReasoningEffortLevel.None,
ReasoningEffort.Low => ChatReasoningEffortLevel.Low,
ReasoningEffort.Medium => ChatReasoningEffortLevel.Medium,
ReasoningEffort.High => ChatReasoningEffortLevel.High,
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIClientExtensions.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIClientExtensions.cs
index e4008e4380f..c65a55e4472 100644
--- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIClientExtensions.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIClientExtensions.cs
@@ -119,11 +119,12 @@ public static IChatClient AsIChatClient(this ChatClient chatClient) =>
/// Gets an for use with this .
/// The client.
+ /// The default model ID to use for the chat client.
/// An that can be used to converse via the .
/// is .
[Experimental(DiagnosticIds.Experiments.AIOpenAIResponses)]
- public static IChatClient AsIChatClient(this ResponsesClient responseClient) =>
- new OpenAIResponsesChatClient(responseClient);
+ public static IChatClient AsIChatClient(this ResponsesClient responseClient, string? defaultModelId = null) =>
+ new OpenAIResponsesChatClient(responseClient, defaultModelId);
/// Gets an for use with this .
/// The instance to be accessed as an .
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIImageGenerator.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIImageGenerator.cs
index dab7f82b5a6..9a7aebbaac3 100644
--- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIImageGenerator.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIImageGenerator.cs
@@ -8,10 +8,7 @@
using System.IO;
using System.Linq;
using System.Net.Mime;
-using System.Reflection;
using System.Runtime.InteropServices;
-using System.Text.Json;
-using System.Text.Json.Serialization.Metadata;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Shared.DiagnosticIds;
@@ -19,8 +16,6 @@
using OpenAI;
using OpenAI.Images;
-#pragma warning disable S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields
-
namespace Microsoft.Extensions.AI;
/// Represents an for an OpenAI or .
@@ -110,20 +105,11 @@ void IDisposable.Dispose()
/// Converts a to a .
private static ImageGenerationResponse ToImageGenerationResponse(GeneratedImageCollection generatedImages)
{
- string contentType = "image/png"; // Default content type for images
-
- // OpenAI doesn't expose the content type, so we need to read from the internal JSON representation.
- // https://github.com/openai/openai-dotnet/issues/561
- var additionalRawData = typeof(GeneratedImageCollection)
- .GetProperty("SerializedAdditionalRawData", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
- ?.GetValue(generatedImages) as IDictionary;
-
- if (additionalRawData?.TryGetValue("output_format", out var outputFormat) ?? false)
- {
- var stringJsonTypeInfo = (JsonTypeInfo)AIJsonUtilities.DefaultOptions.GetTypeInfo(typeof(string));
- var outputFormatString = JsonSerializer.Deserialize(outputFormat, stringJsonTypeInfo);
- contentType = $"image/{outputFormatString}";
- }
+#pragma warning disable OPENAI001
+ string contentType = generatedImages.OutputFileFormat?.ToString() is { } outputFormat ?
+ $"image/{outputFormat}" :
+ "image/png"; // Default content type for images
+#pragma warning restore OPENAI001
List contents = [];
@@ -175,15 +161,15 @@ private OpenAI.Images.ImageGenerationOptions ToOpenAIImageGenerationOptions(Imag
if (result.OutputFileFormat is null)
{
- if (options?.MediaType?.Equals("image/png", StringComparison.OrdinalIgnoreCase) == true)
+ if (options?.MediaType?.Equals("image/png", StringComparison.OrdinalIgnoreCase) is true)
{
result.OutputFileFormat = GeneratedImageFileFormat.Png;
}
- else if (options?.MediaType?.Equals("image/jpeg", StringComparison.OrdinalIgnoreCase) == true)
+ else if (options?.MediaType?.Equals("image/jpeg", StringComparison.OrdinalIgnoreCase) is true)
{
result.OutputFileFormat = GeneratedImageFileFormat.Jpeg;
}
- else if (options?.MediaType?.Equals("image/webp", StringComparison.OrdinalIgnoreCase) == true)
+ else if (options?.MediaType?.Equals("image/webp", StringComparison.OrdinalIgnoreCase) is true)
{
result.OutputFileFormat = GeneratedImageFileFormat.Webp;
}
@@ -208,6 +194,22 @@ private ImageEditOptions ToOpenAIImageEditOptions(ImageGenerationOptions? option
{
ImageEditOptions result = options?.RawRepresentationFactory?.Invoke(this) as ImageEditOptions ?? new();
+ if (result.OutputFileFormat is null)
+ {
+ if (options?.MediaType?.Equals("image/png", StringComparison.OrdinalIgnoreCase) is true)
+ {
+ result.OutputFileFormat = GeneratedImageFileFormat.Png;
+ }
+ else if (options?.MediaType?.Equals("image/jpeg", StringComparison.OrdinalIgnoreCase) is true)
+ {
+ result.OutputFileFormat = GeneratedImageFileFormat.Jpeg;
+ }
+ else if (options?.MediaType?.Equals("image/webp", StringComparison.OrdinalIgnoreCase) is true)
+ {
+ result.OutputFileFormat = GeneratedImageFileFormat.Webp;
+ }
+ }
+
result.ResponseFormat ??= options?.ResponseFormat switch
{
ImageGenerationResponseFormat.Uri => GeneratedImageFormat.Uri,
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIRealtimeConversationClient.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIRealtimeConversationClient.cs
index c40a7c525ff..83d1bf2ca98 100644
--- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIRealtimeConversationClient.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIRealtimeConversationClient.cs
@@ -11,16 +11,16 @@ namespace Microsoft.Extensions.AI;
[Experimental(DiagnosticIds.Experiments.AIOpenAIRealtime)]
internal sealed class OpenAIRealtimeConversationClient
{
- public static ConversationFunctionTool ToOpenAIConversationFunctionTool(AIFunctionDeclaration aiFunction, ChatOptions? options = null)
+ public static RealtimeFunctionTool ToOpenAIRealtimeFunctionTool(AIFunctionDeclaration aiFunction, ChatOptions? options = null)
{
bool? strict =
OpenAIClientExtensions.HasStrict(aiFunction.AdditionalProperties) ??
OpenAIClientExtensions.HasStrict(options?.AdditionalProperties);
- return new ConversationFunctionTool(aiFunction.Name)
+ return new RealtimeFunctionTool(aiFunction.Name)
{
- Description = aiFunction.Description,
- Parameters = OpenAIClientExtensions.ToOpenAIFunctionParameters(aiFunction, strict),
+ FunctionDescription = aiFunction.Description,
+ FunctionParameters = OpenAIClientExtensions.ToOpenAIFunctionParameters(aiFunction, strict),
};
}
}
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs
index 1d1c5d37674..bfdb4843c95 100644
--- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs
@@ -8,6 +8,7 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
+using System.Net.Mime;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
@@ -49,28 +50,27 @@ private static readonly Func>));
- // Workaround for https://github.com/openai/openai-dotnet/pull/874.
- // The OpenAI library doesn't yet expose InputImageUrl as a public property, so we access it via reflection.
- // Replace this with the actual public property once it's available (e.g., part.InputImageUrl).
- private static readonly PropertyInfo? _inputImageUrlProperty =
- Type.GetType("OpenAI.Responses.InternalItemContentInputImage, OpenAI")?.GetProperty("ImageUrl");
-
/// Metadata about the client.
private readonly ChatClientMetadata _metadata;
/// The underlying .
private readonly ResponsesClient _responseClient;
+ /// The default model ID to use for the chat client.
+ private readonly string? _defaultModelId;
+
/// Initializes a new instance of the class for the specified .
/// The underlying client.
+ /// The default model ID to use for the chat client.
/// is .
- public OpenAIResponsesChatClient(ResponsesClient responseClient)
+ public OpenAIResponsesChatClient(ResponsesClient responseClient, string? defaultModelId)
{
_ = Throw.IfNull(responseClient);
_responseClient = responseClient;
+ _defaultModelId = defaultModelId;
- _metadata = new("openai", responseClient.Endpoint, responseClient.Model);
+ _metadata = new("openai", responseClient.Endpoint, defaultModelId);
}
///
@@ -737,7 +737,7 @@ private CreateResponseOptions AsCreateResponseOptions(ChatOptions? options, out
{
return new()
{
- Model = _responseClient.Model,
+ Model = _defaultModelId,
};
}
@@ -753,7 +753,7 @@ private CreateResponseOptions AsCreateResponseOptions(ChatOptions? options, out
result.BackgroundModeEnabled ??= options.AllowBackgroundResponses;
result.MaxOutputTokenCount ??= options.MaxOutputTokens;
- result.Model ??= options.ModelId ?? _responseClient.Model;
+ result.Model ??= options.ModelId ?? _defaultModelId;
result.Temperature ??= options.Temperature;
result.TopP ??= options.TopP;
result.ReasoningOptions ??= ToOpenAIResponseReasoningOptions(options.Reasoning);
@@ -864,7 +864,7 @@ ChatResponseFormatJson jsonFormat when OpenAIClientExtensions.StrictSchemaTransf
ResponseReasoningEffortLevel? effortLevel = reasoning.Effort switch
{
- ReasoningEffort.None => new ResponseReasoningEffortLevel("none"),
+ ReasoningEffort.None => ResponseReasoningEffortLevel.None,
ReasoningEffort.Low => ResponseReasoningEffortLevel.Low,
ReasoningEffort.Medium => ResponseReasoningEffortLevel.Medium,
ReasoningEffort.High => ResponseReasoningEffortLevel.High,
@@ -965,7 +965,7 @@ internal static IEnumerable ToOpenAIResponseItems(IEnumerable ToAIContents(IEnumerable con
{
content = new DataContent(part.InputFileBytes, part.InputFileBytesMediaType ?? "application/octet-stream") { Name = part.InputFilename };
}
- else if (_inputImageUrlProperty?.GetValue(part) is string inputImageUrl)
+ else if (part.InputImageUri is { } inputImageUrl)
{
- if (inputImageUrl.StartsWith("data:", StringComparison.OrdinalIgnoreCase))
- {
- content = new DataContent(inputImageUrl);
- }
- else if (Uri.TryCreate(inputImageUrl, UriKind.Absolute, out Uri? imageUri))
- {
- content = new UriContent(imageUri, "image/*");
- }
- else
- {
- content = null;
- }
+ content = inputImageUrl.Scheme.Equals("data", StringComparison.OrdinalIgnoreCase) ?
+ new DataContent(inputImageUrl) :
+ new UriContent(inputImageUrl, MediaTypeMap.GetMediaType(inputImageUrl.AbsoluteUri) ?? "image/*");
}
else
{
diff --git a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/templates/AIChatWeb-CSharp/AIChatWeb-CSharp.Web/Program.cs b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/templates/AIChatWeb-CSharp/AIChatWeb-CSharp.Web/Program.cs
index 4b041f4a15f..89a231105e9 100644
--- a/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/templates/AIChatWeb-CSharp/AIChatWeb-CSharp.Web/Program.cs
+++ b/src/ProjectTemplates/Microsoft.Extensions.AI.Templates/templates/AIChatWeb-CSharp/AIChatWeb-CSharp.Web/Program.cs
@@ -49,8 +49,8 @@
var openAIClient = new OpenAIClient(
new ApiKeyCredential(builder.Configuration["OpenAI:Key"] ?? throw new InvalidOperationException("Missing configuration: OpenAI:Key. See the README for details.")));
-#pragma warning disable OPENAI001 // GetResponsesClient(string) is experimental and subject to change or removal in future updates.
-var chatClient = openAIClient.GetResponsesClient("gpt-4o-mini").AsIChatClient();
+#pragma warning disable OPENAI001 // GetResponsesClient() is experimental and subject to change or removal in future updates.
+var chatClient = openAIClient.GetResponsesClient().AsIChatClient("gpt-4o-mini");
#pragma warning restore OPENAI001
var embeddingGenerator = openAIClient.GetEmbeddingClient("text-embedding-3-small").AsIEmbeddingGenerator();
@@ -66,7 +66,7 @@
#endif
var azureOpenAIEndpoint = new Uri(new Uri(builder.Configuration["AzureOpenAI:Endpoint"] ?? throw new InvalidOperationException("Missing configuration: AzureOpenAi:Endpoint. See the README for details.")), "/openai/v1");
#if (IsManagedIdentity)
-#pragma warning disable OPENAI001 // OpenAIClient(AuthenticationPolicy, OpenAIClientOptions) and GetResponsesClient(string) are experimental and subject to change or removal in future updates.
+#pragma warning disable OPENAI001 // OpenAIClient(AuthenticationPolicy, OpenAIClientOptions) and GetResponsesClient() are experimental and subject to change or removal in future updates.
var azureOpenAi = new OpenAIClient(
new BearerTokenPolicy(new DefaultAzureCredential(), "https://ai.azure.com/.default"),
new OpenAIClientOptions { Endpoint = azureOpenAIEndpoint });
@@ -75,9 +75,9 @@
var openAIOptions = new OpenAIClientOptions { Endpoint = azureOpenAIEndpoint };
var azureOpenAi = new OpenAIClient(new ApiKeyCredential(builder.Configuration["AzureOpenAI:Key"] ?? throw new InvalidOperationException("Missing configuration: AzureOpenAi:Key. See the README for details.")), openAIOptions);
-#pragma warning disable OPENAI001 // GetResponsesClient(string) is experimental and subject to change or removal in future updates.
+#pragma warning disable OPENAI001 // GetResponsesClient() is experimental and subject to change or removal in future updates.
#endif
-var chatClient = azureOpenAi.GetResponsesClient("gpt-4o-mini").AsIChatClient();
+var chatClient = azureOpenAi.GetResponsesClient().AsIChatClient("gpt-4o-mini");
#pragma warning restore OPENAI001
var embeddingGenerator = azureOpenAi.GetEmbeddingClient("text-embedding-3-small").AsIEmbeddingGenerator();
diff --git a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/IntegrationTestHelpers.cs b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/IntegrationTestHelpers.cs
index 42a0be5c416..51fba1d7582 100644
--- a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/IntegrationTestHelpers.cs
+++ b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/IntegrationTestHelpers.cs
@@ -19,7 +19,7 @@ internal static class IntegrationTestHelpers
{
var configuration = TestRunnerConfiguration.Instance;
- string? apiKey = configuration["OpenAI:Key"];
+ string? apiKey = configuration["OpenAI:Key"] ?? configuration["AI:OpenAI:ApiKey"];
string? mode = configuration["OpenAI:Mode"];
if (string.Equals(mode, "AzureOpenAI", StringComparison.OrdinalIgnoreCase))
diff --git a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIChatClientTests.cs b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIChatClientTests.cs
index be9201eed56..26f18be1677 100644
--- a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIChatClientTests.cs
+++ b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIChatClientTests.cs
@@ -1697,9 +1697,9 @@ public async Task ChatOptions_ModelId_OverridesClientModel_Streaming()
"temperature":0.5,
"messages":[{"role":"user","content":"hello"}],
"model":"gpt-4o",
+ "max_completion_tokens":20,
"stream":true,
- "stream_options":{"include_usage":true},
- "max_completion_tokens":20
+ "stream_options":{"include_usage":true}
}
""";
diff --git a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIConversionTests.cs b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIConversionTests.cs
index d2e4dd39867..0eb093518e2 100644
--- a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIConversionTests.cs
+++ b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIConversionTests.cs
@@ -380,8 +380,7 @@ public void AsOpenAIResponseTool_WithHostedImageGenerationToolWithAdditionalProp
public void AsOpenAIResponseTool_WithHostedImageGenerationToolWithInputImageMask_ProducesValidImageGenerationTool()
{
var inputImageMask = new ImageGenerationToolInputImageMask(
- BinaryData.FromBytes([0x89, 0x50, 0x4E, 0x47]),
- "image/png");
+ new Uri(new DataContent((byte[])[0x89, 0x50, 0x4E, 0x47], "image/png").Uri));
var imageGenTool = new HostedImageGenerationTool(new Dictionary
{
@@ -595,12 +594,12 @@ public void AsOpenAIResponseTool_WithNullTool_ThrowsArgumentNullException()
[Fact]
public void AsOpenAIConversationFunctionTool_ProducesValidInstance()
{
- var tool = _testFunction.AsOpenAIConversationFunctionTool();
+ var tool = _testFunction.AsOpenAIRealtimeFunctionTool();
Assert.NotNull(tool);
- Assert.Equal("test_function", tool.Name);
- Assert.Equal("A test function for conversion", tool.Description);
- ValidateSchemaParameters(tool.Parameters);
+ Assert.Equal("test_function", tool.FunctionName);
+ Assert.Equal("A test function for conversion", tool.FunctionDescription);
+ ValidateSchemaParameters(tool.FunctionParameters);
}
[Fact]
@@ -1458,7 +1457,6 @@ public void AsOpenAIResponse_WithRawRepresentation_ReturnsOriginal()
Temperature = 0.7f,
TopP = 0.9f,
PreviousResponseId = "prev-id",
- Instructions = "Test instructions"
};
var chatResponse = new ChatResponse(new ChatMessage(ChatRole.Assistant, "Test"))
@@ -1523,7 +1521,7 @@ public void AsOpenAIResponse_WithChatOptions_IncludesOptionsInResponse()
Assert.Equal(500, openAIResponse.MaxOutputTokenCount);
Assert.True(openAIResponse.ParallelToolCallsEnabled);
Assert.Equal("conv_123", openAIResponse.ConversationOptions?.ConversationId);
- Assert.Equal("You are a helpful assistant.", openAIResponse.Instructions);
+ Assert.Equal("You are a helpful assistant.", Assert.IsAssignableFrom(openAIResponse.Instructions.Single()).Content.Single().Text);
Assert.Equal(0.8f, openAIResponse.Temperature);
Assert.Equal(0.95f, openAIResponse.TopP);
}
@@ -1659,7 +1657,7 @@ public void AsOpenAIResponse_WithDefaultValues_UsesExpectedDefaults()
Assert.Null(openAIResponse.Temperature);
Assert.Null(openAIResponse.TopP);
Assert.Null(openAIResponse.ConversationOptions);
- Assert.Null(openAIResponse.Instructions);
+ Assert.Empty(openAIResponse.Instructions);
Assert.NotNull(openAIResponse.OutputItems);
}
diff --git a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIImageGeneratorIntegrationTests.cs b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIImageGeneratorIntegrationTests.cs
index ce0cdb7cf82..bd969aca969 100644
--- a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIImageGeneratorIntegrationTests.cs
+++ b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIImageGeneratorIntegrationTests.cs
@@ -7,6 +7,6 @@ public class OpenAIImageGeneratorIntegrationTests : ImageGeneratorIntegrationTes
{
protected override IImageGenerator? CreateGenerator()
=> IntegrationTestHelpers.GetOpenAIClient()?
- .GetImageClient(TestRunnerConfiguration.Instance["OpenAI:ImageModel"] ?? "dall-e-3")
+ .GetImageClient(TestRunnerConfiguration.Instance["OpenAI:ImageModel"] ?? "gpt-image-1-mini")
.AsIImageGenerator();
}
diff --git a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientIntegrationTests.cs b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientIntegrationTests.cs
index 0bf3f7fcf0f..56d8135da62 100644
--- a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientIntegrationTests.cs
+++ b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientIntegrationTests.cs
@@ -19,8 +19,10 @@ public class OpenAIResponseClientIntegrationTests : ChatClientIntegrationTests
{
protected override IChatClient? CreateChatClient() =>
IntegrationTestHelpers.GetOpenAIClient()
- ?.GetResponsesClient(TestRunnerConfiguration.Instance["OpenAI:ChatModel"] ?? "gpt-4o-mini")
- .AsIChatClient();
+ ?.GetResponsesClient()
+ .AsIChatClient(TestRunnerConfiguration.Instance["OpenAI:ChatModel"] ?? "gpt-4o-mini");
+
+ private static string ReasoningModel => "gpt-5-nano";
public override bool FunctionInvokingChatClientSetsConversationId => true;
@@ -563,13 +565,14 @@ public async Task ReasoningContent_NonStreaming_RoundtripsEncryptedContent()
ChatOptions chatOptions = new()
{
+ ModelId = ReasoningModel,
+ Reasoning = new()
+ {
+ Effort = ReasoningEffort.Low,
+ Output = ReasoningOutput.Full,
+ },
RawRepresentationFactory = _ => new CreateResponseOptions
{
- ReasoningOptions = new ResponseReasoningOptions
- {
- ReasoningEffortLevel = ResponseReasoningEffortLevel.Low,
- ReasoningSummaryVerbosity = ResponseReasoningSummaryVerbosity.Detailed
- },
StoredOutputEnabled = false,
IncludedProperties = { IncludedResponseProperty.ReasoningEncryptedContent },
},
@@ -598,7 +601,7 @@ public async Task ReasoningContent_NonStreaming_RoundtripsEncryptedContent()
var response2 = await ChatClient.GetResponseAsync(chatHistory, chatOptions);
Assert.NotNull(response2);
- Assert.Contains("6", response2.Text);
+ Assert.True(response2.Text.Contains("6") || response2.Text.Contains("six"));
// 3. Serialize/deserialize to drop RawRepresentations, then make third request
string json = JsonSerializer.Serialize(chatHistory, AIJsonUtilities.DefaultOptions);
@@ -643,13 +646,14 @@ public async Task ReasoningContent_Streaming_RoundtripsEncryptedContent()
ChatOptions chatOptions = new()
{
+ ModelId = ReasoningModel,
+ Reasoning = new()
+ {
+ Effort = ReasoningEffort.Low,
+ Output = ReasoningOutput.Full,
+ },
RawRepresentationFactory = _ => new CreateResponseOptions
{
- ReasoningOptions = new ResponseReasoningOptions
- {
- ReasoningEffortLevel = ResponseReasoningEffortLevel.Low,
- ReasoningSummaryVerbosity = ResponseReasoningSummaryVerbosity.Detailed
- },
StoredOutputEnabled = false,
IncludedProperties = { IncludedResponseProperty.ReasoningEncryptedContent },
},
diff --git a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientTests.cs b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientTests.cs
index 5b8aa030e71..14f272498cd 100644
--- a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientTests.cs
+++ b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientTests.cs
@@ -40,7 +40,7 @@ public void AsIChatClient_ProducesExpectedMetadata()
var client = new OpenAIClient(new ApiKeyCredential("key"), new OpenAIClientOptions { Endpoint = endpoint });
- IChatClient chatClient = client.GetResponsesClient(model).AsIChatClient();
+ IChatClient chatClient = client.GetResponsesClient().AsIChatClient(model);
var metadata = chatClient.GetService();
Assert.Equal("openai", metadata?.ProviderName);
Assert.Equal(endpoint, metadata?.ProviderUri);
@@ -50,8 +50,8 @@ public void AsIChatClient_ProducesExpectedMetadata()
[Fact]
public void GetService_SuccessfullyReturnsUnderlyingClient()
{
- ResponsesClient openAIClient = new OpenAIClient(new ApiKeyCredential("key")).GetResponsesClient("model");
- IChatClient chatClient = openAIClient.AsIChatClient();
+ ResponsesClient openAIClient = new OpenAIClient(new ApiKeyCredential("key")).GetResponsesClient();
+ IChatClient chatClient = openAIClient.AsIChatClient("model");
Assert.Same(chatClient, chatClient.GetService());
Assert.Same(openAIClient, chatClient.GetService());
@@ -296,14 +296,11 @@ public async Task BasicReasoningResponse_Streaming()
List updates = [];
await foreach (var update in client.GetStreamingResponseAsync("Calculate the sum of the first 5 positive integers.", new()
{
- RawRepresentationFactory = options => new CreateResponseOptions
+ Reasoning = new()
{
- ReasoningOptions = new()
- {
- ReasoningEffortLevel = ResponseReasoningEffortLevel.Low,
- ReasoningSummaryVerbosity = ResponseReasoningSummaryVerbosity.Detailed
- }
- }
+ Effort = ReasoningEffort.Low,
+ Output = ReasoningOutput.Full,
+ },
}))
{
updates.Add(update);
@@ -5596,7 +5593,7 @@ public async Task ResponseWithInputImageHttpUrl_ParsesAsUriContent()
var imageContent = userMessage.Contents.OfType().FirstOrDefault();
Assert.NotNull(imageContent);
Assert.Equal("https://example.com/image.png", imageContent.Uri.ToString());
- Assert.Equal("image/*", imageContent.MediaType);
+ Assert.Equal("image/png", imageContent.MediaType);
var assistantMessage = response.Messages.LastOrDefault(m => m.Role == ChatRole.Assistant);
Assert.NotNull(assistantMessage);
@@ -6121,8 +6118,8 @@ public async Task OpenAIApiTypeTag_SetToResponses(bool streaming)
using VerbatimHttpHandler handler = new(new HttpHandlerExpectedInput(), Output);
using HttpClient httpClient = new(handler);
using IChatClient client = new OpenAIClient(new ApiKeyCredential("apikey"), new OpenAIClientOptions { Transport = new HttpClientPipelineTransport(httpClient) })
- .GetResponsesClient("gpt-4o-mini")
- .AsIChatClient()
+ .GetResponsesClient()
+ .AsIChatClient("gpt-4o-mini")
.AsBuilder()
.UseOpenTelemetry(sourceName: sourceName)
.Build();
@@ -6147,8 +6144,8 @@ private static IChatClient CreateResponseClient(HttpClient httpClient, string mo
new OpenAIClient(
new ApiKeyCredential("apikey"),
new OpenAIClientOptions { Transport = new HttpClientPipelineTransport(httpClient) })
- .GetResponsesClient(modelId)
- .AsIChatClient();
+ .GetResponsesClient()
+ .AsIChatClient(modelId);
private static string ResponseStatusToRequestValue(ResponseStatus status)
{
diff --git a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.oai_aais.verified/aichatweb/Program.cs b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.oai_aais.verified/aichatweb/Program.cs
index 4bfb1ca7796..bc156c8638d 100644
--- a/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.oai_aais.verified/aichatweb/Program.cs
+++ b/test/ProjectTemplates/Microsoft.Extensions.AI.Templates.IntegrationTests/Snapshots/aichatweb/aichatweb.oai_aais.verified/aichatweb/Program.cs
@@ -17,8 +17,8 @@
var openAIClient = new OpenAIClient(
new ApiKeyCredential(builder.Configuration["OpenAI:Key"] ?? throw new InvalidOperationException("Missing configuration: OpenAI:Key. See the README for details.")));
-#pragma warning disable OPENAI001 // GetResponsesClient(string) is experimental and subject to change or removal in future updates.
-var chatClient = openAIClient.GetResponsesClient("gpt-4o-mini").AsIChatClient();
+#pragma warning disable OPENAI001 // GetResponsesClient() is experimental and subject to change or removal in future updates.
+var chatClient = openAIClient.GetResponsesClient().AsIChatClient("gpt-4o-mini");
#pragma warning restore OPENAI001
var embeddingGenerator = openAIClient.GetEmbeddingClient("text-embedding-3-small").AsIEmbeddingGenerator();