Skip to content

Commit d649cbb

Browse files
authored
Update to OpenAI 2.5.0 (#6839)
1 parent 7200baa commit d649cbb

File tree

8 files changed

+96
-34
lines changed

8 files changed

+96
-34
lines changed

eng/packages/General.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
<PackageVersion Include="Microsoft.ML.Tokenizers" Version="$(MicrosoftMLTokenizersVersion)" />
1717
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
1818
<PackageVersion Include="OllamaSharp" Version="5.1.9" />
19-
<PackageVersion Include="OpenAI" Version="2.4.0" />
19+
<PackageVersion Include="OpenAI" Version="2.5.0" />
2020
<PackageVersion Include="Polly" Version="8.4.2" />
2121
<PackageVersion Include="Polly.Core" Version="8.4.2" />
2222
<PackageVersion Include="Polly.Extensions" Version="8.4.2" />

src/Libraries/Microsoft.Extensions.AI.OpenAI/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22

33
## NOT YET RELEASED
44

5+
- Updated to depend on OpenAI 2.5.0.
56
- Added M.E.AI to OpenAI conversions for response format types.
67
- Added `ResponseTool` to `AITool` conversions.
78

89
## 9.9.0-preview.1.25458.4
910

10-
- Updated to depend on OpenAI 2.4.0
11+
- Updated to depend on OpenAI 2.4.0.
1112
- Updated tool mappings to recognize any `AIFunctionDeclaration`.
1213
- Updated to accommodate the additions in `Microsoft.Extensions.AI.Abstractions`.
1314
- Fixed handling of annotated but empty content in the `AsIChatClient` for `AssistantClient`.

src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIEmbeddingGenerator.cs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,37 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System;
5+
using System.ClientModel;
6+
using System.ClientModel.Primitives;
57
using System.Collections.Generic;
68
using System.Linq;
9+
using System.Reflection;
710
using System.Threading;
811
using System.Threading.Tasks;
912
using Microsoft.Shared.Diagnostics;
1013
using OpenAI.Embeddings;
1114

1215
#pragma warning disable S1067 // Expressions should not be too complex
1316
#pragma warning disable S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields
17+
#pragma warning disable EA0011 // Consider removing unnecessary conditional access operator (?)
1418

1519
namespace Microsoft.Extensions.AI;
1620

1721
/// <summary>An <see cref="IEmbeddingGenerator{String, Embedding}"/> for an OpenAI <see cref="EmbeddingClient"/>.</summary>
1822
internal sealed class OpenAIEmbeddingGenerator : IEmbeddingGenerator<string, Embedding<float>>
1923
{
24+
// This delegate instance is used to call the internal overload of GenerateEmbeddingsAsync that accepts
25+
// a RequestOptions. This should be replaced once a better way to pass RequestOptions is available.
26+
private static readonly Func<EmbeddingClient, IEnumerable<string>, OpenAI.Embeddings.EmbeddingGenerationOptions, RequestOptions, Task<ClientResult<OpenAIEmbeddingCollection>>>?
27+
_generateEmbeddingsAsync =
28+
(Func<EmbeddingClient, IEnumerable<string>, OpenAI.Embeddings.EmbeddingGenerationOptions, RequestOptions, Task<ClientResult<OpenAIEmbeddingCollection>>>?)
29+
typeof(EmbeddingClient)
30+
.GetMethod(
31+
nameof(EmbeddingClient.GenerateEmbeddingsAsync), BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
32+
null, [typeof(IEnumerable<string>), typeof(OpenAI.Embeddings.EmbeddingGenerationOptions), typeof(RequestOptions)], null)
33+
?.CreateDelegate(
34+
typeof(Func<EmbeddingClient, IEnumerable<string>, OpenAI.Embeddings.EmbeddingGenerationOptions, RequestOptions, Task<ClientResult<OpenAIEmbeddingCollection>>>));
35+
2036
/// <summary>Metadata about the embedding generator.</summary>
2137
private readonly EmbeddingGeneratorMetadata _metadata;
2238

@@ -49,7 +65,10 @@ public async Task<GeneratedEmbeddings<Embedding<float>>> GenerateAsync(IEnumerab
4965
{
5066
OpenAI.Embeddings.EmbeddingGenerationOptions? openAIOptions = ToOpenAIOptions(options);
5167

52-
var embeddings = (await _embeddingClient.GenerateEmbeddingsAsync(values, openAIOptions, cancellationToken).ConfigureAwait(false)).Value;
68+
var t = _generateEmbeddingsAsync is not null ?
69+
_generateEmbeddingsAsync(_embeddingClient, values, openAIOptions, cancellationToken.ToRequestOptions(streaming: false)) :
70+
_embeddingClient.GenerateEmbeddingsAsync(values, openAIOptions, cancellationToken);
71+
var embeddings = (await t.ConfigureAwait(false)).Value;
5372

5473
return new(embeddings.Select(e =>
5574
new Embedding<float>(e.ToFloats())

src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,7 @@ public OpenAIResponsesChatClient(OpenAIResponseClient responseClient)
6565

6666
_responseClient = responseClient;
6767

68-
// https://github.com/openai/openai-dotnet/issues/662
69-
// Update to avoid reflection once OpenAIResponseClient.Model is exposed publicly.
70-
string? model = typeof(OpenAIResponseClient).GetField("_model", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
71-
?.GetValue(responseClient) as string;
72-
73-
_metadata = new("openai", responseClient.Endpoint, model);
68+
_metadata = new("openai", responseClient.Endpoint, responseClient.Model);
7469
}
7570

7671
/// <inheritdoc />
@@ -469,27 +464,19 @@ private ResponseCreationOptions ToOpenAIResponseCreationOptions(ChatOptions? opt
469464
break;
470465

471466
case HostedCodeInterpreterTool codeTool:
472-
string json;
473-
if (codeTool.Inputs is { Count: > 0 } inputs)
474-
{
475-
string jsonArray = JsonSerializer.Serialize(
476-
inputs.OfType<HostedFileContent>().Select(c => c.FileId),
477-
OpenAIJsonContext.Default.IEnumerableString);
478-
json = $$"""{"type":"code_interpreter","container":{"type":"auto",files:{{jsonArray}}} }""";
479-
}
480-
else
481-
{
482-
json = """{"type":"code_interpreter","container":{"type":"auto"}}""";
483-
}
484-
485-
result.Tools.Add(ModelReaderWriter.Read<ResponseTool>(BinaryData.FromString(json)));
467+
result.Tools.Add(
468+
ResponseTool.CreateCodeInterpreterTool(
469+
new CodeInterpreterToolContainer(codeTool.Inputs?.OfType<HostedFileContent>().Select(f => f.FileId).ToList() is { Count: > 0 } ids ?
470+
CodeInterpreterToolContainerConfiguration.CreateAutomaticContainerConfiguration(ids) :
471+
new())));
486472
break;
487473

488474
case HostedMcpServerTool mcpTool:
489475
McpTool responsesMcpTool = ResponseTool.CreateMcpTool(
490476
mcpTool.ServerName,
491477
mcpTool.Url,
492-
mcpTool.Headers);
478+
serverDescription: mcpTool.ServerDescription,
479+
headers: mcpTool.Headers);
493480

494481
if (mcpTool.AllowedTools is not null)
495482
{
@@ -673,8 +660,8 @@ internal static IEnumerable<ResponseItem> ToOpenAIResponseItems(IEnumerable<Chat
673660
break;
674661

675662
case McpServerToolApprovalRequestContent mcpApprovalRequestContent:
676-
// BUG https://github.com/openai/openai-dotnet/issues/664: Needs to be able to set an approvalRequestId
677663
yield return ResponseItem.CreateMcpApprovalRequestItem(
664+
mcpApprovalRequestContent.Id,
678665
mcpApprovalRequestContent.ToolCall.ServerName,
679666
mcpApprovalRequestContent.ToolCall.ToolName,
680667
BinaryData.FromBytes(JsonSerializer.SerializeToUtf8Bytes(mcpApprovalRequestContent.ToolCall.Arguments!, OpenAIJsonContext.Default.IReadOnlyDictionaryStringObject)));

test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIChatClientTests.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1586,6 +1586,19 @@ private static async Task DataContentMessage_Image_AdditionalPropertyDetail_NonS
15861586
}, response.Usage.AdditionalCounts);
15871587
}
15881588

1589+
[Fact]
1590+
public async Task RequestHeaders_UserAgent_ContainsMEAI()
1591+
{
1592+
using var handler = new ThrowUserAgentExceptionHandler();
1593+
using HttpClient httpClient = new(handler);
1594+
using IChatClient client = CreateChatClient(httpClient, "gpt-4o-mini");
1595+
1596+
InvalidOperationException e = await Assert.ThrowsAsync<InvalidOperationException>(() => client.GetResponseAsync("hello"));
1597+
1598+
Assert.StartsWith("User-Agent header: OpenAI", e.Message);
1599+
Assert.Contains("MEAI", e.Message);
1600+
}
1601+
15891602
private static IChatClient CreateChatClient(HttpClient httpClient, string modelId) =>
15901603
new OpenAIClient(new ApiKeyCredential("apikey"), new OpenAIClientOptions { Transport = new HttpClientPipelineTransport(httpClient) })
15911604
.GetChatClient(modelId)

test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIEmbeddingGeneratorTests.cs

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,7 @@ public async Task GetEmbeddingsAsync_ExpectedRequestResponse()
125125

126126
using VerbatimHttpHandler handler = new(Input, Output);
127127
using HttpClient httpClient = new(handler);
128-
using IEmbeddingGenerator<string, Embedding<float>> generator = new OpenAIClient(new ApiKeyCredential("apikey"), new OpenAIClientOptions
129-
{
130-
Transport = new HttpClientPipelineTransport(httpClient),
131-
}).GetEmbeddingClient("text-embedding-3-small").AsIEmbeddingGenerator();
128+
using IEmbeddingGenerator<string, Embedding<float>> generator = CreateEmbeddingGenerator(httpClient, "text-embedding-3-small");
132129

133130
var response = await generator.GenerateAsync([
134131
"hello, world!",
@@ -188,10 +185,7 @@ public async Task EmbeddingGenerationOptions_DoNotOverwrite_NotNullPropertiesInR
188185

189186
using VerbatimHttpHandler handler = new(Input, Output);
190187
using HttpClient httpClient = new(handler);
191-
using IEmbeddingGenerator<string, Embedding<float>> generator = new OpenAIClient(new ApiKeyCredential("apikey"), new OpenAIClientOptions
192-
{
193-
Transport = new HttpClientPipelineTransport(httpClient),
194-
}).GetEmbeddingClient("text-embedding-3-small").AsIEmbeddingGenerator();
188+
using IEmbeddingGenerator<string, Embedding<float>> generator = CreateEmbeddingGenerator(httpClient, "text-embedding-3-small");
195189

196190
var response = await generator.GenerateAsync([
197191
"hello, world!",
@@ -221,4 +215,24 @@ public async Task EmbeddingGenerationOptions_DoNotOverwrite_NotNullPropertiesInR
221215
Assert.Contains(e.Vector.ToArray(), f => !f.Equals(0));
222216
}
223217
}
218+
219+
[Fact]
220+
public async Task RequestHeaders_UserAgent_ContainsMEAI()
221+
{
222+
using var handler = new ThrowUserAgentExceptionHandler();
223+
using HttpClient httpClient = new(handler);
224+
using IEmbeddingGenerator<string, Embedding<float>> generator = CreateEmbeddingGenerator(httpClient, "text-embedding-3-small");
225+
226+
InvalidOperationException e = await Assert.ThrowsAsync<InvalidOperationException>(() => generator.GenerateAsync("hello"));
227+
228+
Assert.StartsWith("User-Agent header: OpenAI", e.Message);
229+
Assert.Contains("MEAI", e.Message);
230+
}
231+
232+
private static IEmbeddingGenerator<string, Embedding<float>> CreateEmbeddingGenerator(HttpClient httpClient, string modelId) =>
233+
new OpenAIClient(
234+
new ApiKeyCredential("apikey"),
235+
new OpenAIClientOptions { Transport = new HttpClientPipelineTransport(httpClient) })
236+
.GetEmbeddingClient(modelId)
237+
.AsIEmbeddingGenerator();
224238
}

test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientTests.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1034,7 +1034,7 @@ public async Task McpToolCall_ApprovalNotRequired_NonStreaming(bool rawTool)
10341034
using IChatClient client = CreateResponseClient(httpClient, "gpt-4o-mini");
10351035

10361036
AITool mcpTool = rawTool ?
1037-
ResponseTool.CreateMcpTool("deepwiki", new("https://mcp.deepwiki.com/mcp"), toolCallApprovalPolicy: new McpToolCallApprovalPolicy(GlobalMcpToolCallApprovalPolicy.NeverRequireApproval)).AsAITool() :
1037+
ResponseTool.CreateMcpTool("deepwiki", serverUri: new("https://mcp.deepwiki.com/mcp"), toolCallApprovalPolicy: new McpToolCallApprovalPolicy(GlobalMcpToolCallApprovalPolicy.NeverRequireApproval)).AsAITool() :
10381038
new HostedMcpServerTool("deepwiki", "https://mcp.deepwiki.com/mcp")
10391039
{
10401040
ApprovalMode = HostedMcpServerToolApprovalMode.NeverRequire,
@@ -1506,6 +1506,19 @@ public async Task McpToolCall_ApprovalNotRequired_Streaming()
15061506
Assert.Equal(1569, response.Usage.TotalTokenCount);
15071507
}
15081508

1509+
[Fact]
1510+
public async Task RequestHeaders_UserAgent_ContainsMEAI()
1511+
{
1512+
using var handler = new ThrowUserAgentExceptionHandler();
1513+
using HttpClient httpClient = new(handler);
1514+
using IChatClient client = CreateResponseClient(httpClient, "gpt-4o-mini");
1515+
1516+
InvalidOperationException e = await Assert.ThrowsAsync<InvalidOperationException>(() => client.GetResponseAsync("hello"));
1517+
1518+
Assert.StartsWith("User-Agent header: OpenAI", e.Message);
1519+
Assert.Contains("MEAI", e.Message);
1520+
}
1521+
15091522
private static IChatClient CreateResponseClient(HttpClient httpClient, string modelId) =>
15101523
new OpenAIClient(
15111524
new ApiKeyCredential("apikey"),
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Net.Http;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
9+
namespace Microsoft.Extensions.AI;
10+
11+
internal sealed class ThrowUserAgentExceptionHandler : HttpMessageHandler
12+
{
13+
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) =>
14+
throw new InvalidOperationException($"User-Agent header: {request.Headers.UserAgent}");
15+
}

0 commit comments

Comments
 (0)