chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace Step03.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Food Ingredients used in steps such GatherIngredientStep, CutFoodStep, FryFoodStep
|
||||
/// </summary>
|
||||
public enum FoodIngredients
|
||||
{
|
||||
Pototoes,
|
||||
Fish,
|
||||
Buns,
|
||||
Sauce,
|
||||
Condiments,
|
||||
None
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extensions to have access to friendly string names for <see cref="FoodIngredients"/>
|
||||
/// </summary>
|
||||
public static class FoodIngredientsExtensions
|
||||
{
|
||||
private static readonly Dictionary<FoodIngredients, string> s_foodIngredientsStrings = new()
|
||||
{
|
||||
{ FoodIngredients.Pototoes, "Potatoes" },
|
||||
{ FoodIngredients.Fish, "Fish" },
|
||||
{ FoodIngredients.Buns, "Buns" },
|
||||
{ FoodIngredients.Sauce, "Sauce" },
|
||||
{ FoodIngredients.Condiments, "Condiments" },
|
||||
{ FoodIngredients.None, "None" }
|
||||
};
|
||||
|
||||
public static string ToFriendlyString(this FoodIngredients ingredient)
|
||||
{
|
||||
return s_foodIngredientsStrings[ingredient];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace Step03.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Food Items that can be prepared by the PrepareSingleFoodItemProcess
|
||||
/// </summary>
|
||||
public enum FoodItem
|
||||
{
|
||||
PotatoFries,
|
||||
FriedFish,
|
||||
FishSandwich,
|
||||
FishAndChips
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extensions to have access to friendly string names for <see cref="FoodItem"/>
|
||||
/// </summary>
|
||||
public static class FoodItemExtensions
|
||||
{
|
||||
private static readonly Dictionary<FoodItem, string> s_foodItemsStrings = new()
|
||||
{
|
||||
{ FoodItem.PotatoFries, "Potato Fries" },
|
||||
{ FoodItem.FriedFish, "Fried Fish" },
|
||||
{ FoodItem.FishSandwich, "Fish Sandwich" },
|
||||
{ FoodItem.FishAndChips, "Fish & Chips" },
|
||||
};
|
||||
|
||||
public static string ToFriendlyString(this FoodItem item)
|
||||
{
|
||||
return s_foodItemsStrings[item];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text.Json;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Step03.Models;
|
||||
using Step03.Steps;
|
||||
|
||||
namespace Step03.Processes;
|
||||
|
||||
/// <summary>
|
||||
/// Sample process that showcases how to create a process with a fan in/fan out behavior and use of existing processes as steps.<br/>
|
||||
/// Visual reference of this process can be found in the <see href="https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithProcesses/README.md#fish-and-chips-preparation-process" >diagram</see>
|
||||
/// </summary>
|
||||
public static class FishAndChipsProcess
|
||||
{
|
||||
public static class ProcessEvents
|
||||
{
|
||||
public const string PrepareFishAndChips = nameof(PrepareFishAndChips);
|
||||
public const string FishAndChipsReady = nameof(FishAndChipsReady);
|
||||
public const string FishAndChipsIngredientOutOfStock = nameof(FishAndChipsIngredientOutOfStock);
|
||||
}
|
||||
|
||||
public static ProcessBuilder CreateProcess(string processName = "FishAndChipsProcess")
|
||||
{
|
||||
var processBuilder = new ProcessBuilder(processName);
|
||||
var makeFriedFishStep = processBuilder.AddStepFromProcess(FriedFishProcess.CreateProcess());
|
||||
var makePotatoFriesStep = processBuilder.AddStepFromProcess(PotatoFriesProcess.CreateProcess());
|
||||
var addCondimentsStep = processBuilder.AddStepFromType<AddFishAndChipsCondimentsStep>();
|
||||
// An additional step that is the only one that emits an public event in a process can be added to maintain event names unique
|
||||
var externalStep = processBuilder.AddStepFromType<ExternalFishAndChipsStep>();
|
||||
|
||||
processBuilder
|
||||
.OnInputEvent(ProcessEvents.PrepareFishAndChips)
|
||||
.SendEventTo(makeFriedFishStep.WhereInputEventIs(FriedFishProcess.ProcessEvents.PrepareFriedFish))
|
||||
.SendEventTo(makePotatoFriesStep.WhereInputEventIs(PotatoFriesProcess.ProcessEvents.PreparePotatoFries));
|
||||
|
||||
makeFriedFishStep
|
||||
.OnEvent(FriedFishProcess.ProcessEvents.FriedFishReady)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(addCondimentsStep, parameterName: "fishActions"));
|
||||
|
||||
makePotatoFriesStep
|
||||
.OnEvent(PotatoFriesProcess.ProcessEvents.PotatoFriesReady)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(addCondimentsStep, parameterName: "potatoActions"));
|
||||
|
||||
addCondimentsStep
|
||||
.OnEvent(AddFishAndChipsCondimentsStep.OutputEvents.CondimentsAdded)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(externalStep));
|
||||
|
||||
return processBuilder;
|
||||
}
|
||||
|
||||
public static ProcessBuilder CreateProcessWithStatefulSteps(string processName = "FishAndChipsWithStatefulStepsProcess")
|
||||
{
|
||||
var processBuilder = new ProcessBuilder(processName);
|
||||
var makeFriedFishStep = processBuilder.AddStepFromProcess(FriedFishProcess.CreateProcessWithStatefulStepsV1());
|
||||
var makePotatoFriesStep = processBuilder.AddStepFromProcess(PotatoFriesProcess.CreateProcessWithStatefulSteps());
|
||||
var addCondimentsStep = processBuilder.AddStepFromType<AddFishAndChipsCondimentsStep>();
|
||||
// An additional step that is the only one that emits an public event in a process can be added to maintain event names unique
|
||||
var externalStep = processBuilder.AddStepFromType<ExternalFishAndChipsStep>();
|
||||
|
||||
processBuilder
|
||||
.OnInputEvent(ProcessEvents.PrepareFishAndChips)
|
||||
.SendEventTo(makeFriedFishStep.WhereInputEventIs(FriedFishProcess.ProcessEvents.PrepareFriedFish))
|
||||
.SendEventTo(makePotatoFriesStep.WhereInputEventIs(PotatoFriesProcess.ProcessEvents.PreparePotatoFries));
|
||||
|
||||
makeFriedFishStep
|
||||
.OnEvent(FriedFishProcess.ProcessEvents.FriedFishReady)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(addCondimentsStep, parameterName: "fishActions"));
|
||||
|
||||
makePotatoFriesStep
|
||||
.OnEvent(PotatoFriesProcess.ProcessEvents.PotatoFriesReady)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(addCondimentsStep, parameterName: "potatoActions"));
|
||||
|
||||
addCondimentsStep
|
||||
.OnEvent(AddFishAndChipsCondimentsStep.OutputEvents.CondimentsAdded)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(externalStep));
|
||||
|
||||
return processBuilder;
|
||||
}
|
||||
|
||||
private sealed class AddFishAndChipsCondimentsStep : KernelProcessStep
|
||||
{
|
||||
public static class ProcessFunctions
|
||||
{
|
||||
public const string AddCondiments = nameof(AddCondiments);
|
||||
}
|
||||
|
||||
public static class OutputEvents
|
||||
{
|
||||
public const string CondimentsAdded = nameof(CondimentsAdded);
|
||||
}
|
||||
|
||||
[KernelFunction(ProcessFunctions.AddCondiments)]
|
||||
public async Task AddCondimentsAsync(KernelProcessStepContext context, List<string> fishActions, List<string> potatoActions)
|
||||
{
|
||||
Console.WriteLine($"ADD_CONDIMENTS: Added condiments to Fish & Chips - Fish: {JsonSerializer.Serialize(fishActions)}, Potatoes: {JsonSerializer.Serialize(potatoActions)}");
|
||||
fishActions.AddRange(potatoActions);
|
||||
fishActions.Add(FoodIngredients.Condiments.ToFriendlyString());
|
||||
await context.EmitEventAsync(new() { Id = OutputEvents.CondimentsAdded, Data = fishActions });
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ExternalFishAndChipsStep : ExternalStep
|
||||
{
|
||||
public ExternalFishAndChipsStep() : base(ProcessEvents.FishAndChipsReady) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
using Step03.Models;
|
||||
using Step03.Steps;
|
||||
|
||||
namespace Step03.Processes;
|
||||
|
||||
/// <summary>
|
||||
/// Sample process that showcases how to create a process with sequential steps and use of existing processes as steps.<br/>
|
||||
/// Visual reference of this process can be found in the <see href="https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithProcesses/README.md#fish-sandwich-preparation-process" >diagram</see>
|
||||
/// </summary>
|
||||
public static class FishSandwichProcess
|
||||
{
|
||||
public static class ProcessEvents
|
||||
{
|
||||
public const string PrepareFishSandwich = nameof(PrepareFishSandwich);
|
||||
public const string FishSandwichReady = nameof(FishSandwichReady);
|
||||
}
|
||||
|
||||
public static ProcessBuilder CreateProcess(string processName = "FishSandwichProcess")
|
||||
{
|
||||
var processBuilder = new ProcessBuilder(processName);
|
||||
var makeFriedFishStep = processBuilder.AddStepFromProcess(FriedFishProcess.CreateProcess());
|
||||
var addBunsStep = processBuilder.AddStepFromType<AddBunsStep>();
|
||||
var addSpecialSauceStep = processBuilder.AddStepFromType<AddSpecialSauceStep>();
|
||||
// An additional step that is the only one that emits an public event in a process can be added to maintain event names unique
|
||||
var externalStep = processBuilder.AddStepFromType<ExternalFriedFishStep>();
|
||||
|
||||
processBuilder
|
||||
.OnInputEvent(ProcessEvents.PrepareFishSandwich)
|
||||
.SendEventTo(makeFriedFishStep.WhereInputEventIs(FriedFishProcess.ProcessEvents.PrepareFriedFish));
|
||||
|
||||
makeFriedFishStep
|
||||
.OnEvent(FriedFishProcess.ProcessEvents.FriedFishReady)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(addBunsStep));
|
||||
|
||||
addBunsStep
|
||||
.OnEvent(AddBunsStep.OutputEvents.BunsAdded)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(addSpecialSauceStep));
|
||||
|
||||
addSpecialSauceStep
|
||||
.OnEvent(AddSpecialSauceStep.OutputEvents.SpecialSauceAdded)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(externalStep));
|
||||
|
||||
return processBuilder;
|
||||
}
|
||||
|
||||
public static ProcessBuilder CreateProcessWithStatefulStepsV1(string processName = "FishSandwichWithStatefulStepsProcess")
|
||||
{
|
||||
var processBuilder = new ProcessBuilder(processName) { Version = "FishSandwich.V1" };
|
||||
var makeFriedFishStep = processBuilder.AddStepFromProcess(FriedFishProcess.CreateProcessWithStatefulStepsV1());
|
||||
var addBunsStep = processBuilder.AddStepFromType<AddBunsStep>();
|
||||
var addSpecialSauceStep = processBuilder.AddStepFromType<AddSpecialSauceStep>();
|
||||
// An additional step that is the only one that emits an public event in a process can be added to maintain event names unique
|
||||
var externalStep = processBuilder.AddStepFromType<ExternalFriedFishStep>();
|
||||
|
||||
processBuilder
|
||||
.OnInputEvent(ProcessEvents.PrepareFishSandwich)
|
||||
.SendEventTo(makeFriedFishStep.WhereInputEventIs(FriedFishProcess.ProcessEvents.PrepareFriedFish));
|
||||
|
||||
makeFriedFishStep
|
||||
.OnEvent(FriedFishProcess.ProcessEvents.FriedFishReady)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(addBunsStep));
|
||||
|
||||
addBunsStep
|
||||
.OnEvent(AddBunsStep.OutputEvents.BunsAdded)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(addSpecialSauceStep));
|
||||
|
||||
addSpecialSauceStep
|
||||
.OnEvent(AddSpecialSauceStep.OutputEvents.SpecialSauceAdded)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(externalStep));
|
||||
|
||||
return processBuilder;
|
||||
}
|
||||
|
||||
public static ProcessBuilder CreateProcessWithStatefulStepsV2(string processName = "FishSandwichWithStatefulStepsProcess")
|
||||
{
|
||||
var processBuilder = new ProcessBuilder(processName) { Version = "FishSandwich.V2" };
|
||||
var makeFriedFishStep = processBuilder.AddStepFromProcess(FriedFishProcess.CreateProcessWithStatefulStepsV2("FriedFishStep"), aliases: ["FriedFishWithStatefulStepsProcess"]);
|
||||
var addBunsStep = processBuilder.AddStepFromType<AddBunsStep>();
|
||||
var addSpecialSauceStep = processBuilder.AddStepFromType<AddSpecialSauceStep>();
|
||||
// An additional step that is the only one that emits an public event in a process can be added to maintain event names unique
|
||||
var externalStep = processBuilder.AddStepFromType<ExternalFriedFishStep>();
|
||||
|
||||
processBuilder
|
||||
.OnInputEvent(ProcessEvents.PrepareFishSandwich)
|
||||
.SendEventTo(makeFriedFishStep.WhereInputEventIs(FriedFishProcess.ProcessEvents.PrepareFriedFish));
|
||||
|
||||
makeFriedFishStep
|
||||
.OnEvent(FriedFishProcess.ProcessEvents.FriedFishReady)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(addBunsStep));
|
||||
|
||||
addBunsStep
|
||||
.OnEvent(AddBunsStep.OutputEvents.BunsAdded)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(addSpecialSauceStep));
|
||||
|
||||
addSpecialSauceStep
|
||||
.OnEvent(AddSpecialSauceStep.OutputEvents.SpecialSauceAdded)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(externalStep));
|
||||
|
||||
return processBuilder;
|
||||
}
|
||||
|
||||
private sealed class AddBunsStep : KernelProcessStep
|
||||
{
|
||||
public static class ProcessFunctions
|
||||
{
|
||||
public const string AddBuns = nameof(AddBuns);
|
||||
}
|
||||
|
||||
public static class OutputEvents
|
||||
{
|
||||
public const string BunsAdded = nameof(BunsAdded);
|
||||
}
|
||||
|
||||
[KernelFunction(ProcessFunctions.AddBuns)]
|
||||
public async Task SliceFoodAsync(KernelProcessStepContext context, List<string> foodActions)
|
||||
{
|
||||
Console.WriteLine($"BUNS_ADDED_STEP: Buns added to ingredient {foodActions.First()}");
|
||||
foodActions.Add(FoodIngredients.Buns.ToFriendlyString());
|
||||
await context.EmitEventAsync(new() { Id = OutputEvents.BunsAdded, Data = foodActions });
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class AddSpecialSauceStep : KernelProcessStep
|
||||
{
|
||||
public static class ProcessFunctions
|
||||
{
|
||||
public const string AddSpecialSauce = nameof(AddSpecialSauce);
|
||||
}
|
||||
|
||||
public static class OutputEvents
|
||||
{
|
||||
public const string SpecialSauceAdded = nameof(SpecialSauceAdded);
|
||||
}
|
||||
|
||||
[KernelFunction(ProcessFunctions.AddSpecialSauce)]
|
||||
public async Task SliceFoodAsync(KernelProcessStepContext context, List<string> foodActions)
|
||||
{
|
||||
Console.WriteLine($"SPECIAL_SAUCE_ADDED: Special sauce added to ingredient {foodActions.First()}");
|
||||
foodActions.Add(FoodIngredients.Sauce.ToFriendlyString());
|
||||
await context.EmitEventAsync(new() { Id = OutputEvents.SpecialSauceAdded, Data = foodActions, Visibility = KernelProcessEventVisibility.Public });
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ExternalFriedFishStep : ExternalStep
|
||||
{
|
||||
public ExternalFriedFishStep() : base(ProcessEvents.FishSandwichReady) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Process;
|
||||
using Step03.Models;
|
||||
using Step03.Steps;
|
||||
namespace Step03.Processes;
|
||||
|
||||
/// <summary>
|
||||
/// Sample process that showcases how to create a process with sequential steps and reuse of existing steps.<br/>
|
||||
/// </summary>
|
||||
public static class FriedFishProcess
|
||||
{
|
||||
public static class ProcessEvents
|
||||
{
|
||||
public const string PrepareFriedFish = nameof(PrepareFriedFish);
|
||||
// When multiple processes use the same final step, the should event marked as public
|
||||
// so that the step event can be used as the output event of the process too.
|
||||
// In these samples both fried fish and potato fries end with FryStep success
|
||||
public const string FriedFishReady = 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);
|
||||
|
||||
var gatherIngredientsStep = processBuilder.AddStepFromType<GatherFriedFishIngredientsStep>();
|
||||
var chopStep = processBuilder.AddStepFromType<CutFoodStep>();
|
||||
var fryStep = processBuilder.AddStepFromType<FryFoodStep>();
|
||||
|
||||
processBuilder
|
||||
.OnInputEvent(ProcessEvents.PrepareFriedFish)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(gatherIngredientsStep));
|
||||
|
||||
gatherIngredientsStep
|
||||
.OnEvent(GatherFriedFishIngredientsStep.OutputEvents.IngredientsGathered)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(chopStep, functionName: CutFoodStep.ProcessStepFunctions.ChopFood));
|
||||
|
||||
chopStep
|
||||
.OnEvent(CutFoodStep.OutputEvents.ChoppingReady)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(fryStep));
|
||||
|
||||
fryStep
|
||||
.OnEvent(FryFoodStep.OutputEvents.FoodRuined)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(gatherIngredientsStep));
|
||||
|
||||
return processBuilder;
|
||||
}
|
||||
|
||||
public static ProcessBuilder CreateProcessWithStatefulStepsV1(string processName = "FriedFishWithStatefulStepsProcess")
|
||||
{
|
||||
// It is recommended to specify process version in case this process is used as a step by another process
|
||||
var processBuilder = new ProcessBuilder(processName) { Version = "FriedFishProcess.v1" }; ;
|
||||
|
||||
var gatherIngredientsStep = processBuilder.AddStepFromType<GatherFriedFishIngredientsWithStockStep>();
|
||||
var chopStep = processBuilder.AddStepFromType<CutFoodStep>();
|
||||
var fryStep = processBuilder.AddStepFromType<FryFoodStep>();
|
||||
|
||||
processBuilder
|
||||
.OnInputEvent(ProcessEvents.PrepareFriedFish)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(gatherIngredientsStep));
|
||||
|
||||
gatherIngredientsStep
|
||||
.OnEvent(GatherFriedFishIngredientsWithStockStep.OutputEvents.IngredientsGathered)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(chopStep, functionName: CutFoodWithSharpeningStep.ProcessStepFunctions.ChopFood));
|
||||
|
||||
chopStep
|
||||
.OnEvent(CutFoodWithSharpeningStep.OutputEvents.ChoppingReady)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(fryStep));
|
||||
|
||||
fryStep
|
||||
.OnEvent(FryFoodStep.OutputEvents.FoodRuined)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(gatherIngredientsStep));
|
||||
|
||||
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 CreateProcessWithStatefulStepsV2(string processName = "FriedFishWithStatefulStepsProcess")
|
||||
{
|
||||
// It is recommended to specify process version in case this process is used as a step by another process
|
||||
var processBuilder = new ProcessBuilder(processName) { Version = "FriedFishProcess.v2" };
|
||||
|
||||
var gatherIngredientsStep = processBuilder.AddStepFromType<GatherFriedFishIngredientsWithStockStep>(id: "gatherFishIngredientStep", aliases: ["GatherFriedFishIngredientsWithStockStep"]);
|
||||
var chopStep = processBuilder.AddStepFromType<CutFoodWithSharpeningStep>(id: "chopFishStep", aliases: ["CutFoodStep"]);
|
||||
var fryStep = processBuilder.AddStepFromType<FryFoodStep>(id: "fryFishStep", aliases: ["FryFoodStep"]);
|
||||
|
||||
processBuilder
|
||||
.OnInputEvent(ProcessEvents.PrepareFriedFish)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(gatherIngredientsStep));
|
||||
|
||||
gatherIngredientsStep
|
||||
.OnEvent(GatherFriedFishIngredientsWithStockStep.OutputEvents.IngredientsGathered)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(chopStep, functionName: CutFoodWithSharpeningStep.ProcessStepFunctions.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.ProcessStepFunctions.SharpenKnife));
|
||||
|
||||
chopStep
|
||||
.OnEvent(CutFoodWithSharpeningStep.OutputEvents.KnifeSharpened)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(chopStep, functionName: CutFoodWithSharpeningStep.ProcessStepFunctions.ChopFood));
|
||||
|
||||
fryStep
|
||||
.OnEvent(FryFoodStep.OutputEvents.FoodRuined)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(gatherIngredientsStep));
|
||||
|
||||
return processBuilder;
|
||||
}
|
||||
|
||||
[KernelProcessStepMetadata("GatherFishIngredient.V1")]
|
||||
private sealed class GatherFriedFishIngredientsStep : GatherIngredientsStep
|
||||
{
|
||||
public GatherFriedFishIngredientsStep() : base(FoodIngredients.Fish) { }
|
||||
}
|
||||
|
||||
[KernelProcessStepMetadata("GatherFishIngredient.V2")]
|
||||
private sealed class GatherFriedFishIngredientsWithStockStep : GatherIngredientsWithStockStep
|
||||
{
|
||||
public GatherFriedFishIngredientsWithStockStep() : base(FoodIngredients.Fish) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
using Step03.Models;
|
||||
using Step03.Steps;
|
||||
|
||||
namespace Step03.Processes;
|
||||
|
||||
/// <summary>
|
||||
/// Sample process that showcases how to create a process with sequential steps and reuse of existing steps.<br/>
|
||||
/// </summary>
|
||||
public static class PotatoFriesProcess
|
||||
{
|
||||
public static class ProcessEvents
|
||||
{
|
||||
public const string PreparePotatoFries = nameof(PreparePotatoFries);
|
||||
// When multiple processes use the same final step, the should event marked as public
|
||||
// so that the step event can be used as the output event of the process too.
|
||||
// In these samples both fried fish and potato fries end with FryStep success
|
||||
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);
|
||||
|
||||
var gatherIngredientsStep = processBuilder.AddStepFromType<GatherPotatoFriesIngredientsStep>();
|
||||
var sliceStep = processBuilder.AddStepFromType<CutFoodStep>("sliceStep");
|
||||
var fryStep = processBuilder.AddStepFromType<FryFoodStep>();
|
||||
|
||||
processBuilder
|
||||
.OnInputEvent(ProcessEvents.PreparePotatoFries)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(gatherIngredientsStep));
|
||||
|
||||
gatherIngredientsStep
|
||||
.OnEvent(GatherPotatoFriesIngredientsStep.OutputEvents.IngredientsGathered)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(sliceStep, functionName: CutFoodStep.ProcessStepFunctions.SliceFood));
|
||||
|
||||
sliceStep
|
||||
.OnEvent(CutFoodStep.OutputEvents.SlicingReady)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(fryStep));
|
||||
|
||||
fryStep
|
||||
.OnEvent(FryFoodStep.OutputEvents.FoodRuined)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(gatherIngredientsStep));
|
||||
|
||||
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.ProcessStepFunctions.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.ProcessStepFunctions.SharpenKnife));
|
||||
|
||||
sliceStep
|
||||
.OnEvent(CutFoodWithSharpeningStep.OutputEvents.KnifeSharpened)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(sliceStep, functionName: CutFoodWithSharpeningStep.ProcessStepFunctions.SliceFood));
|
||||
|
||||
fryStep
|
||||
.OnEvent(FryFoodStep.OutputEvents.FoodRuined)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(gatherIngredientsStep));
|
||||
|
||||
return processBuilder;
|
||||
}
|
||||
|
||||
private sealed class GatherPotatoFriesIngredientsStep : GatherIngredientsStep
|
||||
{
|
||||
public GatherPotatoFriesIngredientsStep() : base(FoodIngredients.Pototoes) { }
|
||||
}
|
||||
|
||||
private sealed class GatherPotatoFriesIngredientsWithStockStep : GatherIngredientsWithStockStep
|
||||
{
|
||||
public GatherPotatoFriesIngredientsWithStockStep() : base(FoodIngredients.Pototoes) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Text.Json;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Step03.Models;
|
||||
using Step03.Steps;
|
||||
|
||||
namespace Step03.Processes;
|
||||
|
||||
/// <summary>
|
||||
/// Sample process that showcases how to create a selecting fan out process
|
||||
/// For a visual reference of the FriedFishProcess check this <see href="https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithProcesses/README.md#single-order-preparation-process" >diagram</see>
|
||||
/// </summary>
|
||||
public static class SingleFoodItemProcess
|
||||
{
|
||||
public static class ProcessEvents
|
||||
{
|
||||
public const string SingleOrderReceived = nameof(SingleOrderReceived);
|
||||
public const string SingleOrderReady = nameof(SingleOrderReady);
|
||||
}
|
||||
|
||||
public static ProcessBuilder CreateProcess(string processName = "SingleFoodItemProcess")
|
||||
{
|
||||
var processBuilder = new ProcessBuilder(processName);
|
||||
|
||||
var dispatchOrderStep = processBuilder.AddStepFromType<DispatchSingleOrderStep>();
|
||||
var makeFriedFishStep = processBuilder.AddStepFromProcess(FriedFishProcess.CreateProcess());
|
||||
var makePotatoFriesStep = processBuilder.AddStepFromProcess(PotatoFriesProcess.CreateProcess());
|
||||
var makeFishSandwichStep = processBuilder.AddStepFromProcess(FishSandwichProcess.CreateProcess());
|
||||
var makeFishAndChipsStep = processBuilder.AddStepFromProcess(FishAndChipsProcess.CreateProcess());
|
||||
var packOrderStep = processBuilder.AddStepFromType<PackOrderStep>();
|
||||
var externalStep = processBuilder.AddStepFromType<ExternalSingleOrderStep>();
|
||||
|
||||
processBuilder
|
||||
.OnInputEvent(ProcessEvents.SingleOrderReceived)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(dispatchOrderStep));
|
||||
|
||||
dispatchOrderStep
|
||||
.OnEvent(DispatchSingleOrderStep.OutputEvents.PrepareFriedFish)
|
||||
.SendEventTo(makeFriedFishStep.WhereInputEventIs(FriedFishProcess.ProcessEvents.PrepareFriedFish));
|
||||
|
||||
dispatchOrderStep
|
||||
.OnEvent(DispatchSingleOrderStep.OutputEvents.PrepareFries)
|
||||
.SendEventTo(makePotatoFriesStep.WhereInputEventIs(PotatoFriesProcess.ProcessEvents.PreparePotatoFries));
|
||||
|
||||
dispatchOrderStep
|
||||
.OnEvent(DispatchSingleOrderStep.OutputEvents.PrepareFishSandwich)
|
||||
.SendEventTo(makeFishSandwichStep.WhereInputEventIs(FishSandwichProcess.ProcessEvents.PrepareFishSandwich));
|
||||
|
||||
dispatchOrderStep
|
||||
.OnEvent(DispatchSingleOrderStep.OutputEvents.PrepareFishAndChips)
|
||||
.SendEventTo(makeFishAndChipsStep.WhereInputEventIs(FishAndChipsProcess.ProcessEvents.PrepareFishAndChips));
|
||||
|
||||
makeFriedFishStep
|
||||
.OnEvent(FriedFishProcess.ProcessEvents.FriedFishReady)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(packOrderStep));
|
||||
|
||||
makePotatoFriesStep
|
||||
.OnEvent(PotatoFriesProcess.ProcessEvents.PotatoFriesReady)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(packOrderStep));
|
||||
|
||||
makeFishSandwichStep
|
||||
.OnEvent(FishSandwichProcess.ProcessEvents.FishSandwichReady)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(packOrderStep));
|
||||
|
||||
makeFishAndChipsStep
|
||||
.OnEvent(FishAndChipsProcess.ProcessEvents.FishAndChipsReady)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(packOrderStep));
|
||||
|
||||
packOrderStep
|
||||
.OnEvent(PackOrderStep.OutputEvents.FoodPacked)
|
||||
.SendEventTo(new ProcessFunctionTargetBuilder(externalStep));
|
||||
|
||||
return processBuilder;
|
||||
}
|
||||
|
||||
private sealed class DispatchSingleOrderStep : KernelProcessStep
|
||||
{
|
||||
public static class ProcessFunctions
|
||||
{
|
||||
public const string PrepareSingleOrder = nameof(PrepareSingleOrder);
|
||||
}
|
||||
|
||||
public static class OutputEvents
|
||||
{
|
||||
public const string PrepareFries = nameof(PrepareFries);
|
||||
public const string PrepareFriedFish = nameof(PrepareFriedFish);
|
||||
public const string PrepareFishSandwich = nameof(PrepareFishSandwich);
|
||||
public const string PrepareFishAndChips = nameof(PrepareFishAndChips);
|
||||
}
|
||||
|
||||
[KernelFunction(ProcessFunctions.PrepareSingleOrder)]
|
||||
public async Task DispatchSingleOrderAsync(KernelProcessStepContext context, FoodItem foodItem)
|
||||
{
|
||||
var foodName = foodItem.ToFriendlyString();
|
||||
Console.WriteLine($"DISPATCH_SINGLE_ORDER: Dispatching '{foodName}'!");
|
||||
var foodActions = new List<string>();
|
||||
|
||||
switch (foodItem)
|
||||
{
|
||||
case FoodItem.PotatoFries:
|
||||
await context.EmitEventAsync(new() { Id = OutputEvents.PrepareFries, Data = foodActions });
|
||||
break;
|
||||
case FoodItem.FriedFish:
|
||||
await context.EmitEventAsync(new() { Id = OutputEvents.PrepareFriedFish, Data = foodActions });
|
||||
break;
|
||||
case FoodItem.FishSandwich:
|
||||
await context.EmitEventAsync(new() { Id = OutputEvents.PrepareFishSandwich, Data = foodActions });
|
||||
break;
|
||||
case FoodItem.FishAndChips:
|
||||
await context.EmitEventAsync(new() { Id = OutputEvents.PrepareFishAndChips, Data = foodActions });
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class PackOrderStep : KernelProcessStep
|
||||
{
|
||||
public static class ProcessFunctions
|
||||
{
|
||||
public const string PackFood = nameof(PackFood);
|
||||
}
|
||||
public static class OutputEvents
|
||||
{
|
||||
public const string FoodPacked = nameof(FoodPacked);
|
||||
}
|
||||
|
||||
[KernelFunction(ProcessFunctions.PackFood)]
|
||||
public async Task PackFoodAsync(KernelProcessStepContext context, List<string> foodActions)
|
||||
{
|
||||
Console.WriteLine($"PACKING_FOOD: Food {foodActions.First()} Packed! - {JsonSerializer.Serialize(foodActions)}");
|
||||
await context.EmitEventAsync(new() { Id = OutputEvents.FoodPacked });
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ExternalSingleOrderStep : ExternalStep
|
||||
{
|
||||
public ExternalSingleOrderStep() : base(ProcessEvents.SingleOrderReady) { }
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"stepsState": {
|
||||
"FriedFishWithStatefulStepsProcess": {
|
||||
"$type": "Process",
|
||||
"stepsState": {
|
||||
"GatherFriedFishIngredientsWithStockStep": {
|
||||
"$type": "Step",
|
||||
"id": "7d4c02d000a744f490f2b5f9bad721fb",
|
||||
"name": "GatherFriedFishIngredientsWithStockStep",
|
||||
"versionInfo": "GatherFishIngredient.V2",
|
||||
"state": {
|
||||
"IngredientsStock": 2
|
||||
}
|
||||
},
|
||||
"CutFoodStep": {
|
||||
"$type": "Step",
|
||||
"id": "f147010a57d34587a3dc1ed4677e5163",
|
||||
"name": "CutFoodStep",
|
||||
"versionInfo": "CutFoodStep.V1"
|
||||
},
|
||||
"FryFoodStep": {
|
||||
"$type": "Step",
|
||||
"id": "78cc5af4106549afb74d7a6813016f87",
|
||||
"name": "FryFoodStep",
|
||||
"versionInfo": "FryFoodStep.V1"
|
||||
}
|
||||
},
|
||||
"id": "282717158b9f49e5b1acce81429610e0",
|
||||
"name": "FriedFishWithStatefulStepsProcess",
|
||||
"versionInfo": "FriedFishProcess.v1"
|
||||
},
|
||||
"AddBunsStep": {
|
||||
"$type": "Step",
|
||||
"id": "31e953154e574470911d168a39588ed8",
|
||||
"name": "AddBunsStep",
|
||||
"versionInfo": "v1"
|
||||
},
|
||||
"AddSpecialSauceStep": {
|
||||
"$type": "Step",
|
||||
"id": "67ee29ff28e4446d8046417675ec21e8",
|
||||
"name": "AddSpecialSauceStep",
|
||||
"versionInfo": "v1"
|
||||
},
|
||||
"ExternalFriedFishStep": {
|
||||
"$type": "Step",
|
||||
"id": "873b1c8dee45412e975a5e8db2ed0b43",
|
||||
"name": "ExternalFriedFishStep",
|
||||
"versionInfo": "v1"
|
||||
}
|
||||
},
|
||||
"id": "af40089f-e57b-46d1-a15b-40c0d7f3800f",
|
||||
"name": "FishSandwichWithStatefulStepsProcess",
|
||||
"versionInfo": "FishSandwich.V1"
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"$type": "Process",
|
||||
"stepsState": {
|
||||
"FriedFishWithStatefulStepsProcess": {
|
||||
"$type": "Process",
|
||||
"stepsState": {
|
||||
"GatherFriedFishIngredientsWithStockStep": {
|
||||
"$type": "Step",
|
||||
"id": "2908f8c88cf0476a8e0075c3a8020d5d",
|
||||
"name": "GatherFriedFishIngredientsWithStockStep",
|
||||
"versionInfo": "GatherFishIngredient.V2",
|
||||
"state": {
|
||||
"IngredientsStock": 1
|
||||
}
|
||||
},
|
||||
"CutFoodStep": {
|
||||
"$type": "Step",
|
||||
"id": "014388cf0bbd41119b8730dfc4b0b459",
|
||||
"name": "CutFoodStep",
|
||||
"versionInfo": "CutFoodStep.V1"
|
||||
},
|
||||
"FryFoodStep": {
|
||||
"$type": "Step",
|
||||
"id": "c55af0425d864c4e97b6ae67bd715480",
|
||||
"name": "FryFoodStep",
|
||||
"versionInfo": "FryFoodStep.V1"
|
||||
}
|
||||
},
|
||||
"id": "cab89a17aeae4b9a97568967dbf1ea47",
|
||||
"name": "FriedFishWithStatefulStepsProcess",
|
||||
"versionInfo": "FriedFishProcess.v1"
|
||||
},
|
||||
"AddBunsStep": {
|
||||
"$type": "Step",
|
||||
"id": "35d09b83dea24ddf8e0c24fbe6a3746c",
|
||||
"name": "AddBunsStep",
|
||||
"versionInfo": "v1"
|
||||
},
|
||||
"AddSpecialSauceStep": {
|
||||
"$type": "Step",
|
||||
"id": "aa0d408976574afea94387e3da7ca111",
|
||||
"name": "AddSpecialSauceStep",
|
||||
"versionInfo": "v1"
|
||||
},
|
||||
"ExternalFriedFishStep": {
|
||||
"$type": "Step",
|
||||
"id": "2eda38b8ee8745a4ab8b21f4fa01d173",
|
||||
"name": "ExternalFriedFishStep",
|
||||
"versionInfo": "v1"
|
||||
}
|
||||
},
|
||||
"id": "973b06f1-a522-4d2d-9e1c-ec45a07e275c",
|
||||
"name": "FishSandwichWithStatefulStepsProcess",
|
||||
"versionInfo": "FishSandwich.V1"
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"stepsState": {
|
||||
"GatherFriedFishIngredientsWithStockStep": {
|
||||
"$type": "Step",
|
||||
"id": "77c2f967cd354e66a51828e9755d2f07",
|
||||
"name": "GatherFriedFishIngredientsWithStockStep",
|
||||
"versionInfo": "GatherFishIngredient.V2",
|
||||
"state": {
|
||||
"IngredientsStock": 4
|
||||
}
|
||||
},
|
||||
"CutFoodStep": {
|
||||
"$type": "Step",
|
||||
"id": "9276d03e64c44a6792d5fd81bd0dc143",
|
||||
"name": "CutFoodStep",
|
||||
"versionInfo": "CutFoodStep.V1"
|
||||
},
|
||||
"FryFoodStep": {
|
||||
"$type": "Step",
|
||||
"id": "af2a00be4fe2408181ab5654318ed56b",
|
||||
"name": "FryFoodStep",
|
||||
"versionInfo": "FryFoodStep.V1"
|
||||
}
|
||||
},
|
||||
"id": "2050a24b-3e9d-418a-8413-74cadf4f6b4c",
|
||||
"name": "FriedFishWithStatefulStepsProcess",
|
||||
"versionInfo": "FriedFishProcess.v1"
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"$type": "Process",
|
||||
"stepsState": {
|
||||
"GatherFriedFishIngredientsWithStockStep": {
|
||||
"$type": "Step",
|
||||
"id": "92a4cda38c7248648b0aa7ffaaa57f21",
|
||||
"name": "GatherFriedFishIngredientsWithStockStep",
|
||||
"versionInfo": "GatherFishIngredient.V2",
|
||||
"state": {
|
||||
"IngredientsStock": 1
|
||||
}
|
||||
},
|
||||
"CutFoodStep": {
|
||||
"$type": "Step",
|
||||
"id": "7ace89e38e1c48b0b3a700b40d160c68",
|
||||
"name": "CutFoodStep",
|
||||
"versionInfo": "CutFoodStep.V1"
|
||||
},
|
||||
"FryFoodStep": {
|
||||
"$type": "Step",
|
||||
"id": "09bc39ba6d9745439c7c792b8dac0af7",
|
||||
"name": "FryFoodStep",
|
||||
"versionInfo": "FryFoodStep.V1"
|
||||
}
|
||||
},
|
||||
"id": "669c5850-9efc-4585-b3f0-9291a4471887",
|
||||
"name": "FriedFishWithStatefulStepsProcess",
|
||||
"versionInfo": "FriedFishProcess.v1"
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"$type": "Process",
|
||||
"stepsState": {
|
||||
"GatherFriedFishIngredientsWithStockStep": {
|
||||
"$type": "Step",
|
||||
"id": "92a4cda38c7248648b0aa7ffaaa57f21",
|
||||
"name": "GatherFriedFishIngredientsWithStockStep",
|
||||
"versionInfo": "GatherFishIngredient.V2",
|
||||
"state": {
|
||||
"IngredientsStock": 0
|
||||
}
|
||||
},
|
||||
"CutFoodStep": {
|
||||
"$type": "Step",
|
||||
"id": "7ace89e38e1c48b0b3a700b40d160c68",
|
||||
"name": "CutFoodStep",
|
||||
"versionInfo": "CutFoodStep.V1"
|
||||
},
|
||||
"FryFoodStep": {
|
||||
"$type": "Step",
|
||||
"id": "09bc39ba6d9745439c7c792b8dac0af7",
|
||||
"name": "FryFoodStep",
|
||||
"versionInfo": "FryFoodStep.V1"
|
||||
}
|
||||
},
|
||||
"id": "669c5850-9efc-4585-b3f0-9291a4471887",
|
||||
"name": "FriedFishWithStatefulStepsProcess",
|
||||
"versionInfo": "FriedFishProcess.v1"
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Process.Models;
|
||||
using Microsoft.SemanticKernel.Process.Tools;
|
||||
using Step03.Processes;
|
||||
using Utilities;
|
||||
|
||||
namespace Step03;
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrate creation of <see cref="KernelProcess"/> and
|
||||
/// eliciting different food related events.
|
||||
/// For visual reference of the processes used here check the diagram in: https://github.com/microsoft/semantic-kernel/tree/main/dotnet/samples/GettingStartedWithProcesses/README.md#step03a_foodPreparation
|
||||
/// </summary>
|
||||
public class Step03a_FoodPreparation(ITestOutputHelper output) : BaseTest(output, redirectSystemConsoleOutput: true)
|
||||
{
|
||||
// Target Open AI Services
|
||||
protected override bool ForceOpenAI => true;
|
||||
|
||||
#region Stateless Processes
|
||||
[Fact]
|
||||
public async Task UsePrepareFriedFishProcessAsync()
|
||||
{
|
||||
var process = FriedFishProcess.CreateProcess();
|
||||
await UsePrepareSpecificProductAsync(process, FriedFishProcess.ProcessEvents.PrepareFriedFish);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UsePreparePotatoFriesProcessAsync()
|
||||
{
|
||||
var process = PotatoFriesProcess.CreateProcess();
|
||||
await UsePrepareSpecificProductAsync(process, PotatoFriesProcess.ProcessEvents.PreparePotatoFries);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UsePrepareFishSandwichProcessAsync()
|
||||
{
|
||||
var process = FishSandwichProcess.CreateProcess();
|
||||
|
||||
string mermaidGraph = process.ToMermaid(1);
|
||||
Console.WriteLine($"=== Start - Mermaid Diagram for '{process.Name}' ===");
|
||||
Console.WriteLine(mermaidGraph);
|
||||
Console.WriteLine($"=== End - Mermaid Diagram for '{process.Name}' ===");
|
||||
|
||||
await UsePrepareSpecificProductAsync(process, FishSandwichProcess.ProcessEvents.PrepareFishSandwich);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UsePrepareFishAndChipsProcessAsync()
|
||||
{
|
||||
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.CreateProcessWithStatefulStepsV1();
|
||||
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.CreateProcessWithStatefulStepsV2();
|
||||
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 = "Order 1")
|
||||
{
|
||||
Console.WriteLine($"=== {orderLabel} ===");
|
||||
var runningProcess = await process.StartAsync(kernel, new KernelProcessEvent()
|
||||
{
|
||||
Id = externalTriggerEvent,
|
||||
Data = new List<string>()
|
||||
});
|
||||
return await runningProcess.GetStateAsync();
|
||||
}
|
||||
|
||||
#region Running processes and saving Process State Metadata in a file locally
|
||||
[Fact]
|
||||
public async Task RunAndStoreStatefulFriedFishProcessStateAsync()
|
||||
{
|
||||
Kernel kernel = CreateKernelWithChatCompletion();
|
||||
ProcessBuilder builder = FriedFishProcess.CreateProcessWithStatefulStepsV1();
|
||||
KernelProcess friedFishProcess = builder.Build();
|
||||
|
||||
var executedProcess = await ExecuteProcessWithStateAsync(friedFishProcess, kernel, externalTriggerEvent: FriedFishProcess.ProcessEvents.PrepareFriedFish);
|
||||
var processState = executedProcess.ToProcessStateMetadata();
|
||||
DumpProcessStateMetadataLocally(processState, _statefulFriedFishProcessFilename);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAndStoreStatefulFishSandwichProcessStateAsync()
|
||||
{
|
||||
Kernel kernel = CreateKernelWithChatCompletion();
|
||||
ProcessBuilder builder = FishSandwichProcess.CreateProcessWithStatefulStepsV1();
|
||||
KernelProcess friedFishProcess = builder.Build();
|
||||
|
||||
var executedProcess = await ExecuteProcessWithStateAsync(friedFishProcess, kernel, externalTriggerEvent: FishSandwichProcess.ProcessEvents.PrepareFishSandwich);
|
||||
var processState = executedProcess.ToProcessStateMetadata();
|
||||
DumpProcessStateMetadataLocally(processState, _statefulFishSandwichProcessFilename);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Reading State from local file and apply to existing ProcessBuilder
|
||||
[Fact]
|
||||
public async Task RunStatefulFriedFishProcessFromFileAsync()
|
||||
{
|
||||
var processState = LoadProcessStateMetadata(this._statefulFriedFishProcessFilename);
|
||||
Assert.NotNull(processState);
|
||||
|
||||
Kernel kernel = CreateKernelWithChatCompletion();
|
||||
ProcessBuilder processBuilder = FriedFishProcess.CreateProcessWithStatefulStepsV1();
|
||||
KernelProcess processFromFile = processBuilder.Build(processState);
|
||||
|
||||
await ExecuteProcessWithStateAsync(processFromFile, kernel, externalTriggerEvent: FriedFishProcess.ProcessEvents.PrepareFriedFish);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunStatefulFriedFishProcessWithLowStockFromFileAsync()
|
||||
{
|
||||
var processState = LoadProcessStateMetadata(this._statefulFriedFishLowStockProcessFilename);
|
||||
Assert.NotNull(processState);
|
||||
|
||||
Kernel kernel = CreateKernelWithChatCompletion();
|
||||
ProcessBuilder processBuilder = FriedFishProcess.CreateProcessWithStatefulStepsV1();
|
||||
KernelProcess processFromFile = processBuilder.Build(processState);
|
||||
|
||||
await ExecuteProcessWithStateAsync(processFromFile, kernel, externalTriggerEvent: FriedFishProcess.ProcessEvents.PrepareFriedFish);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunStatefulFriedFishProcessWithNoStockFromFileAsync()
|
||||
{
|
||||
var processState = LoadProcessStateMetadata(this._statefulFriedFishNoStockProcessFilename);
|
||||
Assert.NotNull(processState);
|
||||
|
||||
Kernel kernel = CreateKernelWithChatCompletion();
|
||||
ProcessBuilder processBuilder = FriedFishProcess.CreateProcessWithStatefulStepsV1();
|
||||
KernelProcess processFromFile = processBuilder.Build(processState);
|
||||
|
||||
await ExecuteProcessWithStateAsync(processFromFile, kernel, externalTriggerEvent: FriedFishProcess.ProcessEvents.PrepareFriedFish);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunStatefulFishSandwichProcessFromFileAsync()
|
||||
{
|
||||
var processState = LoadProcessStateMetadata(this._statefulFishSandwichProcessFilename);
|
||||
Assert.NotNull(processState);
|
||||
|
||||
Kernel kernel = CreateKernelWithChatCompletion();
|
||||
ProcessBuilder processBuilder = FishSandwichProcess.CreateProcessWithStatefulStepsV1();
|
||||
KernelProcess processFromFile = processBuilder.Build(processState);
|
||||
|
||||
await ExecuteProcessWithStateAsync(processFromFile, kernel, externalTriggerEvent: FishSandwichProcess.ProcessEvents.PrepareFishSandwich);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunStatefulFishSandwichProcessWithLowStockFromFileAsync()
|
||||
{
|
||||
var processState = LoadProcessStateMetadata(this._statefulFishSandwichLowStockProcessFilename);
|
||||
Assert.NotNull(processState);
|
||||
|
||||
Kernel kernel = CreateKernelWithChatCompletion();
|
||||
ProcessBuilder processBuilder = FishSandwichProcess.CreateProcessWithStatefulStepsV1();
|
||||
KernelProcess processFromFile = processBuilder.Build(processState);
|
||||
|
||||
await ExecuteProcessWithStateAsync(processFromFile, kernel, externalTriggerEvent: FishSandwichProcess.ProcessEvents.PrepareFishSandwich);
|
||||
}
|
||||
|
||||
#region Versioning compatibiily scenarios: Loading State generated with previous version of process
|
||||
[Fact]
|
||||
public async Task RunStatefulFriedFishV2ProcessWithLowStockV1StateFromFileAsync()
|
||||
{
|
||||
var processState = LoadProcessStateMetadata(this._statefulFriedFishLowStockProcessFilename);
|
||||
Assert.NotNull(processState);
|
||||
|
||||
Kernel kernel = CreateKernelWithChatCompletion();
|
||||
ProcessBuilder processBuilder = FriedFishProcess.CreateProcessWithStatefulStepsV2();
|
||||
KernelProcess processFromFile = processBuilder.Build(processState);
|
||||
|
||||
await ExecuteProcessWithStateAsync(processFromFile, kernel, externalTriggerEvent: FriedFishProcess.ProcessEvents.PrepareFriedFish);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunStatefulFishSandwichV2ProcessWithLowStockV1StateFromFileAsync()
|
||||
{
|
||||
var processState = LoadProcessStateMetadata(this._statefulFishSandwichLowStockProcessFilename);
|
||||
Assert.NotNull(processState);
|
||||
|
||||
Kernel kernel = CreateKernelWithChatCompletion();
|
||||
ProcessBuilder processBuilder = FishSandwichProcess.CreateProcessWithStatefulStepsV2();
|
||||
KernelProcess processFromFile = processBuilder.Build(processState);
|
||||
|
||||
await ExecuteProcessWithStateAsync(processFromFile, kernel, externalTriggerEvent: FishSandwichProcess.ProcessEvents.PrepareFishSandwich);
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
#endregion
|
||||
protected async Task UsePrepareSpecificProductAsync(ProcessBuilder processBuilder, string externalTriggerEvent)
|
||||
{
|
||||
// Arrange
|
||||
Kernel kernel = CreateKernelWithChatCompletion();
|
||||
|
||||
// Act
|
||||
KernelProcess kernelProcess = processBuilder.Build();
|
||||
|
||||
// Assert
|
||||
Console.WriteLine($"=== Start SK Process '{processBuilder.Name}' ===");
|
||||
await using var runningProcess = await kernelProcess.StartAsync(kernel, new KernelProcessEvent()
|
||||
{
|
||||
Id = externalTriggerEvent, Data = new List<string>()
|
||||
});
|
||||
Console.WriteLine($"=== End SK Process '{processBuilder.Name}' ===");
|
||||
}
|
||||
|
||||
// Step03a Utils for saving and loading SK Processes from/to repository
|
||||
private readonly string _step03RelativePath = Path.Combine("Step03", "ProcessesStates");
|
||||
private readonly string _statefulFriedFishProcessFilename = "FriedFishProcessStateSuccess.json";
|
||||
private readonly string _statefulFriedFishLowStockProcessFilename = "FriedFishProcessStateSuccessLowStock.json";
|
||||
private readonly string _statefulFriedFishNoStockProcessFilename = "FriedFishProcessStateSuccessNoStock.json";
|
||||
private readonly string _statefulFishSandwichProcessFilename = "FishSandwichStateProcessSuccess.json";
|
||||
private readonly string _statefulFishSandwichLowStockProcessFilename = "FishSandwichStateProcessSuccessLowStock.json";
|
||||
|
||||
private void DumpProcessStateMetadataLocally(KernelProcessStateMetadata processStateInfo, string jsonFilename)
|
||||
{
|
||||
var sampleRelativePath = GetSampleStep03Filepath(jsonFilename);
|
||||
ProcessStateMetadataUtilities.DumpProcessStateMetadataLocally(processStateInfo, sampleRelativePath);
|
||||
}
|
||||
|
||||
private KernelProcessStateMetadata? LoadProcessStateMetadata(string jsonFilename)
|
||||
{
|
||||
var sampleRelativePath = GetSampleStep03Filepath(jsonFilename);
|
||||
return ProcessStateMetadataUtilities.LoadProcessStateMetadata(sampleRelativePath);
|
||||
}
|
||||
|
||||
private string GetSampleStep03Filepath(string jsonFilename)
|
||||
{
|
||||
return Path.Combine(this._step03RelativePath, jsonFilename);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
using Step03.Models;
|
||||
using Step03.Processes;
|
||||
|
||||
namespace Step03;
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrate creation of <see cref="KernelProcess"/> and
|
||||
/// eliciting different food related events.
|
||||
/// For visual reference of the processes used here check the diagram in: https://github.com/microsoft/semantic-kernel/tree/main/dotnet/samples/GettingStartedWithProcesses/README.md#step03b_foodOrdering
|
||||
/// </summary>
|
||||
public class Step03b_FoodOrdering(ITestOutputHelper output) : BaseTest(output, redirectSystemConsoleOutput: true)
|
||||
{
|
||||
// Target Open AI Services
|
||||
protected override bool ForceOpenAI => true;
|
||||
|
||||
[Fact]
|
||||
public async Task UseSingleOrderFriedFishAsync()
|
||||
{
|
||||
await UsePrepareFoodOrderProcessSingleItemAsync(FoodItem.FriedFish);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UseSingleOrderPotatoFriesAsync()
|
||||
{
|
||||
await UsePrepareFoodOrderProcessSingleItemAsync(FoodItem.PotatoFries);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UseSingleOrderFishSandwichAsync()
|
||||
{
|
||||
await UsePrepareFoodOrderProcessSingleItemAsync(FoodItem.FishSandwich);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UseSingleOrderFishAndChipsAsync()
|
||||
{
|
||||
await UsePrepareFoodOrderProcessSingleItemAsync(FoodItem.FishAndChips);
|
||||
}
|
||||
|
||||
protected async Task UsePrepareFoodOrderProcessSingleItemAsync(FoodItem foodItem)
|
||||
{
|
||||
Kernel kernel = CreateKernelWithChatCompletion();
|
||||
KernelProcess kernelProcess = SingleFoodItemProcess.CreateProcess().Build();
|
||||
|
||||
await using var runningProcess = await kernelProcess.StartAsync(kernel, new KernelProcessEvent()
|
||||
{
|
||||
Id = SingleFoodItemProcess.ProcessEvents.SingleOrderReceived,
|
||||
Data = foodItem
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Process;
|
||||
|
||||
namespace Step03.Steps;
|
||||
|
||||
/// <summary>
|
||||
/// Step used in the Processes Samples:
|
||||
/// - Step_03_FoodPreparation.cs
|
||||
/// </summary>
|
||||
[KernelProcessStepMetadata("CutFoodStep.V1")]
|
||||
public class CutFoodStep : KernelProcessStep
|
||||
{
|
||||
public static class ProcessStepFunctions
|
||||
{
|
||||
public const string ChopFood = nameof(ChopFood);
|
||||
public const string SliceFood = nameof(SliceFood);
|
||||
}
|
||||
|
||||
public static class OutputEvents
|
||||
{
|
||||
public const string ChoppingReady = nameof(ChoppingReady);
|
||||
public const string SlicingReady = nameof(SlicingReady);
|
||||
}
|
||||
|
||||
[KernelFunction(ProcessStepFunctions.ChopFood)]
|
||||
public async Task ChopFoodAsync(KernelProcessStepContext context, List<string> foodActions)
|
||||
{
|
||||
var foodToBeCut = foodActions.First();
|
||||
foodActions.Add(this.getActionString(foodToBeCut, "chopped"));
|
||||
Console.WriteLine($"CUTTING_STEP: Ingredient {foodToBeCut} has been chopped!");
|
||||
await context.EmitEventAsync(new() { Id = OutputEvents.ChoppingReady, Data = foodActions });
|
||||
}
|
||||
|
||||
[KernelFunction(ProcessStepFunctions.SliceFood)]
|
||||
public async Task SliceFoodAsync(KernelProcessStepContext context, List<string> foodActions)
|
||||
{
|
||||
var foodToBeCut = foodActions.First();
|
||||
foodActions.Add(this.getActionString(foodToBeCut, "sliced"));
|
||||
Console.WriteLine($"CUTTING_STEP: Ingredient {foodToBeCut} has been sliced!");
|
||||
await context.EmitEventAsync(new() { Id = OutputEvents.SlicingReady, Data = foodActions });
|
||||
}
|
||||
|
||||
private string getActionString(string food, string action)
|
||||
{
|
||||
return $"{food}_{action}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Process;
|
||||
|
||||
namespace Step03.Steps;
|
||||
|
||||
/// <summary>
|
||||
/// Step used in the Processes Samples:
|
||||
/// - Step_03_FoodPreparation.cs
|
||||
/// </summary>
|
||||
[KernelProcessStepMetadata("CutFoodStep.V2")]
|
||||
public class CutFoodWithSharpeningStep : KernelProcessStep<CutFoodWithSharpeningState>
|
||||
{
|
||||
public static class ProcessStepFunctions
|
||||
{
|
||||
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(ProcessStepFunctions.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(ProcessStepFunctions.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(ProcessStepFunctions.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
|
||||
{
|
||||
public int KnifeSharpness { get; set; } = 5;
|
||||
|
||||
internal int _needsSharpeningLimit = 3;
|
||||
internal int _sharpeningBoost = 5;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// 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 ExternalStep(string externalEventName) : KernelProcessStep
|
||||
{
|
||||
private readonly string _externalEventName = externalEventName;
|
||||
|
||||
[KernelFunction]
|
||||
public async Task EmitExternalEventAsync(KernelProcessStepContext context, object data)
|
||||
{
|
||||
await context.EmitEventAsync(new() { Id = this._externalEventName, Data = data, Visibility = KernelProcessEventVisibility.Public });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Process;
|
||||
|
||||
namespace Step03.Steps;
|
||||
|
||||
/// <summary>
|
||||
/// Step used in the Processes Samples:
|
||||
/// - Step_03_FoodPreparation.cs
|
||||
/// </summary>
|
||||
[KernelProcessStepMetadata("FryFoodStep.V1")]
|
||||
public class FryFoodStep : KernelProcessStep
|
||||
{
|
||||
public static class ProcessStepFunctions
|
||||
{
|
||||
public const string FryFood = nameof(FryFood);
|
||||
}
|
||||
|
||||
public static class OutputEvents
|
||||
{
|
||||
public const string FoodRuined = nameof(FoodRuined);
|
||||
public const string FriedFoodReady = nameof(FriedFoodReady);
|
||||
}
|
||||
|
||||
private readonly Random _randomSeed = new();
|
||||
|
||||
[KernelFunction(ProcessStepFunctions.FryFood)]
|
||||
public async Task FryFoodAsync(KernelProcessStepContext context, List<string> foodActions)
|
||||
{
|
||||
var foodToFry = foodActions.First();
|
||||
// This step may fail sometimes
|
||||
int fryerMalfunction = _randomSeed.Next(0, 10);
|
||||
|
||||
// foodToFry could potentially be used to set the frying temperature and cooking duration
|
||||
if (fryerMalfunction < 5)
|
||||
{
|
||||
// Oh no! Food got burnt :(
|
||||
foodActions.Add($"{foodToFry}_frying_failed");
|
||||
Console.WriteLine($"FRYING_STEP: Ingredient {foodToFry} got burnt while frying :(");
|
||||
await context.EmitEventAsync(new() { Id = OutputEvents.FoodRuined, Data = foodActions });
|
||||
return;
|
||||
}
|
||||
|
||||
foodActions.Add($"{foodToFry}_frying_succeeded");
|
||||
Console.WriteLine($"FRYING_STEP: Ingredient {foodToFry} is ready!");
|
||||
await context.EmitEventAsync(new() { Id = OutputEvents.FriedFoodReady, Data = foodActions, Visibility = KernelProcessEventVisibility.Public });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
using Microsoft.SemanticKernel;
|
||||
using Step03.Models;
|
||||
namespace Step03.Steps;
|
||||
|
||||
/// <summary>
|
||||
/// 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 GatherIngredientsStep : KernelProcessStep
|
||||
{
|
||||
public static class ProcessStepFunctions
|
||||
{
|
||||
public const string GatherIngredients = nameof(GatherIngredients);
|
||||
}
|
||||
|
||||
public static class OutputEvents
|
||||
{
|
||||
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>
|
||||
/// <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(ProcessStepFunctions.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 (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 ProcessStepFunctions
|
||||
{
|
||||
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(ProcessStepFunctions.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
|
||||
{
|
||||
public int IngredientsStock { get; set; } = 5;
|
||||
}
|
||||
Reference in New Issue
Block a user