.NET: [BREAKING] Change namespaces of the Microsoft.Agents.AI.OpenAI classes (#2627)

* change namespaces for agents and extension methods of the Microsoft.Agents.AI.OpenAI package

* remove unnecessary namespace

* remove unused namespaces

* fix compilation issues and rrolled back removed run methods

* sort usings

* add extension methods for AIAgent to work with OpenAI Responses primitives

* Move OpenAIChatClientAgent and OpenAIResponseClientAgent to samples

* sort usings

* sort usings
This commit is contained in:
SergeyMenshykh
2025-12-09 12:58:44 +00:00
committed by GitHub
parent 71d9d268dd
commit 69225b4aa2
58 changed files with 362 additions and 58 deletions
@@ -5,11 +5,11 @@ using OpenAI.Chat;
namespace Microsoft.Agents.AI.OpenAI;
internal sealed class AsyncStreamingUpdateCollectionResult : AsyncCollectionResult<StreamingChatCompletionUpdate>
internal sealed class AsyncStreamingChatCompletionUpdateCollectionResult : AsyncCollectionResult<StreamingChatCompletionUpdate>
{
private readonly IAsyncEnumerable<AgentRunResponseUpdate> _updates;
internal AsyncStreamingUpdateCollectionResult(IAsyncEnumerable<AgentRunResponseUpdate> updates)
internal AsyncStreamingChatCompletionUpdateCollectionResult(IAsyncEnumerable<AgentRunResponseUpdate> updates)
{
this._updates = updates;
}
@@ -0,0 +1,48 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ClientModel;
using OpenAI.Responses;
namespace Microsoft.Agents.AI.OpenAI;
internal sealed class AsyncStreamingResponseUpdateCollectionResult : AsyncCollectionResult<StreamingResponseUpdate>
{
private readonly IAsyncEnumerable<AgentRunResponseUpdate> _updates;
internal AsyncStreamingResponseUpdateCollectionResult(IAsyncEnumerable<AgentRunResponseUpdate> updates)
{
this._updates = updates;
}
public override ContinuationToken? GetContinuationToken(ClientResult page) => null;
public override async IAsyncEnumerable<ClientResult> GetRawPagesAsync()
{
yield return ClientResult.FromValue(this._updates, new StreamingUpdatePipelineResponse(this._updates));
}
protected async override IAsyncEnumerable<StreamingResponseUpdate> GetValuesFromPageAsync(ClientResult page)
{
var updates = ((ClientResult<IAsyncEnumerable<AgentRunResponseUpdate>>)page).Value;
await foreach (var update in updates.ConfigureAwait(false))
{
switch (update.RawRepresentation)
{
case StreamingResponseUpdate rawUpdate:
yield return rawUpdate;
break;
case Extensions.AI.ChatResponseUpdate { RawRepresentation: StreamingResponseUpdate rawUpdate }:
yield return rawUpdate;
break;
default:
// TODO: The OpenAI library does not currently expose model factory methods for creating
// StreamingResponseUpdates. We are thus unable to manufacture such instances when there isn't
// already one in the update and instead skip them.
break;
}
}
}
}
@@ -1,12 +1,12 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ClientModel;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.OpenAI;
using Microsoft.Shared.Diagnostics;
using OpenAI.Chat;
using OpenAI.Responses;
namespace OpenAI;
namespace Microsoft.Agents.AI;
/// <summary>
/// Provides extension methods for <see cref="AIAgent"/> to simplify interaction with OpenAI chat messages
@@ -69,6 +69,60 @@ public static class AIAgentWithOpenAIExtensions
IAsyncEnumerable<AgentRunResponseUpdate> response = agent.RunStreamingAsync([.. messages.AsChatMessages()], thread, options, cancellationToken);
return new AsyncStreamingUpdateCollectionResult(response);
return new AsyncStreamingChatCompletionUpdateCollectionResult(response);
}
/// <summary>
/// Runs the AI agent with a collection of OpenAI response items and returns the response as a native OpenAI <see cref="OpenAIResponse"/>.
/// </summary>
/// <param name="agent">The AI agent to run.</param>
/// <param name="messages">The collection of OpenAI response items to send to the agent.</param>
/// <param name="thread">The conversation thread to continue with this invocation. If not provided, creates a new thread. The thread will be mutated with the provided messages and agent response.</param>
/// <param name="options">Optional parameters for agent invocation.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>A <see cref="Task{OpenAIResponse}"/> representing the asynchronous operation that returns a native OpenAI <see cref="OpenAIResponse"/> response.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="agent"/> or <paramref name="messages"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException">Thrown when the agent's response cannot be converted to an <see cref="OpenAIResponse"/>, typically when the underlying representation is not an OpenAI response.</exception>
/// <exception cref="NotSupportedException">Thrown when any message in <paramref name="messages"/> has a type that is not supported by the message conversion method.</exception>
/// <remarks>
/// This method converts the OpenAI response items to the Microsoft Extensions AI format using the appropriate conversion method,
/// runs the agent with the converted message collection, and then extracts the native OpenAI <see cref="OpenAIResponse"/> from the response using <see cref="AgentRunResponseExtensions.AsOpenAIResponse"/>.
/// </remarks>
public static async Task<OpenAIResponse> RunAsync(this AIAgent agent, IEnumerable<ResponseItem> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
{
Throw.IfNull(agent);
Throw.IfNull(messages);
var response = await agent.RunAsync(messages.AsChatMessages(), thread, options, cancellationToken).ConfigureAwait(false);
return response.AsOpenAIResponse();
}
/// <summary>
/// Runs the AI agent in streaming mode with a collection of OpenAI response items and returns the response as a collection of native OpenAI <see cref="StreamingResponseUpdate"/>.
/// </summary>
/// <param name="agent">The AI agent to run.</param>
/// <param name="messages">The collection of OpenAI response items to send to the agent.</param>
/// <param name="thread">The conversation thread to continue with this invocation. If not provided, creates a new thread. The thread will be mutated with the provided messages and agent response updates.</param>
/// <param name="options">Optional parameters for agent invocation.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>An <see cref="AsyncCollectionResult{StreamingResponseUpdate}"/> representing the asynchronous enumerable that yields native OpenAI <see cref="StreamingResponseUpdate"/> instances as they are streamed.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="agent"/> or <paramref name="messages"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException">Thrown when the agent's response cannot be converted to <see cref="StreamingResponseUpdate"/> instances, typically when the underlying representation is not an OpenAI response.</exception>
/// <exception cref="NotSupportedException">Thrown when any message in <paramref name="messages"/> has a type that is not supported by the message conversion method.</exception>
/// <remarks>
/// This method converts the OpenAI response items to the Microsoft Extensions AI format using the appropriate conversion method,
/// runs the agent in streaming mode, and then yields native OpenAI <see cref="StreamingResponseUpdate"/> instances as they are produced.
/// The method attempts to extract <see cref="StreamingResponseUpdate"/> from the underlying response representation. If a raw update is not available,
/// it is skipped because the OpenAI library does not currently expose model factory methods for creating such instances.
/// </remarks>
public static AsyncCollectionResult<StreamingResponseUpdate> RunStreamingAsync(this AIAgent agent, IEnumerable<ResponseItem> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
{
Throw.IfNull(agent);
Throw.IfNull(messages);
IAsyncEnumerable<AgentRunResponseUpdate> response = agent.RunStreamingAsync([.. messages.AsChatMessages()], thread, options, cancellationToken);
return new AsyncStreamingResponseUpdateCollectionResult(response);
}
}
@@ -5,7 +5,7 @@ using Microsoft.Shared.Diagnostics;
using OpenAI.Chat;
using OpenAI.Responses;
namespace OpenAI;
namespace Microsoft.Agents.AI;
/// <summary>
/// Provides extension methods for <see cref="AgentRunResponse"/> and <see cref="AgentRunResponseUpdate"/> instances to
@@ -5,9 +5,8 @@ using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;
using Microsoft.Shared.Diagnostics;
using OpenAI.Assistants;
namespace OpenAI;
namespace OpenAI.Assistants;
/// <summary>
/// Provides extension methods for OpenAI <see cref="AssistantClient"/>
@@ -4,9 +4,8 @@ using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;
using Microsoft.Shared.Diagnostics;
using OpenAI.Chat;
namespace OpenAI;
namespace OpenAI.Chat;
/// <summary>
/// Provides extension methods for <see cref="ChatClient"/>
@@ -4,9 +4,8 @@ using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;
using Microsoft.Shared.Diagnostics;
using OpenAI.Responses;
namespace OpenAI;
namespace OpenAI.Responses;
/// <summary>
/// Provides extension methods for <see cref="OpenAIResponseClient"/>
@@ -1,97 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;
using Microsoft.Shared.Diagnostics;
using OpenAI.Chat;
using ChatMessage = OpenAI.Chat.ChatMessage;
namespace OpenAI;
/// <summary>
/// Provides an <see cref="AIAgent"/> backed by an OpenAI chat completion implementation.
/// </summary>
public class OpenAIChatClientAgent : DelegatingAIAgent
{
/// <summary>
/// Initialize an instance of <see cref="OpenAIChatClientAgent"/>
/// </summary>
/// <param name="client">Instance of <see cref="ChatClient"/></param>
/// <param name="instructions">Optional instructions for the agent.</param>
/// <param name="name">Optional name for the agent.</param>
/// <param name="description">Optional description for the agent.</param>
/// <param name="loggerFactory">Optional instance of <see cref="ILoggerFactory"/></param>
public OpenAIChatClientAgent(
ChatClient client,
string? instructions = null,
string? name = null,
string? description = null,
ILoggerFactory? loggerFactory = null) :
this(client, new()
{
Name = name,
Description = description,
ChatOptions = new ChatOptions() { Instructions = instructions },
}, loggerFactory)
{
}
/// <summary>
/// Initialize an instance of <see cref="OpenAIChatClientAgent"/>
/// </summary>
/// <param name="client">Instance of <see cref="ChatClient"/></param>
/// <param name="options">Options to create the agent.</param>
/// <param name="loggerFactory">Optional instance of <see cref="ILoggerFactory"/></param>
public OpenAIChatClientAgent(
ChatClient client, ChatClientAgentOptions options, ILoggerFactory? loggerFactory = null) :
base(new ChatClientAgent(Throw.IfNull(client).AsIChatClient(), options, loggerFactory))
{
}
/// <summary>
/// Run the agent with the provided message and arguments.
/// </summary>
/// <param name="messages">The messages to pass to the agent.</param>
/// <param name="thread">The conversation thread to continue with this invocation. If not provided, creates a new thread. The thread will be mutated with the provided messages and agent response.</param>
/// <param name="options">Optional parameters for agent invocation.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>A <see cref="ChatCompletion"/> containing the list of <see cref="ChatMessage"/> items.</returns>
public virtual async Task<ChatCompletion> RunAsync(
IEnumerable<ChatMessage> messages,
AgentThread? thread = null,
AgentRunOptions? options = null,
CancellationToken cancellationToken = default)
{
var response = await this.RunAsync(messages.AsChatMessages(), thread, options, cancellationToken).ConfigureAwait(false);
return response.AsOpenAIChatCompletion();
}
/// <summary>
/// Run the agent streaming with the provided message and arguments.
/// </summary>
/// <param name="messages">The messages to pass to the agent.</param>
/// <param name="thread">The conversation thread to continue with this invocation. If not provided, creates a new thread. The thread will be mutated with the provided messages and agent response.</param>
/// <param name="options">Optional parameters for agent invocation.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>A <see cref="ChatCompletion"/> containing the list of <see cref="ChatMessage"/> items.</returns>
public virtual IAsyncEnumerable<StreamingChatCompletionUpdate> RunStreamingAsync(
IEnumerable<ChatMessage> messages,
AgentThread? thread = null,
AgentRunOptions? options = null,
CancellationToken cancellationToken = default)
{
var response = this.RunStreamingAsync(messages.AsChatMessages(), thread, options, cancellationToken);
return response.AsChatResponseUpdatesAsync().AsOpenAIStreamingChatCompletionUpdatesAsync(cancellationToken);
}
/// <inheritdoc/>
public sealed override Task<AgentRunResponse> RunAsync(IEnumerable<Microsoft.Extensions.AI.ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) =>
base.RunAsync(messages, thread, options, cancellationToken);
/// <inheritdoc/>
public override IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(IEnumerable<Microsoft.Extensions.AI.ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) =>
base.RunStreamingAsync(messages, thread, options, cancellationToken);
}
@@ -1,115 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Runtime.CompilerServices;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;
using Microsoft.Shared.Diagnostics;
using OpenAI.Responses;
namespace OpenAI;
/// <summary>
/// Provides an <see cref="AIAgent"/> backed by an OpenAI Responses implementation.
/// </summary>
public class OpenAIResponseClientAgent : DelegatingAIAgent
{
/// <summary>
/// Initialize an instance of <see cref="OpenAIResponseClientAgent"/>.
/// </summary>
/// <param name="client">Instance of <see cref="OpenAIResponseClient"/></param>
/// <param name="instructions">Optional instructions for the agent.</param>
/// <param name="name">Optional name for the agent.</param>
/// <param name="description">Optional description for the agent.</param>
/// <param name="loggerFactory">Optional instance of <see cref="ILoggerFactory"/></param>
public OpenAIResponseClientAgent(
OpenAIResponseClient client,
string? instructions = null,
string? name = null,
string? description = null,
ILoggerFactory? loggerFactory = null) :
this(client, new()
{
Name = name,
Description = description,
ChatOptions = new ChatOptions() { Instructions = instructions },
}, loggerFactory)
{
}
/// <summary>
/// Initialize an instance of <see cref="OpenAIResponseClientAgent"/>.
/// </summary>
/// <param name="client">Instance of <see cref="OpenAIResponseClient"/></param>
/// <param name="options">Options to create the agent.</param>
/// <param name="loggerFactory">Optional instance of <see cref="ILoggerFactory"/></param>
public OpenAIResponseClientAgent(
OpenAIResponseClient client, ChatClientAgentOptions options, ILoggerFactory? loggerFactory = null) :
base(new ChatClientAgent(Throw.IfNull(client).AsIChatClient(), options, loggerFactory))
{
}
/// <summary>
/// Run the agent with the provided message and arguments.
/// </summary>
/// <param name="messages">The messages to pass to the agent.</param>
/// <param name="thread">The conversation thread to continue with this invocation. If not provided, creates a new thread. The thread will be mutated with the provided messages and agent response.</param>
/// <param name="options">Optional parameters for agent invocation.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>A <see cref="OpenAIResponse"/> containing the list of <see cref="ChatMessage"/> items.</returns>
public virtual async Task<OpenAIResponse> RunAsync(
IEnumerable<ResponseItem> messages,
AgentThread? thread = null,
AgentRunOptions? options = null,
CancellationToken cancellationToken = default)
{
var response = await this.RunAsync(messages.AsChatMessages(), thread, options, cancellationToken).ConfigureAwait(false);
return response.AsOpenAIResponse();
}
/// <summary>
/// Run the agent streaming with the provided message and arguments.
/// </summary>
/// <param name="messages">The messages to pass to the agent.</param>
/// <param name="thread">The conversation thread to continue with this invocation. If not provided, creates a new thread. The thread will be mutated with the provided messages and agent response.</param>
/// <param name="options">Optional parameters for agent invocation.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>A <see cref="OpenAIResponse"/> containing the list of <see cref="ChatMessage"/> items.</returns>
public virtual async IAsyncEnumerable<StreamingResponseUpdate> RunStreamingAsync(
IEnumerable<ResponseItem> messages,
AgentThread? thread = null,
AgentRunOptions? options = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var response = this.RunStreamingAsync(messages.AsChatMessages(), thread, options, cancellationToken);
await foreach (var update in response.ConfigureAwait(false))
{
switch (update.RawRepresentation)
{
case StreamingResponseUpdate rawUpdate:
yield return rawUpdate;
break;
case ChatResponseUpdate { RawRepresentation: StreamingResponseUpdate rawUpdate }:
yield return rawUpdate;
break;
default:
// TODO: The OpenAI library does not currently expose model factory methods for creating
// StreamingResponseUpdates. We are thus unable to manufacture such instances when there isn't
// already one in the update and instead skip them.
break;
}
}
}
/// <inheritdoc/>
public sealed override Task<AgentRunResponse> RunAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) =>
base.RunAsync(messages, thread, options, cancellationToken);
/// <inheritdoc/>
public sealed override IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) =>
base.RunStreamingAsync(messages, thread, options, cancellationToken);
}