Skip to content

Commit

Permalink
.Net: SK Process - Sample 3 Food related samples (#9087)
Browse files Browse the repository at this point in the history
### Description
Adding more SK Processes samples:

- Creating SK Process Event Builder: helps link process events to Steps
internal events
- Adding samples to showcase:
    - reuse of steps]
    - use of steps specific functions
    - how to make use of step functions that output multiple events
    - use of cycles: FoodRuined -> must prepare the food from scratch

For additional details check the `GettingStartedWithProcesses/README.md`
file

### 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 😄
  • Loading branch information
esttenorio authored Oct 8, 2024
1 parent 68187f3 commit a4ddbaa
Show file tree
Hide file tree
Showing 13 changed files with 872 additions and 0 deletions.
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

0 comments on commit a4ddbaa

Please sign in to comment.