mirror of
https://github.com/microsoft/semantic-kernel.git
synced 2026-10-12 02:19:06 +00:00
.Net: Fix and add back FlowOrchestrator (#4307)
## Summary This pull request includes a set of changes to the Orchestration.Flow project. The changes include adding a new project, Experimental.Orchestration.Flow, and updating the Example FlowOrchestrator to use it. Additionally, the email plugin has been refactored and a chat completion service has been added. Unused code has been removed, and integration and unit tests have been updated. The ReActEngine and FlowExecutor classes have been refactored to use KernelArguments and FunctionResult instead of ContextVariables and Dictionary<string, string>. The FlowOrchestrator and related extensions have been added to the Experimental.Orchestration.Flow namespace. Finally, plugin configurations have been updated to use input_variables and execution_settings instead of input.parameters and completion. ## Changes - Added Experimental.Orchestration.Flow project - Added Experimental.Orchestration.Flow.IntegrationTests project - Added Experimental.Orchestration.Flow.UnitTests project - Updated Example FlowOrchestrator to use Experimental.Orchestration.Flow - Refactored email plugin to include a new EmailPluginV2 class and added chat completion service - Removed unused code related to an example of the plugin - Updated SendEmailPlugin class to use KernelArguments instead of ContextVariables - Updated IFlowExecutor interface to use FunctionResult instead of ContextVariables - Updated ChatHistorySerializerTest class to use the ChatCompletion namespace instead of the AI.ChatCompletion namespace - Added new KernelFunctions to FlowExecutor class for executing flows and steps - Refactored FlowExecutor to use KernelFunctionFactory - Refactored FlowExecutor to use KernelArguments and FunctionResult instead of ContextVariables and Dictionary<string, string> - Refactored ReActEngine to use KernelArguments instead of ContextVariablesExtensions - Added extension methods for FunctionResult and KernelArguments - Added FlowOrchestrator class to Experimental.Orchestration.Flow namespace - Modified PromptTemplateConfigExtensions.cs to set max tokens - Modified FlowOrchestrator.cs to use IKernelBuilder and IFlowStatusProvider interfaces - Updated CheckRepeatStep and CheckStartStep plugin configurations to use input_variables - Updated ReActEngine and gpt4 plugin configurations to use execution_settings - Updated ReActEngine GPT4 config.json file --- *Powered by [Microsoft Semantic Kernel](https://github.com/microsoft/semantic-kernel)* ### Motivation and Context Adopt latest interfaces and add back Experimental FlowOrchestrator. <!-- Thank you for your contribution to the semantic-kernel repo! Please help reviewers and future users, providing the following information: 1. Why is this change required? 2. What problem does it solve? 3. What scenario does it contribute to? 4. If it fixes an open issue, please link to the issue here. --> ### Description <!-- Describe your changes, the overall approach, the underlying design. These notes will help understanding how your code works. Thanks! --> ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [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 😄
This commit is contained in:
@@ -189,6 +189,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Experimental.Agents", "src\
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Experimental.Agents.UnitTests", "src\Experimental\Agents.UnitTests\Experimental.Agents.UnitTests.csproj", "{4AD80279-9AC1-476F-8103-E6CD5E4FD525}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Experimental.Orchestration.Flow", "src\Experimental\Orchestration.Flow\Experimental.Orchestration.Flow.csproj", "{B0CE8C69-EC56-4825-94AB-01CA7E8BA55B}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Experimental.Orchestration.Flow.IntegrationTests", "src\Experimental\Orchestration.Flow.IntegrationTests\Experimental.Orchestration.Flow.IntegrationTests.csproj", "{3A4B8F90-3E74-43E0-800C-84F8AA9B5BF3}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Experimental.Orchestration.Flow.UnitTests", "src\Experimental\Orchestration.Flow.UnitTests\Experimental.Orchestration.Flow.UnitTests.csproj", "{731CC542-8BE9-42D4-967D-99206EC2B310}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -439,6 +445,24 @@ Global
|
||||
{4AD80279-9AC1-476F-8103-E6CD5E4FD525}.Publish|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4AD80279-9AC1-476F-8103-E6CD5E4FD525}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4AD80279-9AC1-476F-8103-E6CD5E4FD525}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B0CE8C69-EC56-4825-94AB-01CA7E8BA55B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B0CE8C69-EC56-4825-94AB-01CA7E8BA55B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B0CE8C69-EC56-4825-94AB-01CA7E8BA55B}.Publish|Any CPU.ActiveCfg = Publish|Any CPU
|
||||
{B0CE8C69-EC56-4825-94AB-01CA7E8BA55B}.Publish|Any CPU.Build.0 = Publish|Any CPU
|
||||
{B0CE8C69-EC56-4825-94AB-01CA7E8BA55B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B0CE8C69-EC56-4825-94AB-01CA7E8BA55B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3A4B8F90-3E74-43E0-800C-84F8AA9B5BF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3A4B8F90-3E74-43E0-800C-84F8AA9B5BF3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3A4B8F90-3E74-43E0-800C-84F8AA9B5BF3}.Publish|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3A4B8F90-3E74-43E0-800C-84F8AA9B5BF3}.Publish|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3A4B8F90-3E74-43E0-800C-84F8AA9B5BF3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3A4B8F90-3E74-43E0-800C-84F8AA9B5BF3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{731CC542-8BE9-42D4-967D-99206EC2B310}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{731CC542-8BE9-42D4-967D-99206EC2B310}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{731CC542-8BE9-42D4-967D-99206EC2B310}.Publish|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{731CC542-8BE9-42D4-967D-99206EC2B310}.Publish|Any CPU.Build.0 = Debug|Any CPU
|
||||
{731CC542-8BE9-42D4-967D-99206EC2B310}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{731CC542-8BE9-42D4-967D-99206EC2B310}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -504,6 +528,9 @@ Global
|
||||
{24503383-A8C4-4255-9998-28D70FE8E99A} = {0247C2C9-86C3-45BA-8873-28B0948EDC0C}
|
||||
{5438D1E3-E03D-444B-BBBA-478F93161AA8} = {A2357CF8-3BB9-45A1-93F1-B366C9B63658}
|
||||
{4AD80279-9AC1-476F-8103-E6CD5E4FD525} = {A2357CF8-3BB9-45A1-93F1-B366C9B63658}
|
||||
{B0CE8C69-EC56-4825-94AB-01CA7E8BA55B} = {A2357CF8-3BB9-45A1-93F1-B366C9B63658}
|
||||
{3A4B8F90-3E74-43E0-800C-84F8AA9B5BF3} = {A2357CF8-3BB9-45A1-93F1-B366C9B63658}
|
||||
{731CC542-8BE9-42D4-967D-99206EC2B310} = {A2357CF8-3BB9-45A1-93F1-B366C9B63658}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {FBDC56A3-86AD-4323-AA0F-201E59123B83}
|
||||
|
||||
+79
-60
@@ -10,12 +10,11 @@ using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.AI;
|
||||
using Microsoft.SemanticKernel.AI.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
||||
using Microsoft.SemanticKernel.Experimental.Orchestration;
|
||||
using Microsoft.SemanticKernel.Memory;
|
||||
using Microsoft.SemanticKernel.Plugins.Core;
|
||||
using Microsoft.SemanticKernel.Plugins.Memory;
|
||||
using Microsoft.SemanticKernel.Plugins.Web;
|
||||
using Microsoft.SemanticKernel.Plugins.Web.Bing;
|
||||
using NCalcPlugins;
|
||||
@@ -24,13 +23,13 @@ using NCalcPlugins;
|
||||
* This example shows how to use FlowOrchestrator to execute a given flow with interaction with client.
|
||||
*/
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static class Example63_FlowOrchestrator
|
||||
public static class Example74_FlowOrchestrator
|
||||
{
|
||||
private static readonly Flow s_flow = FlowSerializer.DeserializeFromYaml(@"
|
||||
name: FlowOrchestrator_Example_Flow
|
||||
goal: answer question and send email
|
||||
steps:
|
||||
- goal: What is the tallest mountain on Earth? How tall is it divided by 2?
|
||||
- goal: What is the tallest mountain in Asia? How tall is it divided by 2?
|
||||
plugins:
|
||||
- WebSearchEnginePlugin
|
||||
- LanguageCalculatorPlugin
|
||||
@@ -93,7 +92,7 @@ provides:
|
||||
sw.Start();
|
||||
Console.WriteLine("Flow: " + s_flow.Name);
|
||||
Console.WriteLine("Please type the question you'd like to ask");
|
||||
ContextVariables? result;
|
||||
FunctionResult? result = null;
|
||||
string? goal = null;
|
||||
do
|
||||
{
|
||||
@@ -108,21 +107,36 @@ provides:
|
||||
|
||||
if (string.IsNullOrEmpty(goal))
|
||||
{
|
||||
goal = input;
|
||||
s_flow.Steps.First().Goal = input;
|
||||
}
|
||||
|
||||
result = await orchestrator.ExecuteFlowAsync(s_flow, sessionId, input);
|
||||
Console.WriteLine("Assistant: " + result.ToString());
|
||||
try
|
||||
{
|
||||
result = await orchestrator.ExecuteFlowAsync(s_flow, sessionId, input);
|
||||
}
|
||||
catch (KernelException ex)
|
||||
{
|
||||
Console.WriteLine("Error: " + ex.Message);
|
||||
Console.WriteLine("Please try again.");
|
||||
continue;
|
||||
}
|
||||
|
||||
var responses = result.GetValue<List<string>>()!;
|
||||
foreach (var response in responses)
|
||||
{
|
||||
Console.WriteLine("Assistant: " + response);
|
||||
}
|
||||
|
||||
if (result.IsComplete(s_flow))
|
||||
{
|
||||
Console.WriteLine("\tEmail Address: " + result["email_addresses"]);
|
||||
Console.WriteLine("\tEmail Payload: " + result["email"]);
|
||||
Console.WriteLine("\tEmail Address: " + result.Metadata!["email_addresses"]);
|
||||
Console.WriteLine("\tEmail Payload: " + result.Metadata!["email"]);
|
||||
|
||||
Console.WriteLine("Flow completed, exiting");
|
||||
break;
|
||||
}
|
||||
} while (!string.IsNullOrEmpty(result.ToString()) && result.ToString() != "[]");
|
||||
} while (result == null || result.GetValue<List<string>>()?.Count > 0);
|
||||
|
||||
Console.WriteLine("Time Taken: " + sw.Elapsed);
|
||||
Console.WriteLine("*****************************************************");
|
||||
@@ -159,8 +173,8 @@ provides:
|
||||
var result = await orchestrator.ExecuteFlowAsync(s_flow, sessionId, question).ConfigureAwait(false);
|
||||
|
||||
Console.WriteLine("Question: " + question);
|
||||
Console.WriteLine("Answer: " + result["answer"]);
|
||||
Console.WriteLine("Assistant: " + result.ToString());
|
||||
Console.WriteLine("Answer: " + result.Metadata!["answer"]);
|
||||
Console.WriteLine("Assistant: " + result.GetValue<List<string>>()!.Single());
|
||||
|
||||
string[] userInputs = new[]
|
||||
{
|
||||
@@ -175,7 +189,11 @@ provides:
|
||||
{
|
||||
Console.WriteLine($"User: {t}");
|
||||
result = await orchestrator.ExecuteFlowAsync(s_flow, sessionId, t).ConfigureAwait(false);
|
||||
Console.WriteLine("Assistant: " + result.ToString());
|
||||
var responses = result.GetValue<List<string>>()!;
|
||||
foreach (var response in responses)
|
||||
{
|
||||
Console.WriteLine("Assistant: " + response);
|
||||
}
|
||||
|
||||
if (result.IsComplete(s_flow))
|
||||
{
|
||||
@@ -183,8 +201,8 @@ provides:
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("\tEmail Address: " + result["email_addresses"]);
|
||||
Console.WriteLine("\tEmail Payload: " + result["email"]);
|
||||
Console.WriteLine("\tEmail Address: " + result.Metadata!["email_addresses"]);
|
||||
Console.WriteLine("\tEmail Payload: " + result.Metadata!["email"]);
|
||||
|
||||
Console.WriteLine("Time Taken: " + sw.Elapsed);
|
||||
Console.WriteLine("*****************************************************");
|
||||
@@ -200,16 +218,15 @@ provides:
|
||||
return config;
|
||||
}
|
||||
|
||||
private static KernelBuilder GetKernelBuilder(ILoggerFactory loggerFactory)
|
||||
private static IKernelBuilder GetKernelBuilder(ILoggerFactory loggerFactory)
|
||||
{
|
||||
var builder = Kernel.CreateBuilder();
|
||||
|
||||
return builder
|
||||
.WithAzureOpenAIChatCompletion(
|
||||
.AddAzureOpenAIChatCompletion(
|
||||
TestConfiguration.AzureOpenAI.ChatDeploymentName,
|
||||
TestConfiguration.AzureOpenAI.Endpoint,
|
||||
TestConfiguration.AzureOpenAI.ApiKey)
|
||||
.WithLoggerFactory(loggerFactory);
|
||||
TestConfiguration.AzureOpenAI.ApiKey);
|
||||
}
|
||||
|
||||
public sealed class ChatPlugin
|
||||
@@ -223,10 +240,12 @@ provides:
|
||||
The email should conform the regex: {EmailRegex}
|
||||
|
||||
If I cannot answer, say that I don't know.
|
||||
Do not expose the regex unless asked.
|
||||
|
||||
# IMPORTANT
|
||||
Do not expose the regex in your response.
|
||||
";
|
||||
|
||||
private readonly IChatCompletion _chat;
|
||||
private readonly IChatCompletionService _chat;
|
||||
|
||||
private int MaxTokens { get; set; } = 256;
|
||||
|
||||
@@ -234,7 +253,7 @@ Do not expose the regex unless asked.
|
||||
|
||||
public ChatPlugin(Kernel kernel)
|
||||
{
|
||||
this._chat = kernel.GetService<IChatCompletion>();
|
||||
this._chat = kernel.GetRequiredService<IChatCompletionService>();
|
||||
this._chatRequestSettings = new OpenAIPromptExecutionSettings
|
||||
{
|
||||
MaxTokens = this.MaxTokens,
|
||||
@@ -247,29 +266,28 @@ Do not expose the regex unless asked.
|
||||
[Description("Useful to assist in configuration of email address, must be called after email provided")]
|
||||
public async Task<string> CollectEmailAsync(
|
||||
[Description("The email address provided by the user, pass no matter what the value is")]
|
||||
string email_address,
|
||||
ContextVariables variables)
|
||||
string email_addresses,
|
||||
KernelArguments arguments)
|
||||
{
|
||||
var chat = this._chat.CreateNewChat(SystemPrompt);
|
||||
var chat = new ChatHistory(SystemPrompt);
|
||||
chat.AddUserMessage(Goal);
|
||||
|
||||
ChatHistory? chatHistory = variables.GetChatHistory();
|
||||
if (chatHistory?.Any() ?? false)
|
||||
ChatHistory? chatHistory = arguments.GetChatHistory();
|
||||
if (chatHistory?.Count > 0)
|
||||
{
|
||||
chat.AddRange(chatHistory);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(email) && IsValidEmail(email))
|
||||
if (!string.IsNullOrEmpty(email_addresses) && IsValidEmail(email_addresses))
|
||||
{
|
||||
variables["email_addresses"] = email;
|
||||
|
||||
return "Thanks for providing the info, the following email would be used in subsequent steps: " + email;
|
||||
return "Thanks for providing the info, the following email would be used in subsequent steps: " + email_addresses;
|
||||
}
|
||||
|
||||
variables["email_addresses"] = string.Empty;
|
||||
variables.PromptInput();
|
||||
arguments["email_addresses"] = string.Empty;
|
||||
arguments.PromptInput();
|
||||
|
||||
return await this._chat.GenerateMessageAsync(chat, this._chatRequestSettings).ConfigureAwait(false);
|
||||
var response = await this._chat.GetChatMessageContentAsync(chat).ConfigureAwait(false);
|
||||
return response.Content ?? string.Empty;
|
||||
}
|
||||
|
||||
private static bool IsValidEmail(string email)
|
||||
@@ -282,24 +300,26 @@ Do not expose the regex unless asked.
|
||||
|
||||
public sealed class EmailPluginV2
|
||||
{
|
||||
private readonly JsonSerializerOptions _serializerOptions = new() { WriteIndented = true };
|
||||
|
||||
[KernelFunction]
|
||||
[Description("Send email")]
|
||||
public string SendEmail(
|
||||
[Description("target email addresses")]
|
||||
string email_addresses,
|
||||
string emailAddresses,
|
||||
[Description("answer, which is going to be the email content")]
|
||||
string answer,
|
||||
ContextVariables variables)
|
||||
KernelArguments arguments)
|
||||
{
|
||||
var contract = new Email()
|
||||
{
|
||||
Address = email_addresses,
|
||||
Address = emailAddresses,
|
||||
Content = answer,
|
||||
};
|
||||
|
||||
// for demo purpose only
|
||||
string emailPayload = JsonSerializer.Serialize(contract, new JsonSerializerOptions() { WriteIndented = true });
|
||||
variables["email"] = emailPayload;
|
||||
string emailPayload = JsonSerializer.Serialize(contract, this._serializerOptions);
|
||||
arguments["email"] = emailPayload;
|
||||
|
||||
return "Here's the API contract I will post to mail server: " + emailPayload;
|
||||
}
|
||||
@@ -312,28 +332,28 @@ Do not expose the regex unless asked.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//*****************************************************
|
||||
//Executing RunExampleAsync
|
||||
//Flow: FlowOrchestrator_Example_Flow
|
||||
//Question: What is the tallest mountain on Earth? How tall is it divided by 2?
|
||||
//Answer: The tallest mountain on Earth is Mount Everest, which is 29,031.69 feet (8,848.86 meters) above sea level. Half of its height is 14,515.845 feet (4,424.43 meters).
|
||||
//Assistant: ["Please provide a valid email address."]
|
||||
//Question: What is the tallest mountain in Asia? How tall is it divided by 2?
|
||||
//Answer: The tallest mountain in Asia is Mount Everest and its height divided by 2 is 14516.
|
||||
//Assistant: Please provide a valid email address.
|
||||
//User: my email is bad*email&address
|
||||
//Assistant: ["I\u0027m sorry, but \u0022bad*email\u0026address\u0022 is not a valid email address. A valid email address should have the format \[email protected]\u0022."]
|
||||
//Assistant: I'm sorry, but "bad*email&address" does not conform to the standard email format. Please provide a valid email address.
|
||||
//User: my email is [email protected]
|
||||
//Assistant: ["Do you want to send it to another email address?"]
|
||||
//Assistant: Did the user indicate whether they want to repeat the previous step?
|
||||
//User: yes
|
||||
//Assistant: ["Please provide a valid email address."]
|
||||
//Assistant: Please enter a valid email address.
|
||||
//User: I also want to notify [email protected]
|
||||
//Assistant: ["Do you want to send it to another email address?"]
|
||||
//Assistant: Did the user indicate whether they want to repeat the previous step?
|
||||
//User: no I don't need notify any more address
|
||||
//Assistant: []
|
||||
// Email Address: ["[email protected]","[email protected]"]
|
||||
// Email Payload: {
|
||||
// "Address": "[\[email protected]\u0022,\[email protected]\u0022]",
|
||||
// "Content": "The tallest mountain on Earth is Mount Everest, which is 29,031.69 feet (8,848.86 meters) above sea level. Half of its height is 14,515.845 feet (4,424.43 meters)."
|
||||
// "Content": "The tallest mountain in Asia is Mount Everest and its height divided by 2 is 14516."
|
||||
//}
|
||||
//Time Taken: 00:00:24.2450785
|
||||
//Time Taken: 00:00:21.9681103
|
||||
//*****************************************************
|
||||
|
||||
//*****************************************************
|
||||
@@ -341,19 +361,18 @@ Do not expose the regex unless asked.
|
||||
//Flow: FlowOrchestrator_Example_Flow
|
||||
//Please type the question you'd like to ask
|
||||
//User:
|
||||
//What is the length of the longest river in ireland?
|
||||
//Assistant: ["Please provide a valid email address."]
|
||||
//What is the tallest mountain in Asia? How tall is it divided by 2?
|
||||
//Assistant: Please enter a valid email address.
|
||||
//User:
|
||||
//foo@bar.com
|
||||
//Assistant: ["Do you want to send it to another email address?"]
|
||||
//foo@hotmail.com
|
||||
//Assistant: Do you want to send it to another email address?
|
||||
//User:
|
||||
//no
|
||||
//Assistant: []
|
||||
// Email Address: ["[email protected]"]
|
||||
//no I don't
|
||||
// Email Address: ["[email protected]"]
|
||||
// Email Payload: {
|
||||
// "Address": "[\u0022foo@bar.com\u0022]",
|
||||
// "Content": "The longest river in Ireland is the River Shannon with a length of 360 km (223 miles)."
|
||||
// "Address": "[\u0022foo@hotmail.com\u0022]",
|
||||
// "Content": "The tallest mountain in Asia is Mount Everest and its height divided by 2 is 14515.845."
|
||||
//}
|
||||
//Flow completed, exiting
|
||||
//Time Taken: 00:00:44.0215449
|
||||
//Time Taken: 00:01:47.0752303
|
||||
//*****************************************************
|
||||
@@ -16,7 +16,6 @@
|
||||
<Compile Remove="Example28_ActionPlanner.cs" />
|
||||
<Compile Remove="Example31_CustomPlanner.cs" />
|
||||
<Compile Remove="Example51_StepwisePlanner.cs" />
|
||||
<Compile Remove="Example63_FlowOrchestrator.cs" />
|
||||
<Compile Remove="RepoUtils\PlanExtensions.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@@ -57,6 +56,7 @@
|
||||
<ProjectReference Include="..\..\src\Connectors\Connectors.Memory.Redis\Connectors.Memory.Redis.csproj" />
|
||||
<ProjectReference Include="..\..\src\Connectors\Connectors.Memory.Pinecone\Connectors.Memory.Pinecone.csproj" />
|
||||
<ProjectReference Include="..\..\src\Experimental\Agents\Experimental.Agents.csproj" />
|
||||
<ProjectReference Include="..\..\src\Experimental\Orchestration.Flow\Experimental.Orchestration.Flow.csproj" />
|
||||
<ProjectReference Include="..\..\src\Extensions\PromptTemplates.Handlebars\PromptTemplates.Handlebars.csproj" />
|
||||
<ProjectReference Include="..\..\src\Planners\Planners.Handlebars\Planners.Handlebars.csproj" />
|
||||
<ProjectReference Include="..\..\src\Planners\Planners.OpenAI\Planners.OpenAI.csproj" />
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.AI;
|
||||
using Microsoft.SemanticKernel.AI.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Connectors.OpenAI;
|
||||
using Microsoft.SemanticKernel.Experimental.Orchestration;
|
||||
|
||||
namespace SemanticKernel.Experimental.Orchestration.Flow.IntegrationTests;
|
||||
|
||||
@@ -26,7 +25,7 @@ If I cannot answer, say that I don't know.
|
||||
Do not expose the regex unless asked.
|
||||
";
|
||||
|
||||
private readonly IChatCompletion _chat;
|
||||
private readonly IChatCompletionService _chat;
|
||||
|
||||
private int MaxTokens { get; set; } = 256;
|
||||
|
||||
@@ -34,7 +33,7 @@ Do not expose the regex unless asked.
|
||||
|
||||
public CollectEmailPlugin(Kernel kernel)
|
||||
{
|
||||
this._chat = kernel.GetService<IChatCompletion>();
|
||||
this._chat = kernel.GetRequiredService<IChatCompletionService>();
|
||||
this._chatRequestSettings = new OpenAIPromptExecutionSettings
|
||||
{
|
||||
MaxTokens = this.MaxTokens,
|
||||
@@ -47,29 +46,33 @@ Do not expose the regex unless asked.
|
||||
[Description("Useful to assist in configuration of email address, must be called after email provided")]
|
||||
public async Task<string> CollectEmailAsync(
|
||||
[Description("The email address provided by the user, pass no matter what the value is")]
|
||||
// ReSharper disable once InconsistentNaming
|
||||
#pragma warning disable CA1707 // Identifiers should not contain underscores
|
||||
string email_address,
|
||||
ContextVariables variables)
|
||||
#pragma warning restore CA1707 // Identifiers should not contain underscores
|
||||
KernelArguments arguments)
|
||||
{
|
||||
var chat = this._chat.CreateNewChat(SystemPrompt);
|
||||
var chat = new ChatHistory(SystemPrompt);
|
||||
chat.AddUserMessage(Goal);
|
||||
|
||||
ChatHistory? chatHistory = variables.GetChatHistory();
|
||||
if (chatHistory?.Any() ?? false)
|
||||
ChatHistory? chatHistory = arguments.GetChatHistory();
|
||||
if (chatHistory?.Count > 0)
|
||||
{
|
||||
chat.AddRange(chatHistory);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(email_address) && IsValidEmail(email_address))
|
||||
{
|
||||
variables["email_address"] = email_address;
|
||||
|
||||
return "Thanks for providing the info, the following email would be used in subsequent steps: " + email_address;
|
||||
}
|
||||
|
||||
// invalid email, prompt user to provide a valid email
|
||||
variables["email_address"] = string.Empty;
|
||||
variables.PromptInput();
|
||||
return await this._chat.GenerateMessageAsync(chat, this._chatRequestSettings).ConfigureAwait(false);
|
||||
arguments["email_address"] = string.Empty;
|
||||
arguments.PromptInput();
|
||||
|
||||
var response = await this._chat.GetChatMessageContentAsync(chat).ConfigureAwait(false);
|
||||
|
||||
return response.Content ?? string.Empty;
|
||||
}
|
||||
|
||||
private static bool IsValidEmail(string email)
|
||||
|
||||
+2
-1
@@ -6,7 +6,7 @@
|
||||
<RollForward>LatestMajor</RollForward>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<IsPackable>false</IsPackable>
|
||||
<NoWarn>CA2007,VSTHRD111,SKEXP0102</NoWarn>
|
||||
<NoWarn>CA2007,VSTHRD111,SKEXP0102,SKEXP0052,SKEXP0054</NoWarn>
|
||||
<UserSecretsId>b7762d10-e29b-4bb1-8b74-b6d69a667dd4</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
@@ -32,6 +32,7 @@
|
||||
<ProjectReference Include="..\..\Connectors\Connectors.OpenAI\Connectors.OpenAI.csproj" />
|
||||
<ProjectReference Include="..\..\Plugins\Plugins.Memory\Plugins.Memory.csproj" />
|
||||
<ProjectReference Include="..\..\Plugins\Plugins.Web\Plugins.Web.csproj" />
|
||||
<ProjectReference Include="..\Orchestration.Flow\Experimental.Orchestration.Flow.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="testsettings.json">
|
||||
|
||||
+10
-7
@@ -2,11 +2,13 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Plugins.Memory;
|
||||
using Microsoft.SemanticKernel.Experimental.Orchestration;
|
||||
using Microsoft.SemanticKernel.Memory;
|
||||
using Microsoft.SemanticKernel.Plugins.Web;
|
||||
using Microsoft.SemanticKernel.Plugins.Web.Bing;
|
||||
using SemanticKernel.Experimental.Orchestration.Flow.IntegrationTests.TestSettings;
|
||||
@@ -56,7 +58,7 @@ public sealed class FlowOrchestratorTests : IDisposable
|
||||
Microsoft.SemanticKernel.Experimental.Orchestration.Flow flow = FlowSerializer.DeserializeFromYaml(@"
|
||||
goal: answer question and sent email
|
||||
steps:
|
||||
- goal: What is the tallest mountain on Earth? How tall is it divided by 2?
|
||||
- goal: What is the tallest mountain in Asia? How tall is it divided by 2?
|
||||
plugins:
|
||||
- WebSearchEnginePlugin
|
||||
provides:
|
||||
@@ -83,28 +85,29 @@ steps:
|
||||
config: new FlowOrchestratorConfig() { MaxStepIterations = 20 });
|
||||
|
||||
// Act
|
||||
var result = await flowOrchestrator.ExecuteFlowAsync(flow, sessionId, "What is the tallest mountain on Earth? How tall is it divided by 2?");
|
||||
var result = await flowOrchestrator.ExecuteFlowAsync(flow, sessionId, "What is the tallest mountain in Asia? How tall is it divided by 2?");
|
||||
|
||||
// Assert
|
||||
// Loose assertion -- make sure that the plan was executed and pause when it needs interact with user to get more input
|
||||
Assert.Contains("email", result.ToString(), StringComparison.InvariantCultureIgnoreCase);
|
||||
var response = result.GetValue<List<string>>()!.First();
|
||||
Assert.Contains("email", response, StringComparison.InvariantCultureIgnoreCase);
|
||||
|
||||
// Act
|
||||
result = await flowOrchestrator.ExecuteFlowAsync(flow, sessionId, $"my email is {dummyAddress}");
|
||||
|
||||
// Assert
|
||||
var emailPayload = result["email"];
|
||||
var emailPayload = result.Metadata!["email"] as string;
|
||||
Assert.Contains(dummyAddress, emailPayload, StringComparison.InvariantCultureIgnoreCase);
|
||||
Assert.Contains("Everest", emailPayload, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
private KernelBuilder InitializeKernelBuilder()
|
||||
private IKernelBuilder InitializeKernelBuilder()
|
||||
{
|
||||
AzureOpenAIConfiguration? azureOpenAIConfiguration = this._configuration.GetSection("AzureOpenAI").Get<AzureOpenAIConfiguration>();
|
||||
Assert.NotNull(azureOpenAIConfiguration);
|
||||
|
||||
return Kernel.CreateBuilder()
|
||||
.WithAzureOpenAIChatCompletion(
|
||||
.AddAzureOpenAIChatCompletion(
|
||||
deploymentName: azureOpenAIConfiguration.ChatDeploymentName!,
|
||||
endpoint: azureOpenAIConfiguration.Endpoint,
|
||||
apiKey: azureOpenAIConfiguration.ApiKey);
|
||||
|
||||
@@ -13,9 +13,12 @@ public sealed class SendEmailPlugin
|
||||
[KernelFunction]
|
||||
[Description("Send email")]
|
||||
public string SendEmail(
|
||||
// ReSharper disable once InconsistentNaming
|
||||
#pragma warning disable CA1707 // Identifiers should not contain underscores
|
||||
string email_address,
|
||||
#pragma warning restore CA1707 // Identifiers should not contain underscores
|
||||
string answer,
|
||||
ContextVariables variables)
|
||||
KernelArguments variables)
|
||||
{
|
||||
var contract = new Email()
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel.AI.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Experimental.Orchestration.Execution;
|
||||
using Xunit;
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ public interface IFlowExecutor
|
||||
/// <param name="flow">Flow</param>
|
||||
/// <param name="sessionId">Session id, which is used to track the execution status.</param>
|
||||
/// <param name="input">The input from client to continue the execution.</param>
|
||||
/// <param name="contextVariables">The request context variables </param>
|
||||
/// <param name="kernelArguments">The request kernel arguments </param>
|
||||
/// <returns>The execution context</returns>
|
||||
Task<ContextVariables> ExecuteAsync(Flow flow, string sessionId, string input, ContextVariables contextVariables);
|
||||
Task<FunctionResult> ExecuteFlowAsync(Flow flow, string sessionId, string input, KernelArguments kernelArguments);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SemanticKernel.AI.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Experimental.Orchestration.Execution;
|
||||
|
||||
namespace Microsoft.SemanticKernel.Experimental.Orchestration.Abstractions;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using Microsoft.SemanticKernel.AI.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
|
||||
namespace Microsoft.SemanticKernel.Experimental.Orchestration.Execution;
|
||||
|
||||
|
||||
@@ -7,8 +7,10 @@ using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.SemanticKernel.AI.ChatCompletion;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Experimental.Orchestration.Abstractions;
|
||||
using Microsoft.SemanticKernel.Experimental.Orchestration.Extensions;
|
||||
|
||||
namespace Microsoft.SemanticKernel.Experimental.Orchestration.Execution;
|
||||
|
||||
@@ -94,6 +96,16 @@ internal class FlowExecutor : IFlowExecutor
|
||||
/// </summary>
|
||||
private readonly KernelFunction _checkStartStepFunction;
|
||||
|
||||
/// <summary>
|
||||
/// ExecuteFlow function
|
||||
/// </summary>
|
||||
private readonly KernelFunction _executeFlowFunction;
|
||||
|
||||
/// <summary>
|
||||
/// ExecuteStep function
|
||||
/// </summary>
|
||||
private readonly KernelFunction _executeStepFunction;
|
||||
|
||||
internal FlowExecutor(IKernelBuilder kernelBuilder, IFlowStatusProvider statusProvider, Dictionary<object, string?> globalPluginCollection, FlowOrchestratorConfig? config = null)
|
||||
{
|
||||
this._kernelBuilder = kernelBuilder;
|
||||
@@ -105,34 +117,35 @@ internal class FlowExecutor : IFlowExecutor
|
||||
this._flowStatusProvider = statusProvider;
|
||||
this._globalPluginCollection = globalPluginCollection;
|
||||
|
||||
var checkRepeatStepPrompt = EmbeddedResource.Read("Plugins.CheckRepeatStep.skprompt.txt")!;
|
||||
var checkRepeatStepConfig = PromptTemplateConfig.FromJson(EmbeddedResource.Read("Plugins.CheckRepeatStep.config.json")!);
|
||||
this._checkRepeatStepFunction = CreateSemanticFunction(this._systemKernel, "CheckRepeatStep", checkRepeatStepPrompt, checkRepeatStepConfig);
|
||||
checkRepeatStepConfig.Template = EmbeddedResource.Read("Plugins.CheckRepeatStep.skprompt.txt")!;
|
||||
this._checkRepeatStepFunction = KernelFunctionFactory.CreateFromPrompt(checkRepeatStepConfig);
|
||||
|
||||
var checkStartStepPrompt = EmbeddedResource.Read("Plugins.CheckStartStep.skprompt.txt")!;
|
||||
var checkStartStepConfig = PromptTemplateConfig.FromJson(EmbeddedResource.Read("Plugins.CheckStartStep.config.json")!);
|
||||
this._checkStartStepFunction = CreateSemanticFunction(this._systemKernel, "CheckStartStep", checkStartStepPrompt, checkStartStepConfig);
|
||||
|
||||
this._systemKernel.Plugins.Add(new KernelPlugin(RestrictedPluginName, new[]
|
||||
{
|
||||
this._checkRepeatStepFunction,
|
||||
this._checkStartStepFunction,
|
||||
}));
|
||||
checkStartStepConfig.Template = EmbeddedResource.Read("Plugins.CheckStartStep.skprompt.txt")!;
|
||||
this._checkStartStepFunction = KernelFunctionFactory.CreateFromPrompt(checkStartStepConfig);
|
||||
|
||||
this._config.ExcludedPlugins.Add(RestrictedPluginName);
|
||||
this._reActEngine = new ReActEngine(this._systemKernel, this._logger, this._config);
|
||||
|
||||
this._executeFlowFunction = KernelFunctionFactory.CreateFromMethod(this.ExecuteFlowAsync, "ExecuteFlow", "Execute a flow");
|
||||
this._executeStepFunction = KernelFunctionFactory.CreateFromMethod(this.ExecuteStepAsync, "ExecuteStep", "Execute a flow step");
|
||||
}
|
||||
|
||||
public async Task<ContextVariables> ExecuteAsync(Flow flow, string sessionId, string input, ContextVariables contextVariables)
|
||||
public async Task<FunctionResult> ExecuteFlowAsync(Flow flow, string sessionId, string input, KernelArguments kernelArguments)
|
||||
{
|
||||
Verify.NotNull(flow, nameof(flow));
|
||||
|
||||
this._logger?.LogInformation("Executing flow {FlowName} with sessionId={SessionId}.", flow.Name, sessionId);
|
||||
if (this._logger.IsEnabled(LogLevel.Information))
|
||||
{
|
||||
this._logger.LogInformation("Executing flow {FlowName} with sessionId={SessionId}.", flow.Name, sessionId);
|
||||
}
|
||||
|
||||
var sortedSteps = flow.SortSteps();
|
||||
|
||||
var rootContext = contextVariables.Clone();
|
||||
var rootContext = new KernelArguments(kernelArguments);
|
||||
|
||||
// populate persisted state variables
|
||||
// populate persisted state arguments
|
||||
ExecutionState executionState = await this._flowStatusProvider.GetExecutionStateAsync(sessionId).ConfigureAwait(false);
|
||||
List<string> outputs = new();
|
||||
|
||||
@@ -143,26 +156,26 @@ internal class FlowExecutor : IFlowExecutor
|
||||
|
||||
foreach (var kv in executionState.Variables)
|
||||
{
|
||||
rootContext.Set(kv.Key, kv.Value);
|
||||
rootContext[kv.Key] = kv.Value;
|
||||
}
|
||||
|
||||
this.ValidateStep(step, rootContext);
|
||||
|
||||
// init step execution state
|
||||
string stepKey = $"{stepIndex}_{step.Goal}";
|
||||
if (!executionState.StepStates.ContainsKey(stepKey))
|
||||
if (!executionState.StepStates.TryGetValue(stepKey, out ExecutionState.StepExecutionState? stepState))
|
||||
{
|
||||
executionState.StepStates.Add(stepKey, new ExecutionState.StepExecutionState());
|
||||
stepState = new ExecutionState.StepExecutionState();
|
||||
executionState.StepStates.Add(stepKey, stepState);
|
||||
}
|
||||
|
||||
ExecutionState.StepExecutionState stepState = executionState.StepStates[stepKey];
|
||||
var stepId = $"{stepKey}_{stepState.ExecutionCount}";
|
||||
|
||||
var continueLoop = false;
|
||||
var completed = step.Provides.All(_ => executionState.Variables.ContainsKey(_));
|
||||
if (!completed)
|
||||
{
|
||||
// On the first iteration of an Optional or ZeroOrMore step, we need to check whether the user wants to start the stepstep
|
||||
// On the first iteration of an Optional or ZeroOrMore step, we need to check whether the user wants to start the step
|
||||
if (step.CompletionType is CompletionType.Optional or CompletionType.ZeroOrMore && stepState.Status == ExecutionState.Status.NotStarted)
|
||||
{
|
||||
RepeatOrStartStepResult? startStep = await this.CheckStartStepAsync(rootContext, step, sessionId, stepId, input).ConfigureAwait(false);
|
||||
@@ -183,7 +196,11 @@ internal class FlowExecutor : IFlowExecutor
|
||||
{
|
||||
stepState.Status = ExecutionState.Status.InProgress;
|
||||
await this._flowStatusProvider.SaveExecutionStateAsync(sessionId, executionState).ConfigureAwait(false);
|
||||
this._logger?.LogInformation("Need to start step {StepIndex} for iteration={Iteration}, goal={StepGoal}.", stepIndex, stepState.ExecutionCount, step.Goal);
|
||||
|
||||
if (this._logger?.IsEnabled(LogLevel.Information) ?? false)
|
||||
{
|
||||
this._logger.LogInformation("Need to start step {StepIndex} for iteration={Iteration}, goal={StepGoal}.", stepIndex, stepState.ExecutionCount, step.Goal);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -194,35 +211,43 @@ internal class FlowExecutor : IFlowExecutor
|
||||
}
|
||||
|
||||
await this.CompleteStepAsync(rootContext, sessionId, executionState, step, stepState).ConfigureAwait(false);
|
||||
this._logger?.LogInformation("Completed step {StepIndex} with iteration={Iteration}, goal={StepGoal}.", stepIndex, stepState.ExecutionCount, step.Goal);
|
||||
|
||||
if (this._logger?.IsEnabled(LogLevel.Information) ?? false)
|
||||
{
|
||||
this._logger.LogInformation("Completed step {StepIndex} with iteration={Iteration}, goal={StepGoal}.", stepIndex, stepState.ExecutionCount, step.Goal);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// execute step
|
||||
this._logger?.LogInformation(
|
||||
"Executing step {StepIndex} for iteration={Iteration}, goal={StepGoal}, input={Input}.", stepIndex,
|
||||
stepState.ExecutionCount, step.Goal, input);
|
||||
if (this._logger?.IsEnabled(LogLevel.Information) ?? false)
|
||||
{
|
||||
this._logger.LogInformation(
|
||||
"Executing step {StepIndex} for iteration={Iteration}, goal={StepGoal}, input={Input}.", stepIndex,
|
||||
stepState.ExecutionCount, step.Goal, input);
|
||||
}
|
||||
|
||||
Kernel stepKernel = this._kernelBuilder.Build();
|
||||
var stepVariables = new ContextVariables();
|
||||
var stepArguments = new KernelArguments();
|
||||
foreach (var key in step.Requires)
|
||||
{
|
||||
stepVariables.Set(key, rootContext[key]);
|
||||
stepArguments[key] = rootContext[key];
|
||||
}
|
||||
|
||||
foreach (var key in step.Passthrough)
|
||||
{
|
||||
if (rootContext.TryGetValue(key, out var val))
|
||||
{
|
||||
stepVariables.Set(key, val);
|
||||
stepArguments[key] = val;
|
||||
}
|
||||
}
|
||||
|
||||
ContextVariables? stepResult;
|
||||
FunctionResult? stepResult;
|
||||
if (step is Flow flowStep)
|
||||
{
|
||||
stepResult = await this.ExecuteAsync(flowStep, $"{sessionId}_{stepId}", input, stepVariables).ConfigureAwait(false);
|
||||
stepResult = await this.ExecuteFlowAsync(flowStep, $"{sessionId}_{stepId}", input, stepArguments).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -232,7 +257,7 @@ internal class FlowExecutor : IFlowExecutor
|
||||
stepKernel.ImportPluginFromObject(plugin, plugin.GetType().Name);
|
||||
}
|
||||
|
||||
stepResult = await this.ExecuteStepAsync(step, sessionId, stepId, input, stepKernel, stepVariables).ConfigureAwait(false);
|
||||
stepResult = await this.ExecuteStepAsync(step, sessionId, stepId, input, stepKernel, stepArguments).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(stepResult.ToString()) && (stepResult.IsPromptInput() || stepResult.IsTerminateFlow()))
|
||||
@@ -247,60 +272,63 @@ internal class FlowExecutor : IFlowExecutor
|
||||
outputs.Add(stepResult.ToString());
|
||||
}
|
||||
}
|
||||
else if (stepResult.TryGetValue(Constants.ChatPluginVariables.ExitLoopName, out var exitResponse))
|
||||
else if (stepResult.TryGetExitLoopResponse(out string? exitResponse))
|
||||
{
|
||||
stepState.Status = ExecutionState.Status.Completed;
|
||||
foreach (var variable in step.Provides)
|
||||
{
|
||||
if (!stepResult.ContainsKey(variable))
|
||||
{
|
||||
stepResult[variable] = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
var metadata = stepResult.Metadata!
|
||||
.Where(kvp => step.Provides.Contains(kvp.Key))
|
||||
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
||||
stepResult = new FunctionResult(stepResult.Function, stepResult.GetValue<object>(), metadata: metadata);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(exitResponse))
|
||||
{
|
||||
outputs.Add(exitResponse);
|
||||
outputs.Add(exitResponse!);
|
||||
}
|
||||
|
||||
this._logger?.LogInformation("Exiting loop for step {StepIndex} with iteration={Iteration}, goal={StepGoal}.", stepIndex, stepState.ExecutionCount, step.Goal);
|
||||
if (this._logger?.IsEnabled(LogLevel.Information) ?? false)
|
||||
{
|
||||
this._logger.LogInformation("Exiting loop for step {StepIndex} with iteration={Iteration}, goal={StepGoal}.", stepIndex, stepState.ExecutionCount, step.Goal);
|
||||
}
|
||||
}
|
||||
else if (stepResult.IsContinueLoop())
|
||||
{
|
||||
continueLoop = true;
|
||||
this._logger?.LogInformation("Continuing to the next loop iteration for step {StepIndex} with iteration={Iteration}, goal={StepGoal}.", stepIndex, stepState.ExecutionCount, step.Goal);
|
||||
|
||||
if (this._logger?.IsEnabled(LogLevel.Information) ?? false)
|
||||
{
|
||||
this._logger.LogInformation("Continuing to the next loop iteration for step {StepIndex} with iteration={Iteration}, goal={StepGoal}.", stepIndex, stepState.ExecutionCount, step.Goal);
|
||||
}
|
||||
}
|
||||
|
||||
// check if current execution is complete by checking whether all variables are already provided
|
||||
// check if current execution is complete by checking whether all arguments are already provided
|
||||
completed = true;
|
||||
foreach (var variable in step.Provides)
|
||||
{
|
||||
if (!stepResult.ContainsKey(variable))
|
||||
if (!stepResult.Metadata!.ContainsKey(variable))
|
||||
{
|
||||
completed = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
executionState.Variables[variable] = stepResult[variable];
|
||||
stepState.AddOrUpdateVariable(stepState.ExecutionCount, variable,
|
||||
stepResult[variable]);
|
||||
executionState.Variables[variable] = (string)stepResult.Metadata[variable]!;
|
||||
stepState.AddOrUpdateVariable(stepState.ExecutionCount, variable, (string)stepResult.Metadata[variable]!);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var variable in step.Passthrough)
|
||||
{
|
||||
if (stepResult.ContainsKey(variable))
|
||||
if (stepResult.Metadata!.TryGetValue(variable, out object? variableValue))
|
||||
{
|
||||
executionState.Variables[variable] = stepResult[variable];
|
||||
stepState.AddOrUpdateVariable(stepState.ExecutionCount, variable,
|
||||
stepResult[variable]);
|
||||
executionState.Variables[variable] = (string)variableValue!;
|
||||
stepState.AddOrUpdateVariable(stepState.ExecutionCount, variable, (string)variableValue!);
|
||||
|
||||
// propagate variables to root context, needed if Flow itself is a step
|
||||
// propagate arguments to root context, needed if Flow itself is a step
|
||||
this.PropagateVariable(rootContext, stepResult, variable);
|
||||
}
|
||||
}
|
||||
|
||||
// propagate variables to root context, needed if Flow itself is a step
|
||||
// propagate arguments to root context, needed if Flow itself is a step
|
||||
foreach (var variable in Constants.ChatPluginVariables.ControlVariables)
|
||||
{
|
||||
this.PropagateVariable(rootContext, stepResult, variable);
|
||||
@@ -309,7 +337,10 @@ internal class FlowExecutor : IFlowExecutor
|
||||
|
||||
if (completed)
|
||||
{
|
||||
this._logger?.LogInformation("Completed step {StepIndex} for iteration={Iteration}, goal={StepGoal}.", stepIndex, stepState.ExecutionCount, step.Goal);
|
||||
if (this._logger?.IsEnabled(LogLevel.Information) ?? false)
|
||||
{
|
||||
this._logger.LogInformation("Completed step {StepIndex} for iteration={Iteration}, goal={StepGoal}.", stepIndex, stepState.ExecutionCount, step.Goal);
|
||||
}
|
||||
|
||||
if (step.CompletionType is CompletionType.AtLeastOnce or CompletionType.ZeroOrMore && stepState.Status != ExecutionState.Status.Completed)
|
||||
{
|
||||
@@ -327,7 +358,12 @@ internal class FlowExecutor : IFlowExecutor
|
||||
{
|
||||
// unconfirmed, prompt user
|
||||
outputs.Add(repeatStep.Prompt!);
|
||||
this._logger?.LogInformation("Unclear intention, need follow up to check whether to repeat the step");
|
||||
|
||||
if (this._logger?.IsEnabled(LogLevel.Information) ?? false)
|
||||
{
|
||||
this._logger.LogInformation("Unclear intention, need follow up to check whether to repeat the step");
|
||||
}
|
||||
|
||||
await this._flowStatusProvider.SaveExecutionStateAsync(sessionId, executionState).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
@@ -341,13 +377,21 @@ internal class FlowExecutor : IFlowExecutor
|
||||
|
||||
stepState.ExecutionCount++;
|
||||
await this._flowStatusProvider.SaveExecutionStateAsync(sessionId, executionState).ConfigureAwait(false);
|
||||
this._logger?.LogInformation("Need repeat step {StepIndex} for iteration={Iteration}, goal={StepGoal}.", stepIndex, stepState.ExecutionCount, step.Goal);
|
||||
|
||||
if (this._logger?.IsEnabled(LogLevel.Information) ?? false)
|
||||
{
|
||||
this._logger.LogInformation("Need repeat step {StepIndex} for iteration={Iteration}, goal={StepGoal}.", stepIndex, stepState.ExecutionCount, step.Goal);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// completed
|
||||
await this.CompleteStepAsync(rootContext, sessionId, executionState, step, stepState).ConfigureAwait(false);
|
||||
this._logger?.LogInformation("Completed step {StepIndex} with iteration={Iteration}, goal={StepGoal}.", stepIndex, stepState.ExecutionCount, step.Goal);
|
||||
|
||||
if (this._logger?.IsEnabled(LogLevel.Information) ?? false)
|
||||
{
|
||||
this._logger.LogInformation("Completed step {StepIndex} with iteration={Iteration}, goal={StepGoal}.", stepIndex, stepState.ExecutionCount, step.Goal);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -362,25 +406,26 @@ internal class FlowExecutor : IFlowExecutor
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var output in outputs)
|
||||
if (this._logger?.IsEnabled(LogLevel.Information) ?? false)
|
||||
{
|
||||
this._logger?.LogInformation("[Output] {Output}", output);
|
||||
foreach (var output in outputs)
|
||||
{
|
||||
this._logger?.LogInformation("[Output] {Output}", output);
|
||||
}
|
||||
}
|
||||
|
||||
rootContext.Update(JsonSerializer.Serialize(outputs));
|
||||
|
||||
return rootContext;
|
||||
return new FunctionResult(this._executeFlowFunction, outputs, metadata: rootContext);
|
||||
}
|
||||
|
||||
private void PropagateVariable(ContextVariables rootContext, ContextVariables stepResult, string variableName)
|
||||
private void PropagateVariable(KernelArguments rootContext, FunctionResult stepResult, string variableName)
|
||||
{
|
||||
if (stepResult.ContainsKey(variableName))
|
||||
if (stepResult.Metadata!.ContainsKey(variableName))
|
||||
{
|
||||
rootContext[variableName] = stepResult[variableName];
|
||||
rootContext[variableName] = stepResult.Metadata[variableName];
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CompleteStepAsync(ContextVariables context, string sessionId, ExecutionState state, FlowStep step, ExecutionState.StepExecutionState stepState)
|
||||
private async Task CompleteStepAsync(KernelArguments context, string sessionId, ExecutionState state, FlowStep step, ExecutionState.StepExecutionState stepState)
|
||||
{
|
||||
stepState.Status = ExecutionState.Status.Completed;
|
||||
state.CurrentStepIndex++;
|
||||
@@ -393,7 +438,7 @@ internal class FlowExecutor : IFlowExecutor
|
||||
}
|
||||
else
|
||||
{
|
||||
// kvp.Value may contain empty strings when the loop was exited and the variables the step provides weren't set
|
||||
// kvp.Value may contain empty strings when the loop was exited and the arguments the step provides weren't set
|
||||
state.Variables[kvp.Key] = JsonSerializer.Serialize(kvp.Value.Where(x => !string.IsNullOrWhiteSpace(x)).ToList());
|
||||
}
|
||||
}
|
||||
@@ -406,31 +451,35 @@ internal class FlowExecutor : IFlowExecutor
|
||||
await this._flowStatusProvider.SaveExecutionStateAsync(sessionId, state).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void ValidateStep(FlowStep step, ContextVariables context)
|
||||
private void ValidateStep(FlowStep step, KernelArguments context)
|
||||
{
|
||||
if (step.Requires.Any(p => !context.ContainsKey(p)))
|
||||
if (step.Requires.Any(p => !context.ContainsName(p)))
|
||||
{
|
||||
throw new KernelException($"Step {step.Goal} requires variables {string.Join(",", step.Requires.Where(p => !context.ContainsKey(p)))} that are not provided. ");
|
||||
throw new KernelException($"Step {step.Goal} requires arguments {string.Join(",", step.Requires.Where(p => !context.ContainsName(p)))} that are not provided. ");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<RepeatOrStartStepResult?> CheckStartStepAsync(ContextVariables context, FlowStep step, string sessionId, string stepId, string input)
|
||||
private async Task<RepeatOrStartStepResult?> CheckStartStepAsync(KernelArguments context, FlowStep step, string sessionId, string stepId, string input)
|
||||
{
|
||||
context = context.Clone();
|
||||
context.Set("goal", step.Goal);
|
||||
context.Set("message", step.StartingMessage);
|
||||
context = new KernelArguments(context)
|
||||
{
|
||||
["goal"] = step.Goal,
|
||||
["message"] = step.StartingMessage
|
||||
};
|
||||
return await this.CheckRepeatOrStartStepAsync(context, this._checkStartStepFunction, sessionId, $"{stepId}_CheckStartStep", input).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<RepeatOrStartStepResult?> CheckRepeatStepAsync(ContextVariables context, FlowStep step, string sessionId, string nextStepId, string input)
|
||||
private async Task<RepeatOrStartStepResult?> CheckRepeatStepAsync(KernelArguments context, FlowStep step, string sessionId, string nextStepId, string input)
|
||||
{
|
||||
context = context.Clone();
|
||||
context.Set("goal", step.Goal);
|
||||
context.Set("transitionMessage", step.TransitionMessage);
|
||||
context = new KernelArguments(context)
|
||||
{
|
||||
["goal"] = step.Goal,
|
||||
["transitionMessage"] = step.TransitionMessage
|
||||
};
|
||||
return await this.CheckRepeatOrStartStepAsync(context, this._checkRepeatStepFunction, sessionId, $"{nextStepId}_CheckRepeatStep", input).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<RepeatOrStartStepResult?> CheckRepeatOrStartStepAsync(ContextVariables context, KernelFunction function, string sessionId, string checkRepeatOrStartStepId, string input)
|
||||
private async Task<RepeatOrStartStepResult?> CheckRepeatOrStartStepAsync(KernelArguments context, KernelFunction function, string sessionId, string checkRepeatOrStartStepId, string input)
|
||||
{
|
||||
var chatHistory = await this._flowStatusProvider.GetChatHistoryAsync(sessionId, checkRepeatOrStartStepId).ConfigureAwait(false);
|
||||
if (chatHistory != null)
|
||||
@@ -443,13 +492,21 @@ internal class FlowExecutor : IFlowExecutor
|
||||
}
|
||||
|
||||
var scratchPad = this.CreateRepeatOrStartStepScratchPad(chatHistory);
|
||||
context.Set("agentScratchPad", scratchPad);
|
||||
this._logger?.LogInformation("Scratchpad: {ScratchPad}", scratchPad);
|
||||
context["agentScratchPad"] = scratchPad;
|
||||
|
||||
if (this._logger.IsEnabled(LogLevel.Information))
|
||||
{
|
||||
this._logger.LogInformation("Scratchpad: {ScratchPad}", scratchPad);
|
||||
}
|
||||
|
||||
var llmResponse = await this._systemKernel.InvokeAsync(function, context).ConfigureAwait(false);
|
||||
|
||||
string llmResponseText = llmResponse.GetValue<string>()?.Trim() ?? string.Empty;
|
||||
this._logger?.LogInformation("Response from {Function} : {ActionText}", "CheckRepeatOrStartStep", llmResponseText);
|
||||
|
||||
if (this._logger.IsEnabled(LogLevel.Information))
|
||||
{
|
||||
this._logger.LogInformation("Response from {Function} : {ActionText}", "CheckRepeatOrStartStep", llmResponseText);
|
||||
}
|
||||
|
||||
Match finalAnswerMatch = s_finalAnswerRegex.Match(llmResponseText);
|
||||
if (finalAnswerMatch.Success)
|
||||
@@ -480,7 +537,7 @@ internal class FlowExecutor : IFlowExecutor
|
||||
return new RepeatOrStartStepResult(null, prompt);
|
||||
}
|
||||
|
||||
this._logger?.LogWarning("Missing result tag from {Function} : {ActionText}", "CheckRepeatOrStartStep", llmResponseText);
|
||||
this._logger.LogWarning("Missing result tag from {Function} : {ActionText}", "CheckRepeatOrStartStep", llmResponseText);
|
||||
chatHistory.AddSystemMessage(llmResponseText + "\nI should provide either [QUESTION] or [FINAL_ANSWER]");
|
||||
await this._flowStatusProvider.SaveChatHistoryAsync(sessionId, checkRepeatOrStartStepId, chatHistory).ConfigureAwait(false);
|
||||
return null;
|
||||
@@ -504,13 +561,13 @@ internal class FlowExecutor : IFlowExecutor
|
||||
scratchPadLines.Add("[THOUGHT]");
|
||||
}
|
||||
|
||||
scratchPadLines.Add(message.Content);
|
||||
scratchPadLines.Add(message.Content!);
|
||||
}
|
||||
|
||||
return string.Join("\n", scratchPadLines).Trim();
|
||||
}
|
||||
|
||||
private async Task<ContextVariables> ExecuteStepAsync(FlowStep step, string sessionId, string stepId, string input, Kernel kernel, ContextVariables variables)
|
||||
private async Task<FunctionResult> ExecuteStepAsync(FlowStep step, string sessionId, string stepId, string input, Kernel kernel, KernelArguments arguments)
|
||||
{
|
||||
var stepsTaken = await this._flowStatusProvider.GetReActStepsAsync(sessionId, stepId).ConfigureAwait(false);
|
||||
var lastStep = stepsTaken.LastOrDefault();
|
||||
@@ -523,15 +580,15 @@ internal class FlowExecutor : IFlowExecutor
|
||||
var question = step.Goal;
|
||||
foreach (var variable in step.Requires)
|
||||
{
|
||||
if (!variable.StartsWith("_", StringComparison.InvariantCulture) && variables[variable].Length <= this._config.MaxVariableLength)
|
||||
if (!variable.StartsWith("_", StringComparison.InvariantCulture) && ((string)arguments[variable]!).Length <= this._config.MaxVariableLength)
|
||||
{
|
||||
question += $"\n - {variable}: {JsonSerializer.Serialize(variables[variable])}";
|
||||
question += $"\n - {variable}: {JsonSerializer.Serialize(arguments[variable])}";
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = stepsTaken.Count; i < this._config.MaxStepIterations; i++)
|
||||
{
|
||||
var actionStep = await this._reActEngine.GetNextStepAsync(kernel, variables, question, stepsTaken).ConfigureAwait(false);
|
||||
var actionStep = await this._reActEngine.GetNextStepAsync(kernel, arguments, question, stepsTaken).ConfigureAwait(false);
|
||||
|
||||
if (actionStep is null)
|
||||
{
|
||||
@@ -541,20 +598,23 @@ internal class FlowExecutor : IFlowExecutor
|
||||
|
||||
stepsTaken.Add(actionStep);
|
||||
|
||||
this._logger?.LogInformation("Thought: {Thought}", actionStep.Thought);
|
||||
if (this._logger?.IsEnabled(LogLevel.Information) ?? false)
|
||||
{
|
||||
this._logger.LogInformation("Thought: {Thought}", actionStep.Thought);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(actionStep.Action!))
|
||||
{
|
||||
if (actionStep.Action!.Contains(Constants.StopAndPromptFunctionName))
|
||||
{
|
||||
string prompt = actionStep.ActionVariables![Constants.StopAndPromptParameterName];
|
||||
variables.Update(prompt);
|
||||
variables.TerminateFlow();
|
||||
arguments.TerminateFlow();
|
||||
|
||||
return variables;
|
||||
return new FunctionResult(this._executeStepFunction, prompt, metadata: arguments);
|
||||
}
|
||||
|
||||
var actionContextVariables = new ContextVariables();
|
||||
foreach (var kvp in variables)
|
||||
var actionContextVariables = new KernelArguments();
|
||||
foreach (var kvp in arguments)
|
||||
{
|
||||
if (step.Requires.Contains(kvp.Key) || step.Passthrough.Contains(kvp.Key))
|
||||
{
|
||||
@@ -573,43 +633,43 @@ internal class FlowExecutor : IFlowExecutor
|
||||
chatHistory.AddUserMessage(input);
|
||||
}
|
||||
|
||||
string? actionResult;
|
||||
try
|
||||
{
|
||||
await Task.Delay(this._config.MinIterationTimeMs).ConfigureAwait(false);
|
||||
var result = await this._reActEngine.InvokeActionAsync(actionStep, input, chatHistory, kernel, actionContextVariables).ConfigureAwait(false);
|
||||
actionResult = await this._reActEngine.InvokeActionAsync(actionStep, input, chatHistory, kernel, actionContextVariables).ConfigureAwait(false);
|
||||
|
||||
if (string.IsNullOrEmpty(result))
|
||||
if (string.IsNullOrEmpty(actionResult))
|
||||
{
|
||||
actionStep.Observation = "Got no result from action";
|
||||
}
|
||||
else
|
||||
{
|
||||
actionStep.Observation = $"{AuthorRole.Assistant.Label}: {result}\n";
|
||||
variables.Update(result);
|
||||
chatHistory.AddAssistantMessage(result);
|
||||
actionStep.Observation = $"{AuthorRole.Assistant.Label}: {actionResult}\n";
|
||||
chatHistory.AddAssistantMessage(actionResult);
|
||||
await this._flowStatusProvider.SaveChatHistoryAsync(sessionId, stepId, chatHistory).ConfigureAwait(false);
|
||||
|
||||
foreach (var passthroughParam in step.Passthrough)
|
||||
{
|
||||
if (actionContextVariables.TryGetValue(passthroughParam, out string? paramValue) && !string.IsNullOrEmpty(paramValue))
|
||||
if (actionContextVariables.TryGetValue(passthroughParam, out object? paramValue) && paramValue is string paramStringValue && !string.IsNullOrEmpty(paramStringValue))
|
||||
{
|
||||
variables.Set(passthroughParam, actionContextVariables[passthroughParam]);
|
||||
arguments[passthroughParam] = actionContextVariables[passthroughParam];
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var providedParam in step.Provides)
|
||||
{
|
||||
if (actionContextVariables.TryGetValue(providedParam, out string? paramValue) && !string.IsNullOrEmpty(paramValue))
|
||||
if (actionContextVariables.TryGetValue(providedParam, out object? paramValue) && paramValue is string paramStringValue && !string.IsNullOrEmpty(paramStringValue))
|
||||
{
|
||||
variables.Set(providedParam, actionContextVariables[providedParam]);
|
||||
arguments[providedParam] = actionContextVariables[providedParam];
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var variable in Constants.ChatPluginVariables.ControlVariables)
|
||||
{
|
||||
if (actionContextVariables.TryGetValue(variable, out string? variableValue))
|
||||
if (actionContextVariables.TryGetValue(variable, out object? variableValue))
|
||||
{
|
||||
variables.Set(variable, variableValue);
|
||||
arguments[variable] = variableValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -622,7 +682,7 @@ internal class FlowExecutor : IFlowExecutor
|
||||
|
||||
continue;
|
||||
}
|
||||
catch (Exception ex) when (!ex.IsCriticalException())
|
||||
catch (Exception ex) when (!ex.IsNonRetryable())
|
||||
{
|
||||
actionStep.Observation = $"Error invoking action {actionStep.Action} : {ex.Message}";
|
||||
this._logger?.LogWarning(ex, "Error invoking action {Action}", actionStep.Action);
|
||||
@@ -630,51 +690,55 @@ internal class FlowExecutor : IFlowExecutor
|
||||
continue;
|
||||
}
|
||||
|
||||
this._logger?.LogInformation("Observation: {Observation}", actionStep.Observation);
|
||||
if (this._logger?.IsEnabled(LogLevel.Information) ?? false)
|
||||
{
|
||||
this._logger.LogInformation("Observation: {Observation}", actionStep.Observation);
|
||||
}
|
||||
|
||||
await this._flowStatusProvider.SaveReActStepsAsync(sessionId, stepId, stepsTaken).ConfigureAwait(false);
|
||||
|
||||
if (!string.IsNullOrEmpty(variables.Input))
|
||||
if (!string.IsNullOrEmpty(actionResult))
|
||||
{
|
||||
if (variables.IsTerminateFlow())
|
||||
if (arguments.IsTerminateFlow())
|
||||
{
|
||||
// Terminate the flow without another round of reasoning, to save the LLM reasoning calls.
|
||||
// This is not suggested unless plugin has performance requirement and has explicitly set the control variable.
|
||||
return variables;
|
||||
return new FunctionResult(this._executeStepFunction, actionResult, metadata: arguments);
|
||||
}
|
||||
|
||||
foreach (var variable in Constants.ChatPluginVariables.ControlVariables)
|
||||
{
|
||||
if (variables.ContainsKey(variable))
|
||||
if (arguments.ContainsName(variable))
|
||||
{
|
||||
// redirect control to client
|
||||
return variables;
|
||||
return new FunctionResult(this._executeStepFunction, actionResult, metadata: arguments);
|
||||
}
|
||||
}
|
||||
|
||||
if (!step.Provides.Except(variables.Where(v => !string.IsNullOrEmpty(v.Value)).Select(_ => _.Key)).Any())
|
||||
if (!step.Provides.Except(arguments.Where(v => !string.IsNullOrEmpty((string)v.Value!)).Select(_ => _.Key)).Any())
|
||||
{
|
||||
// step is complete
|
||||
return variables;
|
||||
return new FunctionResult(this._executeStepFunction, actionResult, metadata: arguments);
|
||||
}
|
||||
|
||||
// continue to next iteration
|
||||
continue;
|
||||
}
|
||||
|
||||
this._logger?.LogInformation("Action: No result from action");
|
||||
this._logger?.LogWarning("Action: No result from action");
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(actionStep.FinalAnswer))
|
||||
{
|
||||
if (step.Provides.Count() == 1)
|
||||
{
|
||||
variables.Set(step.Provides.Single(), actionStep.FinalAnswer);
|
||||
return variables;
|
||||
arguments[step.Provides.Single()] = actionStep.FinalAnswer;
|
||||
return new FunctionResult(this._executeStepFunction, actionStep.FinalAnswer, metadata: arguments);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
actionStep.Observation = "ACTION $JSON_BLOB must be provided as part of thought process.";
|
||||
this._logger?.LogInformation("Action: No action to take");
|
||||
this._logger?.LogWarning("Action: No action to take");
|
||||
}
|
||||
|
||||
// continue to next iteration
|
||||
@@ -684,13 +748,6 @@ internal class FlowExecutor : IFlowExecutor
|
||||
throw new KernelException($"Failed to complete step {stepId} for session {sessionId}.");
|
||||
}
|
||||
|
||||
private static KernelFunction CreateSemanticFunction(Kernel kernel, string functionName, string promptTemplate, PromptTemplateConfig config)
|
||||
{
|
||||
var factory = new KernelPromptTemplateFactory(kernel.LoggerFactory);
|
||||
var template = factory.Create(promptTemplate, config);
|
||||
return kernel.CreateFunctionFromPrompt(template, config, functionName);
|
||||
}
|
||||
|
||||
private class RepeatOrStartStepResult
|
||||
{
|
||||
public RepeatOrStartStepResult(bool? execute, string? prompt = null)
|
||||
|
||||
@@ -5,7 +5,7 @@ using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.SemanticKernel.AI.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Experimental.Orchestration.Abstractions;
|
||||
using Microsoft.SemanticKernel.Experimental.Orchestration.Execution;
|
||||
using Microsoft.SemanticKernel.Memory;
|
||||
|
||||
@@ -7,7 +7,8 @@ using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.SemanticKernel.AI.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Experimental.Orchestration.Extensions;
|
||||
|
||||
namespace Microsoft.SemanticKernel.Experimental.Orchestration.Execution;
|
||||
|
||||
@@ -119,18 +120,18 @@ internal sealed class ReActEngine
|
||||
if (!string.IsNullOrEmpty(promptTemplate))
|
||||
{
|
||||
var modelPromptTemplate = EmbeddedResource.Read($"Plugins.ReActEngine.{modelId}.skprompt.txt", false);
|
||||
promptTemplate = string.IsNullOrEmpty(modelPromptTemplate) ? promptTemplate : modelPromptTemplate!;
|
||||
promptConfig.Template = string.IsNullOrEmpty(modelPromptTemplate) ? promptTemplate : modelPromptTemplate!;
|
||||
}
|
||||
}
|
||||
|
||||
this._reActFunction = this.ImportSemanticFunction(systemKernel, "ReActFunction", promptTemplate!, promptConfig);
|
||||
this._reActFunction = systemKernel.CreateFunctionFromPrompt(promptConfig);
|
||||
}
|
||||
|
||||
internal async Task<ReActStep?> GetNextStepAsync(Kernel kernel, ContextVariables variables, string question, List<ReActStep> previousSteps)
|
||||
internal async Task<ReActStep?> GetNextStepAsync(Kernel kernel, KernelArguments arguments, string question, List<ReActStep> previousSteps)
|
||||
{
|
||||
variables.Set("question", question);
|
||||
arguments["question"] = question;
|
||||
var scratchPad = this.CreateScratchPad(previousSteps);
|
||||
variables.Set("agentScratchPad", scratchPad);
|
||||
arguments["agentScratchPad"] = scratchPad;
|
||||
|
||||
var availableFunctions = this.GetAvailableFunctions(kernel).ToArray();
|
||||
if (availableFunctions.Length == 1)
|
||||
@@ -139,7 +140,12 @@ internal sealed class ReActEngine
|
||||
if (firstActionFunction.Parameters.Count == 0)
|
||||
{
|
||||
var action = $"{firstActionFunction.PluginName}.{firstActionFunction.Name}";
|
||||
this._logger?.LogDebug($"Auto selecting {Action} as it is the only function available and it has no parameters.", action);
|
||||
|
||||
if (this._logger.IsEnabled(LogLevel.Debug))
|
||||
{
|
||||
this._logger?.LogDebug("Auto selecting {Action} as it is the only function available and it has no parameters.", action);
|
||||
}
|
||||
|
||||
return new ReActStep
|
||||
{
|
||||
Action = action
|
||||
@@ -148,16 +154,23 @@ internal sealed class ReActEngine
|
||||
}
|
||||
|
||||
var functionDesc = this.GetFunctionDescriptions(availableFunctions);
|
||||
variables.Set("functionDescriptions", functionDesc);
|
||||
arguments["functionDescriptions"] = functionDesc;
|
||||
|
||||
this._logger?.LogInformation("question: {Question}", question);
|
||||
this._logger?.LogInformation("functionDescriptions: {FunctionDescriptions}", functionDesc);
|
||||
this._logger?.LogInformation("Scratchpad: {ScratchPad}", scratchPad);
|
||||
if (this._logger.IsEnabled(LogLevel.Information))
|
||||
{
|
||||
this._logger?.LogInformation("question: {Question}", question);
|
||||
this._logger?.LogInformation("functionDescriptions: {FunctionDescriptions}", functionDesc);
|
||||
this._logger?.LogInformation("Scratchpad: {ScratchPad}", scratchPad);
|
||||
}
|
||||
|
||||
var llmResponse = await this._reActFunction.InvokeAsync(kernel, variables).ConfigureAwait(false);
|
||||
var llmResponse = await this._reActFunction.InvokeAsync(kernel, arguments).ConfigureAwait(false);
|
||||
|
||||
string llmResponseText = llmResponse.GetValue<string>()!.Trim();
|
||||
this._logger?.LogDebug("Response : {ActionText}", llmResponseText);
|
||||
|
||||
if (this._logger?.IsEnabled(LogLevel.Debug) ?? false)
|
||||
{
|
||||
this._logger?.LogDebug("Response : {ActionText}", llmResponseText);
|
||||
}
|
||||
|
||||
var actionStep = this.ParseResult(llmResponseText);
|
||||
|
||||
@@ -172,31 +185,30 @@ internal sealed class ReActEngine
|
||||
return actionStep;
|
||||
}
|
||||
|
||||
internal async Task<string> InvokeActionAsync(ReActStep actionStep, string chatInput, ChatHistory chatHistory, Kernel kernel, ContextVariables contextVariables)
|
||||
internal async Task<string> InvokeActionAsync(ReActStep actionStep, string chatInput, ChatHistory chatHistory, Kernel kernel, KernelArguments contextVariables)
|
||||
{
|
||||
var variables = actionStep.ActionVariables ?? new Dictionary<string, string>();
|
||||
|
||||
variables[Constants.ActionVariableNames.ChatInput] = chatInput;
|
||||
variables[Constants.ActionVariableNames.ChatHistory] = ChatHistorySerializer.Serialize(chatHistory);
|
||||
this._logger?.LogInformation("Action: {Action}({ActionVariables})", actionStep.Action, JsonSerializer.Serialize(variables));
|
||||
|
||||
var availableFunctions = this.GetAvailableFunctions(kernel);
|
||||
var targetFunction = availableFunctions.FirstOrDefault(f => ToFullyQualifiedName(f) == actionStep.Action);
|
||||
if (targetFunction is null)
|
||||
if (this._logger.IsEnabled(LogLevel.Information))
|
||||
{
|
||||
throw new MissingMethodException($"The function '{actionStep.Action}' was not found.");
|
||||
this._logger?.LogInformation("Action: {Action}({ActionVariables})", actionStep.Action, JsonSerializer.Serialize(variables));
|
||||
}
|
||||
|
||||
var availableFunctions = this.GetAvailableFunctions(kernel);
|
||||
var targetFunction = availableFunctions.FirstOrDefault(f => ToFullyQualifiedName(f) == actionStep.Action) ?? throw new MissingMethodException($"The function '{actionStep.Action}' was not found.");
|
||||
var function = kernel.Plugins.GetFunction(targetFunction.PluginName, targetFunction.Name);
|
||||
var functionView = function.Metadata;
|
||||
|
||||
var actionContextVariables = this.CreateActionContextVariables(variables, kernel, contextVariables);
|
||||
var actionContextVariables = this.CreateActionKernelArguments(variables, contextVariables);
|
||||
|
||||
foreach (var parameter in functionView.Parameters)
|
||||
{
|
||||
if (!actionContextVariables.ContainsKey(parameter.Name))
|
||||
if (!actionContextVariables.ContainsName(parameter.Name))
|
||||
{
|
||||
actionContextVariables.Set(parameter.Name, parameter.DefaultValue ?? string.Empty);
|
||||
actionContextVariables[parameter.Name] = parameter.DefaultValue ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,44 +218,35 @@ internal sealed class ReActEngine
|
||||
|
||||
foreach (var variable in actionContextVariables)
|
||||
{
|
||||
contextVariables.Set(variable.Key, variable.Value);
|
||||
contextVariables[variable.Key] = variable.Value;
|
||||
}
|
||||
|
||||
this._logger?.LogDebug("Invoked {FunctionName}. Result: {Result}", targetFunction.Name, result.GetValue<string>());
|
||||
if (this._logger?.IsEnabled(LogLevel.Debug) ?? false)
|
||||
{
|
||||
this._logger?.LogDebug("Invoked {FunctionName}. Result: {Result}", targetFunction.Name, result.GetValue<string>());
|
||||
}
|
||||
|
||||
return result.GetValue<string>() ?? string.Empty;
|
||||
}
|
||||
catch (Exception e) when (!e.IsCriticalException())
|
||||
catch (Exception e) when (!e.IsNonRetryable())
|
||||
{
|
||||
this._logger?.LogError(e, "Something went wrong in action step: {0}.{1}. Error: {2}", targetFunction.PluginName, targetFunction.Name, e.Message);
|
||||
return $"Something went wrong in action step: {targetFunction.PluginName}.{targetFunction.Name}. Error: {e.Message} {e.InnerException?.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
private ContextVariables CreateActionContextVariables(Dictionary<string, string> actionVariables, Kernel kernel, ContextVariables contextVariables)
|
||||
private KernelArguments CreateActionKernelArguments(Dictionary<string, string> actionVariables, KernelArguments context)
|
||||
{
|
||||
var actionContext = contextVariables.Clone();
|
||||
var actionContext = new KernelArguments(context);
|
||||
|
||||
foreach (var kvp in actionVariables)
|
||||
{
|
||||
actionContext.Set(kvp.Key, kvp.Value);
|
||||
actionContext[kvp.Key] = kvp.Value;
|
||||
}
|
||||
|
||||
return actionContext;
|
||||
}
|
||||
|
||||
private KernelFunction ImportSemanticFunction(Kernel kernel, string functionName, string promptTemplate, PromptTemplateConfig config)
|
||||
{
|
||||
var factory = new KernelPromptTemplateFactory(kernel.LoggerFactory);
|
||||
var template = factory.Create(promptTemplate, config);
|
||||
|
||||
var plugin = new KernelPlugin(RestrictedPluginName);
|
||||
|
||||
kernel.Plugins.Add(plugin);
|
||||
|
||||
return plugin.AddFunctionFromPrompt(template, config, functionName);
|
||||
}
|
||||
|
||||
private string CreateScratchPad(List<ReActStep> stepsTaken)
|
||||
{
|
||||
if (stepsTaken.Count == 0)
|
||||
@@ -266,7 +269,11 @@ internal sealed class ReActEngine
|
||||
{
|
||||
if (scratchPadLines.Count / 4.0 > (this._config.MaxTokens * 0.75))
|
||||
{
|
||||
this._logger.LogDebug("Scratchpad is too long, truncating. Skipping {CountSkipped} steps.", i + 1);
|
||||
if (this._logger.IsEnabled(LogLevel.Debug))
|
||||
{
|
||||
this._logger.LogDebug("Scratchpad is too long, truncating. Skipping {CountSkipped} steps.", i + 1);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -412,7 +419,7 @@ internal sealed class ReActEngine
|
||||
{
|
||||
var inputs = string.Join("\n", function.Parameters.Select(parameter =>
|
||||
{
|
||||
var defaultValueString = string.IsNullOrEmpty(parameter.DefaultValue) ? string.Empty : $"(default='{parameter.DefaultValue}')";
|
||||
var defaultValueString = parameter.DefaultValue is not string value || string.IsNullOrEmpty(value) ? string.Empty : $"(default='{parameter.DefaultValue}')";
|
||||
return $" - {parameter.Name}: {parameter.Description} {defaultValueString}";
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Linq;
|
||||
using Microsoft.SemanticKernel.AI.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Experimental.Orchestration.Execution;
|
||||
|
||||
namespace Microsoft.SemanticKernel.Experimental.Orchestration;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for <see cref="ContextVariables"/>
|
||||
/// </summary>
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static class ContextVariablesExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Check if we should prompt user for input based on current context.
|
||||
/// </summary>
|
||||
/// <param name="variables">Context variables.</param>
|
||||
internal static bool IsPromptInput(this ContextVariables variables)
|
||||
{
|
||||
return variables.TryGetValue(Constants.ChatPluginVariables.PromptInputName, out string? promptInput)
|
||||
&& promptInput == Constants.ChatPluginVariables.DefaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if we should force the next iteration loop based on current context.
|
||||
/// </summary>
|
||||
/// <param name="variables">Context variables.</param>
|
||||
internal static bool IsContinueLoop(this ContextVariables variables)
|
||||
{
|
||||
return variables.TryGetValue(Constants.ChatPluginVariables.ContinueLoopName, out string? continueLoop)
|
||||
&& continueLoop == Constants.ChatPluginVariables.DefaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if we should terminate flow based on current context.
|
||||
/// </summary>
|
||||
/// <param name="variables">Context variables.</param>
|
||||
public static bool IsTerminateFlow(this ContextVariables variables)
|
||||
{
|
||||
return variables.TryGetValue(Constants.ChatPluginVariables.StopFlowName, out string? stopFlow)
|
||||
&& stopFlow == Constants.ChatPluginVariables.DefaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if all variables to be provided with the flow is available in the context
|
||||
/// </summary>
|
||||
/// <param name="variables">Context variables.</param>
|
||||
/// <param name="flow">flow</param>
|
||||
/// <returns></returns>
|
||||
public static bool IsComplete(this ContextVariables variables, Flow flow)
|
||||
{
|
||||
return flow.Provides.All(variables.ContainsKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get <see cref="ChatHistory"/> from context.
|
||||
/// </summary>
|
||||
/// <param name="variables">Context variables.</param>
|
||||
/// <returns>The chat history</returns>
|
||||
public static ChatHistory? GetChatHistory(this ContextVariables variables)
|
||||
{
|
||||
if (variables.TryGetValue(Constants.ActionVariableNames.ChatHistory, out string? chatHistoryText) && !string.IsNullOrEmpty(chatHistoryText))
|
||||
{
|
||||
return ChatHistorySerializer.Deserialize(chatHistoryText);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get latest chat input from context.
|
||||
/// </summary>
|
||||
/// <param name="variables">Context variables.</param>
|
||||
/// <returns>The latest chat input.</returns>
|
||||
public static string GetChatInput(this ContextVariables variables)
|
||||
{
|
||||
if (variables.TryGetValue(Constants.ActionVariableNames.ChatInput, out string? chatInput))
|
||||
{
|
||||
return chatInput;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signal the orchestrator to prompt user for input with current function response.
|
||||
/// </summary>
|
||||
/// <param name="variables">Context variables.</param>
|
||||
public static void PromptInput(this ContextVariables variables)
|
||||
{
|
||||
// Cant prompt the user for input and exit the execution at the same time
|
||||
if (!variables.ContainsKey(Constants.ChatPluginVariables.ExitLoopName))
|
||||
{
|
||||
variables.Set(Constants.ChatPluginVariables.PromptInputName, Constants.ChatPluginVariables.DefaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signal the orchestrator to exit out of the AtLeastOnce or ZeroOrMore loop. If response is non-null, that value will be outputted to the user.
|
||||
/// </summary>
|
||||
/// <param name="variables">Context variables.</param>
|
||||
/// <param name="response">context</param>
|
||||
public static void ExitLoop(this ContextVariables variables, string? response = null)
|
||||
{
|
||||
// Cant prompt the user for input and exit the execution at the same time
|
||||
if (!variables.ContainsKey(Constants.ChatPluginVariables.PromptInputName))
|
||||
{
|
||||
variables.Set(Constants.ChatPluginVariables.ExitLoopName, response ?? string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signal the orchestrator to go to the next iteration of the loop in the AtLeastOnce or ZeroOrMore step.
|
||||
/// </summary>
|
||||
/// <param name="variables">Context variables.</param>
|
||||
public static void ContinueLoop(this ContextVariables variables)
|
||||
{
|
||||
variables.Set(Constants.ChatPluginVariables.ContinueLoopName, Constants.ChatPluginVariables.DefaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signal the orchestrator to terminate the flow.
|
||||
/// </summary>
|
||||
/// <param name="variables">context</param>
|
||||
public static void TerminateFlow(this ContextVariables variables)
|
||||
{
|
||||
variables.Set(Constants.ChatPluginVariables.StopFlowName, Constants.ChatPluginVariables.DefaultValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace Microsoft.SemanticKernel.Experimental.Orchestration.Extensions;
|
||||
|
||||
internal static class ExceptionExtensions
|
||||
{
|
||||
internal static bool IsNonRetryable(this Exception ex)
|
||||
{
|
||||
bool isContentFilterException = ex is HttpOperationException
|
||||
{
|
||||
StatusCode: HttpStatusCode.BadRequest, InnerException: { }
|
||||
} hoe && hoe.InnerException.Message.Contains("content_filter");
|
||||
|
||||
return isContentFilterException || ex.IsCriticalException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Linq;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Experimental.Orchestration.Execution;
|
||||
|
||||
namespace Microsoft.SemanticKernel.Experimental.Orchestration;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for <see cref="FunctionResult"/>
|
||||
/// </summary>
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static class FunctionResultExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Check if we should prompt user for input based on function result.
|
||||
/// </summary>
|
||||
/// <param name="result">Function result.</param>
|
||||
internal static bool IsPromptInput(this FunctionResult result)
|
||||
{
|
||||
return result.Metadata!.TryGetValue(Constants.ChatPluginVariables.PromptInputName, out object? promptInput)
|
||||
&& promptInput is Constants.ChatPluginVariables.DefaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if we should force the next iteration loop based on function result.
|
||||
/// </summary>
|
||||
/// <param name="result">Function result.</param>
|
||||
internal static bool IsContinueLoop(this FunctionResult result)
|
||||
{
|
||||
return result.Metadata!.TryGetValue(Constants.ChatPluginVariables.ContinueLoopName, out object? continueLoop)
|
||||
&& continueLoop is Constants.ChatPluginVariables.DefaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if we should exit the loop based on function result.
|
||||
/// </summary>
|
||||
/// <param name="result">Function result.</param>
|
||||
/// <param name="response">The response to exit loop</param>
|
||||
internal static bool TryGetExitLoopResponse(this FunctionResult result, out string? response)
|
||||
{
|
||||
if (result.Metadata!.TryGetValue(Constants.ChatPluginVariables.ExitLoopName, out object? exitLoop)
|
||||
&& exitLoop is string exitLoopResponse)
|
||||
{
|
||||
response = exitLoopResponse;
|
||||
return true;
|
||||
}
|
||||
|
||||
response = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if we should terminate flow based on function result.
|
||||
/// </summary>
|
||||
/// <param name="result">Function result.</param>
|
||||
public static bool IsTerminateFlow(this FunctionResult result)
|
||||
{
|
||||
return result.Metadata!.TryGetValue(Constants.ChatPluginVariables.StopFlowName, out object? stopFlow)
|
||||
&& stopFlow is Constants.ChatPluginVariables.DefaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if all arguments to be provided with the flow is available in the context
|
||||
/// </summary>
|
||||
/// <param name="result">Function result.</param>
|
||||
/// <param name="flow">flow</param>
|
||||
/// <returns></returns>
|
||||
public static bool IsComplete(this FunctionResult result, Flow flow)
|
||||
{
|
||||
return flow.Provides.All(result.Metadata!.ContainsKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get <see cref="ChatHistory"/> from context.
|
||||
/// </summary>
|
||||
/// <param name="result">Function result.</param>
|
||||
/// <returns>The chat history</returns>
|
||||
public static ChatHistory? GetChatHistory(this FunctionResult result)
|
||||
{
|
||||
if (result.Metadata!.TryGetValue(Constants.ActionVariableNames.ChatHistory, out object? chatHistory)
|
||||
&& chatHistory is string chatHistoryText
|
||||
&& !string.IsNullOrEmpty(chatHistoryText))
|
||||
{
|
||||
return ChatHistorySerializer.Deserialize(chatHistoryText!);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get latest chat input from context.
|
||||
/// </summary>
|
||||
/// <param name="result">Function result.</param>
|
||||
/// <returns>The latest chat input.</returns>
|
||||
public static string GetChatInput(this FunctionResult result)
|
||||
{
|
||||
if (result.Metadata!.TryGetValue(Constants.ActionVariableNames.ChatInput, out object? chatInput)
|
||||
&& chatInput is string chatInputString)
|
||||
{
|
||||
return chatInputString;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Linq;
|
||||
using Microsoft.SemanticKernel.ChatCompletion;
|
||||
using Microsoft.SemanticKernel.Experimental.Orchestration.Execution;
|
||||
|
||||
namespace Microsoft.SemanticKernel.Experimental.Orchestration;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for <see cref="KernelArguments"/>
|
||||
/// </summary>
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static class KernelArgumentsExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Check if we should prompt user for input based on current context.
|
||||
/// </summary>
|
||||
/// <param name="variables">Context arguments.</param>
|
||||
internal static bool IsPromptInput(this KernelArguments variables)
|
||||
{
|
||||
return variables.TryGetValue(Constants.ChatPluginVariables.PromptInputName, out object? promptInput)
|
||||
&& promptInput is Constants.ChatPluginVariables.DefaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if we should force the next iteration loop based on current context.
|
||||
/// </summary>
|
||||
/// <param name="arguments">Context arguments.</param>
|
||||
internal static bool IsContinueLoop(this KernelArguments arguments)
|
||||
{
|
||||
return arguments.TryGetValue(Constants.ChatPluginVariables.ContinueLoopName, out object? continueLoop)
|
||||
&& continueLoop is Constants.ChatPluginVariables.DefaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if we should terminate flow based on current context.
|
||||
/// </summary>
|
||||
/// <param name="arguments">Context arguments.</param>
|
||||
public static bool IsTerminateFlow(this KernelArguments arguments)
|
||||
{
|
||||
return arguments.TryGetValue(Constants.ChatPluginVariables.StopFlowName, out object? stopFlow)
|
||||
&& stopFlow is Constants.ChatPluginVariables.DefaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if all arguments to be provided with the flow is available in the context
|
||||
/// </summary>
|
||||
/// <param name="arguments">Context arguments.</param>
|
||||
/// <param name="flow">flow</param>
|
||||
/// <returns></returns>
|
||||
public static bool IsComplete(this KernelArguments arguments, Flow flow)
|
||||
{
|
||||
return flow.Provides.All(arguments.ContainsName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get <see cref="ChatHistory"/> from context.
|
||||
/// </summary>
|
||||
/// <param name="arguments">Context arguments.</param>
|
||||
/// <returns>The chat history</returns>
|
||||
public static ChatHistory? GetChatHistory(this KernelArguments arguments)
|
||||
{
|
||||
if (arguments.TryGetValue(Constants.ActionVariableNames.ChatHistory, out object? chatHistory)
|
||||
&& chatHistory is string chatHistoryText
|
||||
&& !string.IsNullOrEmpty(chatHistoryText))
|
||||
{
|
||||
return ChatHistorySerializer.Deserialize(chatHistoryText!);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get latest chat input from context.
|
||||
/// </summary>
|
||||
/// <param name="arguments">Context arguments.</param>
|
||||
/// <returns>The latest chat input.</returns>
|
||||
public static string GetChatInput(this KernelArguments arguments)
|
||||
{
|
||||
if (arguments.TryGetValue(Constants.ActionVariableNames.ChatInput, out object? chatInput)
|
||||
&& chatInput is string chatInputString)
|
||||
{
|
||||
return chatInputString;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signal the orchestrator to prompt user for input with current function response.
|
||||
/// </summary>
|
||||
/// <param name="arguments">Context arguments.</param>
|
||||
public static void PromptInput(this KernelArguments arguments)
|
||||
{
|
||||
// Cant prompt the user for input and exit the execution at the same time
|
||||
if (!arguments.ContainsName(Constants.ChatPluginVariables.ExitLoopName))
|
||||
{
|
||||
arguments[Constants.ChatPluginVariables.PromptInputName] = Constants.ChatPluginVariables.DefaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signal the orchestrator to exit out of the AtLeastOnce or ZeroOrMore loop. If response is non-null, that value will be outputted to the user.
|
||||
/// </summary>
|
||||
/// <param name="arguments">Context arguments.</param>
|
||||
/// <param name="response">context</param>
|
||||
public static void ExitLoop(this KernelArguments arguments, string? response = null)
|
||||
{
|
||||
// Cant prompt the user for input and exit the execution at the same time
|
||||
if (!arguments.ContainsName(Constants.ChatPluginVariables.PromptInputName))
|
||||
{
|
||||
arguments[Constants.ChatPluginVariables.ExitLoopName] = response ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signal the orchestrator to go to the next iteration of the loop in the AtLeastOnce or ZeroOrMore step.
|
||||
/// </summary>
|
||||
/// <param name="arguments">Context arguments.</param>
|
||||
public static void ContinueLoop(this KernelArguments arguments)
|
||||
{
|
||||
arguments[Constants.ChatPluginVariables.ContinueLoopName] = Constants.ChatPluginVariables.DefaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signal the orchestrator to terminate the flow.
|
||||
/// </summary>
|
||||
/// <param name="arguments">context</param>
|
||||
public static void TerminateFlow(this KernelArguments arguments)
|
||||
{
|
||||
arguments[Constants.ChatPluginVariables.StopFlowName] = Constants.ChatPluginVariables.DefaultValue;
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -1,7 +1,5 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel.AI;
|
||||
|
||||
namespace Microsoft.SemanticKernel.Experimental.Orchestration;
|
||||
|
||||
/// <summary>
|
||||
@@ -16,11 +14,13 @@ internal static class PromptTemplateConfigExtensions
|
||||
/// <param name="maxTokens">Value of max tokens to set</param>
|
||||
internal static void SetMaxTokens(this PromptTemplateConfig config, int maxTokens)
|
||||
{
|
||||
PromptExecutionSettings executionSettings = config.GetDefaultRequestSettings() ?? new();
|
||||
if (config.ModelSettings.Count == 0)
|
||||
var executionSettings = config.ExecutionSettings;
|
||||
foreach (var setting in executionSettings)
|
||||
{
|
||||
config.ModelSettings.Add(executionSettings);
|
||||
if (setting.Value.ExtensionData != null)
|
||||
{
|
||||
setting.Value.ExtensionData["max_tokens"] = maxTokens;
|
||||
}
|
||||
}
|
||||
executionSettings.ExtensionData["max_tokens"] = maxTokens;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ public class FlowOrchestrator
|
||||
/// <param name="validator">The flow validator.</param>
|
||||
/// <param name="config">Optional configuration object</param>
|
||||
public FlowOrchestrator(
|
||||
KernelBuilder kernelBuilder,
|
||||
IKernelBuilder kernelBuilder,
|
||||
IFlowStatusProvider flowStatusProvider,
|
||||
Dictionary<object, string?>? globalPluginCollection = null,
|
||||
IFlowValidator? validator = null,
|
||||
@@ -54,14 +54,14 @@ public class FlowOrchestrator
|
||||
/// <param name="flow">goal to achieve</param>
|
||||
/// <param name="sessionId">execution session id</param>
|
||||
/// <param name="input">current input</param>
|
||||
/// <param name="contextVariables">execution context variables</param>
|
||||
/// <returns>ContextVariables, which includes a json array of strings as output. The flow result is also exposed through the context when completes.</returns>
|
||||
public async Task<ContextVariables> ExecuteFlowAsync(
|
||||
/// <param name="kernelArguments">execution kernel arguments</param>
|
||||
/// <returns>KernelArguments, which includes a json array of strings as output. The flow result is also exposed through the context when completes.</returns>
|
||||
public async Task<FunctionResult> ExecuteFlowAsync(
|
||||
[Description("The flow to execute")] Flow flow,
|
||||
[Description("Execution session id")] string sessionId,
|
||||
[Description("Current input")] string input,
|
||||
[Description("Execution context variables")]
|
||||
ContextVariables? contextVariables = null)
|
||||
[Description("Execution arguments")]
|
||||
KernelArguments? kernelArguments = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -73,6 +73,6 @@ public class FlowOrchestrator
|
||||
}
|
||||
|
||||
var executor = new FlowExecutor(this._kernelBuilder, this._flowStatusProvider, this._globalPluginCollection, this._config);
|
||||
return await executor.ExecuteAsync(flow, sessionId, input, contextVariables ?? new ContextVariables(null)).ConfigureAwait(false);
|
||||
return await executor.ExecuteFlowAsync(flow, sessionId, input, kernelArguments ?? new KernelArguments(null)).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.SemanticKernel.AI;
|
||||
using Microsoft.SemanticKernel.Experimental.Orchestration.Execution;
|
||||
|
||||
namespace Microsoft.SemanticKernel.Experimental.Orchestration;
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"name": "CheckRepeatStep",
|
||||
"description": "Given the chat history, determine if user would like to execute the previous task for one more time. If not concluded, generate the next message for follow up.",
|
||||
"type": "completion",
|
||||
"completion": {
|
||||
"max_tokens": 400,
|
||||
"temperature": 0,
|
||||
"top_p": 1,
|
||||
"presence_penalty": 0,
|
||||
"frequency_penalty": 0,
|
||||
"stop_sequences": [ "[RESPONSE]", "[Goal]" ]
|
||||
"execution_settings": {
|
||||
"default": {
|
||||
"max_tokens": 400,
|
||||
"temperature": 0,
|
||||
"top_p": 1,
|
||||
"presence_penalty": 0.0,
|
||||
"frequency_penalty": 0.0,
|
||||
"stop_sequences": ["[RESPONSE]"]
|
||||
}
|
||||
},
|
||||
"input": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "goal",
|
||||
"description": "The goal of proposed step",
|
||||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"name": "transitionMessage",
|
||||
"description": "The transition message",
|
||||
"defaultValue": "Did you want to try the previous step again?"
|
||||
},
|
||||
{
|
||||
"name": "agentScratchPad",
|
||||
"description": "The agent's scratch pad",
|
||||
"defaultValue": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
"input_variables": [
|
||||
{
|
||||
"name": "goal",
|
||||
"description": "The goal of proposed step",
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "transitionMessage",
|
||||
"description": "The transition message",
|
||||
"default": "Did you want to try the previous step again?"
|
||||
},
|
||||
{
|
||||
"name": "agentScratchPad",
|
||||
"description": "The agent's scratch pad",
|
||||
"default": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"name": "CheckStartStep",
|
||||
"description": "Given the chat history, determine if user would like to begin executing the current task. If not concluded, generate the next message for follow up.",
|
||||
"type": "completion",
|
||||
"completion": {
|
||||
"max_tokens": 400,
|
||||
"temperature": 0,
|
||||
"top_p": 1,
|
||||
"presence_penalty": 0,
|
||||
"frequency_penalty": 0,
|
||||
"stop_sequences": ["[RESPONSE]"]
|
||||
"execution_settings": {
|
||||
"default": {
|
||||
"max_tokens": 400,
|
||||
"temperature": 0,
|
||||
"top_p": 1,
|
||||
"presence_penalty": 0.0,
|
||||
"frequency_penalty": 0.0,
|
||||
"stop_sequences": ["[RESPONSE]"]
|
||||
}
|
||||
},
|
||||
"input": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "goal",
|
||||
"description": "The goal of proposed step",
|
||||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
"description": "The message to display to the user",
|
||||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"name": "agentScratchPad",
|
||||
"description": "The agent's scratch pad",
|
||||
"defaultValue": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
"input_variables": [
|
||||
{
|
||||
"name": "goal",
|
||||
"description": "The goal of proposed step",
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
"description": "The message to display to the user",
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "agentScratchPad",
|
||||
"description": "The agent's scratch pad",
|
||||
"default": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,32 +1,31 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"description": "Given a request or command or goal generate multi-step plan to reach the goal. After each step LLM is called to perform the reasoning for the next step.",
|
||||
"type": "completion",
|
||||
"completion": {
|
||||
"max_tokens": 400,
|
||||
"temperature": 0,
|
||||
"top_p": 0.1,
|
||||
"presence_penalty": 0,
|
||||
"frequency_penalty": 0,
|
||||
"stop_sequences": [ "[OBSERVATION]", "[Observation]", "[QUESTION]"]
|
||||
"execution_settings": {
|
||||
"default": {
|
||||
"max_tokens": 400,
|
||||
"temperature": 0.1,
|
||||
"top_p": 1,
|
||||
"presence_penalty": 0.0,
|
||||
"frequency_penalty": 0.0,
|
||||
"stop_sequences": ["[OBSERVATION]", "[Observation]", "[QUESTION]"]
|
||||
}
|
||||
},
|
||||
"input": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "question",
|
||||
"description": "The question to answer",
|
||||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"name": "agentScratchPad",
|
||||
"description": "The agent's scratch pad",
|
||||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"name": "functionDescriptions",
|
||||
"description": "The manual of the agent's functions",
|
||||
"defaultValue": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
"input_variables": [
|
||||
{
|
||||
"name": "question",
|
||||
"description": "The question to answer",
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "agentScratchPad",
|
||||
"description": "The agent's scratch pad",
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "functionDescriptions",
|
||||
"description": "The manual of the agent's functions",
|
||||
"default": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"description": "Given a request or command or goal generate multi-step plan to reach the goal. After each step LLM is called to perform the reasoning for the next step.",
|
||||
"type": "completion",
|
||||
"completion": {
|
||||
"max_tokens": 1024,
|
||||
"temperature": 0,
|
||||
"top_p": 1,
|
||||
"presence_penalty": 0,
|
||||
"frequency_penalty": 0,
|
||||
"stop_sequences": ["[OBSERVATION]"]
|
||||
"description":
|
||||
"Given a request or command or goal generate multi-step plan to reach the goal. After each step LLM is called to perform the reasoning for the next step.",
|
||||
"execution_settings": {
|
||||
"default": {
|
||||
"max_tokens": 400,
|
||||
"temperature": 0.1,
|
||||
"top_p": 1,
|
||||
"presence_penalty": 0.0,
|
||||
"frequency_penalty": 0.0,
|
||||
"stop_sequences": ["[OBSERVATION]"]
|
||||
}
|
||||
},
|
||||
"input": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "question",
|
||||
"description": "The question to answer",
|
||||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"name": "agentScratchPad",
|
||||
"description": "The agent's scratch pad",
|
||||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"name": "functionDescriptions",
|
||||
"description": "The manual of the agent's functions",
|
||||
"defaultValue": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
"input_variables": [
|
||||
{
|
||||
"name": "question",
|
||||
"description": "The question to answer",
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "agentScratchPad",
|
||||
"description": "The agent's scratch pad",
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "functionDescriptions",
|
||||
"description": "The manual of the agent's functions",
|
||||
"default": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -3,9 +3,7 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
Reference in New Issue
Block a user