Skip to content

.Net: SK Process - Sample 3 Food related samples #9087

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
Oct 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
cc70261
SK Process - Sample 3 food related samples
esttenorio Oct 4, 2024
2779369
updating missing readme entry
esttenorio Oct 4, 2024
df17854
fixing spelling errors
esttenorio Oct 4, 2024
4a05adb
adding missing samples and updating readme
esttenorio Oct 4, 2024
1822220
Fixing comments and README diagrams
esttenorio Oct 4, 2024
739e804
Merge branch 'main' into estenori/processes-samples-3
esttenorio Oct 4, 2024
97473bb
Merge branch 'main' into estenori/processes-samples-3
esttenorio Oct 4, 2024
e2bc3a7
foodSamples with full support of subprocesses
esttenorio Oct 5, 2024
e0b9534
fixing minor errors
esttenorio Oct 5, 2024
8cc869c
passing foodActions done between steps to track actions
esttenorio Oct 7, 2024
de52024
adding addional external last step to processes
esttenorio Oct 7, 2024
70606f2
Merge branch 'main' into estenori/processes-samples-3
esttenorio Oct 7, 2024
aa7c5f9
updating gathering step
esttenorio Oct 7, 2024
9ba633a
fixing samples to some use additional external step and some use step…
esttenorio Oct 7, 2024
b42bdb8
spelling error
esttenorio Oct 7, 2024
feb8b6d
Merge branch 'main' into estenori/processes-samples-3
esttenorio Oct 7, 2024
d098f57
spelling error fix
esttenorio Oct 7, 2024
36ae0d8
deleting unused file
esttenorio Oct 7, 2024
b44acce
fixing fish and chips sample
esttenorio Oct 7, 2024
03cbfe8
Merge branch 'main' into estenori/processes-samples-3
esttenorio Oct 7, 2024
07b46c5
Merge branch 'main' into estenori/processes-samples-3
esttenorio Oct 8, 2024
1954ea9
updating sample after nested processes multiple input fix
esttenorio Oct 8, 2024
7f3698f
Merge branch 'main' into estenori/processes-samples-3
esttenorio Oct 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions dotnet/samples/GettingStartedWithProcesses/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +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.
[Step03_FoodPreparation](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithProcesses/Step03/Step03_FoodPreparation.cs)|Showcasing reuse of steps, creation of processes, spawning of multiple events.

### Step01_Processes

Expand Down Expand Up @@ -66,6 +67,100 @@ flowchart LR
Mailer -->|End of Interaction| User
```

### Step03_FoodPreparation

This tutorial contains a set of food recipes associated with the Food Preparation Processes of a restaurant.

The following recipes for preparation of Order Items are defined as SK Processes:

#### Product Preparation Processes

##### Potato Fries Preparation Process

``` mermaid
flowchart LR
PreparePotatoFriesEvent([Prepare Potato <br/> Fries Event])
PotatoFriesReadyEvent([Potato Fries <br/> Ready Event])

GatherIngredientsStep[Gather Ingredients <br/> Step]
CutStep[Cut Food <br/> Step]
FryStep[Fry Food <br/> Step]

PreparePotatoFriesEvent --> GatherIngredientsStep -->| Slice Potatoes <br/> _Ingredients Gathered_ | CutStep --> |**Potato Sliced Ready** <br/> _Food Sliced Ready_ | FryStep --> |_Fried Food Ready_|PotatoFriesReadyEvent
FryStep -->|Fried Potato Ruined <br/> _Fried Food Ruined_| GatherIngredientsStep
```

##### Fried Fish Preparation Process

``` mermaid
flowchart LR
PrepareFriedFishEvent([Prepare Fried <br/> Fish Event])
FriedFishReadyEvent([Fried Fish <br/> Ready Event])

GatherIngredientsStep[Gather Ingredients <br/> Step]
CutStep[Cut Food <br/> Step]
FryStep[Fry Food <br/> Step]

PrepareFriedFishEvent --> GatherIngredientsStep -->| Chop Fish <br/> _Ingredients Gathered_ | CutStep --> |**Fish Chopped Ready** <br/> _Food Chopped Ready_| FryStep --> |_Fried Food Ready_ | FriedFishReadyEvent
FryStep -->|**Fried Fish Ruined** <br/> _Fried Food Ruined_| GatherIngredientsStep
```

##### Fish Sandwich Preparation Process

``` mermaid
flowchart LR
PrepareFishSandwichEvent([Prepare Fish <br/> Sandwich Event])
FishSandwichReadyEvent([Fish Sandwich <br/> Ready Event])

