diff --git a/eng/packages/General.props b/eng/packages/General.props
index 253fb51ce1b..b7066789238 100644
--- a/eng/packages/General.props
+++ b/eng/packages/General.props
@@ -16,7 +16,7 @@
-
+
diff --git a/eng/packages/TestOnly.props b/eng/packages/TestOnly.props
index dcfc7c03525..d8748a4ae9c 100644
--- a/eng/packages/TestOnly.props
+++ b/eng/packages/TestOnly.props
@@ -2,7 +2,7 @@
-
+
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/CHANGELOG.md b/src/Libraries/Microsoft.Extensions.AI.OpenAI/CHANGELOG.md
index 1c1c175ccd4..15859a5744d 100644
--- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/CHANGELOG.md
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/CHANGELOG.md
@@ -4,6 +4,7 @@
- Updated tool mappings to recognize any `AIFunctionDeclaration`.
- Updated to accommodate the additions in `Microsoft.Extensions.AI.Abstractions`.
+- Updated to depend on OpenAI 2.4.0
## 9.8.0-preview.1.25412.6
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIResponsesExtensions.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIResponsesExtensions.cs
index d6b290f431b..51df2653695 100644
--- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIResponsesExtensions.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIResponsesExtensions.cs
@@ -18,7 +18,7 @@ public static class MicrosoftExtensionsAIResponsesExtensions
/// The function to convert.
/// An OpenAI representing .
/// is .
- public static ResponseTool AsOpenAIResponseTool(this AIFunctionDeclaration function) =>
+ public static FunctionTool AsOpenAIResponseTool(this AIFunctionDeclaration function) =>
OpenAIResponsesChatClient.ToResponseTool(Throw.IfNull(function));
/// Creates a sequence of OpenAI instances from the specified input messages.
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIAssistantsChatClient.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIAssistantsChatClient.cs
index 01b78994f38..424185db887 100644
--- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIAssistantsChatClient.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIAssistantsChatClient.cs
@@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Json;
@@ -50,17 +49,9 @@ public OpenAIAssistantsChatClient(AssistantClient assistantClient, string assist
{
_client = Throw.IfNull(assistantClient);
_assistantId = Throw.IfNullOrWhitespace(assistantId);
-
_defaultThreadId = defaultThreadId;
- // https://github.com/openai/openai-dotnet/issues/215
- // The endpoint isn't currently exposed, so use reflection to get at it, temporarily. Once packages
- // implement the abstractions directly rather than providing adapters on top of the public APIs,
- // the package can provide such implementations separate from what's exposed in the public API.
- Uri providerUrl = typeof(AssistantClient).GetField("_endpoint", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
- ?.GetValue(assistantClient) as Uri ?? OpenAIClientExtensions.DefaultOpenAIEndpoint;
-
- _metadata = new("openai", providerUrl);
+ _metadata = new("openai", assistantClient.Endpoint);
}
/// Initializes a new instance of the class for the specified .
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIChatClient.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIChatClient.cs
index 415aef5901e..8c65766413f 100644
--- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIChatClient.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIChatClient.cs
@@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Json;
@@ -37,18 +36,9 @@ internal sealed class OpenAIChatClient : IChatClient
/// is .
public OpenAIChatClient(ChatClient chatClient)
{
- _ = Throw.IfNull(chatClient);
+ _chatClient = Throw.IfNull(chatClient);
- _chatClient = chatClient;
-
- // https://github.com/openai/openai-dotnet/issues/215
- // The endpoint and model aren't currently exposed, so use reflection to get at them, temporarily. Once packages
- // implement the abstractions directly rather than providing adapters on top of the public APIs,
- // the package can provide such implementations separate from what's exposed in the public API.
- Uri providerUrl = typeof(ChatClient).GetField("_endpoint", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
- ?.GetValue(chatClient) as Uri ?? OpenAIClientExtensions.DefaultOpenAIEndpoint;
-
- _metadata = new("openai", providerUrl, _chatClient.Model);
+ _metadata = new("openai", chatClient.Endpoint, _chatClient.Model);
}
///
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIEmbeddingGenerator.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIEmbeddingGenerator.cs
index 84f3c5966b8..dbe9bf10237 100644
--- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIEmbeddingGenerator.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIEmbeddingGenerator.cs
@@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Shared.Diagnostics;
@@ -18,9 +17,6 @@ namespace Microsoft.Extensions.AI;
/// An for an OpenAI .
internal sealed class OpenAIEmbeddingGenerator : IEmbeddingGenerator>
{
- /// Default OpenAI endpoint.
- private const string DefaultOpenAIEndpoint = "https://api.openai.com/v1";
-
/// Metadata about the embedding generator.
private readonly EmbeddingGeneratorMetadata _metadata;
@@ -37,24 +33,15 @@ internal sealed class OpenAIEmbeddingGenerator : IEmbeddingGenerator is not positive.
public OpenAIEmbeddingGenerator(EmbeddingClient embeddingClient, int? defaultModelDimensions = null)
{
- _ = Throw.IfNull(embeddingClient);
+ _embeddingClient = Throw.IfNull(embeddingClient);
+ _dimensions = defaultModelDimensions;
+
if (defaultModelDimensions < 1)
{
Throw.ArgumentOutOfRangeException(nameof(defaultModelDimensions), "Value must be greater than 0.");
}
- _embeddingClient = embeddingClient;
- _dimensions = defaultModelDimensions;
-
- // https://github.com/openai/openai-dotnet/issues/215
- // The endpoint and model aren't currently exposed, so use reflection to get at them, temporarily. Once packages
- // implement the abstractions directly rather than providing adapters on top of the public APIs,
- // the package can provide such implementations separate from what's exposed in the public API.
- string providerUrl = (typeof(EmbeddingClient).GetField("_endpoint", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
- ?.GetValue(embeddingClient) as Uri)?.ToString() ??
- DefaultOpenAIEndpoint;
-
- _metadata = CreateMetadata("openai", providerUrl, _embeddingClient.Model, defaultModelDimensions);
+ _metadata = new("openai", embeddingClient.Endpoint, _embeddingClient.Model, defaultModelDimensions);
}
///
@@ -98,10 +85,6 @@ void IDisposable.Dispose()
null;
}
- /// Creates the for this instance.
- private static EmbeddingGeneratorMetadata CreateMetadata(string providerName, string providerUrl, string? defaultModelId, int? defaultModelDimensions) =>
- new(providerName, Uri.TryCreate(providerUrl, UriKind.Absolute, out Uri? providerUri) ? providerUri : null, defaultModelId, defaultModelDimensions);
-
/// Converts an extensions options instance to an OpenAI options instance.
private OpenAI.Embeddings.EmbeddingGenerationOptions ToOpenAIOptions(EmbeddingGenerationOptions? options)
{
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIImageGenerator.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIImageGenerator.cs
index fe1cb399e0d..b2ceb5cb317 100644
--- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIImageGenerator.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIImageGenerator.cs
@@ -45,18 +45,9 @@ internal sealed class OpenAIImageGenerator : IImageGenerator
/// is .
public OpenAIImageGenerator(ImageClient imageClient)
{
- _ = Throw.IfNull(imageClient);
+ _imageClient = Throw.IfNull(imageClient);
- _imageClient = imageClient;
-
- // https://github.com/openai/openai-dotnet/issues/215
- // The endpoint and model aren't currently exposed, so use reflection to get at them, temporarily. Once packages
- // implement the abstractions directly rather than providing adapters on top of the public APIs,
- // the package can provide such implementations separate from what's exposed in the public API.
- Uri providerUrl = typeof(ImageClient).GetField("_endpoint", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
- ?.GetValue(imageClient) as Uri ?? OpenAIClientExtensions.DefaultOpenAIEndpoint;
-
- _metadata = new("openai", providerUrl, _imageClient.Model);
+ _metadata = new("openai", imageClient.Endpoint, _imageClient.Model);
}
///
@@ -143,7 +134,7 @@ private static ImageGenerationResponse ToImageGenerationResponse(GeneratedImageC
// 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
- IDictionary? additionalRawData = typeof(GeneratedImageCollection)
+ var additionalRawData = typeof(GeneratedImageCollection)
.GetProperty("SerializedAdditionalRawData", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
?.GetValue(generatedImages) as IDictionary;
@@ -154,7 +145,7 @@ private static ImageGenerationResponse ToImageGenerationResponse(GeneratedImageC
contentType = $"image/{outputFormatString}";
}
- List contents = new();
+ List contents = [];
foreach (GeneratedImage image in generatedImages)
{
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs
index b5d3c09de32..0acfef52eb0 100644
--- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs
@@ -8,7 +8,6 @@
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
-using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
@@ -47,16 +46,12 @@ public OpenAIResponsesChatClient(OpenAIResponseClient responseClient)
_responseClient = responseClient;
- // https://github.com/openai/openai-dotnet/issues/215
- // The endpoint and model aren't currently exposed, so use reflection to get at them, temporarily. Once packages
- // implement the abstractions directly rather than providing adapters on top of the public APIs,
- // the package can provide such implementations separate from what's exposed in the public API.
- Uri providerUrl = typeof(OpenAIResponseClient).GetField("_endpoint", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
- ?.GetValue(responseClient) as Uri ?? OpenAIClientExtensions.DefaultOpenAIEndpoint;
+ // https://github.com/openai/openai-dotnet/issues/662
+ // Update to avoid reflection once OpenAIResponseClient.Model is exposed publicly.
string? model = typeof(OpenAIResponseClient).GetField("_model", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
?.GetValue(responseClient) as string;
- _metadata = new("openai", providerUrl, model);
+ _metadata = new("openai", responseClient.Endpoint, model);
}
///
@@ -204,7 +199,7 @@ internal static async IAsyncEnumerable FromOpenAIStreamingRe
string? lastMessageId = null;
ChatRole? lastRole = null;
Dictionary outputIndexToMessages = [];
- Dictionary? functionCallInfos = null;
+ Dictionary? functionCallItems = null;
await foreach (var streamingUpdate in streamingResponseUpdates.WithCancellation(cancellationToken).ConfigureAwait(false))
{
@@ -234,7 +229,7 @@ ChatResponseUpdate CreateUpdate(AIContent? content = null) =>
var update = CreateUpdate(ToUsageDetails(completedUpdate.Response) is { } usage ? new UsageContent(usage) : null);
update.FinishReason =
ToFinishReason(completedUpdate.Response?.IncompleteStatusDetails?.Reason) ??
- (functionCallInfos is not null ? ChatFinishReason.ToolCalls :
+ (functionCallItems is not null ? ChatFinishReason.ToolCalls :
ChatFinishReason.Stop);
yield return update;
break;
@@ -248,7 +243,7 @@ ChatResponseUpdate CreateUpdate(AIContent? content = null) =>
break;
case FunctionCallResponseItem fcri:
- (functionCallInfos ??= [])[outputItemAddedUpdate.OutputIndex] = new(fcri);
+ (functionCallItems ??= [])[outputItemAddedUpdate.OutputIndex] = fcri;
break;
}
@@ -283,28 +278,18 @@ ChatResponseUpdate CreateUpdate(AIContent? content = null) =>
break;
}
- case StreamingResponseFunctionCallArgumentsDeltaUpdate functionCallArgumentsDeltaUpdate:
- {
- if (functionCallInfos?.TryGetValue(functionCallArgumentsDeltaUpdate.OutputIndex, out FunctionCallInfo? callInfo) is true)
- {
- _ = (callInfo.Arguments ??= new()).Append(functionCallArgumentsDeltaUpdate.Delta);
- }
-
- goto default;
- }
-
case StreamingResponseFunctionCallArgumentsDoneUpdate functionCallOutputDoneUpdate:
{
- if (functionCallInfos?.TryGetValue(functionCallOutputDoneUpdate.OutputIndex, out FunctionCallInfo? callInfo) is true)
+ if (functionCallItems?.TryGetValue(functionCallOutputDoneUpdate.OutputIndex, out FunctionCallResponseItem? callInfo) is true)
{
- _ = functionCallInfos.Remove(functionCallOutputDoneUpdate.OutputIndex);
+ _ = functionCallItems.Remove(functionCallOutputDoneUpdate.OutputIndex);
var fcc = OpenAIClientExtensions.ParseCallContent(
- callInfo.Arguments?.ToString() ?? string.Empty,
- callInfo.ResponseItem.CallId,
- callInfo.ResponseItem.FunctionName);
+ functionCallOutputDoneUpdate.FunctionArguments.ToString(),
+ callInfo.CallId,
+ callInfo.FunctionName);
- lastMessageId = callInfo.ResponseItem.Id;
+ lastMessageId = callInfo.Id;
lastRole = ChatRole.Assistant;
yield return CreateUpdate(fcc);
@@ -329,18 +314,16 @@ ChatResponseUpdate CreateUpdate(AIContent? content = null) =>
});
break;
- default:
- {
- if (streamingUpdate.GetType() == _internalResponseReasoningSummaryTextDeltaEventType &&
- _summaryTextDeltaProperty?.GetValue(streamingUpdate) is string delta)
- {
- yield return CreateUpdate(new TextReasoningContent(delta));
- break;
- }
+ // Replace with public StreamingResponseReasoningSummaryTextDelta when available
+ case StreamingResponseUpdate when
+ streamingUpdate.GetType() == _internalResponseReasoningSummaryTextDeltaEventType &&
+ _summaryTextDeltaProperty?.GetValue(streamingUpdate) is string delta:
+ yield return CreateUpdate(new TextReasoningContent(delta));
+ break;
+ default:
yield return CreateUpdate();
break;
- }
}
}
}
@@ -351,7 +334,7 @@ void IDisposable.Dispose()
// Nothing to dispose. Implementation required for the IChatClient interface.
}
- internal static ResponseTool ToResponseTool(AIFunctionDeclaration aiFunction, ChatOptions? options = null)
+ internal static FunctionTool ToResponseTool(AIFunctionDeclaration aiFunction, ChatOptions? options = null)
{
bool? strict =
OpenAIClientExtensions.HasStrict(aiFunction.AdditionalProperties) ??
@@ -359,9 +342,9 @@ internal static ResponseTool ToResponseTool(AIFunctionDeclaration aiFunction, Ch
return ResponseTool.CreateFunctionTool(
aiFunction.Name,
- aiFunction.Description,
OpenAIClientExtensions.ToOpenAIFunctionParameters(aiFunction, strict),
- strict ?? false);
+ strict,
+ aiFunction.Description);
}
/// Creates a from a .
@@ -419,17 +402,17 @@ private ResponseCreationOptions ToOpenAIResponseCreationOptions(ChatOptions? opt
break;
case HostedWebSearchTool webSearchTool:
- WebSearchUserLocation? location = null;
- if (webSearchTool.AdditionalProperties.TryGetValue(nameof(WebSearchUserLocation), out object? objLocation))
+ WebSearchToolLocation? location = null;
+ if (webSearchTool.AdditionalProperties.TryGetValue(nameof(WebSearchToolLocation), out object? objLocation))
{
- location = objLocation as WebSearchUserLocation;
+ location = objLocation as WebSearchToolLocation;
}
- WebSearchContextSize? size = null;
- if (webSearchTool.AdditionalProperties.TryGetValue(nameof(WebSearchContextSize), out object? objSize) &&
- objSize is WebSearchContextSize)
+ WebSearchToolContextSize? size = null;
+ if (webSearchTool.AdditionalProperties.TryGetValue(nameof(WebSearchToolContextSize), out object? objSize) &&
+ objSize is WebSearchToolContextSize)
{
- size = (WebSearchContextSize)objSize;
+ size = (WebSearchToolContextSize)objSize;
}
result.Tools.Add(ResponseTool.CreateWebSearchTool(location, size));
@@ -690,14 +673,29 @@ private static void PopulateAnnotations(ResponseContentPart source, AIContent de
{
foreach (var ota in source.OutputTextAnnotations)
{
- (destination.Annotations ??= []).Add(new CitationAnnotation
+ CitationAnnotation ca = new()
{
RawRepresentation = ota,
- AnnotatedRegions = [new TextSpanAnnotatedRegion { StartIndex = ota.UriCitationStartIndex, EndIndex = ota.UriCitationEndIndex }],
- Title = ota.UriCitationTitle,
- Url = ota.UriCitationUri,
- FileId = ota.FileCitationFileId ?? ota.FilePathFileId,
- });
+ };
+
+ switch (ota)
+ {
+ case UriCitationMessageAnnotation ucma:
+ ca.AnnotatedRegions = [new TextSpanAnnotatedRegion { StartIndex = ucma.StartIndex, EndIndex = ucma.EndIndex }];
+ ca.Title = ucma.Title;
+ ca.Url = ucma.Uri;
+ break;
+
+ case FilePathMessageAnnotation fpma:
+ ca.FileId = fpma.FileId;
+ break;
+
+ case FileCitationMessageAnnotation fcma:
+ ca.FileId = fcma.FileId;
+ break;
+ }
+
+ (destination.Annotations ??= []).Add(ca);
}
}
}
@@ -747,12 +745,4 @@ private static List ToResponseContentParts(IList
return parts;
}
-
- /// POCO representing function calling info.
- /// Used to concatenation information for a single function call from across multiple streaming updates.
- private sealed class FunctionCallInfo(FunctionCallResponseItem item)
- {
- public readonly FunctionCallResponseItem ResponseItem = item;
- public StringBuilder? Arguments;
- }
}
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAISpeechToTextClient.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAISpeechToTextClient.cs
index 2e2503335f3..06051a54c26 100644
--- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAISpeechToTextClient.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAISpeechToTextClient.cs
@@ -5,7 +5,6 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
-using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
@@ -37,18 +36,9 @@ internal sealed class OpenAISpeechToTextClient : ISpeechToTextClient
/// The underlying client.
public OpenAISpeechToTextClient(AudioClient audioClient)
{
- _ = Throw.IfNull(audioClient);
+ _audioClient = Throw.IfNull(audioClient);
- _audioClient = audioClient;
-
- // https://github.com/openai/openai-dotnet/issues/215
- // The endpoint and model aren't currently exposed, so use reflection to get at them, temporarily. Once packages
- // implement the abstractions directly rather than providing adapters on top of the public APIs,
- // the package can provide such implementations separate from what's exposed in the public API.
- Uri providerUrl = typeof(AudioClient).GetField("_endpoint", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
- ?.GetValue(audioClient) as Uri ?? OpenAIClientExtensions.DefaultOpenAIEndpoint;
-
- _metadata = new("openai", providerUrl, _audioClient.Model);
+ _metadata = new("openai", audioClient.Endpoint, _audioClient.Model);
}
///
diff --git a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ChatClientIntegrationTests.cs b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ChatClientIntegrationTests.cs
index c87625cf143..448de8d11df 100644
--- a/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ChatClientIntegrationTests.cs
+++ b/test/Libraries/Microsoft.Extensions.AI.Integration.Tests/ChatClientIntegrationTests.cs
@@ -942,7 +942,7 @@ public virtual async Task OpenTelemetry_CanEmitTracesAndMetrics()
var activity = Assert.Single(activities);
Assert.StartsWith("chat", activity.DisplayName);
- Assert.StartsWith("http", (string)activity.GetTagItem("server.address")!);
+ Assert.Contains(".", (string)activity.GetTagItem("server.address")!);
Assert.Equal(chatClient.GetService()?.ProviderUri?.Port, (int)activity.GetTagItem("server.port")!);
Assert.NotNull(activity.Id);
Assert.NotEmpty(activity.Id);
@@ -1276,7 +1276,7 @@ public virtual async Task SummarizingChatReducer_Streaming()
new(ChatRole.Assistant, "That sounds impactful! AI in education has so much potential."),
new(ChatRole.User, "Yes, we focus on personalized learning experiences."),
new(ChatRole.Assistant, "Personalized learning is the future of education!"),
- new(ChatRole.User, "What's my name and profession?")
+ new(ChatRole.User, "Was anyone named in the conversation? Provide their name and job.")
];
StringBuilder sb = new();
@@ -1296,7 +1296,7 @@ public virtual async Task SummarizingChatReducer_Streaming()
Assert.Contains("Bob", m.Text);
},
m => Assert.StartsWith("Personalized learning", m.Text, StringComparison.Ordinal),
- m => Assert.Equal("What's my name and profession?", m.Text));
+ m => Assert.Equal("Was anyone named in the conversation? Provide their name and job.", m.Text));
string responseText = sb.ToString();
Assert.Contains("Bob", responseText);
diff --git a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIChatClientTests.cs b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIChatClientTests.cs
index 640dab54f8e..bdf3b2e7c0a 100644
--- a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIChatClientTests.cs
+++ b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIChatClientTests.cs
@@ -8,8 +8,6 @@
using System.ComponentModel;
using System.Linq;
using System.Net.Http;
-using System.Text.Json;
-using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.Memory;
@@ -655,22 +653,6 @@ public async Task ChatOptions_Overwrite_NullPropertiesInRawRepresentation_Stream
Assert.Equal("Hello! How can I assist you today?", responseText);
}
- /// Used to create the JSON payload for an OpenAI chat tool description.
- internal sealed class ChatToolJson
- {
- [JsonPropertyName("type")]
- public string Type { get; set; } = "object";
-
- [JsonPropertyName("required")]
- public HashSet Required { get; set; } = [];
-
- [JsonPropertyName("properties")]
- public Dictionary Properties { get; set; } = [];
-
- [JsonPropertyName("additionalProperties")]
- public bool AdditionalProperties { get; set; }
- }
-
[Fact]
public async Task StronglyTypedOptions_AllSent()
{
diff --git a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientIntegrationTests.cs b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientIntegrationTests.cs
index 630af9e34b0..007e86288f5 100644
--- a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientIntegrationTests.cs
+++ b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientIntegrationTests.cs
@@ -27,7 +27,7 @@ public async Task UseWebSearch_AnnotationsReflectResults()
SkipIfNotEnabled();
var response = await ChatClient.GetResponseAsync(
- "Write a paragraph about the three most recent blog posts on the .NET blog. Cite your sources.",
+ "Write a paragraph about .NET based on at least three recent news articles. Cite your sources.",
new() { Tools = [new HostedWebSearchTool()] });
ChatMessage m = Assert.Single(response.Messages);
diff --git a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientTests.cs b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientTests.cs
index ca71aee550f..014fae0d39f 100644
--- a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientTests.cs
+++ b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientTests.cs
@@ -8,7 +8,6 @@
using System.ComponentModel;
using System.Linq;
using System.Net.Http;
-using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.Memory;
@@ -543,20 +542,70 @@ public async Task ChatOptions_DoNotOverwrite_NotNullPropertiesInRawRepresentatio
{
const string Input = """
{
- "input":[{"type":"message","role":"user","content":[{"type":"input_text","text":"hello"}]}],
- "model":"gpt-4o-mini",
- "max_output_tokens":10,
- "previous_response_id":"resp_42",
- "top_p":0.5,
- "temperature":0.5,
- "parallel_tool_calls":true,
- "text": {"format": {"type": "text"}
- },
- "tool_choice":"auto",
- "tools":[
- {"description":"Gets the age of the specified person.","name":"GetPersonAge","parameters":{"additionalProperties":false,"properties":{"personName":{"description":"The person whose age is being requested","type":"string"}},"required":["personName"],"type":"object"},"strict":false,"type":"function"},
- {"description":"Gets the age of the specified person.","name":"GetPersonAge","parameters":{"additionalProperties":false,"properties":{"personName":{"description":"The person whose age is being requested","type":"string"}},"required":["personName"],"type":"object"},"strict":false,"type":"function"}
- ]
+ "temperature": 0.5,
+ "top_p": 0.5,
+ "previous_response_id": "resp_42",
+ "model": "gpt-4o-mini",
+ "max_output_tokens": 10,
+ "text": {
+ "format": {
+ "type": "text"
+ }
+ },
+ "tools": [
+ {
+ "type": "function",
+ "name": "GetPersonAge",
+ "description": "Gets the age of the specified person.",
+ "parameters": {
+ "type": "object",
+ "required": [
+ "personName"
+ ],
+ "properties": {
+ "personName": {
+ "description": "The person whose age is being requested",
+ "type": "string"
+ }
+ },
+ "additionalProperties": false
+ },
+ "strict": null
+ },
+ {
+ "type": "function",
+ "name": "GetPersonAge",
+ "description": "Gets the age of the specified person.",
+ "parameters": {
+ "type": "object",
+ "required": [
+ "personName"
+ ],
+ "properties": {
+ "personName": {
+ "description": "The person whose age is being requested",
+ "type": "string"
+ }
+ },
+ "additionalProperties": false
+ },
+ "strict": null
+ }
+ ],
+ "tool_choice": "auto",
+ "input": [
+ {
+ "type": "message",
+ "role": "user",
+ "content": [
+ {
+ "type": "input_text",
+ "text": "hello"
+ }
+ ]
+ }
+ ],
+ "parallel_tool_calls": true
}
""";
@@ -640,7 +689,7 @@ public async Task ChatOptions_DoNotOverwrite_NotNullPropertiesInRawRepresentatio
TextFormat = ResponseTextFormat.CreateTextFormat()
},
};
- openAIOptions.Tools.Add(ToOpenAIResponseChatTool(tool));
+ openAIOptions.Tools.Add(tool.AsOpenAIResponseTool());
return openAIOptions;
},
ModelId = null,
@@ -775,14 +824,6 @@ public async Task MultipleOutputItems_NonStreaming()
Assert.Equal(36, response.Usage.TotalTokenCount);
}
- /// Converts an Extensions function to an OpenAI response chat tool.
- private static ResponseTool ToOpenAIResponseChatTool(AIFunction aiFunction)
- {
- var tool = JsonSerializer.Deserialize(aiFunction.JsonSchema)!;
- var functionParameters = BinaryData.FromBytes(JsonSerializer.SerializeToUtf8Bytes(tool));
- return ResponseTool.CreateFunctionTool(aiFunction.Name, aiFunction.Description, functionParameters);
- }
-
private static IChatClient CreateResponseClient(HttpClient httpClient, string modelId) =>
new OpenAIClient(
new ApiKeyCredential("apikey"),