mirror of
https://github.com/microsoft/semantic-kernel.git
synced 2026-10-12 02:19:06 +00:00
.Net: Processes - Stateful food preparation samples (#9300)
### Description Adding Stateful Food Preparation Samples - refactoring existing gatherIngredients step - adding stateful gatherIngredients step - adding stateful cutFoodStep with knifeSharpening to showcase state persistance and use across functions in same step - adding samples making use of different processes - state resets - and the same process multiple times - last state gets reused ### 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:
@@ -23,7 +23,7 @@ Example|Description
|
||||
---|---
|
||||
[Step01_Processes](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithProcesses/Step01/Step01_Processes.cs)|How to create a simple process with a loop and a conditional exit
|
||||
[Step02_AccountOpening](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithProcesses/Step02/Step02_AccountOpening.cs)|Showcasing processes cycles, fan in, fan out for opening an account.
|
||||
[Step03a_FoodPreparation](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithProcesses/Step03/Step03a_FoodPreparation.cs)|Showcasing reuse of steps, creation of processes, spawning of multiple events with food preparation samples.
|
||||
[Step03a_FoodPreparation](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithProcesses/Step03/Step03a_FoodPreparation.cs)|Showcasing reuse of steps, creation of processes, spawning of multiple events, use of stateful steps with food preparation samples.
|
||||
[Step03b_FoodOrdering](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithProcesses/Step03/Step03b_FoodOrdering.cs)|Showcasing use of subprocesses as steps, spawning of multiple events conditionally reusing the food preparation samples.
|
||||
[Step04_AgentOrchestration](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithProcesses/Step04/Step04_AgentOrchestration.cs)|Showcasing use of process steps in conjunction with the _Agent Framework_.
|
||||
|
||||
@@ -77,7 +77,9 @@ The following recipes for preparation of Order Items are defined as SK Processes
|
||||
|
||||
#### Product Preparation Processes
|
||||
|
||||
##### Potato Fries Preparation Process
|
||||
##### Stateless Product Preparation Processes
|
||||
|
||||
###### Potato Fries Preparation Process
|
||||
|
||||
``` mermaid
|
||||
flowchart LR
|
||||
@@ -92,7 +94,7 @@ flowchart LR
|
||||
FryStep -->|Fried Potato Ruined <br/> _Fried Food Ruined_| GatherIngredientsStep
|
||||
```
|
||||
|
||||
##### Fried Fish Preparation Process
|
||||
###### Fried Fish Preparation Process
|
||||
|
||||
``` mermaid
|
||||
flowchart LR
|
||||
@@ -107,7 +109,7 @@ flowchart LR
|
||||
FryStep -->|**Fried Fish Ruined** <br/> _Fried Food Ruined_| GatherIngredientsStep
|
||||
```
|
||||
|
||||
##### Fish Sandwich Preparation Process
|
||||
###### Fish Sandwich Preparation Process
|
||||
|
||||
``` mermaid
|
||||
flowchart LR
|
||||
@@ -121,7 +123,7 @@ flowchart LR
|
||||
PrepareFishSandwichEvent -->|Prepare Fried Fish| FriedFishStep -->|Fried Fish Ready| AddBunsStep --> |Buns Added | AddSpecialSauceStep --> |Special Sauce Added | FishSandwichReadyEvent
|
||||
```
|
||||
|
||||
##### Fish And Chips Preparation Process
|
||||
###### Fish And Chips Preparation Process
|
||||
|
||||
``` mermaid
|
||||
flowchart LR
|
||||
@@ -137,6 +139,88 @@ flowchart LR
|
||||
AddCondiments -->|Condiments Added| FishAndChipsReadyEvent
|
||||
```
|
||||
|
||||
##### Stateful Product Preparation Processes
|
||||
|
||||
The processes in this subsection contain the following modifications/additions to previously used food preparation processes:
|
||||
|
||||
- The `Gather Ingredients Step` is now stateful and has a predefined number of initial ingredients that are used as orders are prepared. When there are no ingredients left, it emits the `Out of Stock Event`.
|
||||
- The `Cut Food Step` is now a stateful component which has a `Knife Sharpness State` that tracks the Knife Sharpness.
|
||||
- As the `Slice Food` and `Chop Food` Functions get invoked, the Knife Sharpness deteriorates.
|
||||
- The `Cut Food Step` has an additional input function `Sharpen Knife Function`.
|
||||
- The new `Sharpen Knife Function` sharpens the knife and increases the Knife Sharpness - Knife Sharpness State.
|
||||
- From time to time, the `Cut Food Step`'s functions `SliceFood` and `ChopFood` will fail and emit a `Knife Needs Sharpening Event` that then triggers the `Sharpen Knife Function`.
|
||||
|
||||
|
||||
###### Potato Fries Preparation With Knife Sharpening and Ingredient Stock Process
|
||||
|
||||
The following processes is a modification on the process [Potato Fries Preparation](#potato-fries-preparation-process)
|
||||
with the the stateful steps mentioned previously.
|
||||
|
||||
``` mermaid
|
||||
flowchart LR
|
||||
PreparePotatoFriesEvent([Prepare Potato <br/> Fries Event])
|
||||
PotatoFriesReadyEvent([Potato Fries <br/> Ready Event])
|
||||
OutOfStock([Ingredients <br/> Out of Stock <br/> Event])
|
||||
|
||||
FryStep[Fry Food <br/> Step]
|
||||
|
||||
subgraph GatherIngredientsStep[Gather Ingredients Step]
|
||||
GatherIngredientsFunction[Gather Potato <br/> Function]
|
||||
IngredientsState[(Ingredients <br/> Stock <br/> State)]
|
||||
end
|
||||
subgraph CutStep ["Cut Food Step"]
|
||||
direction LR
|
||||
SliceFoodFunction[Slice Food <br/> Function]
|
||||
SharpenKnifeFunction[Sharpen Knife <br/> Function]
|
||||
CutState[(Knife <br/> Sharpness <br/> State)]
|
||||
end
|
||||
|
||||
CutStep --> |**Potato Sliced Ready** <br/> _Food Sliced Ready_ | FryStep --> |_Fried Food Ready_|PotatoFriesReadyEvent
|
||||
FryStep -->|Fried Potato Ruined <br/> _Fried Food Ruined_| GatherIngredientsStep
|
||||
GatherIngredientsStep --> OutOfStock
|
||||
|
||||
SliceFoodFunction --> |Knife Needs Sharpening| SharpenKnifeFunction
|
||||
SharpenKnifeFunction --> |Knife Sharpened| SliceFoodFunction
|
||||
|
||||
GatherIngredientsStep -->| Slice Potatoes <br/> _Ingredients Gathered_ | CutStep
|
||||
PreparePotatoFriesEvent --> GatherIngredientsStep
|
||||
```
|
||||
|
||||
###### Fried Fish Preparation With Knife Sharpening and Ingredient Stock Process
|
||||
|
||||
The following process is a modification on the process [Fried Fish Preparation](#fried-fish-preparation-process)
|
||||
with the the stateful steps mentioned previously.
|
||||
|
||||
``` mermaid
|
||||
flowchart LR
|
||||
PrepareFriedFishEvent([Prepare Fried <br/> Fish Event])
|
||||
FriedFishReadyEvent([Fried Fish <br/> Ready Event])
|
||||
OutOfStock([Ingredients <br/> Out of Stock <br/> Event])
|
||||
|
||||
FryStep[Fry Food <br/> Step]
|
||||
|
||||
subgraph GatherIngredientsStep[Gather Ingredients Step]
|
||||
GatherIngredientsFunction[Gather Fish <br/> Function]
|
||||
IngredientsState[(Ingredients <br/> Stock <br/> State)]
|
||||
end
|
||||
subgraph CutStep ["Cut Food Step"]
|
||||
direction LR
|
||||
ChopFoodFunction[Chop Food <br/> Function]
|
||||
SharpenKnifeFunction[Sharpen Knife <br/> Function]
|
||||
CutState[(Knife <br/> Sharpness <br/> State)]
|
||||
end
|
||||
|
||||
CutStep --> |**Fish Chopped Ready** <br/> _Food Chopped Ready_| FryStep --> |_Fried Food Ready_|FriedFishReadyEvent
|
||||
FryStep -->|**Fried Fish Ruined** <br/> _Fried Food Ruined_| GatherIngredientsStep
|
||||
GatherIngredientsStep --> OutOfStock
|
||||
|
||||
ChopFoodFunction --> |Knife Needs Sharpening| SharpenKnifeFunction
|
||||
SharpenKnifeFunction --> |Knife Sharpened| ChopFoodFunction
|
||||
|
||||
GatherIngredientsStep -->| Chop Fish <br/> _Ingredients Gathered_ | CutStep
|
||||
PrepareFriedFishEvent --> GatherIngredientsStep
|
||||
```
|
||||
|
||||
### Step03b_FoodOrdering
|
||||
|
||||
#### Single Order Preparation Process
|
||||
|
||||
@@ -7,7 +7,6 @@ namespace Step03.Processes;
|
||||
|
||||
/// <summary>
|
||||
/// Sample process that showcases how to create a process with sequential steps and reuse of existing steps.<br/>
|
||||
/// For a visual reference of the FriedFishProcess check this <see href="https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithProcesses/README.md#fried-fish-preparation-process" >diagram</see>
|
||||
/// </summary>
|
||||
public static class FriedFishProcess
|
||||
{
|
||||
@@ -20,6 +19,12 @@ public static class FriedFishProcess
|
||||
public const string FriedFishReady = nameof(FryFoodStep.OutputEvents.FriedFoodReady);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For a visual reference of the FriedFishProcess check this
|
||||
/// <see href="https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithProcesses/README.md#fried-fish-preparation-process" >diagram</see>
|
||||
/// </summary>
|
||||
/// <param name="processName">name of the process</param>
|
||||
/// <returns><see cref="ProcessBuilder"/></returns>
|
||||
public static ProcessBuilder CreateProcess(string processName = "FriedFishProcess")
|
||||
{
|
||||
var processBuilder = new ProcessBuilder(processName);
|
||||
@@ -47,21 +52,58 @@ public static class FriedFishProcess
|
||||
return processBuilder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For a visual reference of the FriedFishProcess with stateful steps check this
|
||||
/// <see href="https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithProcesses/README.md#fried-fish-preparation-with-knife-sharpening-and-ingredient-stock-process" >diagram</see>
|
||||
/// </summary>
|
||||
/// <param name="processName">name of the process</param>
|
||||
/// <returns><see cref="ProcessBuilder"/></returns>
|
||||
public static ProcessBuilder CreateProcessWithStatefulSteps(string processName = "FriedFishWithStatefulStepsProcess")
|
||||
{
|
||||
var processBuilder = new ProcessBuilder(processName);
|
||||
|
||||
var gatherIngredientsStep = processBuilder.AddStepFromType<GatherFriedFishIngredientsWithStockStep>();
|
||||
var chopStep = processBuilder.AddStepFromType<CutFoodWithSharpeningStep>("chopStep");
|
||||
var fryStep = processBuilder.AddStepFromType<FryFoodStep>();
|
||||
|
||||
processBuilder
|
||||
.OnInputEvent(ProcessEvents.PrepareFriedFish)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(gatherIngredientsStep));
|
||||
|
||||
gatherIngredientsStep
|
||||
.OnEvent(GatherFriedFishIngredientsWithStockStep.OutputEvents.IngredientsGathered)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(chopStep, functionName: CutFoodWithSharpeningStep.Functions.ChopFood));
|
||||
|
||||
gatherIngredientsStep
|
||||
.OnEvent(GatherFriedFishIngredientsWithStockStep.OutputEvents.IngredientsOutOfStock)
|
||||
.StopProcess();
|
||||
|
||||
chopStep
|
||||
.OnEvent(CutFoodWithSharpeningStep.OutputEvents.ChoppingReady)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(fryStep));
|
||||
|
||||
chopStep
|
||||
.OnEvent(CutFoodWithSharpeningStep.OutputEvents.KnifeNeedsSharpening)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(chopStep, functionName: CutFoodWithSharpeningStep.Functions.SharpenKnife));
|
||||
|
||||
chopStep
|
||||
.OnEvent(CutFoodWithSharpeningStep.OutputEvents.KnifeSharpened)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(chopStep, functionName: CutFoodWithSharpeningStep.Functions.ChopFood));
|
||||
|
||||
fryStep
|
||||
.OnEvent(FryFoodStep.OutputEvents.FoodRuined)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(gatherIngredientsStep));
|
||||
|
||||
return processBuilder;
|
||||
}
|
||||
|
||||
private sealed class GatherFriedFishIngredientsStep : GatherIngredientsStep
|
||||
{
|
||||
public override async Task GatherIngredientsAsync(KernelProcessStepContext context, List<string> foodActions)
|
||||
{
|
||||
var ingredient = FoodIngredients.Fish.ToFriendlyString();
|
||||
var updatedFoodActions = new List<string>();
|
||||
updatedFoodActions.AddRange(foodActions);
|
||||
if (updatedFoodActions.Count == 0)
|
||||
{
|
||||
updatedFoodActions.Add(ingredient);
|
||||
}
|
||||
updatedFoodActions.Add($"{ingredient}_gathered");
|
||||
public GatherFriedFishIngredientsStep() : base(FoodIngredients.Fish) { }
|
||||
}
|
||||
|
||||
Console.WriteLine($"GATHER_INGREDIENT: Gathered ingredient {ingredient}");
|
||||
await context.EmitEventAsync(new() { Id = OutputEvents.IngredientsGathered, Data = updatedFoodActions });
|
||||
}
|
||||
private sealed class GatherFriedFishIngredientsWithStockStep : GatherIngredientsWithStockStep
|
||||
{
|
||||
public GatherFriedFishIngredientsWithStockStep() : base(FoodIngredients.Fish) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ namespace Step03.Processes;
|
||||
|
||||
/// <summary>
|
||||
/// Sample process that showcases how to create a process with sequential steps and reuse of existing steps.<br/>
|
||||
/// For a visual reference of the FriedFishProcess check this <see href="https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithProcesses/README.md#potato-fries-preparation-process" >diagram</see>
|
||||
/// </summary>
|
||||
public static class PotatoFriesProcess
|
||||
{
|
||||
@@ -21,6 +20,12 @@ public static class PotatoFriesProcess
|
||||
public const string PotatoFriesReady = nameof(FryFoodStep.OutputEvents.FriedFoodReady);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For a visual reference of the PotatoFriesProcess check this
|
||||
/// <see href="https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithProcesses/README.md#potato-fries-preparation-process" >diagram</see>
|
||||
/// </summary>
|
||||
/// <param name="processName">name of the process</param>
|
||||
/// <returns><see cref="ProcessBuilder"/></returns>
|
||||
public static ProcessBuilder CreateProcess(string processName = "PotatoFriesProcess")
|
||||
{
|
||||
var processBuilder = new ProcessBuilder(processName);
|
||||
@@ -48,26 +53,58 @@ public static class PotatoFriesProcess
|
||||
return processBuilder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For a visual reference of the PotatoFriesProcess with stateful steps check this
|
||||
/// <see href="https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithProcesses/README.md#potato-fries-preparation-with-knife-sharpening-and-ingredient-stock-process" >diagram</see>
|
||||
/// </summary>
|
||||
/// <param name="processName">name of the process</param>
|
||||
/// <returns><see cref="ProcessBuilder"/></returns>
|
||||
public static ProcessBuilder CreateProcessWithStatefulSteps(string processName = "PotatoFriesWithStatefulStepsProcess")
|
||||
{
|
||||
var processBuilder = new ProcessBuilder(processName);
|
||||
|
||||
var gatherIngredientsStep = processBuilder.AddStepFromType<GatherPotatoFriesIngredientsWithStockStep>();
|
||||
var sliceStep = processBuilder.AddStepFromType<CutFoodWithSharpeningStep>("sliceStep");
|
||||
var fryStep = processBuilder.AddStepFromType<FryFoodStep>();
|
||||
|
||||
processBuilder
|
||||
.OnInputEvent(ProcessEvents.PreparePotatoFries)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(gatherIngredientsStep));
|
||||
|
||||
gatherIngredientsStep
|
||||
.OnEvent(GatherPotatoFriesIngredientsWithStockStep.OutputEvents.IngredientsGathered)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(sliceStep, functionName: CutFoodWithSharpeningStep.Functions.SliceFood));
|
||||
|
||||
gatherIngredientsStep
|
||||
.OnEvent(GatherPotatoFriesIngredientsWithStockStep.OutputEvents.IngredientsOutOfStock)
|
||||
.StopProcess();
|
||||
|
||||
sliceStep
|
||||
.OnEvent(CutFoodWithSharpeningStep.OutputEvents.SlicingReady)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(fryStep));
|
||||
|
||||
sliceStep
|
||||
.OnEvent(CutFoodWithSharpeningStep.OutputEvents.KnifeNeedsSharpening)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(sliceStep, functionName: CutFoodWithSharpeningStep.Functions.SharpenKnife));
|
||||
|
||||
sliceStep
|
||||
.OnEvent(CutFoodWithSharpeningStep.OutputEvents.KnifeSharpened)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(sliceStep, functionName: CutFoodWithSharpeningStep.Functions.SliceFood));
|
||||
|
||||
fryStep
|
||||
.OnEvent(FryFoodStep.OutputEvents.FoodRuined)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(gatherIngredientsStep));
|
||||
|
||||
return processBuilder;
|
||||
}
|
||||
|
||||
private sealed class GatherPotatoFriesIngredientsStep : GatherIngredientsStep
|
||||
{
|
||||
public override async Task GatherIngredientsAsync(KernelProcessStepContext context, List<string> foodActions)
|
||||
{
|
||||
var ingredient = FoodIngredients.Pototoes.ToFriendlyString();
|
||||
var updatedFoodActions = new List<string>();
|
||||
updatedFoodActions.AddRange(foodActions);
|
||||
if (updatedFoodActions.Count == 0)
|
||||
{
|
||||
updatedFoodActions.Add(ingredient);
|
||||
}
|
||||
updatedFoodActions.Add($"{ingredient}_gathered");
|
||||
|
||||
Console.WriteLine($"GATHER_INGREDIENT: Gathered ingredient {ingredient}");
|
||||
await context.EmitEventAsync(new() { Id = OutputEvents.IngredientsGathered, Data = updatedFoodActions });
|
||||
}
|
||||
public GatherPotatoFriesIngredientsStep() : base(FoodIngredients.Pototoes) { }
|
||||
}
|
||||
|
||||
private sealed class ExternalPotatoFriesStep : ExternalStep
|
||||
private sealed class GatherPotatoFriesIngredientsWithStockStep : GatherIngredientsWithStockStep
|
||||
{
|
||||
public ExternalPotatoFriesStep() : base(ProcessEvents.PotatoFriesReady) { }
|
||||
public GatherPotatoFriesIngredientsWithStockStep() : base(FoodIngredients.Pototoes) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ public class Step03a_FoodPreparation(ITestOutputHelper output) : BaseTest(output
|
||||
// Target Open AI Services
|
||||
protected override bool ForceOpenAI => true;
|
||||
|
||||
#region Stateless Processes
|
||||
[Fact]
|
||||
public async Task UsePrepareFriedFishProcessAsync()
|
||||
{
|
||||
@@ -42,7 +43,75 @@ public class Step03a_FoodPreparation(ITestOutputHelper output) : BaseTest(output
|
||||
var process = FishAndChipsProcess.CreateProcess();
|
||||
await UsePrepareSpecificProductAsync(process, FishAndChipsProcess.ProcessEvents.PrepareFishAndChips);
|
||||
}
|
||||
#endregion
|
||||
#region Stateful Processes
|
||||
/// <summary>
|
||||
/// Test case that showcase when the same process is build multiple times, it will have different initial states
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Fact]
|
||||
public async Task UsePrepareStatefulFriedFishProcessNoSharedStateAsync()
|
||||
{
|
||||
var processBuilder = FriedFishProcess.CreateProcessWithStatefulSteps();
|
||||
var externalTriggerEvent = FriedFishProcess.ProcessEvents.PrepareFriedFish;
|
||||
|
||||
Kernel kernel = CreateKernelWithChatCompletion();
|
||||
|
||||
// Assert
|
||||
Console.WriteLine($"=== Start SK Process '{processBuilder.Name}' ===");
|
||||
await ExecuteProcessWithStateAsync(processBuilder.Build(), kernel, externalTriggerEvent, "Order 1");
|
||||
await ExecuteProcessWithStateAsync(processBuilder.Build(), kernel, externalTriggerEvent, "Order 2");
|
||||
Console.WriteLine($"=== End SK Process '{processBuilder.Name}' ===");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test case that showcase when the same process is build once and used multiple times, it will have share the state
|
||||
/// and the state of the steps will become the initial state of the next running process
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Fact]
|
||||
public async Task UsePrepareStatefulFriedFishProcessSharedStateAsync()
|
||||
{
|
||||
var processBuilder = FriedFishProcess.CreateProcessWithStatefulSteps();
|
||||
var externalTriggerEvent = FriedFishProcess.ProcessEvents.PrepareFriedFish;
|
||||
|
||||
Kernel kernel = CreateKernelWithChatCompletion();
|
||||
KernelProcess kernelProcess = processBuilder.Build();
|
||||
|
||||
Console.WriteLine($"=== Start SK Process '{processBuilder.Name}' ===");
|
||||
await ExecuteProcessWithStateAsync(kernelProcess, kernel, externalTriggerEvent, "Order 1");
|
||||
await ExecuteProcessWithStateAsync(kernelProcess, kernel, externalTriggerEvent, "Order 2");
|
||||
await ExecuteProcessWithStateAsync(kernelProcess, kernel, externalTriggerEvent, "Order 3");
|
||||
Console.WriteLine($"=== End SK Process '{processBuilder.Name}' ===");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UsePrepareStatefulPotatoFriesProcessSharedStateAsync()
|
||||
{
|
||||
var processBuilder = PotatoFriesProcess.CreateProcessWithStatefulSteps();
|
||||
var externalTriggerEvent = PotatoFriesProcess.ProcessEvents.PreparePotatoFries;
|
||||
|
||||
Kernel kernel = CreateKernelWithChatCompletion();
|
||||
KernelProcess kernelProcess = processBuilder.Build();
|
||||
|
||||
Console.WriteLine($"=== Start SK Process '{processBuilder.Name}' ===");
|
||||
await ExecuteProcessWithStateAsync(kernelProcess, kernel, externalTriggerEvent, "Order 1");
|
||||
await ExecuteProcessWithStateAsync(kernelProcess, kernel, externalTriggerEvent, "Order 2");
|
||||
await ExecuteProcessWithStateAsync(kernelProcess, kernel, externalTriggerEvent, "Order 3");
|
||||
Console.WriteLine($"=== End SK Process '{processBuilder.Name}' ===");
|
||||
}
|
||||
|
||||
private async Task<KernelProcess> ExecuteProcessWithStateAsync(KernelProcess process, Kernel kernel, string externalTriggerEvent, string orderLabel)
|
||||
{
|
||||
Console.WriteLine($"=== {orderLabel} ===");
|
||||
var runningProcess = await process.StartAsync(kernel, new KernelProcessEvent()
|
||||
{
|
||||
Id = externalTriggerEvent,
|
||||
Data = new List<string>()
|
||||
});
|
||||
return await runningProcess.GetStateAsync();
|
||||
}
|
||||
#endregion
|
||||
protected async Task UsePrepareSpecificProductAsync(ProcessBuilder processBuilder, string externalTriggerEvent)
|
||||
{
|
||||
// Arrange
|
||||
@@ -52,9 +121,11 @@ public class Step03a_FoodPreparation(ITestOutputHelper output) : BaseTest(output
|
||||
KernelProcess kernelProcess = processBuilder.Build();
|
||||
|
||||
// Assert
|
||||
Console.WriteLine($"=== Start SK Process '{processBuilder.Name}' ===");
|
||||
using var runningProcess = await kernelProcess.StartAsync(kernel, new KernelProcessEvent()
|
||||
{
|
||||
Id = externalTriggerEvent, Data = new List<string>()
|
||||
});
|
||||
Console.WriteLine($"=== End SK Process '{processBuilder.Name}' ===");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
|
||||
namespace Step03.Steps;
|
||||
|
||||
/// <summary>
|
||||
/// Step used in the Processes Samples:
|
||||
/// - Step_03_FoodPreparation.cs
|
||||
/// </summary>
|
||||
public class CutFoodWithSharpeningStep : KernelProcessStep<CutFoodWithSharpeningState>
|
||||
{
|
||||
public static class Functions
|
||||
{
|
||||
public const string ChopFood = nameof(ChopFood);
|
||||
public const string SliceFood = nameof(SliceFood);
|
||||
public const string SharpenKnife = nameof(SharpenKnife);
|
||||
}
|
||||
|
||||
public static class OutputEvents
|
||||
{
|
||||
public const string ChoppingReady = nameof(ChoppingReady);
|
||||
public const string SlicingReady = nameof(SlicingReady);
|
||||
public const string KnifeNeedsSharpening = nameof(KnifeNeedsSharpening);
|
||||
public const string KnifeSharpened = nameof(KnifeSharpened);
|
||||
}
|
||||
|
||||
internal CutFoodWithSharpeningState? _state;
|
||||
|
||||
public override ValueTask ActivateAsync(KernelProcessStepState<CutFoodWithSharpeningState> state)
|
||||
{
|
||||
_state = state.State;
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
[KernelFunction(Functions.ChopFood)]
|
||||
public async Task ChopFoodAsync(KernelProcessStepContext context, List<string> foodActions)
|
||||
{
|
||||
var foodToBeCut = foodActions.First();
|
||||
if (this.KnifeNeedsSharpening())
|
||||
{
|
||||
Console.WriteLine($"CUTTING_STEP: Dull knife, cannot chop {foodToBeCut} - needs sharpening.");
|
||||
await context.EmitEventAsync(new() { Id = OutputEvents.KnifeNeedsSharpening, Data = foodActions });
|
||||
return;
|
||||
}
|
||||
// Update knife sharpness
|
||||
this._state!.KnifeSharpness--;
|
||||
|
||||
// Chop food
|
||||
foodActions.Add(this.getActionString(foodToBeCut, "chopped"));
|
||||
Console.WriteLine($"CUTTING_STEP: Ingredient {foodToBeCut} has been chopped! - knife sharpness: {this._state.KnifeSharpness}");
|
||||
await context.EmitEventAsync(new() { Id = OutputEvents.ChoppingReady, Data = foodActions });
|
||||
}
|
||||
|
||||
[KernelFunction(Functions.SliceFood)]
|
||||
public async Task SliceFoodAsync(KernelProcessStepContext context, List<string> foodActions)
|
||||
{
|
||||
var foodToBeCut = foodActions.First();
|
||||
if (this.KnifeNeedsSharpening())
|
||||
{
|
||||
Console.WriteLine($"CUTTING_STEP: Dull knife, cannot slice {foodToBeCut} - needs sharpening.");
|
||||
await context.EmitEventAsync(new() { Id = OutputEvents.KnifeNeedsSharpening, Data = foodActions });
|
||||
return;
|
||||
}
|
||||
// Update knife sharpness
|
||||
this._state!.KnifeSharpness--;
|
||||
|
||||
// Slice food
|
||||
foodActions.Add(this.getActionString(foodToBeCut, "sliced"));
|
||||
Console.WriteLine($"CUTTING_STEP: Ingredient {foodToBeCut} has been sliced! - knife sharpness: {this._state.KnifeSharpness}");
|
||||
await context.EmitEventAsync(new() { Id = OutputEvents.SlicingReady, Data = foodActions });
|
||||
}
|
||||
|
||||
[KernelFunction(Functions.SharpenKnife)]
|
||||
public async Task SharpenKnifeAsync(KernelProcessStepContext context, List<string> foodActions)
|
||||
{
|
||||
this._state!.KnifeSharpness += this._state._sharpeningBoost;
|
||||
Console.WriteLine($"KNIFE SHARPENED: Knife sharpness is now {this._state.KnifeSharpness}!");
|
||||
await context.EmitEventAsync(new() { Id = OutputEvents.KnifeSharpened, Data = foodActions });
|
||||
}
|
||||
|
||||
private bool KnifeNeedsSharpening() => this._state?.KnifeSharpness == this._state?._needsSharpeningLimit;
|
||||
|
||||
private string getActionString(string food, string action)
|
||||
{
|
||||
return $"{food}_{action}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The state object for the <see cref="CutFoodWithSharpeningStep"/>.
|
||||
/// </summary>
|
||||
public sealed class CutFoodWithSharpeningState
|
||||
{
|
||||
internal int KnifeSharpness { get; set; } = 5;
|
||||
|
||||
internal int _needsSharpeningLimit = 3;
|
||||
internal int _sharpeningBoost = 5;
|
||||
}
|
||||
@@ -31,7 +31,7 @@ public class FryFoodStep : KernelProcessStep
|
||||
int fryerMalfunction = _randomSeed.Next(0, 10);
|
||||
|
||||
// foodToFry could potentially be used to set the frying temperature and cooking duration
|
||||
if (fryerMalfunction > 6)
|
||||
if (fryerMalfunction < 5)
|
||||
{
|
||||
// Oh no! Food got burnt :(
|
||||
foodActions.Add($"{foodToFry}_frying_failed");
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
using Microsoft.SemanticKernel;
|
||||
using Step03.Models;
|
||||
namespace Step03.Steps;
|
||||
|
||||
/// <summary>
|
||||
@@ -18,6 +19,13 @@ public class GatherIngredientsStep : KernelProcessStep
|
||||
public const string IngredientsGathered = nameof(IngredientsGathered);
|
||||
}
|
||||
|
||||
private readonly FoodIngredients _ingredient;
|
||||
|
||||
public GatherIngredientsStep(FoodIngredients ingredient)
|
||||
{
|
||||
this._ingredient = ingredient;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method to be overridden by the user set custom ingredients to be gathered and events to be triggered
|
||||
/// </summary>
|
||||
@@ -27,6 +35,90 @@ public class GatherIngredientsStep : KernelProcessStep
|
||||
[KernelFunction(Functions.GatherIngredients)]
|
||||
public virtual async Task GatherIngredientsAsync(KernelProcessStepContext context, List<string> foodActions)
|
||||
{
|
||||
await context.EmitEventAsync(new() { Id = OutputEvents.IngredientsGathered, Data = foodActions });
|
||||
var ingredient = this._ingredient.ToFriendlyString();
|
||||
var updatedFoodActions = new List<string>();
|
||||
updatedFoodActions.AddRange(foodActions);
|
||||
if (updatedFoodActions.Count == 0)
|
||||
{
|
||||
updatedFoodActions.Add(ingredient);
|
||||
}
|
||||
updatedFoodActions.Add($"{ingredient}_gathered");
|
||||
|
||||
Console.WriteLine($"GATHER_INGREDIENT: Gathered ingredient {ingredient}");
|
||||
await context.EmitEventAsync(new() { Id = OutputEvents.IngredientsGathered, Data = updatedFoodActions });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stateful Step used as base by many other cooking processes
|
||||
/// When used in other processes a new step is based on this one with custom GatherIngredientsAsync functionality
|
||||
/// </summary>
|
||||
public class GatherIngredientsWithStockStep : KernelProcessStep<GatherIngredientsState>
|
||||
{
|
||||
public static class Functions
|
||||
{
|
||||
public const string GatherIngredients = nameof(GatherIngredients);
|
||||
}
|
||||
|
||||
public static class OutputEvents
|
||||
{
|
||||
public const string IngredientsGathered = nameof(IngredientsGathered);
|
||||
public const string IngredientsOutOfStock = nameof(IngredientsOutOfStock);
|
||||
}
|
||||
|
||||
private readonly FoodIngredients _ingredient;
|
||||
|
||||
public GatherIngredientsWithStockStep(FoodIngredients ingredient)
|
||||
{
|
||||
this._ingredient = ingredient;
|
||||
}
|
||||
|
||||
internal GatherIngredientsState? _state;
|
||||
|
||||
public override ValueTask ActivateAsync(KernelProcessStepState<GatherIngredientsState> state)
|
||||
{
|
||||
_state = state.State;
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method to be overridden by the user set custom ingredients to be gathered and events to be triggered
|
||||
/// </summary>
|
||||
/// <param name="context">The context for the current step and process. <see cref="KernelProcessStepContext"/></param>
|
||||
/// <param name="foodActions">list of actions taken to the food</param>
|
||||
/// <returns></returns>
|
||||
[KernelFunction(Functions.GatherIngredients)]
|
||||
public virtual async Task GatherIngredientsAsync(KernelProcessStepContext context, List<string> foodActions)
|
||||
{
|
||||
var ingredient = this._ingredient.ToFriendlyString(); ;
|
||||
var updatedFoodActions = new List<string>();
|
||||
updatedFoodActions.AddRange(foodActions);
|
||||
|
||||
if (this._state!.IngredientsStock == 0)
|
||||
{
|
||||
Console.WriteLine($"GATHER_INGREDIENT: Could not gather {ingredient} - OUT OF STOCK!");
|
||||
await context.EmitEventAsync(new() { Id = OutputEvents.IngredientsOutOfStock, Data = updatedFoodActions });
|
||||
return;
|
||||
}
|
||||
|
||||
if (updatedFoodActions.Count == 0)
|
||||
{
|
||||
updatedFoodActions.Add(ingredient);
|
||||
}
|
||||
updatedFoodActions.Add($"{ingredient}_gathered");
|
||||
|
||||
// Updating stock of ingredients
|
||||
this._state.IngredientsStock--;
|
||||
|
||||
Console.WriteLine($"GATHER_INGREDIENT: Gathered ingredient {ingredient} - remaining: {this._state.IngredientsStock}");
|
||||
await context.EmitEventAsync(new() { Id = OutputEvents.IngredientsGathered, Data = updatedFoodActions });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The state object for the <see cref="GatherIngredientsWithStockStep"/>.
|
||||
/// </summary>
|
||||
public sealed class GatherIngredientsState
|
||||
{
|
||||
internal int IngredientsStock { get; set; } = 5;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user