From f2df866541f5a17b402c7aed407ad85de327d244 Mon Sep 17 00:00:00 2001 From: Chris <66376200+crickman@users.noreply.github.com> Date: Mon, 21 Oct 2024 14:12:33 -0700 Subject: [PATCH] .Net Agents - Emit streaming function call content for assistant agent (#9359) ### Motivation and Context Expose `StreamingFunctionCallUpdateContent` for streaming output of `OpenAIAssistantAgent` ### Description @matthewbolanos identified assistant streaming wasn't aligned with chat-completion in providing a message with `StreamingFunctionCallUpdateContent` for a function-call. ### Contribution Checklist - [X] The code builds clean without any errors or warnings - [X] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [X] All unit tests pass, and I have added new tests where possible - [X] I didn't break anyone :smile: --- .../Agents/ChatCompletion_Streaming.cs | 6 +++++ .../Agents/OpenAIAssistant_Streaming.cs | 10 +++++-- .../Step08_Assistant.cs | 4 +-- .../OpenAI/Internal/AssistantThreadActions.cs | 26 +++++++++++++------ 4 files changed, 34 insertions(+), 12 deletions(-) diff --git a/dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs b/dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs index 7a23efe6e112..6d11dd80ff91 100644 --- a/dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs +++ b/dotnet/samples/Concepts/Agents/ChatCompletion_Streaming.cs @@ -81,6 +81,12 @@ private async Task InvokeAgentAsync(ChatCompletionAgent agent, ChatHistory chat, { if (string.IsNullOrEmpty(response.Content)) { + StreamingFunctionCallUpdateContent? functionCall = response.Items.OfType().SingleOrDefault(); + if (!string.IsNullOrEmpty(functionCall?.Name)) + { + Console.WriteLine($"\n# {response.Role} - {response.AuthorName ?? "*"}: FUNCTION CALL - {functionCall.Name}"); + } + continue; } diff --git a/dotnet/samples/Concepts/Agents/OpenAIAssistant_Streaming.cs b/dotnet/samples/Concepts/Agents/OpenAIAssistant_Streaming.cs index e394b8c49dad..39ff0f0fb97c 100644 --- a/dotnet/samples/Concepts/Agents/OpenAIAssistant_Streaming.cs +++ b/dotnet/samples/Concepts/Agents/OpenAIAssistant_Streaming.cs @@ -68,8 +68,8 @@ await OpenAIAssistantAgent.CreateAsync( string threadId = await agent.CreateThreadAsync(new OpenAIThreadCreationOptions { Metadata = AssistantSampleMetadata }); // Respond to user input - await InvokeAgentAsync(agent, threadId, "What is the special soup?"); - await InvokeAgentAsync(agent, threadId, "What is the special drink?"); + await InvokeAgentAsync(agent, threadId, "What is the special soup and its price?"); + await InvokeAgentAsync(agent, threadId, "What is the special drink and its price?"); // Output the entire chat history await DisplayChatHistoryAsync(agent, threadId); @@ -120,6 +120,12 @@ private async Task InvokeAgentAsync(OpenAIAssistantAgent agent, string threadId, { if (string.IsNullOrEmpty(response.Content)) { + StreamingFunctionCallUpdateContent? functionCall = response.Items.OfType().SingleOrDefault(); + if (functionCall != null) + { + Console.WriteLine($"\n# {response.Role} - {response.AuthorName ?? "*"}: FUNCTION CALL - {functionCall.Name}"); + } + continue; } diff --git a/dotnet/samples/GettingStartedWithAgents/Step08_Assistant.cs b/dotnet/samples/GettingStartedWithAgents/Step08_Assistant.cs index 32c03a40a638..1e952810e51e 100644 --- a/dotnet/samples/GettingStartedWithAgents/Step08_Assistant.cs +++ b/dotnet/samples/GettingStartedWithAgents/Step08_Assistant.cs @@ -43,8 +43,8 @@ await OpenAIAssistantAgent.CreateAsync( try { await InvokeAgentAsync("Hello"); - await InvokeAgentAsync("What is the special soup?"); - await InvokeAgentAsync("What is the special drink?"); + await InvokeAgentAsync("What is the special soup and its price?"); + await InvokeAgentAsync("What is the special drink and its price?"); await InvokeAgentAsync("Thank you"); } finally diff --git a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs index 2a2a7989f116..65f483cc9a8a 100644 --- a/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs +++ b/dotnet/src/Agents/OpenAI/Internal/AssistantThreadActions.cs @@ -404,6 +404,15 @@ public static async IAsyncEnumerable InvokeStreamin { yield return toolContent; } + else + { + yield return + new StreamingChatMessageContent(AuthorRole.Assistant, null) + { + AuthorName = agent.Name, + Items = [new StreamingFunctionCallUpdateContent(detailsUpdate.ToolCallId, detailsUpdate.FunctionName, detailsUpdate.FunctionArguments)] + }; + } } else if (update is RunStepUpdate stepUpdate) { @@ -493,15 +502,16 @@ await RetrieveMessageAsync( { foreach (RunStepToolCall toolCall in step.Details.ToolCalls) { - switch (toolCall.ToolKind) + if (toolCall.ToolKind == RunStepToolCallKind.Function) + { + messages?.Add(GenerateFunctionResultContent(agent.GetName(), stepFunctionResults[step.Id], step)); + stepFunctionResults.Remove(step.Id); + break; + } + + if (toolCall.ToolKind == RunStepToolCallKind.CodeInterpreter) { - case RunStepToolCallKind.CodeInterpreter: - messages?.Add(GenerateCodeInterpreterContent(agent.GetName(), toolCall.CodeInterpreterInput, step)); - break; - case RunStepToolCallKind.Function: - messages?.Add(GenerateFunctionResultContent(agent.GetName(), stepFunctionResults[step.Id], step)); - stepFunctionResults.Remove(step.Id); - break; + messages?.Add(GenerateCodeInterpreterContent(agent.GetName(), toolCall.CodeInterpreterInput, step)); } } }