FriedFishStep[[Fried Fish <br/> Process Step]]
AddBunsStep[Add Buns <br/> Step]
AddSpecialSauceStep[Add Special <br/> Sauce Step]

PrepareFishSandwichEvent -->|Prepare Fried Fish| FriedFishStep -->|Fried Fish Ready| AddBunsStep --> |Buns Added | AddSpecialSauceStep --> |Special Sauce Added | FishSandwichReadyEvent
```

##### Fish And Chips Preparation Process

``` mermaid
flowchart LR
PrepareFishAndChipsEvent([Prepare <br/> Fish And Chips <br/> Event])
FishAndChipsReadyEvent([Fish And Chips <br/> Ready Event])

FriedFishStep[[Fried Fish <br/> Process Step]]
PotatoFriesStep[[Potato Fries <br/> Process Step]]
AddCondiments[Add Condiments <br/> Step ]

PrepareFishAndChipsEvent -->|Prepare Fried Fish| FriedFishStep --> |Fried Fish Ready| AddCondiments
PrepareFishAndChipsEvent -->|Prepare Potato Fries| PotatoFriesStep -->|Potato Fries Ready| AddCondiments
AddCondiments -->|Condiments Added| FishAndChipsReadyEvent
```

#### Single Order Preparation Process

Now with the existing product preparation processes, they can be used to create an even more complex process that can decide what product order to dispatch.

```mermaid
graph TD
PrepareSingleOrderEvent([Prepare Single Order <br/> Event])
SingleOrderReadyEvent([Single Order <br/> Ready Event])
OrderPackedEvent([Order Packed <br/> Event])

DispatchOrderStep{{Dispatch <br/> Order Step}}
FriedFishStep[[Fried Fish <br/> Process Step]]
PotatoFriesStep[[Potato Fries <br/> Process Step]]
FishSandwichStep[[Fish Sandwich <br/> Process Step]]
FishAndChipsStep[[Fish & Chips <br/> Process Step]]

PackFoodStep[Pack Food <br/> Step]

PrepareSingleOrderEvent -->|Order Received| DispatchOrderStep
DispatchOrderStep -->|Prepare Fried Fish| FriedFishStep -->|Fried Fish Ready| SingleOrderReadyEvent
DispatchOrderStep -->|Prepare Potato Fries| PotatoFriesStep -->|Potato Fries Ready| SingleOrderReadyEvent
DispatchOrderStep -->|Prepare Fish Sandwich| FishSandwichStep -->|Fish Sandwich Ready| SingleOrderReadyEvent
DispatchOrderStep -->|Prepare Fish & Chips| FishAndChipsStep -->|Fish & Chips Ready| SingleOrderReadyEvent

SingleOrderReadyEvent-->PackFoodStep --> OrderPackedEvent
```

## Running Examples with Filters
Examples may be explored and ran within _Visual Studio_ using _Test Explorer_.
Expand Down
Original file line number Diff line number Diff line change
@@ -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];
}
}
Original file line number Diff line number Diff line change
@@ -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];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// 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/tree/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 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));

processBuilder
.OnInputEvent(ProcessEvents.PrepareFishAndChips)
.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 Functions
{
public const string AddCondiments = nameof(AddCondiments);
}

public static class OutputEvents
{
public const string CondimentsAdded = nameof(CondimentsAdded);
}

[KernelFunction(Functions.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) { }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// 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/tree/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;
}

private sealed class AddBunsStep : KernelProcessStep
{
public static class Functions
{
public const string AddBuns = nameof(AddBuns);
}

public static class OutputEvents
{
public const string BunsAdded = nameof(BunsAdded);
}

[KernelFunction(Functions.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 Functions
{
public const string AddSpecialSauce = nameof(AddSpecialSauce);
}

public static class OutputEvents
{
public const string SpecialSauceAdded = nameof(SpecialSauceAdded);
}

[KernelFunction(Functions.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) { }
}
}
Loading
Loading