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
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;

namespace OpenAI.Assistants;

/// <summary>Provides extension methods for working with content associated with OpenAI.Assistants.</summary>
public static class MicrosoftExtensionsAIAssistantsExtensions
{
/// <summary>Creates an OpenAI <see cref="FunctionToolDefinition"/> from an <see cref="AIFunction"/>.</summary>
/// <param name="function">The function to convert.</param>
/// <returns>An OpenAI <see cref="FunctionToolDefinition"/> representing <paramref name="function"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="function"/> is <see langword="null"/>.</exception>
public static FunctionToolDefinition AsOpenAIAssistantsFunctionToolDefinition(this AIFunction function) =>
OpenAIAssistantsChatClient.ToOpenAIAssistantsFunctionToolDefinition(Throw.IfNull(function));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;

namespace OpenAI.Chat;

/// <summary>Provides extension methods for working with content associated with OpenAI.Chat.</summary>
public static class MicrosoftExtensionsAIChatExtensions
{
/// <summary>Creates an OpenAI <see cref="ChatTool"/> from an <see cref="AIFunction"/>.</summary>
/// <param name="function">The function to convert.</param>
/// <returns>An OpenAI <see cref="ChatTool"/> representing <paramref name="function"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="function"/> is <see langword="null"/>.</exception>
public static ChatTool AsOpenAIChatTool(this AIFunction function) =>
OpenAIChatClient.ToOpenAIChatTool(Throw.IfNull(function));

/// <summary>Creates a sequence of OpenAI <see cref="ChatMessage"/> instances from the specified input messages.</summary>
/// <param name="messages">The input messages to convert.</param>
/// <returns>A sequence of OpenAI chat messages.</returns>
public static IEnumerable<ChatMessage> AsOpenAIChatMessages(this IEnumerable<Microsoft.Extensions.AI.ChatMessage> messages) =>
OpenAIChatClient.ToOpenAIChatMessages(Throw.IfNull(messages), chatOptions: null);

/// <summary>Creates a Microsoft.Extensions.AI <see cref="ChatResponse"/> from a <see cref="ChatCompletion"/>.</summary>
/// <param name="chatCompletion">The <see cref="ChatCompletion"/> 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>
public static ChatResponse AsChatResponse(this ChatCompletion chatCompletion, ChatCompletionOptions? options = null) =>
OpenAIChatClient.FromOpenAIChatCompletion(Throw.IfNull(chatCompletion), options);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;

namespace OpenAI.Realtime;

/// <summary>Provides extension methods for working with content associated with OpenAI.Realtime.</summary>
public static class MicrosoftExtensionsAIRealtimeExtensions
{
/// <summary>Creates an OpenAI <see cref="ConversationFunctionTool"/> from an <see cref="AIFunction"/>.</summary>
/// <param name="function">The function to convert.</param>
/// <returns>An OpenAI <see cref="ConversationFunctionTool"/> representing <paramref name="function"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="function"/> is <see langword="null"/>.</exception>
public static ConversationFunctionTool AsOpenAIConversationFunctionTool(this AIFunction function) =>
OpenAIRealtimeConversationClient.ToOpenAIConversationFunctionTool(Throw.IfNull(function));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;

namespace OpenAI.Responses;

/// <summary>Provides extension methods for working with content associated with OpenAI.Responses.</summary>
public static class MicrosoftExtensionsAIResponsesExtensions
{
/// <summary>Creates an OpenAI <see cref="ResponseTool"/> from an <see cref="AIFunction"/>.</summary>
/// <param name="function">The function to convert.</param>
/// <returns>An OpenAI <see cref="ResponseTool"/> representing <paramref name="function"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="function"/> is <see langword="null"/>.</exception>
public static ResponseTool AsOpenAIResponseTool(this AIFunction function) =>
OpenAIResponsesChatClient.ToResponseTool(Throw.IfNull(function));

/// <summary>Creates a sequence of OpenAI <see cref="ResponseItem"/> instances from the specified input messages.</summary>
/// <param name="messages">The input messages to convert.</param>
/// <returns>A sequence of OpenAI response items.</returns>
public static IEnumerable<ResponseItem> AsOpenAIResponseItems(this IEnumerable<ChatMessage> messages) =>
OpenAIResponsesChatClient.ToOpenAIResponseItems(Throw.IfNull(messages));

/// <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>
/// <param name="options">The options employed in the creation of the response.</param>
/// <returns>A converted <see cref="ChatResponse"/>.</returns>
public static ChatResponse AsChatResponse(this OpenAIResponse response, ResponseCreationOptions? options = null) =>
OpenAIResponsesChatClient.FromOpenAIResponse(Throw.IfNull(response), options);
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@

namespace Microsoft.Extensions.AI;

/// <summary>Represents an <see cref="IChatClient"/> for an Azure.AI.Agents.Persistent <see cref="AssistantClient"/>.</summary>
internal sealed class OpenAIAssistantChatClient : IChatClient
/// <summary>Represents an <see cref="IChatClient"/> for an OpenAI <see cref="AssistantClient"/>.</summary>
internal sealed class OpenAIAssistantsChatClient : IChatClient
{
/// <summary>The underlying <see cref="AssistantClient" />.</summary>
private readonly AssistantClient _client;
Expand All @@ -44,8 +44,8 @@ internal sealed class OpenAIAssistantChatClient : IChatClient
/// <summary>List of tools associated with the assistant.</summary>
private IReadOnlyList<ToolDefinition>? _assistantTools;

/// <summary>Initializes a new instance of the <see cref="OpenAIAssistantChatClient"/> class for the specified <see cref="AssistantClient"/>.</summary>
public OpenAIAssistantChatClient(AssistantClient assistantClient, string assistantId, string? defaultThreadId)
/// <summary>Initializes a new instance of the <see cref="OpenAIAssistantsChatClient"/> class for the specified <see cref="AssistantClient"/>.</summary>
public OpenAIAssistantsChatClient(AssistantClient assistantClient, string assistantId, string? defaultThreadId)
{
_client = Throw.IfNull(assistantClient);
_assistantId = Throw.IfNullOrWhitespace(assistantId);
Expand All @@ -62,6 +62,13 @@ public OpenAIAssistantChatClient(AssistantClient assistantClient, string assista
_metadata = new("openai", providerUrl);
}

/// <summary>Initializes a new instance of the <see cref="OpenAIAssistantsChatClient"/> class for the specified <see cref="AssistantClient"/>.</summary>
public OpenAIAssistantsChatClient(AssistantClient assistantClient, Assistant assistant, string? defaultThreadId)
: this(assistantClient, Throw.IfNull(assistant).Id, defaultThreadId)
{
_assistantTools = assistant.Tools;
}

/// <inheritdoc />
public object? GetService(Type serviceType, object? serviceKey = null) =>
serviceType is null ? throw new ArgumentNullException(nameof(serviceType)) :
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#pragma warning disable EA0011 // Consider removing unnecessary conditional access operator (?)
#pragma warning disable S1067 // Expressions should not be too complex
#pragma warning disable S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields
#pragma warning disable SA1202 // Elements should be ordered by access
#pragma warning disable SA1204 // Static elements should appear before instance elements

namespace Microsoft.Extensions.AI;
Expand Down Expand Up @@ -76,7 +77,7 @@ public async Task<ChatResponse> GetResponseAsync(
// Make the call to OpenAI.
var response = await _chatClient.CompleteChatAsync(openAIChatMessages, openAIOptions, cancellationToken).ConfigureAwait(false);

return FromOpenAIChatCompletion(response.Value, options, openAIOptions);
return FromOpenAIChatCompletion(response.Value, openAIOptions);
}

/// <inheritdoc />
Expand Down Expand Up @@ -430,7 +431,7 @@ private static string GetOutputAudioMimeType(ChatCompletionOptions? options) =>
"mp3" or _ => "audio/mpeg",
};

private static ChatResponse FromOpenAIChatCompletion(ChatCompletion openAICompletion, ChatOptions? options, ChatCompletionOptions chatCompletionOptions)
internal static ChatResponse FromOpenAIChatCompletion(ChatCompletion openAICompletion, ChatCompletionOptions? chatCompletionOptions)
{
_ = Throw.IfNull(openAICompletion);

Expand Down Expand Up @@ -461,17 +462,14 @@ private static ChatResponse FromOpenAIChatCompletion(ChatCompletion openAIComple
}

// Also manufacture function calling content items from any tool calls in the response.
if (options?.Tools is { Count: > 0 })
foreach (ChatToolCall toolCall in openAICompletion.ToolCalls)
{
foreach (ChatToolCall toolCall in openAICompletion.ToolCalls)
if (!string.IsNullOrWhiteSpace(toolCall.FunctionName))
{
if (!string.IsNullOrWhiteSpace(toolCall.FunctionName))
{
var callContent = ParseCallContentFromBinaryData(toolCall.FunctionArguments, toolCall.Id, toolCall.FunctionName);
callContent.RawRepresentation = toolCall;
var callContent = ParseCallContentFromBinaryData(toolCall.FunctionArguments, toolCall.Id, toolCall.FunctionName);
callContent.RawRepresentation = toolCall;

returnMessage.Contents.Add(callContent);
}
returnMessage.Contents.Add(callContent);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,15 @@
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using Microsoft.Shared.Diagnostics;
using OpenAI;
using OpenAI.Assistants;
using OpenAI.Audio;
using OpenAI.Chat;
using OpenAI.Embeddings;
using OpenAI.Realtime;
using OpenAI.Responses;

#pragma warning disable S103 // Lines should not be too long
#pragma warning disable S1067 // Expressions should not be too complex
#pragma warning disable SA1515 // Single-line comment should be preceded by blank line
#pragma warning disable CA1305 // Specify IFormatProvider
#pragma warning disable S1135 // Track uses of "TODO" tags

namespace Microsoft.Extensions.AI;

Expand Down Expand Up @@ -120,7 +115,7 @@ public static IChatClient AsIChatClient(this ChatClient chatClient) =>
/// <returns>An <see cref="IChatClient"/> that can be used to converse via the <see cref="OpenAIResponseClient"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="responseClient"/> is <see langword="null"/>.</exception>
public static IChatClient AsIChatClient(this OpenAIResponseClient responseClient) =>
new OpenAIResponseChatClient(responseClient);
new OpenAIResponsesChatClient(responseClient);

/// <summary>Gets an <see cref="IChatClient"/> for use with this <see cref="AssistantClient"/>.</summary>
/// <param name="assistantClient">The <see cref="AssistantClient"/> instance to be accessed as an <see cref="IChatClient"/>.</param>
Expand All @@ -135,7 +130,21 @@ public static IChatClient AsIChatClient(this OpenAIResponseClient responseClient
/// <exception cref="ArgumentNullException"><paramref name="assistantId"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException"><paramref name="assistantId"/> is empty or composed entirely of whitespace.</exception>
public static IChatClient AsIChatClient(this AssistantClient assistantClient, string assistantId, string? threadId = null) =>
new OpenAIAssistantChatClient(assistantClient, assistantId, threadId);
new OpenAIAssistantsChatClient(assistantClient, assistantId, threadId);

/// <summary>Gets an <see cref="IChatClient"/> for use with this <see cref="AssistantClient"/>.</summary>
/// <param name="assistantClient">The <see cref="AssistantClient"/> instance to be accessed as an <see cref="IChatClient"/>.</param>
/// <param name="assistant">The <see cref="Assistant"/> with which to interact.</param>
/// <param name="threadId">
/// An optional existing thread identifier for the chat session. This serves as a default, and may be overridden per call to
/// <see cref="IChatClient.GetResponseAsync"/> or <see cref="IChatClient.GetStreamingResponseAsync"/> via the <see cref="ChatOptions.ConversationId"/>
/// property. If no thread ID is provided via either mechanism, a new thread will be created for the request.
/// </param>
/// <returns>An <see cref="IChatClient"/> instance configured to interact with the specified agent and thread.</returns>
/// <exception cref="ArgumentNullException"><paramref name="assistantClient"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="assistant"/> is <see langword="null"/>.</exception>
public static IChatClient AsIChatClient(this AssistantClient assistantClient, Assistant assistant, string? threadId = null) =>
new OpenAIAssistantsChatClient(assistantClient, assistant, threadId);

/// <summary>Gets an <see cref="ISpeechToTextClient"/> for use with this <see cref="AudioClient"/>.</summary>
/// <param name="audioClient">The client.</param>
Expand All @@ -153,48 +162,6 @@ public static ISpeechToTextClient AsISpeechToTextClient(this AudioClient audioCl
public static IEmbeddingGenerator<string, Embedding<float>> AsIEmbeddingGenerator(this EmbeddingClient embeddingClient, int? defaultModelDimensions = null) =>
new OpenAIEmbeddingGenerator(embeddingClient, defaultModelDimensions);

/// <summary>Creates an OpenAI <see cref="ChatTool"/> from an <see cref="AIFunction"/>.</summary>
/// <param name="function">The function to convert.</param>
/// <returns>An OpenAI <see cref="ChatTool"/> representing <paramref name="function"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="function"/> is <see langword="null"/>.</exception>
public static ChatTool AsOpenAIChatTool(this AIFunction function) =>
OpenAIChatClient.ToOpenAIChatTool(Throw.IfNull(function));

/// <summary>Creates an OpenAI <see cref="FunctionToolDefinition"/> from an <see cref="AIFunction"/>.</summary>
/// <param name="function">The function to convert.</param>
/// <returns>An OpenAI <see cref="FunctionToolDefinition"/> representing <paramref name="function"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="function"/> is <see langword="null"/>.</exception>
public static FunctionToolDefinition AsOpenAIAssistantsFunctionToolDefinition(this AIFunction function) =>
OpenAIAssistantChatClient.ToOpenAIAssistantsFunctionToolDefinition(Throw.IfNull(function));

/// <summary>Creates an OpenAI <see cref="ResponseTool"/> from an <see cref="AIFunction"/>.</summary>
/// <param name="function">The function to convert.</param>
/// <returns>An OpenAI <see cref="ResponseTool"/> representing <paramref name="function"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="function"/> is <see langword="null"/>.</exception>
public static ResponseTool AsOpenAIResponseTool(this AIFunction function) =>
OpenAIResponseChatClient.ToResponseTool(Throw.IfNull(function));

/// <summary>Creates an OpenAI <see cref="ConversationFunctionTool"/> from an <see cref="AIFunction"/>.</summary>
/// <param name="function">The function to convert.</param>
/// <returns>An OpenAI <see cref="ConversationFunctionTool"/> representing <paramref name="function"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="function"/> is <see langword="null"/>.</exception>
public static ConversationFunctionTool AsOpenAIConversationFunctionTool(this AIFunction function) =>
OpenAIRealtimeConversationClient.ToOpenAIConversationFunctionTool(Throw.IfNull(function));

/// <summary>Creates a sequence of OpenAI <see cref="OpenAI.Chat.ChatMessage"/> instances from the specified input messages.</summary>
/// <param name="messages">The input messages to convert.</param>
/// <returns>A sequence of OpenAI chat messages.</returns>
public static IEnumerable<OpenAI.Chat.ChatMessage> AsOpenAIChatMessages(this IEnumerable<ChatMessage> messages) =>
OpenAIChatClient.ToOpenAIChatMessages(Throw.IfNull(messages), chatOptions: null);

/// <summary>Creates a sequence of OpenAI <see cref="OpenAI.Responses.ResponseItem"/> instances from the specified input messages.</summary>
/// <param name="messages">The input messages to convert.</param>
/// <returns>A sequence of OpenAI response items.</returns>
public static IEnumerable<OpenAI.Responses.ResponseItem> AsOpenAIResponseItems(this IEnumerable<ChatMessage> messages) =>
OpenAIResponseChatClient.ToOpenAIResponseItems(Throw.IfNull(messages));

// TODO: Once we're ready to rely on C# 14 features, add an extension property ChatOptions.Strict.

/// <summary>Gets whether the properties specify that strict schema handling is desired.</summary>
internal static bool? HasStrict(IReadOnlyDictionary<string, object?>? additionalProperties) =>
additionalProperties?.TryGetValue(StrictKey, out object? strictObj) is true &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Shared.Diagnostics;
using OpenAI;
using OpenAI.Embeddings;

#pragma warning disable S1067 // Expressions should not be too complex
Expand Down
Loading
Loading