Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion eng/packages/General.props
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<PackageVersion Include="ModelContextProtocol.Core" Version="0.4.0-preview.3" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="OllamaSharp" Version="5.1.9" />
<PackageVersion Include="OpenAI" Version="2.7.0" />
<PackageVersion Include="OpenAI" Version="2.8.0" />
<PackageVersion Include="Polly" Version="8.4.2" />
<PackageVersion Include="Polly.Core" Version="8.4.2" />
<PackageVersion Include="Polly.Extensions" Version="8.4.2" />
Expand Down
2 changes: 2 additions & 0 deletions src/Libraries/Microsoft.Extensions.AI.OpenAI/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## 10.1.1-preview.1.? (NOT YET RELEASED)

- Updated to depend on OpenAI 2.8.0.
- Updated public API signatures in `OpenAIClientExtensions` and `MicrosoftExtensionsAIResponsesExtensions` to match the corresponding breaking changes in OpenAI's Responses APIs.
- Updated to accommodate the additions in `Microsoft.Extensions.AI.Abstractions`.
- Updated the OpenAI Responses and Chat Completion `IChatClient`s to populate `UsageDetails`'s `InputCachedTokenCount` and `ReasoningTokenCount`.
- Updated handling of `HostedWebSearchTool`, `HostedFileSearchTool`, and `HostedImageGenerationTool` to pull OpenAI-specific
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,12 @@ public static IEnumerable<ResponseItem> AsOpenAIResponseItems(this IEnumerable<C
public static IEnumerable<ChatMessage> AsChatMessages(this IEnumerable<ResponseItem> items) =>
OpenAIResponsesChatClient.ToChatMessages(Throw.IfNull(items));

/// <summary>Creates a Microsoft.Extensions.AI <see cref="ChatResponse"/> from an <see cref="OpenAIResponse"/>.</summary>
/// <param name="response">The <see cref="OpenAIResponse"/> to convert to a <see cref="ChatResponse"/>.</param>
/// <summary>Creates a Microsoft.Extensions.AI <see cref="ChatResponse"/> from an <see cref="ResponseResult"/>.</summary>
/// <param name="response">The <see cref="ResponseResult"/> to convert to a <see cref="ChatResponse"/>.</param>
/// <param name="options">The options employed in the creation of the response.</param>
/// <returns>A converted <see cref="ChatResponse"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="response"/> is <see langword="null"/>.</exception>
public static ChatResponse AsChatResponse(this OpenAIResponse response, ResponseCreationOptions? options = null) =>
public static ChatResponse AsChatResponse(this ResponseResult response, CreateResponseOptions? options = null) =>
OpenAIResponsesChatClient.FromOpenAIResponse(Throw.IfNull(response), options, conversationId: null);

/// <summary>
Expand All @@ -74,35 +74,43 @@ public static ChatResponse AsChatResponse(this OpenAIResponse response, Response
/// <returns>A sequence of converted <see cref="ChatResponseUpdate"/> instances.</returns>
/// <exception cref="ArgumentNullException"><paramref name="responseUpdates"/> is <see langword="null"/>.</exception>
public static IAsyncEnumerable<ChatResponseUpdate> AsChatResponseUpdatesAsync(
this IAsyncEnumerable<StreamingResponseUpdate> responseUpdates, ResponseCreationOptions? options = null, CancellationToken cancellationToken = default) =>
this IAsyncEnumerable<StreamingResponseUpdate> responseUpdates, CreateResponseOptions? options = null, CancellationToken cancellationToken = default) =>
OpenAIResponsesChatClient.FromOpenAIStreamingResponseUpdatesAsync(Throw.IfNull(responseUpdates), options, conversationId: null, cancellationToken: cancellationToken);

/// <summary>Creates an OpenAI <see cref="OpenAIResponse"/> from a <see cref="ChatResponse"/>.</summary>
/// <summary>Creates an OpenAI <see cref="ResponseResult"/> from a <see cref="ChatResponse"/>.</summary>
/// <param name="response">The response to convert.</param>
/// <param name="options">The options employed in the creation of the response.</param>
/// <returns>The created <see cref="OpenAIResponse"/>.</returns>
public static OpenAIResponse AsOpenAIResponse(this ChatResponse response, ChatOptions? options = null)
/// <returns>The created <see cref="ResponseResult"/>.</returns>
public static ResponseResult AsOpenAIResponseResult(this ChatResponse response, ChatOptions? options = null)
{
_ = Throw.IfNull(response);

if (response.RawRepresentation is OpenAIResponse openAIResponse)
if (response.RawRepresentation is ResponseResult openAIResponse)
{
return openAIResponse;
}

return OpenAIResponsesModelFactory.OpenAIResponse(
response.ResponseId,
response.CreatedAt ?? default,
ResponseStatus.Completed,
usage: null, // No way to construct a ResponseTokenUsage right now from external to the OpenAI library
maxOutputTokenCount: options?.MaxOutputTokens,
outputItems: OpenAIResponsesChatClient.ToOpenAIResponseItems(response.Messages, options),
parallelToolCallsEnabled: options?.AllowMultipleToolCalls ?? false,
model: response.ModelId ?? options?.ModelId,
temperature: options?.Temperature,
topP: options?.TopP,
previousResponseId: options?.ConversationId,
instructions: options?.Instructions);
ResponseResult result = new()
{
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,
Status = ResponseStatus.Completed,
Temperature = options?.Temperature,
TopP = options?.TopP,
Usage = OpenAIResponsesChatClient.ToResponseTokenUsage(response.Usage),
};

foreach (var responseItem in OpenAIResponsesChatClient.ToOpenAIResponseItems(response.Messages, options))
{
result.OutputItems.Add(responseItem);
}

return result;
}

/// <summary>Adds the <see cref="ResponseTool"/> to the list of <see cref="AITool"/>s.</summary>
Expand All @@ -111,7 +119,7 @@ public static OpenAIResponse AsOpenAIResponse(this ChatResponse response, ChatOp
/// <remarks>
/// <see cref="ResponseTool"/> does not derive from <see cref="AITool"/>, so it cannot be added directly to a list of <see cref="AITool"/>s.
/// Instead, this method wraps the provided <see cref="ResponseTool"/> in an <see cref="AITool"/> and adds that to the list.
/// The <see cref="IChatClient"/> returned by <see cref="OpenAIClientExtensions.AsIChatClient(OpenAIResponseClient)"/> will
/// The <see cref="IChatClient"/> returned by <see cref="OpenAIClientExtensions.AsIChatClient(ResponsesClient)"/> will
/// be able to unwrap the <see cref="ResponseTool"/> when it processes the list of tools and use the provided <paramref name="tool"/> as-is.
/// </remarks>
public static void Add(this IList<AITool> tools, ResponseTool tool)
Expand All @@ -127,7 +135,7 @@ public static void Add(this IList<AITool> tools, ResponseTool tool)
/// <remarks>
/// <para>
/// The returned tool is only suitable for use with the <see cref="IChatClient"/> returned by
/// <see cref="OpenAIClientExtensions.AsIChatClient(OpenAIResponseClient)"/> (or <see cref="IChatClient"/>s that delegate
/// <see cref="OpenAIClientExtensions.AsIChatClient(ResponsesClient)"/> (or <see cref="IChatClient"/>s that delegate
/// to such an instance). It is likely to be ignored by any other <see cref="IChatClient"/> implementation.
/// </para>
/// <para>
Expand All @@ -136,7 +144,7 @@ public static void Add(this IList<AITool> tools, ResponseTool tool)
/// <see cref="HostedFileSearchTool"/>, those types should be preferred instead of this method, as they are more portable,
/// capable of being respected by any <see cref="IChatClient"/> implementation. This method does not attempt to
/// map the supplied <see cref="ResponseTool"/> to any of those types, it simply wraps it as-is:
/// the <see cref="IChatClient"/> returned by <see cref="OpenAIClientExtensions.AsIChatClient(OpenAIResponseClient)"/> will
/// the <see cref="IChatClient"/> returned by <see cref="OpenAIClientExtensions.AsIChatClient(ResponsesClient)"/> will
/// be able to unwrap the <see cref="ResponseTool"/> when it processes the list of tools.
/// </para>
/// </remarks>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ namespace Microsoft.Extensions.AI;
public static class OpenAIClientExtensions
{
/// <summary>Key into AdditionalProperties used to store a strict option.</summary>
private const string StrictKey = "strictJsonSchema";
private const string StrictKey = "strict";

/// <summary>Gets the default OpenAI endpoint.</summary>
internal static Uri DefaultOpenAIEndpoint { get; } = new("https://api.openai.com/v1");
Expand Down Expand Up @@ -111,11 +111,11 @@ static void AppendLine(ref StringBuilder? sb, string propName, JsonNode propNode
public static IChatClient AsIChatClient(this ChatClient chatClient) =>
new OpenAIChatClient(chatClient);

/// <summary>Gets an <see cref="IChatClient"/> for use with this <see cref="OpenAIResponseClient"/>.</summary>
/// <summary>Gets an <see cref="IChatClient"/> for use with this <see cref="ResponsesClient"/>.</summary>
/// <param name="responseClient">The client.</param>
/// <returns>An <see cref="IChatClient"/> that can be used to converse via the <see cref="OpenAIResponseClient"/>.</returns>
/// <returns>An <see cref="IChatClient"/> that can be used to converse via the <see cref="ResponsesClient"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="responseClient"/> is <see langword="null"/>.</exception>
public static IChatClient AsIChatClient(this OpenAIResponseClient responseClient) =>
public static IChatClient AsIChatClient(this ResponsesClient responseClient) =>
new OpenAIResponsesChatClient(responseClient);

/// <summary>Gets an <see cref="IChatClient"/> for use with this <see cref="AssistantClient"/>.</summary>
Expand Down Expand Up @@ -246,6 +246,14 @@ internal static void PatchModelIfNotSet(ref JsonPatch patch, string? modelId)
internal static T? GetProperty<T>(this AITool tool, string name) =>
tool.AdditionalProperties?.TryGetValue(name, out object? value) is true && value is T tValue ? tValue : default;

/// <summary>Gets whether an ID is an OpenAI conversation ID.</summary>
/// <remarks>
/// Technically, OpenAI's IDs are opaque. However, by convention conversation IDs start with "conv_" and
/// we can use that to disambiguate whether we're looking at a conversation ID or something else, like a response ID.
/// </remarks>
internal static bool IsConversationId(string? id) =>
id?.StartsWith("conv_", StringComparison.OrdinalIgnoreCase) is true;

/// <summary>Used to create the JSON payload for an OpenAI tool description.</summary>
internal sealed class ToolJson
{
Expand Down
Loading
Loading