From bb565ebe084bbd6dd7614b5643c416f12fd08952 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Tue, 25 Jun 2024 09:47:21 -0700 Subject: [PATCH 01/11] Checkpoint --- .../Agents/ChatCompletion_Streaming.cs | 71 +++++++++++++++++ .../GettingStartedWithAgents/Step1_Agent.cs | 2 +- .../Abstractions/ChatHistoryKernelAgent.cs | 6 ++ .../Abstractions/IChatHistoryHandler.cs | 14 +++- dotnet/src/Agents/Core/ChatCompletionAgent.cs | 76 +++++++++++++++++-- 5 files changed, 160 insertions(+), 9 deletions(-) create mode 100644 dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs diff --git a/dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs b/dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs new file mode 100644 index 000000000000..08a5acb1421e --- /dev/null +++ b/dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft. All rights reserved. +using System.Text; +using Microsoft.Extensions.Logging; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Agents; +using Microsoft.SemanticKernel.ChatCompletion; + +namespace Agents; + +/// +/// Demonstrate creation of and +/// eliciting its response to three explicit user messages. +/// +public class ChatCompletion_Streaming(ITestOutputHelper output) : BaseTest(output) +{ + private const string ParrotName = "Parrot"; + private const string ParrotInstructions = "Repeat the user message in the voice of a pirate and then end with a parrot sound."; + + [Fact] + public async Task UseStreamingChatCompletionAgentAsync() + { + // Define the agent + ChatCompletionAgent agent = + new() + { + Name = ParrotName, + Instructions = ParrotInstructions, + Kernel = this.CreateKernelWithChatCompletion(), + }; + + /// Create a chat for agent interaction. For more, . + ChatHistory chat = []; + + // Respond to user input + await InvokeAgentAsync("Fortune favors the bold."); + await InvokeAgentAsync("I came, I saw, I conquered."); + await InvokeAgentAsync("Practice makes perfect."); + + // Local function to invoke agent and display the conversation messages. + async Task InvokeAgentAsync(string input) + { + chat.Add(new ChatMessageContent(AuthorRole.User, input)); + + Console.WriteLine($"# {AuthorRole.User}: '{input}'"); + + string? authorName = null; + StringBuilder builder = new(); + await foreach (StreamingChatMessageContent message in agent.InvokeStreamingAsync(chat, this.LoggerFactory.CreateLogger())) // %%% + { + if (authorName != message.AuthorName) + { + if (builder.Length > 0) + { + Console.WriteLine($"\t\t'{builder}'"); + builder.Clear(); + } + + builder.Append(message.Content); + + authorName = message.AuthorName; + Console.WriteLine($"# {message.Role} - {message.AuthorName ?? "*"}:"); + } + } + + if (builder.Length > 0) + { + Console.WriteLine($"\t\t'{builder}'"); + } + } + } +} diff --git a/dotnet/samples/GettingStartedWithAgents/Step1_Agent.cs b/dotnet/samples/GettingStartedWithAgents/Step1_Agent.cs index 682c96001deb..5e4f582351f5 100644 --- a/dotnet/samples/GettingStartedWithAgents/Step1_Agent.cs +++ b/dotnet/samples/GettingStartedWithAgents/Step1_Agent.cs @@ -41,7 +41,7 @@ async Task InvokeAgentAsync(string input) Console.WriteLine($"# {AuthorRole.User}: '{input}'"); - await foreach (var content in chat.InvokeAsync(agent)) + await foreach (ChatMessageContent content in chat.InvokeAsync(agent)) { Console.WriteLine($"# {content.Role} - {content.AuthorName ?? "*"}: '{content.Content}'"); } diff --git a/dotnet/src/Agents/Abstractions/ChatHistoryKernelAgent.cs b/dotnet/src/Agents/Abstractions/ChatHistoryKernelAgent.cs index ee86a7af770e..7e4a02122059 100644 --- a/dotnet/src/Agents/Abstractions/ChatHistoryKernelAgent.cs +++ b/dotnet/src/Agents/Abstractions/ChatHistoryKernelAgent.cs @@ -28,4 +28,10 @@ public abstract IAsyncEnumerable InvokeAsync( IReadOnlyList history, ILogger logger, CancellationToken cancellationToken = default); + + /// + public abstract IAsyncEnumerable InvokeStreamingAsync( + IReadOnlyList history, + ILogger logger, + CancellationToken cancellationToken = default); } diff --git a/dotnet/src/Agents/Abstractions/IChatHistoryHandler.cs b/dotnet/src/Agents/Abstractions/IChatHistoryHandler.cs index f377d38ba58e..254d0bc3dba3 100644 --- a/dotnet/src/Agents/Abstractions/IChatHistoryHandler.cs +++ b/dotnet/src/Agents/Abstractions/IChatHistoryHandler.cs @@ -11,7 +11,7 @@ namespace Microsoft.SemanticKernel.Agents; public interface IChatHistoryHandler { /// - /// Entry point for calling into an agent from a a . + /// Entry point for calling into an agent from a . /// /// The chat history at the point the channel is created. /// The logger associated with the @@ -21,4 +21,16 @@ IAsyncEnumerable InvokeAsync( IReadOnlyList history, ILogger logger, CancellationToken cancellationToken = default); + + /// + /// Entry point for calling into an agent from a for streaming content. + /// + /// The chat history at the point the channel is created. + /// The logger associated with the + /// The to monitor for cancellation requests. The default is . + /// Asynchronous enumeration of streaming content. + public abstract IAsyncEnumerable InvokeStreamingAsync( + IReadOnlyList history, + ILogger logger, + CancellationToken cancellationToken = default); } diff --git a/dotnet/src/Agents/Core/ChatCompletionAgent.cs b/dotnet/src/Agents/Core/ChatCompletionAgent.cs index e8f9378e8a39..4c9b19a95d2e 100644 --- a/dotnet/src/Agents/Core/ChatCompletionAgent.cs +++ b/dotnet/src/Agents/Core/ChatCompletionAgent.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Threading; +using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel.ChatCompletion; @@ -27,14 +28,9 @@ public override async IAsyncEnumerable InvokeAsync( ILogger logger, [EnumeratorCancellation] CancellationToken cancellationToken = default) { - var chatCompletionService = this.Kernel.GetRequiredService(); + IChatCompletionService chatCompletionService = this.Kernel.GetRequiredService(); - ChatHistory chat = []; - if (!string.IsNullOrWhiteSpace(this.Instructions)) - { - chat.Add(new ChatMessageContent(AuthorRole.System, this.Instructions) { AuthorName = this.Name }); - } - chat.AddRange(history); + ChatHistory chat = this.SetupAgentChatHistory(history); int messageCount = chat.Count; @@ -70,4 +66,70 @@ await chatCompletionService.GetChatMessageContentsAsync( yield return message; } } + + /// + public override async IAsyncEnumerable InvokeStreamingAsync( + IReadOnlyList history, + ILogger logger, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + IChatCompletionService chatCompletionService = this.Kernel.GetRequiredService(); + + ChatHistory chat = this.SetupAgentChatHistory(history); + + int messageCount = chat.Count; + + logger.LogDebug("[{MethodName}] Invoking {ServiceType}.", nameof(InvokeAsync), chatCompletionService.GetType()); + + IAsyncEnumerable messages = + chatCompletionService.GetStreamingChatMessageContentsAsync( + chat, + this.ExecutionSettings, + this.Kernel, + cancellationToken); + + if (logger.IsEnabled(LogLevel.Information)) + { + logger.LogInformation("[{MethodName}] Invoked {ServiceType} with streaming messages.", nameof(InvokeAsync), chatCompletionService.GetType()); + } + + // Capture mutated messages related function calling / tools + for (int messageIndex = messageCount; messageIndex < chat.Count; messageIndex++) // %%% + { + ChatMessageContent message = chat[messageIndex]; + + message.AuthorName = this.Name; + + yield return + new StreamingChatMessageContent( + message.Role, + message.Content, + message.InnerContent, + choiceIndex: default, + message.ModelId, + message.Encoding, + message.Metadata); + } + + await foreach (StreamingChatMessageContent message in messages.ConfigureAwait(false)) + { + // TODO: MESSAGE SOURCE - ISSUE #5731 + message.AuthorName = this.Name; + + yield return message; + } + } + + private ChatHistory SetupAgentChatHistory(IReadOnlyList history) + { + ChatHistory chat = []; + + if (!string.IsNullOrWhiteSpace(this.Instructions)) + { + chat.Add(new ChatMessageContent(AuthorRole.System, this.Instructions) { AuthorName = this.Name }); + } + chat.AddRange(history); + + return chat; + } } From 7e256c1aff08fe983d7689ec77deb0eeee113074 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Tue, 25 Jun 2024 10:23:28 -0700 Subject: [PATCH 02/11] Update sample and core fix --- dotnet/SK-dotnet.sln | 2 +- .../Agents/ChatCompletion_Streaming.cs | 24 +++++++++---------- .../Abstractions/ChatHistoryKernelAgent.cs | 1 - .../Abstractions/IChatHistoryHandler.cs | 2 -- dotnet/src/Agents/Core/ChatCompletionAgent.cs | 7 +++--- dotnet/src/Agents/UnitTests/AgentChatTests.cs | 11 +++++++++ .../Connectors.OpenAI/AzureSdk/ClientCore.cs | 19 ++++++++++----- 7 files changed, 40 insertions(+), 26 deletions(-) diff --git a/dotnet/SK-dotnet.sln b/dotnet/SK-dotnet.sln index ab2e6d7d75e8..ea09efdafb41 100644 --- a/dotnet/SK-dotnet.sln +++ b/dotnet/SK-dotnet.sln @@ -802,7 +802,7 @@ Global {107156B4-5A8B-45C7-97A2-4544D7FA19DE} = {9ECD1AA0-75B3-4E25-B0B5-9F0945B64974} {F4243136-252A-4459-A7C4-EE8C056D6B0B} = {158A4E5E-AEE0-4D60-83C7-8E089B2D881D} {F2A1F81E-700E-4C0E-B021-B9EF29AA20BD} = {9ECD1AA0-75B3-4E25-B0B5-9F0945B64974} - {0247C2C9-86C3-45BA-8873-28B0948EDC0C} = {831DDCA2-7D2C-4C31-80DB-6BDB3E1F7AE0} + {0247C2C9-86C3-45BA-8873-28B0948EDC0C} = {A2357CF8-3BB9-45A1-93F1-B366C9B63658} {EB3FC57F-E591-4C88-BCD5-B6A1BC635168} = {0247C2C9-86C3-45BA-8873-28B0948EDC0C} {5DEBAA62-F117-496A-8778-FED3604B70E2} = {24503383-A8C4-4255-9998-28D70FE8E99A} {EC004F12-2F60-4EDD-B3CD-3A504900D929} = {24503383-A8C4-4255-9998-28D70FE8E99A} diff --git a/dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs b/dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs index 08a5acb1421e..f6e83b17c1cc 100644 --- a/dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs +++ b/dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs @@ -43,28 +43,28 @@ async Task InvokeAgentAsync(string input) Console.WriteLine($"# {AuthorRole.User}: '{input}'"); - string? authorName = null; StringBuilder builder = new(); - await foreach (StreamingChatMessageContent message in agent.InvokeStreamingAsync(chat, this.LoggerFactory.CreateLogger())) // %%% + await foreach (StreamingChatMessageContent message in agent.InvokeStreamingAsync(chat)) { - if (authorName != message.AuthorName) + if (string.IsNullOrEmpty(message.Content)) { - if (builder.Length > 0) - { - Console.WriteLine($"\t\t'{builder}'"); - builder.Clear(); - } - - builder.Append(message.Content); + continue; + } - authorName = message.AuthorName; + if (builder.Length == 0) + { Console.WriteLine($"# {message.Role} - {message.AuthorName ?? "*"}:"); } + + Console.WriteLine($"\t Streamed: '{message.Content}'"); + builder.Append(message.Content); } if (builder.Length > 0) { - Console.WriteLine($"\t\t'{builder}'"); + // Display full response and capture in chat history + Console.WriteLine($"\t Complete: '{builder}'"); + chat.Add(new ChatMessageContent(AuthorRole.Assistant, builder.ToString()) { AuthorName = agent.Name }); } } } diff --git a/dotnet/src/Agents/Abstractions/ChatHistoryKernelAgent.cs b/dotnet/src/Agents/Abstractions/ChatHistoryKernelAgent.cs index 45d1cc5df349..02cefc535b77 100644 --- a/dotnet/src/Agents/Abstractions/ChatHistoryKernelAgent.cs +++ b/dotnet/src/Agents/Abstractions/ChatHistoryKernelAgent.cs @@ -37,6 +37,5 @@ public abstract IAsyncEnumerable InvokeAsync( /// public abstract IAsyncEnumerable InvokeStreamingAsync( IReadOnlyList history, - ILogger logger, CancellationToken cancellationToken = default); } diff --git a/dotnet/src/Agents/Abstractions/IChatHistoryHandler.cs b/dotnet/src/Agents/Abstractions/IChatHistoryHandler.cs index 71bedc5c09c5..760a8046e4ca 100644 --- a/dotnet/src/Agents/Abstractions/IChatHistoryHandler.cs +++ b/dotnet/src/Agents/Abstractions/IChatHistoryHandler.cs @@ -23,11 +23,9 @@ IAsyncEnumerable InvokeAsync( /// Entry point for calling into an agent from a for streaming content. /// /// The chat history at the point the channel is created. - /// The logger associated with the /// The to monitor for cancellation requests. The default is . /// Asynchronous enumeration of streaming content. public abstract IAsyncEnumerable InvokeStreamingAsync( IReadOnlyList history, - ILogger logger, CancellationToken cancellationToken = default); } diff --git a/dotnet/src/Agents/Core/ChatCompletionAgent.cs b/dotnet/src/Agents/Core/ChatCompletionAgent.cs index a1e8d6d231a0..5883fc4ad717 100644 --- a/dotnet/src/Agents/Core/ChatCompletionAgent.cs +++ b/dotnet/src/Agents/Core/ChatCompletionAgent.cs @@ -69,7 +69,6 @@ await chatCompletionService.GetChatMessageContentsAsync( /// public override async IAsyncEnumerable InvokeStreamingAsync( IReadOnlyList history, - ILogger logger, [EnumeratorCancellation] CancellationToken cancellationToken = default) { IChatCompletionService chatCompletionService = this.Kernel.GetRequiredService(); @@ -78,7 +77,7 @@ public override async IAsyncEnumerable InvokeStream int messageCount = chat.Count; - logger.LogDebug("[{MethodName}] Invoking {ServiceType}.", nameof(InvokeAsync), chatCompletionService.GetType()); + this.Logger.LogDebug("[{MethodName}] Invoking {ServiceType}.", nameof(InvokeAsync), chatCompletionService.GetType()); IAsyncEnumerable messages = chatCompletionService.GetStreamingChatMessageContentsAsync( @@ -87,9 +86,9 @@ public override async IAsyncEnumerable InvokeStream this.Kernel, cancellationToken); - if (logger.IsEnabled(LogLevel.Information)) + if (this.Logger.IsEnabled(LogLevel.Information)) { - logger.LogInformation("[{MethodName}] Invoked {ServiceType} with streaming messages.", nameof(InvokeAsync), chatCompletionService.GetType()); + this.Logger.LogInformation("[{MethodName}] Invoked {ServiceType} with streaming messages.", nameof(InvokeAsync), chatCompletionService.GetType()); } // Capture mutated messages related function calling / tools diff --git a/dotnet/src/Agents/UnitTests/AgentChatTests.cs b/dotnet/src/Agents/UnitTests/AgentChatTests.cs index bc8e2b42e29a..c38e1d0ca5a5 100644 --- a/dotnet/src/Agents/UnitTests/AgentChatTests.cs +++ b/dotnet/src/Agents/UnitTests/AgentChatTests.cs @@ -144,5 +144,16 @@ public override async IAsyncEnumerable InvokeAsync( yield return new ChatMessageContent(AuthorRole.Assistant, "sup"); } + + public override async IAsyncEnumerable InvokeStreamingAsync( + IReadOnlyList history, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + await Task.Delay(0, cancellationToken); + + this.InvokeCount++; + + yield return new StreamingChatMessageContent(AuthorRole.Assistant, "sup"); + } } } diff --git a/dotnet/src/Connectors/Connectors.OpenAI/AzureSdk/ClientCore.cs b/dotnet/src/Connectors/Connectors.OpenAI/AzureSdk/ClientCore.cs index 8059077d8bf4..78d2d2028cc0 100644 --- a/dotnet/src/Connectors/Connectors.OpenAI/AzureSdk/ClientCore.cs +++ b/dotnet/src/Connectors/Connectors.OpenAI/AzureSdk/ClientCore.cs @@ -693,15 +693,22 @@ internal async IAsyncEnumerable GetStreamingC OpenAIFunctionToolCall.TrackStreamingToolingUpdate(update.ToolCallUpdate, ref toolCallIdsByIndex, ref functionNamesByIndex, ref functionArgumentBuildersByIndex); } - var openAIStreamingChatMessageContent = new OpenAIStreamingChatMessageContent(update, update.ChoiceIndex ?? 0, this.DeploymentOrModelName, metadata) { AuthorName = streamedName }; + AuthorRole? role = null; + if (streamedRole.HasValue) + { + role = new AuthorRole(streamedRole.Value.ToString()); + } + + OpenAIStreamingChatMessageContent openAIStreamingChatMessageContent = new(update, update.ChoiceIndex ?? 0, this.DeploymentOrModelName, metadata) { AuthorName = streamedName, Role = role, }; if (update.ToolCallUpdate is StreamingFunctionToolCallUpdate functionCallUpdate) { - openAIStreamingChatMessageContent.Items.Add(new StreamingFunctionCallUpdateContent( - callId: functionCallUpdate.Id, - name: functionCallUpdate.Name, - arguments: functionCallUpdate.ArgumentsUpdate, - functionCallIndex: functionCallUpdate.ToolCallIndex)); + openAIStreamingChatMessageContent.Items.Add( + new StreamingFunctionCallUpdateContent( + callId: functionCallUpdate.Id, + name: functionCallUpdate.Name, + arguments: functionCallUpdate.ArgumentsUpdate, + functionCallIndex: functionCallUpdate.ToolCallIndex)); } streamedContents?.Add(openAIStreamingChatMessageContent); From 04ab5f772c7d43c66b0e41eceee52e45ed93c913 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Tue, 25 Jun 2024 14:58:53 -0700 Subject: [PATCH 03/11] Sample formatting --- dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs b/dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs index f6e83b17c1cc..441304000a7b 100644 --- a/dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs +++ b/dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs @@ -56,14 +56,14 @@ async Task InvokeAgentAsync(string input) Console.WriteLine($"# {message.Role} - {message.AuthorName ?? "*"}:"); } - Console.WriteLine($"\t Streamed: '{message.Content}'"); + Console.WriteLine($"\t > streamed: '{message.Content}'"); builder.Append(message.Content); } if (builder.Length > 0) { // Display full response and capture in chat history - Console.WriteLine($"\t Complete: '{builder}'"); + Console.WriteLine($"\t > complete: '{builder}'"); chat.Add(new ChatMessageContent(AuthorRole.Assistant, builder.ToString()) { AuthorName = agent.Name }); } } From 9835f47a143558f1e564239e407413a899b899e5 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Wed, 26 Jun 2024 13:05:00 -0700 Subject: [PATCH 04/11] Namespace --- dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs b/dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs index 441304000a7b..f328d7cf6bf3 100644 --- a/dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs +++ b/dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs @@ -1,6 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. using System.Text; -using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; using Microsoft.SemanticKernel.ChatCompletion; From 6a677bc449130b660479a82238bfb675b9f2376e Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Fri, 28 Jun 2024 09:01:31 -0700 Subject: [PATCH 05/11] Tweaks --- dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs | 1 - dotnet/src/Agents/Abstractions/ChatHistoryChannel.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs b/dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs index f328d7cf6bf3..ee6fb9b38f2a 100644 --- a/dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs +++ b/dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs @@ -27,7 +27,6 @@ public async Task UseStreamingChatCompletionAgentAsync() Kernel = this.CreateKernelWithChatCompletion(), }; - /// Create a chat for agent interaction. For more, . ChatHistory chat = []; // Respond to user input diff --git a/dotnet/src/Agents/Abstractions/ChatHistoryChannel.cs b/dotnet/src/Agents/Abstractions/ChatHistoryChannel.cs index 3baeb934a52b..2bb5616ff959 100644 --- a/dotnet/src/Agents/Abstractions/ChatHistoryChannel.cs +++ b/dotnet/src/Agents/Abstractions/ChatHistoryChannel.cs @@ -25,7 +25,7 @@ protected internal sealed override async IAsyncEnumerable In throw new KernelException($"Invalid channel binding for agent: {agent.Id} ({agent.GetType().FullName})"); } - await foreach (var message in historyHandler.InvokeAsync(this._history, cancellationToken).ConfigureAwait(false)) + await foreach (ChatMessageContent message in historyHandler.InvokeAsync(this._history, cancellationToken).ConfigureAwait(false)) { this._history.Add(message); From 00fa5779df2f308d5fe95ad605d955f11227b012 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Mon, 1 Jul 2024 14:22:43 -0700 Subject: [PATCH 06/11] Mutated messages update (consistency) --- dotnet/src/Agents/Abstractions/AgentChat.cs | 6 ---- dotnet/src/Agents/Core/ChatCompletionAgent.cs | 32 ------------------- 2 files changed, 38 deletions(-) diff --git a/dotnet/src/Agents/Abstractions/AgentChat.cs b/dotnet/src/Agents/Abstractions/AgentChat.cs index 7e7dea00a805..e7bd5b9a4d81 100644 --- a/dotnet/src/Agents/Abstractions/AgentChat.cs +++ b/dotnet/src/Agents/Abstractions/AgentChat.cs @@ -223,12 +223,6 @@ protected async IAsyncEnumerable InvokeAgentAsync( this.History.Add(message); messages.Add(message); - // Don't expose function-call and function-result messages to caller. - if (message.Items.All(i => i is FunctionCallContent || i is FunctionResultContent)) - { - continue; - } - // Yield message to caller yield return message; } diff --git a/dotnet/src/Agents/Core/ChatCompletionAgent.cs b/dotnet/src/Agents/Core/ChatCompletionAgent.cs index 5883fc4ad717..25d26da8f80a 100644 --- a/dotnet/src/Agents/Core/ChatCompletionAgent.cs +++ b/dotnet/src/Agents/Core/ChatCompletionAgent.cs @@ -31,8 +31,6 @@ public override async IAsyncEnumerable InvokeAsync( ChatHistory chat = this.SetupAgentChatHistory(history); - int messageCount = chat.Count; - this.Logger.LogDebug("[{MethodName}] Invoking {ServiceType}.", nameof(InvokeAsync), chatCompletionService.GetType()); IReadOnlyList messages = @@ -47,16 +45,6 @@ await chatCompletionService.GetChatMessageContentsAsync( this.Logger.LogInformation("[{MethodName}] Invoked {ServiceType} with message count: {MessageCount}.", nameof(InvokeAsync), chatCompletionService.GetType(), messages.Count); } - // Capture mutated messages related function calling / tools - for (int messageIndex = messageCount; messageIndex < chat.Count; messageIndex++) - { - ChatMessageContent message = chat[messageIndex]; - - message.AuthorName = this.Name; - - yield return message; - } - foreach (ChatMessageContent message in messages ?? []) { // TODO: MESSAGE SOURCE - ISSUE #5731 @@ -75,8 +63,6 @@ public override async IAsyncEnumerable InvokeStream ChatHistory chat = this.SetupAgentChatHistory(history); - int messageCount = chat.Count; - this.Logger.LogDebug("[{MethodName}] Invoking {ServiceType}.", nameof(InvokeAsync), chatCompletionService.GetType()); IAsyncEnumerable messages = @@ -91,24 +77,6 @@ public override async IAsyncEnumerable InvokeStream this.Logger.LogInformation("[{MethodName}] Invoked {ServiceType} with streaming messages.", nameof(InvokeAsync), chatCompletionService.GetType()); } - // Capture mutated messages related function calling / tools - for (int messageIndex = messageCount; messageIndex < chat.Count; messageIndex++) // %%% - { - ChatMessageContent message = chat[messageIndex]; - - message.AuthorName = this.Name; - - yield return - new StreamingChatMessageContent( - message.Role, - message.Content, - message.InnerContent, - choiceIndex: default, - message.ModelId, - message.Encoding, - message.Metadata); - } - await foreach (StreamingChatMessageContent message in messages.ConfigureAwait(false)) { // TODO: MESSAGE SOURCE - ISSUE #5731 From b55ecbd266158a7218d0402812ab97b00f6cac04 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Mon, 1 Jul 2024 14:24:24 -0700 Subject: [PATCH 07/11] .sln rollback --- dotnet/SK-dotnet.sln | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/SK-dotnet.sln b/dotnet/SK-dotnet.sln index ea09efdafb41..ab2e6d7d75e8 100644 --- a/dotnet/SK-dotnet.sln +++ b/dotnet/SK-dotnet.sln @@ -802,7 +802,7 @@ Global {107156B4-5A8B-45C7-97A2-4544D7FA19DE} = {9ECD1AA0-75B3-4E25-B0B5-9F0945B64974} {F4243136-252A-4459-A7C4-EE8C056D6B0B} = {158A4E5E-AEE0-4D60-83C7-8E089B2D881D} {F2A1F81E-700E-4C0E-B021-B9EF29AA20BD} = {9ECD1AA0-75B3-4E25-B0B5-9F0945B64974} - {0247C2C9-86C3-45BA-8873-28B0948EDC0C} = {A2357CF8-3BB9-45A1-93F1-B366C9B63658} + {0247C2C9-86C3-45BA-8873-28B0948EDC0C} = {831DDCA2-7D2C-4C31-80DB-6BDB3E1F7AE0} {EB3FC57F-E591-4C88-BCD5-B6A1BC635168} = {0247C2C9-86C3-45BA-8873-28B0948EDC0C} {5DEBAA62-F117-496A-8778-FED3604B70E2} = {24503383-A8C4-4255-9998-28D70FE8E99A} {EC004F12-2F60-4EDD-B3CD-3A504900D929} = {24503383-A8C4-4255-9998-28D70FE8E99A} From 3490d867112c5ba6d0c2c1d15cdc5f95c6b60187 Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Mon, 1 Jul 2024 14:36:23 -0700 Subject: [PATCH 08/11] Clean-up --- dotnet/src/Agents/Abstractions/AgentChat.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dotnet/src/Agents/Abstractions/AgentChat.cs b/dotnet/src/Agents/Abstractions/AgentChat.cs index e7bd5b9a4d81..7e7dea00a805 100644 --- a/dotnet/src/Agents/Abstractions/AgentChat.cs +++ b/dotnet/src/Agents/Abstractions/AgentChat.cs @@ -223,6 +223,12 @@ protected async IAsyncEnumerable InvokeAgentAsync( this.History.Add(message); messages.Add(message); + // Don't expose function-call and function-result messages to caller. + if (message.Items.All(i => i is FunctionCallContent || i is FunctionResultContent)) + { + continue; + } + // Yield message to caller yield return message; } From a86b1527e646ea67b5fd192929a4b6b0a5d5c9bd Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Tue, 2 Jul 2024 15:00:16 -0700 Subject: [PATCH 09/11] Finalize --- .../GettingStartedWithAgents/Step1_Agent.cs | 2 +- .../GettingStartedWithAgents/Step2_Plugins.cs | 6 +-- .../Abstractions/ChatHistoryKernelAgent.cs | 5 ++- .../Abstractions/IChatHistoryHandler.cs | 5 ++- dotnet/src/Agents/Core/ChatCompletionAgent.cs | 29 ++++++++++++- dotnet/src/Agents/UnitTests/AgentChatTests.cs | 4 +- .../Agents/UnitTests/AggregatorAgentTests.cs | 2 +- .../UnitTests/Core/AgentGroupChatTests.cs | 2 +- .../Core/ChatCompletionAgentTests.cs | 42 +++++++++++++++++++ 9 files changed, 83 insertions(+), 14 deletions(-) diff --git a/dotnet/samples/GettingStartedWithAgents/Step1_Agent.cs b/dotnet/samples/GettingStartedWithAgents/Step1_Agent.cs index 8e375a4546cc..ddab79f032b0 100644 --- a/dotnet/samples/GettingStartedWithAgents/Step1_Agent.cs +++ b/dotnet/samples/GettingStartedWithAgents/Step1_Agent.cs @@ -27,7 +27,7 @@ public async Task UseSingleChatCompletionAgentAsync() }; /// Create a chat for agent interaction. For more, . - ChatHistory chat = new(); + ChatHistory chat = []; // Respond to user input await InvokeAgentAsync("Fortune favors the bold."); diff --git a/dotnet/samples/GettingStartedWithAgents/Step2_Plugins.cs b/dotnet/samples/GettingStartedWithAgents/Step2_Plugins.cs index a28f9013d85e..61737de498be 100644 --- a/dotnet/samples/GettingStartedWithAgents/Step2_Plugins.cs +++ b/dotnet/samples/GettingStartedWithAgents/Step2_Plugins.cs @@ -34,7 +34,7 @@ public async Task UseChatCompletionWithPluginAgentAsync() agent.Kernel.Plugins.Add(plugin); /// Create a chat for agent interaction. For more, . - AgentGroupChat chat = new(); + ChatHistory chat = []; // Respond to user input, invoking functions where appropriate. await InvokeAgentAsync("Hello"); @@ -45,10 +45,10 @@ public async Task UseChatCompletionWithPluginAgentAsync() // Local function to invoke agent and display the conversation messages. async Task InvokeAgentAsync(string input) { - chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, input)); + chat.Add(new ChatMessageContent(AuthorRole.User, input)); Console.WriteLine($"# {AuthorRole.User}: '{input}'"); - await foreach (var content in chat.InvokeAsync(agent)) + await foreach (var content in agent.InvokeAsync(chat)) { Console.WriteLine($"# {content.Role} - {content.AuthorName ?? "*"}: '{content.Content}'"); } diff --git a/dotnet/src/Agents/Abstractions/ChatHistoryKernelAgent.cs b/dotnet/src/Agents/Abstractions/ChatHistoryKernelAgent.cs index 02cefc535b77..3de87da3de06 100644 --- a/dotnet/src/Agents/Abstractions/ChatHistoryKernelAgent.cs +++ b/dotnet/src/Agents/Abstractions/ChatHistoryKernelAgent.cs @@ -3,6 +3,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using Microsoft.SemanticKernel.ChatCompletion; namespace Microsoft.SemanticKernel.Agents; @@ -31,11 +32,11 @@ protected internal sealed override Task CreateChannelAsync(Cancell /// public abstract IAsyncEnumerable InvokeAsync( - IReadOnlyList history, + ChatHistory history, CancellationToken cancellationToken = default); /// public abstract IAsyncEnumerable InvokeStreamingAsync( - IReadOnlyList history, + ChatHistory history, CancellationToken cancellationToken = default); } diff --git a/dotnet/src/Agents/Abstractions/IChatHistoryHandler.cs b/dotnet/src/Agents/Abstractions/IChatHistoryHandler.cs index 760a8046e4ca..8b7dab748c81 100644 --- a/dotnet/src/Agents/Abstractions/IChatHistoryHandler.cs +++ b/dotnet/src/Agents/Abstractions/IChatHistoryHandler.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. using System.Collections.Generic; using System.Threading; +using Microsoft.SemanticKernel.ChatCompletion; namespace Microsoft.SemanticKernel.Agents; @@ -16,7 +17,7 @@ public interface IChatHistoryHandler /// The to monitor for cancellation requests. The default is . /// Asynchronous enumeration of messages. IAsyncEnumerable InvokeAsync( - IReadOnlyList history, + ChatHistory history, CancellationToken cancellationToken = default); /// @@ -26,6 +27,6 @@ IAsyncEnumerable InvokeAsync( /// The to monitor for cancellation requests. The default is . /// Asynchronous enumeration of streaming content. public abstract IAsyncEnumerable InvokeStreamingAsync( - IReadOnlyList history, + ChatHistory history, CancellationToken cancellationToken = default); } diff --git a/dotnet/src/Agents/Core/ChatCompletionAgent.cs b/dotnet/src/Agents/Core/ChatCompletionAgent.cs index 25d26da8f80a..b84d29494b8e 100644 --- a/dotnet/src/Agents/Core/ChatCompletionAgent.cs +++ b/dotnet/src/Agents/Core/ChatCompletionAgent.cs @@ -24,13 +24,15 @@ public sealed class ChatCompletionAgent : ChatHistoryKernelAgent /// public override async IAsyncEnumerable InvokeAsync( - IReadOnlyList history, + ChatHistory history, [EnumeratorCancellation] CancellationToken cancellationToken = default) { IChatCompletionService chatCompletionService = this.Kernel.GetRequiredService(); ChatHistory chat = this.SetupAgentChatHistory(history); + int messageCount = chat.Count; + this.Logger.LogDebug("[{MethodName}] Invoking {ServiceType}.", nameof(InvokeAsync), chatCompletionService.GetType()); IReadOnlyList messages = @@ -45,6 +47,16 @@ await chatCompletionService.GetChatMessageContentsAsync( this.Logger.LogInformation("[{MethodName}] Invoked {ServiceType} with message count: {MessageCount}.", nameof(InvokeAsync), chatCompletionService.GetType(), messages.Count); } + // Capture mutated messages related function calling / tools + for (int messageIndex = messageCount; messageIndex < chat.Count; messageIndex++) + { + ChatMessageContent message = chat[messageIndex]; + + message.AuthorName = this.Name; + + history.Add(message); + } + foreach (ChatMessageContent message in messages ?? []) { // TODO: MESSAGE SOURCE - ISSUE #5731 @@ -56,13 +68,15 @@ await chatCompletionService.GetChatMessageContentsAsync( /// public override async IAsyncEnumerable InvokeStreamingAsync( - IReadOnlyList history, + ChatHistory history, [EnumeratorCancellation] CancellationToken cancellationToken = default) { IChatCompletionService chatCompletionService = this.Kernel.GetRequiredService(); ChatHistory chat = this.SetupAgentChatHistory(history); + int messageCount = chat.Count; + this.Logger.LogDebug("[{MethodName}] Invoking {ServiceType}.", nameof(InvokeAsync), chatCompletionService.GetType()); IAsyncEnumerable messages = @@ -77,6 +91,16 @@ public override async IAsyncEnumerable InvokeStream this.Logger.LogInformation("[{MethodName}] Invoked {ServiceType} with streaming messages.", nameof(InvokeAsync), chatCompletionService.GetType()); } + // Capture mutated messages related function calling / tools + for (int messageIndex = messageCount; messageIndex < chat.Count; messageIndex++) + { + ChatMessageContent message = chat[messageIndex]; + + message.AuthorName = this.Name; + + history.Add(message); + } + await foreach (StreamingChatMessageContent message in messages.ConfigureAwait(false)) { // TODO: MESSAGE SOURCE - ISSUE #5731 @@ -94,6 +118,7 @@ private ChatHistory SetupAgentChatHistory(IReadOnlyList hist { chat.Add(new ChatMessageContent(AuthorRole.System, this.Instructions) { AuthorName = this.Name }); } + chat.AddRange(history); return chat; diff --git a/dotnet/src/Agents/UnitTests/AgentChatTests.cs b/dotnet/src/Agents/UnitTests/AgentChatTests.cs index c38e1d0ca5a5..d5f4a0a8d9e5 100644 --- a/dotnet/src/Agents/UnitTests/AgentChatTests.cs +++ b/dotnet/src/Agents/UnitTests/AgentChatTests.cs @@ -135,7 +135,7 @@ private sealed class TestAgent : ChatHistoryKernelAgent public int InvokeCount { get; private set; } public override async IAsyncEnumerable InvokeAsync( - IReadOnlyList history, + ChatHistory history, [EnumeratorCancellation] CancellationToken cancellationToken = default) { await Task.Delay(0, cancellationToken); @@ -146,7 +146,7 @@ public override async IAsyncEnumerable InvokeAsync( } public override async IAsyncEnumerable InvokeStreamingAsync( - IReadOnlyList history, + ChatHistory history, [EnumeratorCancellation] CancellationToken cancellationToken = default) { await Task.Delay(0, cancellationToken); diff --git a/dotnet/src/Agents/UnitTests/AggregatorAgentTests.cs b/dotnet/src/Agents/UnitTests/AggregatorAgentTests.cs index 0fb1d8817902..d332b9ad345b 100644 --- a/dotnet/src/Agents/UnitTests/AggregatorAgentTests.cs +++ b/dotnet/src/Agents/UnitTests/AggregatorAgentTests.cs @@ -87,7 +87,7 @@ private static Mock CreateMockAgent() Mock agent = new(); ChatMessageContent[] messages = [new ChatMessageContent(AuthorRole.Assistant, "test agent")]; - agent.Setup(a => a.InvokeAsync(It.IsAny>(), It.IsAny())).Returns(() => messages.ToAsyncEnumerable()); + agent.Setup(a => a.InvokeAsync(It.IsAny(), It.IsAny())).Returns(() => messages.ToAsyncEnumerable()); return agent; } diff --git a/dotnet/src/Agents/UnitTests/Core/AgentGroupChatTests.cs b/dotnet/src/Agents/UnitTests/Core/AgentGroupChatTests.cs index 48b652491f53..921e0acce016 100644 --- a/dotnet/src/Agents/UnitTests/Core/AgentGroupChatTests.cs +++ b/dotnet/src/Agents/UnitTests/Core/AgentGroupChatTests.cs @@ -198,7 +198,7 @@ private static Mock CreateMockAgent() Mock agent = new(); ChatMessageContent[] messages = [new ChatMessageContent(AuthorRole.Assistant, "test")]; - agent.Setup(a => a.InvokeAsync(It.IsAny>(), It.IsAny())).Returns(() => messages.ToAsyncEnumerable()); + agent.Setup(a => a.InvokeAsync(It.IsAny(), It.IsAny())).Returns(() => messages.ToAsyncEnumerable()); return agent; } diff --git a/dotnet/src/Agents/UnitTests/Core/ChatCompletionAgentTests.cs b/dotnet/src/Agents/UnitTests/Core/ChatCompletionAgentTests.cs index 5357f0edbd11..ae7657c8189c 100644 --- a/dotnet/src/Agents/UnitTests/Core/ChatCompletionAgentTests.cs +++ b/dotnet/src/Agents/UnitTests/Core/ChatCompletionAgentTests.cs @@ -73,6 +73,48 @@ public async Task VerifyChatCompletionAgentInvocationAsync() Times.Once); } + /// + /// Verify the streaming invocation and response of . + /// + [Fact] + public async Task VerifyChatCompletionAgentStreamingAsync() + { + StreamingChatMessageContent[] returnContent = + [ + new(AuthorRole.Assistant, "wh"), + new(AuthorRole.Assistant, "at?"), + ]; + + var mockService = new Mock(); + mockService.Setup( + s => s.GetStreamingChatMessageContentsAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())).Returns(returnContent.ToAsyncEnumerable()); + + var agent = + new ChatCompletionAgent() + { + Instructions = "test instructions", + Kernel = CreateKernel(mockService.Object), + ExecutionSettings = new(), + }; + + var result = await agent.InvokeStreamingAsync([]).ToArrayAsync(); + + Assert.Equal(2, result.Length); + + mockService.Verify( + x => + x.GetStreamingChatMessageContentsAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny()), + Times.Once); + } + private static Kernel CreateKernel(IChatCompletionService chatCompletionService) { var builder = Kernel.CreateBuilder(); From 9d9831fff0b0d3f8ab1a0ffca58bbe8467fd63df Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Tue, 2 Jul 2024 15:06:32 -0700 Subject: [PATCH 10/11] Namespace --- dotnet/src/Agents/UnitTests/AggregatorAgentTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/dotnet/src/Agents/UnitTests/AggregatorAgentTests.cs b/dotnet/src/Agents/UnitTests/AggregatorAgentTests.cs index d332b9ad345b..c4a974cbadc9 100644 --- a/dotnet/src/Agents/UnitTests/AggregatorAgentTests.cs +++ b/dotnet/src/Agents/UnitTests/AggregatorAgentTests.cs @@ -1,5 +1,4 @@ // Copyright (c) Microsoft. All rights reserved. -using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; From 8071a6355d04671f911fc8374d22703236cc7abc Mon Sep 17 00:00:00 2001 From: Chris Rickman Date: Tue, 2 Jul 2024 15:16:09 -0700 Subject: [PATCH 11/11] Remove delay --- dotnet/src/Agents/UnitTests/AgentChatTests.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dotnet/src/Agents/UnitTests/AgentChatTests.cs b/dotnet/src/Agents/UnitTests/AgentChatTests.cs index d5f4a0a8d9e5..89ff7f02cff2 100644 --- a/dotnet/src/Agents/UnitTests/AgentChatTests.cs +++ b/dotnet/src/Agents/UnitTests/AgentChatTests.cs @@ -145,15 +145,15 @@ public override async IAsyncEnumerable InvokeAsync( yield return new ChatMessageContent(AuthorRole.Assistant, "sup"); } - public override async IAsyncEnumerable InvokeStreamingAsync( + public override IAsyncEnumerable InvokeStreamingAsync( ChatHistory history, - [EnumeratorCancellation] CancellationToken cancellationToken = default) + CancellationToken cancellationToken = default) { - await Task.Delay(0, cancellationToken); - this.InvokeCount++; - yield return new StreamingChatMessageContent(AuthorRole.Assistant, "sup"); + StreamingChatMessageContent[] contents = [new(AuthorRole.Assistant, "sup")]; + + return contents.ToAsyncEnumerable(); } } }