Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 2.3k
.Net: ChatClientAgent (Non-Streaming initial impl)#65
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
98851374e346e5d7b8089b54bb384ce99692cb642f59dd085ad82c7304d48a938ad261d409e8ba66fe79b59012bdcfceaeFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Uh oh!
There was an error while loading. Please reload this page.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
| using System; | ||
| using Microsoft.Extensions.AI; | ||
| using Microsoft.Shared.Diagnostics; | ||
| namespace Microsoft.Agents; | ||
| /// <summary>Provides extensions for configuring <see cref="AgentInvokingChatClient"/> instances.</summary> | ||
| public static class AgentChatClientBuilderExtensions | ||
| { | ||
| /// <summary> | ||
| /// Enables automatic function call invocation on the chat pipeline. | ||
| /// </summary> | ||
| /// <remarks>This works by adding an instance of <see cref="AgentInvokingChatClient"/> with default options.</remarks> | ||
| /// <param name="builder">The <see cref="ChatClientBuilder"/> being used to build the chat pipeline.</param> | ||
| /// <returns>The supplied <paramref name="builder"/>.</returns> | ||
| /// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception> | ||
| public static ChatClientBuilder UseAgentInvocation( | ||
| this ChatClientBuilder builder) | ||
| { | ||
| _ = Throw.IfNull(builder); | ||
| return builder.Use((innerClient, services) => | ||
| { | ||
| return new AgentInvokingChatClient(innerClient); | ||
| }); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
| using Microsoft.Extensions.AI; | ||
| namespace Microsoft.Agents; | ||
| /// <summary> | ||
| /// Internal chat client that handle agent invocation details for the chat client pipeline. | ||
| /// </summary> | ||
| internal sealed class AgentInvokingChatClient : DelegatingChatClient | ||
rogerbarreto marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| { | ||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="AgentInvokingChatClient"/> class. | ||
| /// </summary> | ||
| /// <param name="chatClient">The chat client to invoke agents.</param> | ||
| internal AgentInvokingChatClient(IChatClient chatClient) | ||
| : base(chatClient) | ||
| { | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,151 @@ | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Microsoft.Extensions.AI; | ||
| using Microsoft.Extensions.Logging; | ||
| using Microsoft.Extensions.Logging.Abstractions; | ||
| using Microsoft.Shared.Diagnostics; | ||
| namespace Microsoft.Agents; | ||
| /// <summary> | ||
| /// Represents an agent that can be invoked using a chat client. | ||
| /// </summary> | ||
| public sealed class ChatClientAgent : Agent | ||
| { | ||
| private readonly ChatClientAgentOptions? _agentOptions; | ||
| private readonly ILogger _logger; | ||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="ChatClientAgent"/> class. | ||
| /// </summary> | ||
| /// <param name="chatClient">The chat client to use for invoking the agent.</param> | ||
| /// <param name="options">Optional agent options to configure the agent.</param> | ||
| /// <param name="loggerFactory">Optional logger factory to use for logging.</param> | ||
| public ChatClientAgent(IChatClient chatClient, ChatClientAgentOptions? options = null, ILoggerFactory? loggerFactory = null) | ||
| { | ||
| Throw.IfNull(chatClient); | ||
| this.ChatClient = chatClient.AsAgentInvokingChatClient(); | ||
| this._agentOptions = options; | ||
| this._logger = (loggerFactory ?? chatClient.GetService<ILoggerFactory>() ?? NullLoggerFactory.Instance).CreateLogger<ChatClientAgent>(); | ||
| } | ||
| /// <summary> | ||
| /// The chat client. | ||
| /// </summary> | ||
| public IChatClient ChatClient { get; } | ||
| /// <summary> | ||
| /// Gets the role used for agent instructions. Defaults to "system". | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// Certain versions of "O*" series (deep reasoning) models require the instructions | ||
| /// to be provided as "developer" role. Other versions support neither role and | ||
rogerbarreto marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| /// an agent targeting such a model cannot provide instructions. Agent functionality | ||
| /// will be dictated entirely by the provided plugins. | ||
| /// </remarks> | ||
| public ChatRole InstructionsRole { get; set; } = ChatRole.System; | ||
rogerbarreto marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| /// <inheritdoc/> | ||
| public override string Id => this._agentOptions?.Id ?? base.Id; | ||
| /// <inheritdoc/> | ||
| public override string? Name => this._agentOptions?.Name; | ||
| /// <inheritdoc/> | ||
| public override string? Description => this._agentOptions?.Description; | ||
| /// <inheritdoc/> | ||
| public override string? Instructions => this._agentOptions?.Instructions; | ||
| /// <inheritdoc/> | ||
| public override async Task<ChatResponse> RunAsync( | ||
| IReadOnlyCollection<ChatMessage> messages, | ||
| AgentThread? thread = null, | ||
| AgentRunOptions? options = null, | ||
| CancellationToken cancellationToken = default) | ||
| { | ||
| Throw.IfNull(messages); | ||
| // Retrieve chat options from the provided AgentRunOptions if available. | ||
| ChatOptions? chatOptions = (options as ChatClientAgentRunOptions)?.ChatOptions; | ||
| var chatClientThread = this.ValidateOrCreateThreadType<ChatClientAgentThread>(thread, () => new()); | ||
| // Add any existing messages from the thread to the messages to be sent to the chat client. | ||
| List<ChatMessage> threadMessages = []; | ||
| if (chatClientThread is IMessagesRetrievableThread messagesRetrievableThread) | ||
| { | ||
| await foreach (ChatMessage message in messagesRetrievableThread.GetMessagesAsync(cancellationToken).ConfigureAwait(false)) | ||
| { | ||
| threadMessages.Add(message); | ||
| } | ||
| } | ||
| // Append to the existing thread messages the messages that were passed in to this call. | ||
| threadMessages.AddRange(messages); | ||
| // Update the messages with agent instructions. | ||
| this.UpdateThreadMessagesWithAgentInstructions(threadMessages, options); | ||
| var agentName = this.Name ?? "UnnamedAgent"; | ||
| Type serviceType = this.ChatClient.GetType(); | ||
| this._logger.LogAgentChatClientInvokingAgent(nameof(RunAsync), this.Id, agentName, serviceType); | ||
| ChatResponse chatResponse = await this.ChatClient.GetResponseAsync(threadMessages, chatOptions, cancellationToken).ConfigureAwait(false); | ||
| this._logger.LogAgentChatClientInvokedAgent(nameof(RunAsync), this.Id, agentName, serviceType, messages.Count); | ||
| // Only notify the thread of new messages if the chatResponse was successful to avoid inconsistent messages state in the thread. | ||
| await this.NotifyThreadOfNewMessagesAsync(chatClientThread, messages, cancellationToken).ConfigureAwait(false); | ||
| // Ensure that the author name is set for each message in the response. | ||
| foreach (ChatMessage chatResponseMessage in chatResponse.Messages) | ||
| { | ||
| chatResponseMessage.AuthorName ??= agentName; | ||
| } | ||
| // Convert the chat response messages to a valid IReadOnlyCollection for notification signatures below. | ||
| var chatResponseMessages = chatResponse.Messages.ToArray(); | ||
| await this.NotifyThreadOfNewMessagesAsync(chatClientThread, chatResponseMessages, cancellationToken).ConfigureAwait(false); | ||
rogerbarreto marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| if (options?.OnIntermediateMessages is not null) | ||
| { | ||
| await options.OnIntermediateMessages(chatResponseMessages).ConfigureAwait(false); | ||
| } | ||
| return chatResponse; | ||
| } | ||
| /// <inheritdoc/> | ||
| public override IAsyncEnumerable<ChatResponseUpdate> RunStreamingAsync(IReadOnlyCollection<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) | ||
| { | ||
| throw new System.NotImplementedException(); | ||
rogerbarreto marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } | ||
| /// <inheritdoc/> | ||
| public override AgentThread GetNewThread() => new ChatClientAgentThread(); | ||
| #region Private | ||
| private void UpdateThreadMessagesWithAgentInstructions(List<ChatMessage> threadMessages, AgentRunOptions? options) | ||
| { | ||
| if (!string.IsNullOrWhiteSpace(options?.AdditionalInstructions)) | ||
| { | ||
| threadMessages.Insert(0, new(this.InstructionsRole, options?.AdditionalInstructions) { AuthorName = this.Name }); | ||
| } | ||
| if (!string.IsNullOrWhiteSpace(this.Instructions)) | ||
| { | ||
| threadMessages.Insert(0, new(this.InstructionsRole, this.Instructions) { AuthorName = this.Name }); | ||
| } | ||
| } | ||
| #endregion | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
| using System.Collections.Generic; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Microsoft.Extensions.AI; | ||
| using Microsoft.Shared.Diagnostics; | ||
| namespace Microsoft.Agents; | ||
| /// <summary> | ||
| /// Extensions for <see cref="ChatClientAgent"/> agent types. | ||
| /// </summary> | ||
| public static class ChatClientAgentExtensions | ||
| { | ||
| /// <summary> | ||
| /// Allow running a chat client agent with a <see cref="ChatOptions"/> configuration. | ||
| /// </summary> | ||
| /// <param name="agent">Target agent to run.</param> | ||
| /// <param name="messages">Messages to send to the agent.</param> | ||
| /// <param name="thread">Optional thread to use for the agent.</param> | ||
| /// <param name="agentOptions">Optional agent run options.</param> | ||
| /// <param name="chatOptions">Optional chat options.</param> | ||
| /// <param name="cancellationToken">Optional cancellation token.</param> | ||
| /// <returns>A task representing the asynchronous operation, with the chat response.</returns> | ||
| public static Task<ChatResponse> RunAsync( | ||
| this ChatClientAgent agent, | ||
| IReadOnlyCollection<ChatMessage> messages, | ||
| AgentThread? thread = null, | ||
| AgentRunOptions? agentOptions = null, | ||
| ChatOptions? chatOptions = null, | ||
| CancellationToken cancellationToken = default) | ||
| { | ||
| Throw.IfNull(agent); | ||
| Throw.IfNull(messages); | ||
| return agent.RunAsync(messages, thread, new ChatClientAgentRunOptions(agentOptions, chatOptions), cancellationToken); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
| using System; | ||
| using System.Diagnostics.CodeAnalysis; | ||
| using Microsoft.Extensions.Logging; | ||
| namespace Microsoft.Agents; | ||
| #pragma warning disable SYSLIB1006 // Multiple logging methods cannot use the same event id within a class | ||
| /// <summary> | ||
| /// Extensions for logging <see cref="ChatClientAgent"/> invocations. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// This extension uses the <see cref="LoggerMessageAttribute"/> to | ||
| /// generate logging code at compile time to achieve optimized code. | ||
| /// </remarks> | ||
| [ExcludeFromCodeCoverage] | ||
| internal static partial class ChatClientAgentLogMessages | ||
| { | ||
| /// <summary> | ||
| /// Logs <see cref="ChatClientAgent"/> invoking agent (started). | ||
| /// </summary> | ||
| [LoggerMessage( | ||
| EventId = 0, | ||
| Level = LogLevel.Debug, | ||
| Message = "[{MethodName}] Agent {AgentId}/{AgentName} Invoking service {ServiceType}.")] | ||
| public static partial void LogAgentChatClientInvokingAgent( | ||
| this ILogger logger, | ||
| string methodName, | ||
| string agentId, | ||
| string agentName, | ||
| Type serviceType); | ||
| /// <summary> | ||
| /// Logs <see cref="ChatClientAgent"/> invoked agent (complete). | ||
| /// </summary> | ||
| [LoggerMessage( | ||
| EventId = 0, | ||
| Level = LogLevel.Information, | ||
| Message = "[{MethodName}] Agent {AgentId}/{AgentName} Invoked service {ServiceType} with message count: {MessageCount}.")] | ||
| public static partial void LogAgentChatClientInvokedAgent( | ||
| this ILogger logger, | ||
| string methodName, | ||
| string agentId, | ||
| string agentName, | ||
| Type serviceType, | ||
| int messageCount); | ||
| /// <summary> | ||
| /// Logs <see cref="ChatClientAgent"/> invoked streaming agent (complete). | ||
| /// </summary> | ||
| [LoggerMessage( | ||
| EventId = 0, | ||
| Level = LogLevel.Information, | ||
| Message = "[{MethodName}] Agent {AgentId}/{AgentName} Invoked service {ServiceType}.")] | ||
| public static partial void LogAgentChatClientInvokedStreamingAgent( | ||
| this ILogger logger, | ||
| string methodName, | ||
| string agentId, | ||
| string agentName, | ||
| Type serviceType); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
| namespace Microsoft.Agents; | ||
| /// <summary> | ||
| /// Represents metadata for a chat client agent, including its identifier, name, instructions, and description. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// This class is used to encapsulate information about a chat client agent, such as its unique | ||
| /// identifier, display name, operational instructions, and a descriptive summary. It can be used to store and transfer | ||
| /// agent-related metadata within a chat application. | ||
| /// </remarks> | ||
| public class ChatClientAgentOptions | ||
| { | ||
| /// <summary> | ||
| /// Gets or sets the agent id. | ||
| /// </summary> | ||
| public string? Id { get; set; } | ||
| /// <summary> | ||
| /// Gets or sets the agent name. | ||
| /// </summary> | ||
| public string? Name { get; set; } | ||
rogerbarreto marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| /// <summary> | ||
| /// Gets or sets the agent instructions. | ||
| /// </summary> | ||
| public string? Instructions { get; set; } | ||
| /// <summary> | ||
| /// Gets or sets the agent description. | ||
| /// </summary> | ||
| public string? Description { get; set; } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.