mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET: Update M.A.AI.OpenAI implementations (#1018)
* Update M.A.AI.OpenAI implementations
- There was only a ChatClient-based agent. Add a Responses one, too.
- Fix the public extension methods to create OpenAI types even when not backed by the right OpenAI client.
- Add reusable AgentRunResponse{Update} to ChatResponse{Update} conversion routines that are then used by the OpenAI agents and elsewhere
* Address feedback
This commit is contained in:
committed by
GitHub
Unverified
parent
8dad045c9d
commit
2d20e644df
+83
-2
@@ -14,10 +14,91 @@ using Microsoft.Shared.Diagnostics;
|
||||
namespace Microsoft.Agents.AI;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for working with <see cref="AgentRunResponseUpdate"/> instances.
|
||||
/// Provides extension methods for working with <see cref="AgentRunResponse"/> and <see cref="AgentRunResponseUpdate"/> instances.
|
||||
/// </summary>
|
||||
public static class AgentRunResponseUpdateExtensions
|
||||
public static class AgentRunResponseExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a <see cref="ChatResponse"/> from an <see cref="AgentRunResponse"/>.
|
||||
/// </summary>
|
||||
/// <param name="response">The <see cref="AgentRunResponse"/>.</param>
|
||||
/// <returns>A <see cref="ChatResponse"/> built from <paramref name="response"/>.</returns>
|
||||
/// <remarks>
|
||||
/// If the <paramref name="response"/>'s <see cref="AgentRunResponse.RawRepresentation"/> is a
|
||||
/// <see cref="ChatResponse"/> instance, that instance is returned directly. Otherwise, a new
|
||||
/// <see cref="ChatResponse"/> is created and populated with the data from the <paramref name="response"/>.
|
||||
/// The instance is a shallow copy; any reference-type members (e.g. <see cref="AgentRunResponse.Messages"/>)
|
||||
/// will be shared between the two instances.
|
||||
/// </remarks>
|
||||
public static ChatResponse AsChatResponse(this AgentRunResponse response)
|
||||
{
|
||||
Throw.IfNull(response);
|
||||
|
||||
return
|
||||
response.RawRepresentation as ChatResponse ??
|
||||
new()
|
||||
{
|
||||
AdditionalProperties = response.AdditionalProperties,
|
||||
CreatedAt = response.CreatedAt,
|
||||
Messages = response.Messages,
|
||||
RawRepresentation = response,
|
||||
ResponseId = response.ResponseId,
|
||||
Usage = response.Usage,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="ChatResponseUpdate"/> from an <see cref="AgentRunResponseUpdate"/>.
|
||||
/// </summary>
|
||||
/// <param name="responseUpdate">The <see cref="AgentRunResponseUpdate"/>.</param>
|
||||
/// <returns>A <see cref="ChatResponseUpdate"/> built from <paramref name="responseUpdate"/>.</returns>
|
||||
/// <remarks>
|
||||
/// If the <paramref name="responseUpdate"/>'s <see cref="AgentRunResponseUpdate.RawRepresentation"/> is a
|
||||
/// <see cref="ChatResponseUpdate"/> instance, that instance is returned directly. Otherwise, a new
|
||||
/// <see cref="ChatResponseUpdate"/> is created and populated with the data from the <paramref name="responseUpdate"/>.
|
||||
/// The instance is a shallow copy; any reference-type members (e.g. <see cref="AgentRunResponseUpdate.Contents"/>)
|
||||
/// will be shared between the two instances.
|
||||
/// </remarks>
|
||||
public static ChatResponseUpdate AsChatResponseUpdate(this AgentRunResponseUpdate responseUpdate)
|
||||
{
|
||||
Throw.IfNull(responseUpdate);
|
||||
|
||||
return
|
||||
responseUpdate.RawRepresentation as ChatResponseUpdate ??
|
||||
new()
|
||||
{
|
||||
AdditionalProperties = responseUpdate.AdditionalProperties,
|
||||
AuthorName = responseUpdate.AuthorName,
|
||||
Contents = responseUpdate.Contents,
|
||||
CreatedAt = responseUpdate.CreatedAt,
|
||||
MessageId = responseUpdate.MessageId,
|
||||
RawRepresentation = responseUpdate,
|
||||
ResponseId = responseUpdate.ResponseId,
|
||||
Role = responseUpdate.Role,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an asynchronous enumerable of <see cref="ChatResponseUpdate"/> instances from an asynchronous
|
||||
/// enumerable of <see cref="AgentRunResponseUpdate"/> instances.
|
||||
/// </summary>
|
||||
/// <param name="responseUpdates">The sequence <see cref="AgentRunResponseUpdate"/>.</param>
|
||||
/// <returns>A sequence of <see cref="ChatResponseUpdate"/> instances built from <paramref name="responseUpdates"/>.</returns>
|
||||
/// <remarks>
|
||||
/// Each <see cref="AgentRunResponseUpdate"/> is converted to a <see cref="ChatResponseUpdate"/> using
|
||||
/// <see cref="AsChatResponseUpdate"/>.
|
||||
/// </remarks>
|
||||
public static async IAsyncEnumerable<ChatResponseUpdate> AsChatResponseUpdatesAsync(
|
||||
this IAsyncEnumerable<AgentRunResponseUpdate> responseUpdates)
|
||||
{
|
||||
Throw.IfNull(responseUpdates);
|
||||
|
||||
await foreach (var responseUpdate in responseUpdates.ConfigureAwait(false))
|
||||
{
|
||||
yield return responseUpdate.AsChatResponseUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Combines <see cref="AgentRunResponseUpdate"/> instances into a single <see cref="AgentRunResponse"/>.</summary>
|
||||
/// <param name="updates">The updates to be combined.</param>
|
||||
/// <returns>The combined <see cref="AgentRunResponse"/>.</returns>
|
||||
@@ -22,7 +22,7 @@ namespace Microsoft.Agents.AI;
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The relationship between <see cref="AgentRunResponse"/> and <see cref="AgentRunResponseUpdate"/> is
|
||||
/// codified in the <see cref="AgentRunResponseUpdateExtensions.ToAgentRunResponseAsync"/> and
|
||||
/// codified in the <see cref="AgentRunResponseExtensions.ToAgentRunResponseAsync"/> and
|
||||
/// <see cref="AgentRunResponse.ToAgentRunResponseUpdates"/>, which enable bidirectional conversions
|
||||
/// between the two. Note, however, that the provided conversions may be lossy, for example if multiple
|
||||
/// updates all have different <see cref="RawRepresentation"/> objects whereas there's only one slot for
|
||||
@@ -135,7 +135,7 @@ public class AgentRunResponseUpdate
|
||||
/// Some providers may consider streaming responses to be a single message, and in that case
|
||||
/// the value of this property may be the same as the response ID.
|
||||
///
|
||||
/// This value is used when <see cref="AgentRunResponseUpdateExtensions.ToAgentRunResponseAsync(IAsyncEnumerable{AgentRunResponseUpdate}, System.Threading.CancellationToken)"/>
|
||||
/// This value is used when <see cref="AgentRunResponseExtensions.ToAgentRunResponseAsync(IAsyncEnumerable{AgentRunResponseUpdate}, System.Threading.CancellationToken)"/>
|
||||
/// groups <see cref="AgentRunResponseUpdate"/> instances into <see cref="AgentRunResponse"/> instances.
|
||||
/// The value must be unique to each call to the underlying provider, and must be shared by
|
||||
/// all updates that are part of the same logical message within a streaming response.
|
||||
|
||||
+3
-6
@@ -3,7 +3,7 @@
|
||||
using System.ClientModel;
|
||||
using OpenAI.Chat;
|
||||
|
||||
namespace Microsoft.Agents.AI.OpenAI.ChatClient;
|
||||
namespace Microsoft.Agents.AI.OpenAI;
|
||||
|
||||
internal sealed class AsyncStreamingUpdateCollectionResult : AsyncCollectionResult<StreamingChatCompletionUpdate>
|
||||
{
|
||||
@@ -19,13 +19,10 @@ internal sealed class AsyncStreamingUpdateCollectionResult : AsyncCollectionResu
|
||||
public override IAsyncEnumerable<ClientResult> GetRawPagesAsync() =>
|
||||
AsyncEnumerable.Repeat(ClientResult.FromValue(this._updates, new StreamingUpdatePipelineResponse(this._updates)), 1);
|
||||
|
||||
protected override async IAsyncEnumerable<StreamingChatCompletionUpdate> GetValuesFromPageAsync(ClientResult page)
|
||||
protected override IAsyncEnumerable<StreamingChatCompletionUpdate> GetValuesFromPageAsync(ClientResult page)
|
||||
{
|
||||
var updates = ((ClientResult<IAsyncEnumerable<AgentRunResponseUpdate>>)page).Value;
|
||||
|
||||
await foreach (var update in updates.ConfigureAwait(false))
|
||||
{
|
||||
yield return update.AsStreamingChatCompletionUpdate();
|
||||
}
|
||||
return updates.AsChatResponseUpdatesAsync().AsOpenAIStreamingChatCompletionUpdatesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
using System.ClientModel.Primitives;
|
||||
|
||||
namespace Microsoft.Agents.AI.OpenAI.ChatClient;
|
||||
namespace Microsoft.Agents.AI.OpenAI;
|
||||
|
||||
internal sealed class StreamingUpdatePipelineResponse : PipelineResponse
|
||||
{
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
using System.ClientModel;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.OpenAI.ChatClient;
|
||||
using Microsoft.Agents.AI.OpenAI;
|
||||
using Microsoft.Shared.Diagnostics;
|
||||
using OpenAI.Chat;
|
||||
|
||||
@@ -34,7 +34,7 @@ public static class AIAgentWithOpenAIExtensions
|
||||
/// <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 chat messages 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="ChatCompletion"/> from the response using <see cref="AgentRunResponseExtensions.AsChatCompletion"/>.
|
||||
/// runs the agent with the converted message collection, and then extracts the native OpenAI <see cref="ChatCompletion"/> from the response using <see cref="AgentRunResponseExtensions.AsOpenAIChatCompletion"/>.
|
||||
/// </remarks>
|
||||
public static async Task<ChatCompletion> RunAsync(this AIAgent agent, IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -43,7 +43,7 @@ public static class AIAgentWithOpenAIExtensions
|
||||
|
||||
var response = await agent.RunAsync([.. messages.AsChatMessages()], thread, options, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return response.AsChatCompletion();
|
||||
return response.AsOpenAIChatCompletion();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -60,7 +60,7 @@ public static class AIAgentWithOpenAIExtensions
|
||||
/// <exception cref="NotSupportedException">Thrown when the <paramref name="messages"/> type is not supported by the message conversion method.</exception>
|
||||
/// <remarks>
|
||||
/// This method converts the OpenAI chat messages to the Microsoft Extensions AI format using the appropriate conversion method,
|
||||
/// runs the agent, and then extracts the native OpenAI <see cref="ChatCompletion"/> from the response using <see cref="AgentRunResponseExtensions.AsChatCompletion"/>.
|
||||
/// runs the agent, and then extracts the native OpenAI <see cref="ChatCompletion"/> from the response using <see cref="AgentRunResponseExtensions.AsOpenAIChatCompletion"/>.
|
||||
/// </remarks>
|
||||
public static AsyncCollectionResult<StreamingChatCompletionUpdate> RunStreamingAsync(this AIAgent agent, IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
||||
@@ -1,52 +1,45 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Shared.Diagnostics;
|
||||
using OpenAI.Chat;
|
||||
using OpenAI.Responses;
|
||||
|
||||
namespace OpenAI;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for <see cref="AgentRunResponse"/> to extract native OpenAI response objects
|
||||
/// from the Microsoft Agent Framework responses.
|
||||
/// Provides extension methods for <see cref="AgentRunResponse"/> and <see cref="AgentRunResponseUpdate"/> instances to
|
||||
/// create or extract native OpenAI response objects from the Microsoft Agent Framework responses.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// These extensions enable developers to access the underlying OpenAI SDK objects when working with
|
||||
/// AI agents that are backed by OpenAI services. The methods extract strongly-typed OpenAI responses
|
||||
/// from the <see cref="AgentRunResponse.RawRepresentation"/> property, providing a bridge between
|
||||
/// the Microsoft Extensions AI framework and the native OpenAI SDK types.
|
||||
/// </remarks>
|
||||
public static class AgentRunResponseExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extracts a native OpenAI <see cref="ChatCompletion"/> object from an <see cref="AgentRunResponse"/>.
|
||||
/// Creates or extracts a native OpenAI <see cref="ChatCompletion"/> object from an <see cref="AgentRunResponse"/>.
|
||||
/// </summary>
|
||||
/// <param name="agentResponse">The agent response containing the raw OpenAI representation.</param>
|
||||
/// <returns>The native OpenAI <see cref="ChatCompletion"/> object.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="agentResponse"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the <see cref="AgentRunResponse.RawRepresentation"/> is not a <see cref="ChatCompletion"/> object.
|
||||
/// This typically occurs when the agent response was not generated by an OpenAI chat completion service
|
||||
/// or when the underlying representation has been modified or corrupted.
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This method provides access to the native OpenAI <see cref="ChatCompletion"/> object that was used
|
||||
/// to generate the agent response. This is useful when you need to access OpenAI-specific properties
|
||||
/// or metadata that are not exposed through the Microsoft Extensions AI abstractions.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static ChatCompletion AsChatCompletion(this AgentRunResponse agentResponse)
|
||||
/// <param name="response">The agent response.</param>
|
||||
/// <returns>The OpenAI <see cref="ChatCompletion"/> object.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="response"/> is <see langword="null"/>.</exception>
|
||||
public static ChatCompletion AsOpenAIChatCompletion(this AgentRunResponse response)
|
||||
{
|
||||
Throw.IfNull(agentResponse);
|
||||
Throw.IfNull(response);
|
||||
|
||||
if (agentResponse.RawRepresentation is ChatResponse chatResponse)
|
||||
{
|
||||
return chatResponse.RawRepresentation is ChatCompletion chatCompletion
|
||||
? chatCompletion
|
||||
: throw new ArgumentException("ChatResponse.RawRepresentation must be a ChatCompletion");
|
||||
}
|
||||
throw new ArgumentException("AgentRunResponse.RawRepresentation must be a ChatResponse");
|
||||
return
|
||||
response.RawRepresentation as ChatCompletion ??
|
||||
response.AsChatResponse().AsOpenAIChatCompletion();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates or extracts a native OpenAI <see cref="OpenAIResponse"/> object from an <see cref="AgentRunResponse"/>.
|
||||
/// </summary>
|
||||
/// <param name="response">The agent response.</param>
|
||||
/// <returns>The OpenAI <see cref="OpenAIResponse"/> object.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="response"/> is <see langword="null"/>.</exception>
|
||||
public static OpenAIResponse AsOpenAIResponse(this AgentRunResponse response)
|
||||
{
|
||||
Throw.IfNull(response);
|
||||
|
||||
return
|
||||
response.RawRepresentation as OpenAIResponse ??
|
||||
response.AsChatResponse().AsOpenAIResponse();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Shared.Diagnostics;
|
||||
using OpenAI.Chat;
|
||||
|
||||
namespace Microsoft.Agents.AI;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for <see cref="AgentRunResponseUpdate"/> to extract native OpenAI response objects
|
||||
/// from the Microsoft Agent Framework responses.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// These extensions enable developers to access the underlying OpenAI SDK objects when working with
|
||||
/// AI agents that are backed by OpenAI services. The methods extract strongly-typed OpenAI responses
|
||||
/// from the <see cref="AgentRunResponseUpdate.RawRepresentation"/> property, providing a bridge between
|
||||
/// the Microsoft Extensions AI framework and the native OpenAI SDK types.
|
||||
/// </remarks>
|
||||
public static class AgentRunResponseUpdateExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extracts a native OpenAI <see cref="StreamingChatCompletionUpdate"/> object from an <see cref="AgentRunResponseUpdate"/>.
|
||||
/// </summary>
|
||||
/// <param name="agentResponseUpdate">The agent response containing the raw OpenAI representation.</param>
|
||||
/// <returns>The native OpenAI <see cref="StreamingChatCompletionUpdate"/> object.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="agentResponseUpdate"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Thrown when the <see cref="AgentRunResponseUpdate.RawRepresentation"/> is not a <see cref="ChatResponseUpdate"/> object,
|
||||
/// or when the nested <see cref="ChatResponseUpdate.RawRepresentation"/> is not a <see cref="StreamingChatCompletionUpdate"/> object.
|
||||
/// This typically occurs when the agent response was not generated by an OpenAI streaming chat completion service
|
||||
/// or when the underlying representation has been modified or corrupted.
|
||||
/// </exception>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This method provides access to the native OpenAI <see cref="StreamingChatCompletionUpdate"/> object that was used
|
||||
/// to generate the agent response. This is useful when you need to access OpenAI-specific properties
|
||||
/// or metadata that are not exposed through the Microsoft Extensions AI abstractions.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static StreamingChatCompletionUpdate AsStreamingChatCompletionUpdate(this AgentRunResponseUpdate agentResponseUpdate)
|
||||
{
|
||||
Throw.IfNull(agentResponseUpdate);
|
||||
|
||||
if (agentResponseUpdate.RawRepresentation is ChatResponseUpdate chatResponseUpdate)
|
||||
{
|
||||
return chatResponseUpdate.RawRepresentation is StreamingChatCompletionUpdate streamingChatCompletionUpdate
|
||||
? streamingChatCompletionUpdate
|
||||
: throw new ArgumentException("ChatResponseUpdate.RawRepresentation must be a StreamingChatCompletionUpdate");
|
||||
}
|
||||
throw new ArgumentException("AgentRunResponseUpdate.RawRepresentation must be a ChatResponseUpdate");
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text.Json;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -11,12 +10,10 @@ using ChatMessage = OpenAI.Chat.ChatMessage;
|
||||
namespace OpenAI;
|
||||
|
||||
/// <summary>
|
||||
/// OpenAI chat completion based implementation of <see cref="AIAgent"/>.
|
||||
/// Provides an <see cref="AIAgent"/> backed by an OpenAI chat completion implementation.
|
||||
/// </summary>
|
||||
public class OpenAIChatClientAgent : AIAgent
|
||||
public class OpenAIChatClientAgent : DelegatingAIAgent
|
||||
{
|
||||
private readonly ChatClientAgent _chatClientAgent;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize an instance of <see cref="OpenAIChatClientAgent"/>
|
||||
/// </summary>
|
||||
@@ -30,20 +27,14 @@ public class OpenAIChatClientAgent : AIAgent
|
||||
string? instructions = null,
|
||||
string? name = null,
|
||||
string? description = null,
|
||||
ILoggerFactory? loggerFactory = null)
|
||||
ILoggerFactory? loggerFactory = null) :
|
||||
this(client, new()
|
||||
{
|
||||
Name = name,
|
||||
Description = description,
|
||||
Instructions = instructions,
|
||||
}, loggerFactory)
|
||||
{
|
||||
Throw.IfNull(client);
|
||||
|
||||
var chatClient = client.AsIChatClient();
|
||||
this._chatClientAgent = new(
|
||||
chatClient,
|
||||
new ChatClientAgentOptions()
|
||||
{
|
||||
Name = name,
|
||||
Description = description,
|
||||
Instructions = instructions,
|
||||
},
|
||||
loggerFactory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -52,12 +43,10 @@ public class OpenAIChatClientAgent : AIAgent
|
||||
/// <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)
|
||||
public OpenAIChatClientAgent(
|
||||
ChatClient client, ChatClientAgentOptions options, ILoggerFactory? loggerFactory = null) :
|
||||
base(new ChatClientAgent(Throw.IfNull(client).AsIChatClient(), options, loggerFactory))
|
||||
{
|
||||
Throw.IfNull(client);
|
||||
|
||||
var chatClient = client.AsIChatClient();
|
||||
this._chatClientAgent = new(chatClient, options, loggerFactory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -74,37 +63,35 @@ public class OpenAIChatClientAgent : AIAgent
|
||||
AgentRunOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var response = await this.RunAsync([.. messages.AsChatMessages()], thread, options, cancellationToken).ConfigureAwait(false);
|
||||
var response = await this.RunAsync(messages.AsChatMessages(), thread, options, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return response.AsChatCompletion();
|
||||
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 AgentThread GetNewThread()
|
||||
=> this._chatClientAgent.GetNewThread();
|
||||
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 sealed override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null)
|
||||
=> this._chatClientAgent.DeserializeThread(serializedThread, jsonSerializerOptions);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override Task<AgentRunResponse> RunAsync(
|
||||
IEnumerable<Microsoft.Extensions.AI.ChatMessage> messages,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
=> this._chatClientAgent.RunAsync(messages, thread, options, cancellationToken);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed override IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(
|
||||
IEnumerable<Microsoft.Extensions.AI.ChatMessage> messages,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
=> this._chatClientAgent.RunStreamingAsync(messages, thread, options, cancellationToken);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override object? GetService(Type serviceType, object? serviceKey = null)
|
||||
=> base.GetService(serviceType, serviceKey)
|
||||
?? this._chatClientAgent.GetService(serviceType, serviceKey);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
// 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,
|
||||
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);
|
||||
}
|
||||
@@ -72,19 +72,7 @@ internal sealed class MessageMerger
|
||||
throw new InvalidOperationException($"No updates found for message ID '{messageId}' in response '{this.ResponseId}'.");
|
||||
}
|
||||
|
||||
return updates.Select(oldUpdate =>
|
||||
oldUpdate.RawRepresentation as ChatResponseUpdate ??
|
||||
new()
|
||||
{
|
||||
AdditionalProperties = oldUpdate.AdditionalProperties,
|
||||
AuthorName = oldUpdate.AuthorName,
|
||||
Contents = oldUpdate.Contents,
|
||||
CreatedAt = oldUpdate.CreatedAt,
|
||||
MessageId = oldUpdate.MessageId,
|
||||
RawRepresentation = oldUpdate.RawRepresentation,
|
||||
Role = oldUpdate.Role,
|
||||
ResponseId = oldUpdate.ResponseId,
|
||||
}).ToChatResponse().Messages;
|
||||
return updates.Select(oldUpdate => oldUpdate.AsChatResponseUpdate()).ToChatResponse().Messages;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,15 +182,7 @@ public sealed class OpenTelemetryAgent : DelegatingAIAgent, IDisposable
|
||||
var response = await parentAgent.InnerAgent.RunAsync(messages, fo?.Thread, fo?.Options, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Wrap the response in a ChatResponse so we can pass it back through OpenTelemetryChatClient.
|
||||
return new ChatResponse
|
||||
{
|
||||
AdditionalProperties = response.AdditionalProperties,
|
||||
CreatedAt = response.CreatedAt,
|
||||
Messages = response.Messages,
|
||||
RawRepresentation = response,
|
||||
ResponseId = response.ResponseId,
|
||||
Usage = response.Usage,
|
||||
};
|
||||
return response.AsChatResponse();
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
|
||||
@@ -205,17 +197,7 @@ public sealed class OpenTelemetryAgent : DelegatingAIAgent, IDisposable
|
||||
await foreach (var update in parentAgent.InnerAgent.RunStreamingAsync(messages, fo?.Thread, fo?.Options, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
// Wrap the response updates in ChatResponseUpdates so we can pass them back through OpenTelemetryChatClient.
|
||||
yield return new ChatResponseUpdate
|
||||
{
|
||||
AdditionalProperties = update.AdditionalProperties,
|
||||
AuthorName = update.AuthorName,
|
||||
Contents = update.Contents,
|
||||
CreatedAt = update.CreatedAt,
|
||||
MessageId = update.MessageId,
|
||||
RawRepresentation = update,
|
||||
ResponseId = update.ResponseId,
|
||||
Role = update.Role,
|
||||
};
|
||||
yield return update.AsChatResponseUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user