Skip to content

Commit

Permalink
Python: Add step03 getting started with processes samples. (#9427)
Browse files Browse the repository at this point in the history
### Motivation and Context

Add step03 getting started with processes samples to show how to create
kernel processes related to food preparation and food ordering tasks.

<!-- Thank you for your contribution to the semantic-kernel repo!
Please help reviewers and future users, providing the following
information:
  1. Why is this change required?
  2. What problem does it solve?
  3. What scenario does it contribute to?
  4. If it fixes an open issue, please link to the issue here.
-->

### Description

Add step03 getting started with processes samples.
- Make `emit_event` async
- Simplify how one can create an event to emit by allowing the
process_event to be a string, along with kwargs:

```
await context.emit_event(process_event=CommonEvents.StartBRequested.value, data="Get Going B")
``` 

<!-- Describe your changes, the overall approach, the underlying design.
These notes will help understanding how your code works. Thanks! -->

### Contribution Checklist

<!-- Before submitting this PR, please make sure: -->

- [X] The code builds clean without any errors or warnings
- [X] The PR follows the [SK Contribution
Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md)
and the [pre-submission formatting
script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts)
raises no violations
- [X] All unit tests pass, and I have added new tests where possible
- [X] I didn't break anyone 😄
  • Loading branch information
moonbox3 authored Oct 25, 2024
1 parent 92b2b21 commit 6aa3229
Show file tree
Hide file tree
Showing 26 changed files with 1,224 additions and 37 deletions.
12 changes: 6 additions & 6 deletions python/samples/concepts/processes/cycles_with_fan_in.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ class KickOffStep(KernelProcessStep):

@kernel_function(name=KICK_OFF_FUNCTION)
async def print_welcome_message(self, context: KernelProcessStepContext):
context.emit_event(KernelProcessEvent(id=CommonEvents.StartARequested.value, data="Get Going A"))
context.emit_event(KernelProcessEvent(id=CommonEvents.StartBRequested.value, data="Get Going B"))
await context.emit_event(process_event=CommonEvents.StartARequested.value, data="Get Going A")
await context.emit_event(process_event=CommonEvents.StartBRequested.value, data="Get Going B")


# Define a sample `AStep` step that will emit an event after 1 second.
Expand All @@ -52,7 +52,7 @@ class AStep(KernelProcessStep):
@kernel_function()
async def do_it(self, context: KernelProcessStepContext):
await asyncio.sleep(1)
context.emit_event(KernelProcessEvent(id=CommonEvents.AStepDone.value, data="I did A"))
await context.emit_event(process_event=CommonEvents.AStepDone.value, data="I did A")


# Define a sample `BStep` step that will emit an event after 2 seconds.
Expand All @@ -61,7 +61,7 @@ class BStep(KernelProcessStep):
@kernel_function()
async def do_it(self, context: KernelProcessStepContext):
await asyncio.sleep(2)
context.emit_event(KernelProcessEvent(id=CommonEvents.BStepDone.value, data="I did B"))
await context.emit_event(process_event=CommonEvents.BStepDone.value, data="I did B")


# Define a sample `CStepState` that will keep track of the current cycle.
Expand All @@ -84,9 +84,9 @@ async def do_it(self, context: KernelProcessStepContext, astepdata: str, bstepda
print(f"CStep Current Cycle: {self.state.current_cycle}")
if self.state.current_cycle == 3:
print("CStep Exit Requested")
context.emit_event(process_event=KernelProcessEvent(id=CommonEvents.ExitRequested.value))
await context.emit_event(process_event=CommonEvents.ExitRequested.value)
return
context.emit_event(process_event=KernelProcessEvent(id=CommonEvents.CStepDone.value))
await context.emit_event(process_event=CommonEvents.CStepDone.value)


kernel = Kernel()
Expand Down
19 changes: 8 additions & 11 deletions python/samples/concepts/processes/nested_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
from semantic_kernel.processes.kernel_process.kernel_process_step import KernelProcessStep
from semantic_kernel.processes.kernel_process.kernel_process_step_context import KernelProcessStepContext
from semantic_kernel.processes.kernel_process.kernel_process_step_state import KernelProcessStepState
from semantic_kernel.processes.local_runtime.local_event import KernelProcessEvent
from semantic_kernel.processes.local_runtime.local_kernel_process import start
from semantic_kernel.processes.process_builder import ProcessBuilder
from semantic_kernel.processes.process_types import TState
Expand Down Expand Up @@ -58,17 +57,15 @@ async def repeat(self, message: str, context: KernelProcessStepContext, count: i
self.state.last_message = output
print(f"[REPEAT] {output}")

context.emit_event(
process_event=KernelProcessEvent(
id=ProcessEvents.OutputReadyPublic.value, data=output, visibility=KernelProcessEventVisibility.Public
)
await context.emit_event(
process_event=ProcessEvents.OutputReadyPublic.value,
data=output,
visibility=KernelProcessEventVisibility.Public,
)
context.emit_event(
process_event=KernelProcessEvent(
id=ProcessEvents.OutputReadyInternal.value,
data=output,
visibility=KernelProcessEventVisibility.Internal,
)
await context.emit_event(
process_event=ProcessEvents.OutputReadyInternal.value,
data=output,
visibility=KernelProcessEventVisibility.Internal,
)


Expand Down
185 changes: 184 additions & 1 deletion python/samples/getting_started_with_processes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ The getting started with agents examples include:

Example|Description
---|---
[step01_processes](../getting_started_with_processes/step01_processes.py)|How to create a simple process with a loop and a conditional exit
[step01_processes](../getting_started_with_processes/step01/step01_processes.py)|How to create a simple process with a loop and a conditional exit|
[step03a_food_preparation](../getting_started_with_processes/step03/step03a_food_preparation.py)|Showcasing reuse of steps, creation of processes, spawning of multiple events, use of stateful steps with food preparation samples.
[step03b_food_ordering](../getting_started_with_processes/step03/step03b_food_ordering.py)|Showcasing use of subprocesses as steps, spawning of multiple events conditionally reusing the food preparation samples.

### step01_processes

Expand All @@ -32,6 +34,187 @@ flowchart LR
AssistantResponse--> UserInput
```

### step03a_food_preparation

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

##### Stateless 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
```

##### Stateful Product Preparation Processes

The processes in this subsection contain the following modifications/additions to previously used food preparation processes:

- The `Gather Ingredients Step` is now stateful and has a predefined number of initial ingredients that are used as orders are prepared. When there are no ingredients left, it emits the `Out of Stock Event`.
- The `Cut Food Step` is now a stateful component which has a `Knife Sharpness State` that tracks the Knife Sharpness.
- As the `Slice Food` and `Chop Food` Functions get invoked, the Knife Sharpness deteriorates.
- The `Cut Food Step` has an additional input function `Sharpen Knife Function`.
- The new `Sharpen Knife Function` sharpens the knife and increases the Knife Sharpness - Knife Sharpness State.
- From time to time, the `Cut Food Step`'s functions `SliceFood` and `ChopFood` will fail and emit a `Knife Needs Sharpening Event` that then triggers the `Sharpen Knife Function`.


###### Potato Fries Preparation With Knife Sharpening and Ingredient Stock Process

The following processes is a modification on the process [Potato Fries Preparation](#potato-fries-preparation-process)
with the the stateful steps mentioned previously.

``` mermaid
flowchart LR
PreparePotatoFriesEvent([Prepare Potato <br/> Fries Event])
PotatoFriesReadyEvent([Potato Fries <br/> Ready Event])
OutOfStock([Ingredients <br/> Out of Stock <br/> Event])
FryStep[Fry Food <br/> Step]
subgraph GatherIngredientsStep[Gather Ingredients Step]
GatherIngredientsFunction[Gather Potato <br/> Function]
IngredientsState[(Ingredients <br/> Stock <br/> State)]
end
subgraph CutStep ["Cut Food Step"]
direction LR
SliceFoodFunction[Slice Food <br/> Function]
SharpenKnifeFunction[Sharpen Knife <br/> Function]
CutState[(Knife <br/> Sharpness <br/> State)]
end
CutStep --> |**Potato Sliced Ready** <br/> _Food Sliced Ready_ | FryStep --> |_Fried Food Ready_|PotatoFriesReadyEvent
FryStep -->|Fried Potato Ruined <br/> _Fried Food Ruined_| GatherIngredientsStep
GatherIngredientsStep --> OutOfStock
SliceFoodFunction --> |Knife Needs Sharpening| SharpenKnifeFunction
SharpenKnifeFunction --> |Knife Sharpened| SliceFoodFunction
GatherIngredientsStep -->| Slice Potatoes <br/> _Ingredients Gathered_ | CutStep
PreparePotatoFriesEvent --> GatherIngredientsStep
```

###### Fried Fish Preparation With Knife Sharpening and Ingredient Stock Process

The following process is a modification on the process [Fried Fish Preparation](#fried-fish-preparation-process)
with the the stateful steps mentioned previously.

``` mermaid
flowchart LR
PrepareFriedFishEvent([Prepare Fried <br/> Fish Event])
FriedFishReadyEvent([Fried Fish <br/> Ready Event])
OutOfStock([Ingredients <br/> Out of Stock <br/> Event])
FryStep[Fry Food <br/> Step]
subgraph GatherIngredientsStep[Gather Ingredients Step]
GatherIngredientsFunction[Gather Fish <br/> Function]
IngredientsState[(Ingredients <br/> Stock <br/> State)]
end
subgraph CutStep ["Cut Food Step"]
direction LR
ChopFoodFunction[Chop Food <br/> Function]
SharpenKnifeFunction[Sharpen Knife <br/> Function]
CutState[(Knife <br/> Sharpness <br/> State)]
end
CutStep --> |**Fish Chopped Ready** <br/> _Food Chopped Ready_| FryStep --> |_Fried Food Ready_|FriedFishReadyEvent
FryStep -->|**Fried Fish Ruined** <br/> _Fried Food Ruined_| GatherIngredientsStep
GatherIngredientsStep --> OutOfStock
ChopFoodFunction --> |Knife Needs Sharpening| SharpenKnifeFunction
SharpenKnifeFunction --> |Knife Sharpened| ChopFoodFunction
GatherIngredientsStep -->| Chop Fish <br/> _Ingredients Gathered_ | CutStep
PrepareFriedFishEvent --> GatherIngredientsStep
```

### step03b_food_ordering

#### 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
```

## Configuring the Kernel

Similar to the Semantic Kernel Python concept samples, it is necessary to configure the secrets
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,13 @@ async def get_user_input(self, context: KernelProcessStepContext):
print(f"USER: {user_message}")

if "exit" in user_message:
context.emit_event(KernelProcessEvent(id=ChatBotEvents.Exit.value, data=None))
await context.emit_event(process_event=ChatBotEvents.Exit.value, data=None)
return

self.state.current_input_index += 1

# Emit the user input event
context.emit_event({"id": CommonEvents.UserInputReceived.value, "data": user_message})
await context.emit_event(process_event=CommonEvents.UserInputReceived.value, data=user_message)


class ChatUserInputStep(ScriptedUserInputStep):
Expand Down Expand Up @@ -140,9 +140,7 @@ async def get_chat_response(self, context: "KernelProcessStepContext", user_mess
self.state.chat_messages.append(answer)

# Emit an event: assistantResponse
context.emit_event(
process_event=KernelProcessEvent(id=ChatBotEvents.AssistantResponseGenerated.value, data=answer)
)
await context.emit_event(process_event=ChatBotEvents.AssistantResponseGenerated.value, data=answer)


kernel = Kernel()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Copyright (c) Microsoft. All rights reserved.

from samples.getting_started_with_processes.step03.models.food_ingredients import FoodIngredients
from samples.getting_started_with_processes.step03.models.food_order_item import FoodItem

__all__ = ["FoodIngredients", "FoodItem"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copyright (c) Microsoft. All rights reserved.

from enum import Enum


class FoodIngredients(str, Enum):
POTATOES = "Potatoes"
FISH = "Fish"
BUNS = "Buns"
SAUCE = "Sauce"
CONDIMENTS = "Condiments"
NONE = "None"

def to_friendly_string(self) -> str:
return self.value
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright (c) Microsoft. All rights reserved.

from enum import Enum


class FoodItem(str, Enum):
POTATO_FRIES = "Potato Fries"
FRIED_FISH = "Fried Fish"
FISH_SANDWICH = "Fish Sandwich"
FISH_AND_CHIPS = "Fish & Chips"

def to_friendly_string(self) -> str:
return self.value
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.

from samples.getting_started_with_processes.step03.processes.fish_and_chips_process import FishAndChipsProcess
from samples.getting_started_with_processes.step03.processes.fish_sandwich_process import FishSandwichProcess
from samples.getting_started_with_processes.step03.processes.fried_fish_process import FriedFishProcess

__all__ = ["FishAndChipsProcess", "FriedFishProcess", "FishSandwichProcess"]
Loading

0 comments on commit 6aa3229

Please sign in to comment.