Skip to content

Commit 6aa3229

Browse files
authoredOct 25, 2024
Python: Add step03 getting started with processes samples. (microsoft#9427)
### 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 😄
1 parent 92b2b21 commit 6aa3229

26 files changed

+1224
-37
lines changed
 

‎python/samples/concepts/processes/cycles_with_fan_in.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ class KickOffStep(KernelProcessStep):
4242

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

4848

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

5757

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

6666

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

9191

9292
kernel = Kernel()

‎python/samples/concepts/processes/nested_process.py

+8-11
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
from semantic_kernel.processes.kernel_process.kernel_process_step import KernelProcessStep
1616
from semantic_kernel.processes.kernel_process.kernel_process_step_context import KernelProcessStepContext
1717
from semantic_kernel.processes.kernel_process.kernel_process_step_state import KernelProcessStepState
18-
from semantic_kernel.processes.local_runtime.local_event import KernelProcessEvent
1918
from semantic_kernel.processes.local_runtime.local_kernel_process import start
2019
from semantic_kernel.processes.process_builder import ProcessBuilder
2120
from semantic_kernel.processes.process_types import TState
@@ -58,17 +57,15 @@ async def repeat(self, message: str, context: KernelProcessStepContext, count: i
5857
self.state.last_message = output
5958
print(f"[REPEAT] {output}")
6059

61-
context.emit_event(
62-
process_event=KernelProcessEvent(
63-
id=ProcessEvents.OutputReadyPublic.value, data=output, visibility=KernelProcessEventVisibility.Public
64-
)
60+
await context.emit_event(
61+
process_event=ProcessEvents.OutputReadyPublic.value,
62+
data=output,
63+
visibility=KernelProcessEventVisibility.Public,
6564
)
66-
context.emit_event(
67-
process_event=KernelProcessEvent(
68-
id=ProcessEvents.OutputReadyInternal.value,
69-
data=output,
70-
visibility=KernelProcessEventVisibility.Internal,
71-
)
65+
await context.emit_event(
66+
process_event=ProcessEvents.OutputReadyInternal.value,
67+
data=output,
68+
visibility=KernelProcessEventVisibility.Internal,
7269
)
7370

7471

‎python/samples/getting_started_with_processes/README.md

+184-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ The getting started with agents examples include:
2020

2121
Example|Description
2222
---|---
23-
[step01_processes](../getting_started_with_processes/step01_processes.py)|How to create a simple process with a loop and a conditional exit
23+
[step01_processes](../getting_started_with_processes/step01/step01_processes.py)|How to create a simple process with a loop and a conditional exit|
24+
[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.
25+
[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.
2426

2527
### step01_processes
2628

@@ -32,6 +34,187 @@ flowchart LR
3234
AssistantResponse--> UserInput
3335
```
3436

37+
### step03a_food_preparation
38+
39+
This tutorial contains a set of food recipes associated with the Food Preparation Processes of a restaurant.
40+
41+
The following recipes for preparation of Order Items are defined as SK Processes:
42+
43+
#### Product Preparation Processes
44+
45+
##### Stateless Product Preparation Processes
46+
47+
###### Potato Fries Preparation Process
48+
49+
``` mermaid
50+
flowchart LR
51+
PreparePotatoFriesEvent([Prepare Potato <br/> Fries Event])
52+
PotatoFriesReadyEvent([Potato Fries <br/> Ready Event])
53+
54+
GatherIngredientsStep[Gather Ingredients <br/> Step]
55+
CutStep[Cut Food <br/> Step]
56+
FryStep[Fry Food <br/> Step]
57+
58+
PreparePotatoFriesEvent --> GatherIngredientsStep -->| Slice Potatoes <br/> _Ingredients Gathered_ | CutStep --> |**Potato Sliced Ready** <br/> _Food Sliced Ready_ | FryStep --> |_Fried Food Ready_|PotatoFriesReadyEvent
59+
FryStep -->|Fried Potato Ruined <br/> _Fried Food Ruined_| GatherIngredientsStep
60+
```
61+
62+
###### Fried Fish Preparation Process
63+
64+
``` mermaid
65+
flowchart LR
66+
PrepareFriedFishEvent([Prepare Fried <br/> Fish Event])
67+
FriedFishReadyEvent([Fried Fish <br/> Ready Event])
68+
69+
GatherIngredientsStep[Gather Ingredients <br/> Step]
70+
CutStep[Cut Food <br/> Step]
71+
FryStep[Fry Food <br/> Step]
72+
73+
PrepareFriedFishEvent --> GatherIngredientsStep -->| Chop Fish <br/> _Ingredients Gathered_ | CutStep --> |**Fish Chopped Ready** <br/> _Food Chopped Ready_| FryStep --> |_Fried Food Ready_ | FriedFishReadyEvent
74+
FryStep -->|**Fried Fish Ruined** <br/> _Fried Food Ruined_| GatherIngredientsStep
75+
```
76+
77+
###### Fish Sandwich Preparation Process
78+
79+
``` mermaid
80+
flowchart LR
81+
PrepareFishSandwichEvent([Prepare Fish <br/> Sandwich Event])
82+
FishSandwichReadyEvent([Fish Sandwich <br/> Ready Event])
83+
84+
FriedFishStep[[Fried Fish <br/> Process Step]]
85+
AddBunsStep[Add Buns <br/> Step]
86+
AddSpecialSauceStep[Add Special <br/> Sauce Step]
87+
88+
PrepareFishSandwichEvent -->|Prepare Fried Fish| FriedFishStep -->|Fried Fish Ready| AddBunsStep --> |Buns Added | AddSpecialSauceStep --> |Special Sauce Added | FishSandwichReadyEvent
89+
```
90+
91+
###### Fish And Chips Preparation Process
92+
93+
``` mermaid
94+
flowchart LR
95+
PrepareFishAndChipsEvent([Prepare <br/> Fish And Chips <br/> Event])
96+
FishAndChipsReadyEvent([Fish And Chips <br/> Ready Event])
97+
98+
FriedFishStep[[Fried Fish <br/> Process Step]]
99+
PotatoFriesStep[[Potato Fries <br/> Process Step]]
100+
AddCondiments[Add Condiments <br/> Step ]
101+
102+
PrepareFishAndChipsEvent -->|Prepare Fried Fish| FriedFishStep --> |Fried Fish Ready| AddCondiments
103+
PrepareFishAndChipsEvent -->|Prepare Potato Fries| PotatoFriesStep -->|Potato Fries Ready| AddCondiments
104+
AddCondiments -->|Condiments Added| FishAndChipsReadyEvent
105+
```
106+
107+
##### Stateful Product Preparation Processes
108+
109+
The processes in this subsection contain the following modifications/additions to previously used food preparation processes:
110+
111+
- 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`.
112+
- The `Cut Food Step` is now a stateful component which has a `Knife Sharpness State` that tracks the Knife Sharpness.
113+
- As the `Slice Food` and `Chop Food` Functions get invoked, the Knife Sharpness deteriorates.
114+
- The `Cut Food Step` has an additional input function `Sharpen Knife Function`.
115+
- The new `Sharpen Knife Function` sharpens the knife and increases the Knife Sharpness - Knife Sharpness State.
116+
- 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`.
117+
118+
119+
###### Potato Fries Preparation With Knife Sharpening and Ingredient Stock Process
120+
121+
The following processes is a modification on the process [Potato Fries Preparation](#potato-fries-preparation-process)
122+
with the the stateful steps mentioned previously.
123+
124+
``` mermaid
125+
flowchart LR
126+
PreparePotatoFriesEvent([Prepare Potato <br/> Fries Event])
127+
PotatoFriesReadyEvent([Potato Fries <br/> Ready Event])
128+
OutOfStock([Ingredients <br/> Out of Stock <br/> Event])
129+
130+
FryStep[Fry Food <br/> Step]
131+
132+
subgraph GatherIngredientsStep[Gather Ingredients Step]
133+
GatherIngredientsFunction[Gather Potato <br/> Function]
134+
IngredientsState[(Ingredients <br/> Stock <br/> State)]
135+
end
136+
subgraph CutStep ["Cut Food Step"]
137+
direction LR
138+
SliceFoodFunction[Slice Food <br/> Function]
139+
SharpenKnifeFunction[Sharpen Knife <br/> Function]
140+
CutState[(Knife <br/> Sharpness <br/> State)]
141+
end
142+
143+
CutStep --> |**Potato Sliced Ready** <br/> _Food Sliced Ready_ | FryStep --> |_Fried Food Ready_|PotatoFriesReadyEvent
144+
FryStep -->|Fried Potato Ruined <br/> _Fried Food Ruined_| GatherIngredientsStep
145+
GatherIngredientsStep --> OutOfStock
146+
147+
SliceFoodFunction --> |Knife Needs Sharpening| SharpenKnifeFunction
148+
SharpenKnifeFunction --> |Knife Sharpened| SliceFoodFunction
149+
150+
GatherIngredientsStep -->| Slice Potatoes <br/> _Ingredients Gathered_ | CutStep
151+
PreparePotatoFriesEvent --> GatherIngredientsStep
152+
```
153+
154+
###### Fried Fish Preparation With Knife Sharpening and Ingredient Stock Process
155+
156+
The following process is a modification on the process [Fried Fish Preparation](#fried-fish-preparation-process)
157+
with the the stateful steps mentioned previously.
158+
159+
``` mermaid
160+
flowchart LR
161+
PrepareFriedFishEvent([Prepare Fried <br/> Fish Event])
162+
FriedFishReadyEvent([Fried Fish <br/> Ready Event])
163+
OutOfStock([Ingredients <br/> Out of Stock <br/> Event])
164+
165+
FryStep[Fry Food <br/> Step]
166+
167+
subgraph GatherIngredientsStep[Gather Ingredients Step]
168+
GatherIngredientsFunction[Gather Fish <br/> Function]
169+
IngredientsState[(Ingredients <br/> Stock <br/> State)]
170+
end
171+
subgraph CutStep ["Cut Food Step"]
172+
direction LR
173+
ChopFoodFunction[Chop Food <br/> Function]
174+
SharpenKnifeFunction[Sharpen Knife <br/> Function]
175+
CutState[(Knife <br/> Sharpness <br/> State)]
176+
end
177+
178+
CutStep --> |**Fish Chopped Ready** <br/> _Food Chopped Ready_| FryStep --> |_Fried Food Ready_|FriedFishReadyEvent
179+
FryStep -->|**Fried Fish Ruined** <br/> _Fried Food Ruined_| GatherIngredientsStep
180+
GatherIngredientsStep --> OutOfStock
181+
182+
ChopFoodFunction --> |Knife Needs Sharpening| SharpenKnifeFunction
183+
SharpenKnifeFunction --> |Knife Sharpened| ChopFoodFunction
184+
185+
GatherIngredientsStep -->| Chop Fish <br/> _Ingredients Gathered_ | CutStep
186+
PrepareFriedFishEvent --> GatherIngredientsStep
187+
```
188+
189+
### step03b_food_ordering
190+
191+
#### Single Order Preparation Process
192+
193+
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.
194+
195+
```mermaid
196+
graph TD
197+
PrepareSingleOrderEvent([Prepare Single Order <br/> Event])
198+
SingleOrderReadyEvent([Single Order <br/> Ready Event])
199+
OrderPackedEvent([Order Packed <br/> Event])
200+
201+
DispatchOrderStep{{Dispatch <br/> Order Step}}
202+
FriedFishStep[[Fried Fish <br/> Process Step]]
203+
PotatoFriesStep[[Potato Fries <br/> Process Step]]
204+
FishSandwichStep[[Fish Sandwich <br/> Process Step]]
205+
FishAndChipsStep[[Fish & Chips <br/> Process Step]]
206+
207+
PackFoodStep[Pack Food <br/> Step]
208+
209+
PrepareSingleOrderEvent -->|Order Received| DispatchOrderStep
210+
DispatchOrderStep -->|Prepare Fried Fish| FriedFishStep -->|Fried Fish Ready| SingleOrderReadyEvent
211+
DispatchOrderStep -->|Prepare Potato Fries| PotatoFriesStep -->|Potato Fries Ready| SingleOrderReadyEvent
212+
DispatchOrderStep -->|Prepare Fish Sandwich| FishSandwichStep -->|Fish Sandwich Ready| SingleOrderReadyEvent
213+
DispatchOrderStep -->|Prepare Fish & Chips| FishAndChipsStep -->|Fish & Chips Ready| SingleOrderReadyEvent
214+
215+
SingleOrderReadyEvent-->PackFoodStep --> OrderPackedEvent
216+
```
217+
35218
## Configuring the Kernel
36219

37220
Similar to the Semantic Kernel Python concept samples, it is necessary to configure the secrets

‎python/samples/getting_started_with_processes/step01_processes.py ‎python/samples/getting_started_with_processes/step01/step01_processes.py

+3-5
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,13 @@ async def get_user_input(self, context: KernelProcessStepContext):
7070
print(f"USER: {user_message}")
7171

7272
if "exit" in user_message:
73-
context.emit_event(KernelProcessEvent(id=ChatBotEvents.Exit.value, data=None))
73+
await context.emit_event(process_event=ChatBotEvents.Exit.value, data=None)
7474
return
7575

7676
self.state.current_input_index += 1
7777

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

8181

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

142142
# Emit an event: assistantResponse
143-
context.emit_event(
144-
process_event=KernelProcessEvent(id=ChatBotEvents.AssistantResponseGenerated.value, data=answer)
145-
)
143+
await context.emit_event(process_event=ChatBotEvents.AssistantResponseGenerated.value, data=answer)
146144

147145

148146
kernel = Kernel()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
from samples.getting_started_with_processes.step03.models.food_ingredients import FoodIngredients
4+
from samples.getting_started_with_processes.step03.models.food_order_item import FoodItem
5+
6+
__all__ = ["FoodIngredients", "FoodItem"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
from enum import Enum
4+
5+
6+
class FoodIngredients(str, Enum):
7+
POTATOES = "Potatoes"
8+
FISH = "Fish"
9+
BUNS = "Buns"
10+
SAUCE = "Sauce"
11+
CONDIMENTS = "Condiments"
12+
NONE = "None"
13+
14+
def to_friendly_string(self) -> str:
15+
return self.value
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
from enum import Enum
4+
5+
6+
class FoodItem(str, Enum):
7+
POTATO_FRIES = "Potato Fries"
8+
FRIED_FISH = "Fried Fish"
9+
FISH_SANDWICH = "Fish Sandwich"
10+
FISH_AND_CHIPS = "Fish & Chips"
11+
12+
def to_friendly_string(self) -> str:
13+
return self.value
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
from samples.getting_started_with_processes.step03.processes.fish_and_chips_process import FishAndChipsProcess
4+
from samples.getting_started_with_processes.step03.processes.fish_sandwich_process import FishSandwichProcess
5+
from samples.getting_started_with_processes.step03.processes.fried_fish_process import FriedFishProcess
6+
7+
__all__ = ["FishAndChipsProcess", "FriedFishProcess", "FishSandwichProcess"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
import json
4+
from enum import Enum
5+
6+
from samples.getting_started_with_processes.step03.processes.fried_fish_process import FriedFishProcess
7+
from samples.getting_started_with_processes.step03.processes.potato_fries_process import PotatoFriesProcess
8+
from samples.getting_started_with_processes.step03.steps.external_step import ExternalStep
9+
from semantic_kernel.functions.kernel_function_decorator import kernel_function
10+
from semantic_kernel.processes.kernel_process.kernel_process_step import KernelProcessStep
11+
from semantic_kernel.processes.kernel_process.kernel_process_step_context import KernelProcessStepContext
12+
from semantic_kernel.processes.process_builder import ProcessBuilder
13+
from semantic_kernel.processes.process_function_target_builder import ProcessFunctionTargetBuilder
14+
15+
16+
class AddFishAndChipsCondimentsStep(KernelProcessStep):
17+
class Functions(Enum):
18+
AddCondiments = "AddCondiments"
19+
20+
class OutputEvents(Enum):
21+
CondimentsAdded = "CondimentsAdded"
22+
23+
@kernel_function(name=Functions.AddCondiments.value)
24+
async def add_condiments(
25+
self, context: KernelProcessStepContext, fish_actions: list[str], potato_actions: list[str]
26+
):
27+
print(
28+
f"ADD_CONDIMENTS: Added condiments to Fish & Chips - Fish: {json.dumps(fish_actions)}, Potatoes: {json.dumps(potato_actions)}" # noqa: E501
29+
)
30+
fish_actions.extend(potato_actions)
31+
fish_actions.append("Condiments")
32+
await context.emit_event(
33+
process_event=AddFishAndChipsCondimentsStep.OutputEvents.CondimentsAdded.value, data=fish_actions
34+
)
35+
36+
37+
class FishAndChipsProcess:
38+
class ProcessEvents(Enum):
39+
PrepareFishAndChips = "PrepareFishAndChips"
40+
FishAndChipsReady = "FishAndChipsReady"
41+
42+
class ExternalFishAndChipsStep(ExternalStep):
43+
def __init__(self):
44+
super().__init__(FishAndChipsProcess.ProcessEvents.FishAndChipsReady.value)
45+
46+
@staticmethod
47+
def create_process(process_name: str = "FishAndChipsProcess"):
48+
process_builder = ProcessBuilder(process_name)
49+
make_fried_fish_step = process_builder.add_step_from_process(FriedFishProcess.create_process())
50+
make_potato_fries_step = process_builder.add_step_from_process(PotatoFriesProcess.create_process())
51+
add_condiments_step = process_builder.add_step(AddFishAndChipsCondimentsStep)
52+
external_step = process_builder.add_step(FishAndChipsProcess.ExternalFishAndChipsStep)
53+
54+
process_builder.on_input_event(FishAndChipsProcess.ProcessEvents.PrepareFishAndChips.value).send_event_to(
55+
make_fried_fish_step.where_input_event_is(FriedFishProcess.ProcessEvents.PrepareFriedFish.value)
56+
).send_event_to(
57+
make_potato_fries_step.where_input_event_is(PotatoFriesProcess.ProcessEvents.PreparePotatoFries.value)
58+
)
59+
60+
make_fried_fish_step.on_event(FriedFishProcess.ProcessEvents.FriedFishReady.value).send_event_to(
61+
ProcessFunctionTargetBuilder(add_condiments_step, parameter_name="fishActions")
62+
)
63+
64+
make_potato_fries_step.on_event(PotatoFriesProcess.ProcessEvents.PotatoFriesReady.value).send_event_to(
65+
ProcessFunctionTargetBuilder(add_condiments_step, parameter_name="potatoActions")
66+
)
67+
68+
add_condiments_step.on_event(AddFishAndChipsCondimentsStep.OutputEvents.CondimentsAdded.value).send_event_to(
69+
ProcessFunctionTargetBuilder(external_step, parameter_name="fishActions")
70+
)
71+
72+
return process_builder
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
from enum import Enum
4+
5+
from samples.getting_started_with_processes.step03.processes.fried_fish_process import FriedFishProcess
6+
from samples.getting_started_with_processes.step03.steps.external_step import ExternalStep
7+
from semantic_kernel.functions.kernel_function_decorator import kernel_function
8+
from semantic_kernel.processes.kernel_process.kernel_process_step import KernelProcessStep
9+
from semantic_kernel.processes.kernel_process.kernel_process_step_context import KernelProcessStepContext
10+
from semantic_kernel.processes.process_builder import ProcessBuilder
11+
from semantic_kernel.processes.process_function_target_builder import ProcessFunctionTargetBuilder
12+
13+
14+
class AddBunStep(KernelProcessStep):
15+
class Functions(Enum):
16+
AddBuns = "AddBuns"
17+
18+
class OutputEvents(Enum):
19+
BunsAdded = "BunsAdded"
20+
21+
@kernel_function(name=Functions.AddBuns.value)
22+
async def slice_food(self, context: KernelProcessStepContext, food_actions: list[str]):
23+
print(f"BUNS_ADDED_STEP: Buns added to ingredient {food_actions[0]}")
24+
food_actions.append("Buns")
25+
await context.emit_event(process_event=self.OutputEvents.BunsAdded.value, data=food_actions)
26+
27+
28+
class AddSpecialSauceStep(KernelProcessStep):
29+
class Functions(Enum):
30+
AddSpecialSauce = "AddSpecialSauce"
31+
32+
class OutputEvents(Enum):
33+
SpecialSauceAdded = "SpecialSauceAdded"
34+
35+
@kernel_function(name=Functions.AddSpecialSauce.value)
36+
async def slice_food(self, context: KernelProcessStepContext, food_actions: list[str]):
37+
print(f"SPECIAL_SAUCE_ADDED: Special sauce added to ingredient {food_actions[0]}")
38+
food_actions.append("Sauce")
39+
await context.emit_event(process_event=self.OutputEvents.SpecialSauceAdded.value, data=food_actions)
40+
41+
42+
class ExternalFriedFishStep(ExternalStep):
43+
def __init__(self):
44+
super().__init__(FishSandwichProcess.ProcessEvents.FishSandwichReady.value)
45+
46+
47+
class FishSandwichProcess:
48+
class ProcessEvents(Enum):
49+
PrepareFishSandwich = "PrepareFishSandwich"
50+
FishSandwichReady = "FishSandwichReady"
51+
52+
@staticmethod
53+
def create_process(process_name: str = "FishSandwichProcess"):
54+
process_builder = ProcessBuilder(process_name)
55+
make_fried_fish_step = process_builder.add_step_from_process(FriedFishProcess.create_process())
56+
add_buns_step = process_builder.add_step(AddBunStep)
57+
add_special_sauce_step = process_builder.add_step(AddSpecialSauceStep)
58+
external_step = process_builder.add_step(ExternalFriedFishStep)
59+
60+
process_builder.on_input_event(FishSandwichProcess.ProcessEvents.PrepareFishSandwich.value).send_event_to(
61+
make_fried_fish_step.where_input_event_is(FriedFishProcess.ProcessEvents.PrepareFriedFish.value)
62+
)
63+
64+
make_fried_fish_step.on_event(FriedFishProcess.ProcessEvents.FriedFishReady.value).send_event_to(
65+
ProcessFunctionTargetBuilder(add_buns_step)
66+
)
67+
68+
add_buns_step.on_event(AddBunStep.OutputEvents.BunsAdded.value).send_event_to(
69+
ProcessFunctionTargetBuilder(add_special_sauce_step)
70+
)
71+
72+
add_special_sauce_step.on_event(AddSpecialSauceStep.OutputEvents.SpecialSauceAdded.value).send_event_to(
73+
ProcessFunctionTargetBuilder(external_step)
74+
)
75+
76+
return process_builder
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
from enum import Enum
4+
5+
from samples.getting_started_with_processes.step03.models import FoodIngredients
6+
from samples.getting_started_with_processes.step03.steps import CutFoodStep, FryFoodStep, GatherIngredientsStep
7+
from samples.getting_started_with_processes.step03.steps.cut_food_with_sharpening_step import CutFoodWithSharpeningStep
8+
from samples.getting_started_with_processes.step03.steps.gather_ingredients_step import GatherIngredientsWithStockStep
9+
from semantic_kernel.processes.process_builder import ProcessBuilder
10+
from semantic_kernel.processes.process_function_target_builder import ProcessFunctionTargetBuilder
11+
12+
13+
class GatherFriedFishIngredientsStep(GatherIngredientsStep):
14+
def __init__(self):
15+
super().__init__(FoodIngredients.FISH)
16+
17+
18+
class GatherFriedFishIngredientsWithStockStep(GatherIngredientsWithStockStep):
19+
def __init__(self):
20+
super().__init__(FoodIngredients.FISH)
21+
22+
23+
class FriedFishProcess:
24+
class ProcessEvents(Enum):
25+
PrepareFriedFish = "PrepareFriedFish"
26+
FriedFishReady = FryFoodStep.OutputEvents.FriedFoodReady.value
27+
28+
@staticmethod
29+
def create_process(process_name: str = "FriedFishProcess") -> ProcessBuilder:
30+
process_builder = ProcessBuilder(process_name)
31+
gatherIngredientsStep = process_builder.add_step(GatherFriedFishIngredientsStep)
32+
chopStep = process_builder.add_step(CutFoodStep, name="chopStep")
33+
fryStep = process_builder.add_step(FryFoodStep)
34+
35+
process_builder.on_input_event(FriedFishProcess.ProcessEvents.PrepareFriedFish.value).send_event_to(
36+
gatherIngredientsStep
37+
)
38+
39+
gatherIngredientsStep.on_event(
40+
GatherFriedFishIngredientsStep.OutputEvents.IngredientsGathered.value
41+
).send_event_to(ProcessFunctionTargetBuilder(chopStep, function_name=CutFoodStep.Functions.ChopFood.value))
42+
43+
chopStep.on_event(CutFoodStep.OutputEvents.ChoppingReady.value).send_event_to(
44+
ProcessFunctionTargetBuilder(fryStep)
45+
)
46+
47+
fryStep.on_event(FryFoodStep.OutputEvents.FoodRuined.value).send_event_to(
48+
ProcessFunctionTargetBuilder(gatherIngredientsStep)
49+
)
50+
51+
return process_builder
52+
53+
@staticmethod
54+
def create_process_with_stateful_steps(process_name: str = "FriedFishWithStatefulStepsProcess") -> ProcessBuilder:
55+
process_builder = ProcessBuilder(process_name)
56+
57+
gather_ingredients_step = process_builder.add_step(GatherFriedFishIngredientsWithStockStep)
58+
chop_step = process_builder.add_step(CutFoodWithSharpeningStep, name="chopStep")
59+
fry_step = process_builder.add_step(FryFoodStep)
60+
61+
process_builder.on_input_event(FriedFishProcess.ProcessEvents.PrepareFriedFish.value).send_event_to(
62+
gather_ingredients_step
63+
)
64+
65+
gather_ingredients_step.on_event(
66+
GatherFriedFishIngredientsWithStockStep.OutputEvents.IngredientsGathered.value
67+
).send_event_to(
68+
ProcessFunctionTargetBuilder(chop_step, function_name=CutFoodWithSharpeningStep.Functions.ChopFood.value)
69+
)
70+
71+
gather_ingredients_step.on_event(
72+
GatherFriedFishIngredientsWithStockStep.OutputEvents.IngredientsOutOfStock.value
73+
).stop_process()
74+
75+
chop_step.on_event(CutFoodWithSharpeningStep.OutputEvents.ChoppingReady.value).send_event_to(
76+
ProcessFunctionTargetBuilder(fry_step)
77+
)
78+
79+
chop_step.on_event(CutFoodWithSharpeningStep.OutputEvents.KnifeNeedsSharpening.value).send_event_to(
80+
ProcessFunctionTargetBuilder(
81+
chop_step, function_name=CutFoodWithSharpeningStep.Functions.SharpenKnife.value
82+
)
83+
)
84+
85+
chop_step.on_event(CutFoodWithSharpeningStep.OutputEvents.KnifeSharpened.value).send_event_to(
86+
ProcessFunctionTargetBuilder(chop_step, function_name=CutFoodWithSharpeningStep.Functions.ChopFood.value)
87+
)
88+
89+
fry_step.on_event(FryFoodStep.OutputEvents.FoodRuined.value).send_event_to(
90+
ProcessFunctionTargetBuilder(gather_ingredients_step)
91+
)
92+
93+
return process_builder
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
from enum import Enum
4+
5+
from samples.getting_started_with_processes.step03.models.food_ingredients import FoodIngredients
6+
from samples.getting_started_with_processes.step03.steps.cut_food_step import CutFoodStep
7+
from samples.getting_started_with_processes.step03.steps.cut_food_with_sharpening_step import CutFoodWithSharpeningStep
8+
from samples.getting_started_with_processes.step03.steps.fry_food_step import FryFoodStep
9+
from samples.getting_started_with_processes.step03.steps.gather_ingredients_step import (
10+
GatherIngredientsStep,
11+
GatherIngredientsWithStockStep,
12+
)
13+
from semantic_kernel.processes.process_builder import ProcessBuilder
14+
from semantic_kernel.processes.process_function_target_builder import ProcessFunctionTargetBuilder
15+
16+
17+
class GatherPotatoFriesIngredientsStep(GatherIngredientsStep):
18+
def __init__(self):
19+
super().__init__(FoodIngredients.POTATOES)
20+
21+
22+
class GatherPotatoFriesIngredientsWithStockStep(GatherIngredientsWithStockStep):
23+
def __init__(self):
24+
super().__init__(FoodIngredients.POTATOES)
25+
26+
27+
class PotatoFriesProcess:
28+
class ProcessEvents(Enum):
29+
PreparePotatoFries = "PreparePotatoFries"
30+
PotatoFriesReady = FryFoodStep.OutputEvents.FriedFoodReady.value
31+
32+
@staticmethod
33+
def create_process(process_name: str = "PotatoFriesProcess"):
34+
process_builder = ProcessBuilder(process_name)
35+
gather_ingredients_step = process_builder.add_step(GatherPotatoFriesIngredientsStep)
36+
slice_step = process_builder.add_step(CutFoodStep, name="sliceStep")
37+
fry_step = process_builder.add_step(FryFoodStep)
38+
39+
process_builder.on_input_event(PotatoFriesProcess.ProcessEvents.PreparePotatoFries.value).send_event_to(
40+
gather_ingredients_step
41+
)
42+
43+
gather_ingredients_step.on_event(
44+
GatherPotatoFriesIngredientsStep.OutputEvents.IngredientsGathered.value
45+
).send_event_to(ProcessFunctionTargetBuilder(slice_step, function_name=CutFoodStep.Functions.SliceFood.value))
46+
47+
slice_step.on_event(CutFoodStep.OutputEvents.SlicingReady.value).send_event_to(
48+
ProcessFunctionTargetBuilder(fry_step)
49+
)
50+
51+
fry_step.on_event(FryFoodStep.OutputEvents.FoodRuined.value).send_event_to(
52+
ProcessFunctionTargetBuilder(gather_ingredients_step)
53+
)
54+
55+
return process_builder
56+
57+
@staticmethod
58+
def create_process_with_stateful_steps(process_name: str = "PotatoFriesWithStatefulStepsProcess"):
59+
process_builder = ProcessBuilder(process_name)
60+
gather_ingredients_step = process_builder.add_step(GatherPotatoFriesIngredientsWithStockStep)
61+
slice_step = process_builder.add_step(CutFoodWithSharpeningStep, name="sliceStep")
62+
fry_step = process_builder.add_step(FryFoodStep)
63+
64+
process_builder.on_input_event(PotatoFriesProcess.ProcessEvents.PreparePotatoFries.value).send_event_to(
65+
gather_ingredients_step
66+
)
67+
68+
gather_ingredients_step.on_event(
69+
GatherPotatoFriesIngredientsWithStockStep.OutputEvents.IngredientsGathered.value
70+
).send_event_to(
71+
ProcessFunctionTargetBuilder(slice_step, function_name=CutFoodWithSharpeningStep.Functions.SliceFood.value)
72+
)
73+
74+
gather_ingredients_step.on_event(
75+
GatherPotatoFriesIngredientsWithStockStep.OutputEvents.IngredientsOutOfStock.value
76+
).stop_process()
77+
78+
slice_step.on_event(CutFoodWithSharpeningStep.OutputEvents.SlicingReady.value).send_event_to(
79+
ProcessFunctionTargetBuilder(fry_step)
80+
)
81+
82+
slice_step.on_event(CutFoodWithSharpeningStep.OutputEvents.KnifeNeedsSharpening.value).send_event_to(
83+
ProcessFunctionTargetBuilder(
84+
slice_step, function_name=CutFoodWithSharpeningStep.Functions.SharpenKnife.value
85+
)
86+
)
87+
88+
slice_step.on_event(CutFoodWithSharpeningStep.OutputEvents.KnifeSharpened.value).send_event_to(
89+
ProcessFunctionTargetBuilder(slice_step, function_name=CutFoodWithSharpeningStep.Functions.SliceFood.value)
90+
)
91+
92+
fry_step.on_event(FryFoodStep.OutputEvents.FoodRuined.value).send_event_to(
93+
ProcessFunctionTargetBuilder(gather_ingredients_step)
94+
)
95+
96+
return process_builder
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
from enum import Enum
4+
5+
from samples.getting_started_with_processes.step03.models.food_order_item import FoodItem
6+
from samples.getting_started_with_processes.step03.processes.fish_and_chips_process import FishAndChipsProcess
7+
from samples.getting_started_with_processes.step03.processes.fish_sandwich_process import FishSandwichProcess
8+
from samples.getting_started_with_processes.step03.processes.fried_fish_process import FriedFishProcess
9+
from samples.getting_started_with_processes.step03.processes.potato_fries_process import PotatoFriesProcess
10+
from samples.getting_started_with_processes.step03.steps.external_step import ExternalStep
11+
from semantic_kernel.functions.kernel_function_decorator import kernel_function
12+
from semantic_kernel.processes.kernel_process.kernel_process_step import KernelProcessStep
13+
from semantic_kernel.processes.kernel_process.kernel_process_step_context import KernelProcessStepContext
14+
from semantic_kernel.processes.process_builder import ProcessBuilder
15+
from semantic_kernel.processes.process_function_target_builder import ProcessFunctionTargetBuilder
16+
17+
18+
class PackOrderStep(KernelProcessStep):
19+
class Functions(Enum):
20+
PackFood = "PackFood"
21+
22+
class OutputEvents(Enum):
23+
FoodPacked = "FoodPacked"
24+
25+
@kernel_function(name=Functions.PackFood.value)
26+
async def pack_food(self, context: KernelProcessStepContext, food_actions: list[str]):
27+
print(f"PACKING_FOOD: Food {food_actions[0]} Packed! - {food_actions}")
28+
await context.emit_event(process_event=PackOrderStep.OutputEvents.FoodPacked.value)
29+
30+
31+
class ExternalSingleOrderStep(ExternalStep):
32+
def __init__(self):
33+
super().__init__(SingleFoodItemProcess.ProcessEvents.SingleOrderReady.value)
34+
35+
36+
class DispatchSingleOrderStep(KernelProcessStep):
37+
class Functions(Enum):
38+
PrepareSingleOrder = "PrepareSingleOrder"
39+
40+
class OutputEvents(Enum):
41+
PrepareFries = "PrepareFries"
42+
PrepareFriedFish = "PrepareFriedFish"
43+
PrepareFishSandwich = "PrepareFishSandwich"
44+
PrepareFishAndChips = "PrepareFishAndChips"
45+
46+
@kernel_function(name=Functions.PrepareSingleOrder.value)
47+
async def dispatch_single_order(self, context: KernelProcessStepContext, food_item: FoodItem):
48+
food_name = food_item.to_friendly_string()
49+
print(f"DISPATCH_SINGLE_ORDER: Dispatching '{food_name}'!")
50+
food_actions = []
51+
52+
if food_item == FoodItem.POTATO_FRIES:
53+
await context.emit_event(
54+
process_event=DispatchSingleOrderStep.OutputEvents.PrepareFries.value, data=food_actions
55+
)
56+
57+
elif food_item == FoodItem.FRIED_FISH:
58+
await context.emit_event(
59+
process_event=DispatchSingleOrderStep.OutputEvents.PrepareFriedFish.value, data=food_actions
60+
)
61+
62+
elif food_item == FoodItem.FISH_SANDWICH:
63+
await context.emit_event(
64+
process_event=DispatchSingleOrderStep.OutputEvents.PrepareFishSandwich.value, data=food_actions
65+
)
66+
67+
elif food_item == FoodItem.FISH_AND_CHIPS:
68+
await context.emit_event(
69+
process_event=DispatchSingleOrderStep.OutputEvents.PrepareFishAndChips.value, data=food_actions
70+
)
71+
72+
73+
class SingleFoodItemProcess:
74+
class ProcessEvents(Enum):
75+
SingleOrderReceived = "SingleOrderReceived"
76+
SingleOrderReady = "SingleOrderReady"
77+
78+
@staticmethod
79+
def create_process(process_name: str = "SingleFoodItemProcess"):
80+
process_builder = ProcessBuilder(process_name)
81+
82+
dispatch_order_step = process_builder.add_step(DispatchSingleOrderStep)
83+
make_fried_fish_step = process_builder.add_step_from_process(FriedFishProcess.create_process())
84+
make_potato_fries_step = process_builder.add_step_from_process(PotatoFriesProcess.create_process())
85+
make_fish_sandwich_step = process_builder.add_step_from_process(FishSandwichProcess.create_process())
86+
make_fish_and_chips_step = process_builder.add_step_from_process(FishAndChipsProcess.create_process())
87+
pack_order_step = process_builder.add_step(PackOrderStep)
88+
external_step = process_builder.add_step(ExternalSingleOrderStep)
89+
90+
process_builder.on_input_event(SingleFoodItemProcess.ProcessEvents.SingleOrderReceived).send_event_to(
91+
ProcessFunctionTargetBuilder(dispatch_order_step)
92+
)
93+
94+
dispatch_order_step.on_event(DispatchSingleOrderStep.OutputEvents.PrepareFriedFish.value).send_event_to(
95+
make_fried_fish_step.where_input_event_is(FriedFishProcess.ProcessEvents.PrepareFriedFish.value)
96+
)
97+
98+
dispatch_order_step.on_event(DispatchSingleOrderStep.OutputEvents.PrepareFries.value).send_event_to(
99+
make_potato_fries_step.where_input_event_is(PotatoFriesProcess.ProcessEvents.PreparePotatoFries.value)
100+
)
101+
102+
dispatch_order_step.on_event(DispatchSingleOrderStep.OutputEvents.PrepareFishSandwich.value).send_event_to(
103+
make_fish_sandwich_step.where_input_event_is(FishSandwichProcess.ProcessEvents.PrepareFishSandwich.value)
104+
)
105+
106+
dispatch_order_step.on_event(DispatchSingleOrderStep.OutputEvents.PrepareFishAndChips.value).send_event_to(
107+
make_fish_and_chips_step.where_input_event_is(FishAndChipsProcess.ProcessEvents.PrepareFishAndChips.value)
108+
)
109+
110+
make_fried_fish_step.on_event(FriedFishProcess.ProcessEvents.FriedFishReady.value).send_event_to(
111+
ProcessFunctionTargetBuilder(pack_order_step)
112+
)
113+
114+
make_potato_fries_step.on_event(PotatoFriesProcess.ProcessEvents.PotatoFriesReady.value).send_event_to(
115+
ProcessFunctionTargetBuilder(pack_order_step)
116+
)
117+
118+
make_fish_sandwich_step.on_event(FishSandwichProcess.ProcessEvents.FishSandwichReady.value).send_event_to(
119+
ProcessFunctionTargetBuilder(pack_order_step)
120+
)
121+
122+
make_fish_and_chips_step.on_event(FishAndChipsProcess.ProcessEvents.FishAndChipsReady.value).send_event_to(
123+
ProcessFunctionTargetBuilder(pack_order_step)
124+
)
125+
126+
pack_order_step.on_event(PackOrderStep.OutputEvents.FoodPacked.value).send_event_to(
127+
ProcessFunctionTargetBuilder(external_step)
128+
)
129+
130+
return process_builder
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
import asyncio
4+
5+
from samples.getting_started_with_processes.step03.processes.fish_and_chips_process import FishAndChipsProcess
6+
from samples.getting_started_with_processes.step03.processes.fish_sandwich_process import FishSandwichProcess
7+
from samples.getting_started_with_processes.step03.processes.fried_fish_process import FriedFishProcess
8+
from samples.getting_started_with_processes.step03.processes.potato_fries_process import PotatoFriesProcess
9+
from semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion import OpenAIChatCompletion
10+
from semantic_kernel.kernel import Kernel
11+
from semantic_kernel.processes.kernel_process.kernel_process import KernelProcess
12+
from semantic_kernel.processes.kernel_process.kernel_process_event import KernelProcessEvent
13+
from semantic_kernel.processes.local_runtime.local_kernel_process import start
14+
from semantic_kernel.processes.process_builder import ProcessBuilder
15+
16+
################################################################################################
17+
# Demonstrate the creation of KernelProcess and eliciting different food related events. #
18+
# For visual reference of the processes used here check the diagram in: #
19+
# https://github.com/microsoft/semantic-kernel/tree/main/python/samples/ #
20+
# getting_started_with_processes#step03a_food_preparation #
21+
################################################################################################
22+
23+
# region Helper Methods
24+
25+
26+
def _create_kernel_with_chat_completion(service_id: str) -> Kernel:
27+
kernel = Kernel()
28+
kernel.add_service(OpenAIChatCompletion(service_id=service_id), overwrite=True)
29+
return kernel
30+
31+
32+
async def use_prepare_specific_product(process: ProcessBuilder, external_trigger_event):
33+
kernel = _create_kernel_with_chat_completion("sample")
34+
kernel_process = process.build()
35+
print(f"=== Start SK Process '{process.name}' ===")
36+
_ = await start(
37+
process=kernel_process, kernel=kernel, initial_event=KernelProcessEvent(id=external_trigger_event, data=[])
38+
)
39+
print(f"=== End SK Process '{process.name}' ===")
40+
41+
42+
async def execute_process_with_state(process, kernel, external_trigger_event, order_label) -> KernelProcess:
43+
print(f"=== {order_label} ===")
44+
async with await start(process, kernel, KernelProcessEvent(id=external_trigger_event, data=[])) as running_process:
45+
return await running_process.get_state()
46+
47+
48+
# endregion
49+
50+
# region Stateless Processes
51+
52+
53+
async def use_prepare_fried_fish_process():
54+
process = FriedFishProcess.create_process()
55+
await use_prepare_specific_product(process, FriedFishProcess.ProcessEvents.PrepareFriedFish)
56+
57+
58+
async def use_prepare_potato_fries_process():
59+
process = PotatoFriesProcess.create_process()
60+
await use_prepare_specific_product(process, PotatoFriesProcess.ProcessEvents.PreparePotatoFries)
61+
62+
63+
async def use_prepare_fish_sandwich_process():
64+
process = FishSandwichProcess.create_process()
65+
await use_prepare_specific_product(process, FishSandwichProcess.ProcessEvents.PrepareFishSandwich)
66+
67+
68+
async def use_prepare_fish_and_chips_process():
69+
process = FishAndChipsProcess.create_process()
70+
await use_prepare_specific_product(process, FishAndChipsProcess.ProcessEvents.PrepareFishAndChips)
71+
72+
73+
# endregion
74+
75+
# region Stateful Processes
76+
77+
78+
async def use_prepare_stateful_fried_fish_process_no_shared_state():
79+
process_builder = FriedFishProcess.create_process_with_stateful_steps()
80+
external_trigger_event = FriedFishProcess.ProcessEvents.PrepareFriedFish
81+
82+
kernel = _create_kernel_with_chat_completion("sample")
83+
84+
print(f"=== Start SK Process '{process_builder.name}' ===")
85+
await execute_process_with_state(process_builder.build(), kernel, external_trigger_event, "Order 1")
86+
await execute_process_with_state(process_builder.build(), kernel, external_trigger_event, "Order 2")
87+
print(f"=== End SK Process '{process_builder.name}' ===")
88+
89+
90+
async def use_prepare_stateful_fried_fish_process_shared_state():
91+
process_builder = FriedFishProcess.create_process_with_stateful_steps()
92+
external_trigger_event = FriedFishProcess.ProcessEvents.PrepareFriedFish
93+
94+
kernel = _create_kernel_with_chat_completion("sample")
95+
96+
print(f"=== Start SK Process '{process_builder.name}' ===")
97+
await execute_process_with_state(process_builder.build(), kernel, external_trigger_event, "Order 1")
98+
await execute_process_with_state(process_builder.build(), kernel, external_trigger_event, "Order 2")
99+
await execute_process_with_state(process_builder.build(), kernel, external_trigger_event, "Order 3")
100+
print(f"=== End SK Process '{process_builder.name}' ===")
101+
102+
103+
async def use_prepare_stateful_potato_fries_process_shared_state():
104+
process_builder = PotatoFriesProcess.create_process_with_stateful_steps()
105+
external_trigger_event = PotatoFriesProcess.ProcessEvents.PreparePotatoFries
106+
107+
kernel = _create_kernel_with_chat_completion("sample")
108+
109+
print(f"=== Start SK Process '{process_builder.name}' ===")
110+
await execute_process_with_state(process_builder.build(), kernel, external_trigger_event, "Order 1")
111+
await execute_process_with_state(process_builder.build(), kernel, external_trigger_event, "Order 2")
112+
await execute_process_with_state(process_builder.build(), kernel, external_trigger_event, "Order 3")
113+
print(f"=== End SK Process '{process_builder.name}' ===")
114+
115+
116+
# endregion
117+
118+
119+
if __name__ == "__main__":
120+
asyncio.run(use_prepare_fried_fish_process())
121+
asyncio.run(use_prepare_potato_fries_process())
122+
asyncio.run(use_prepare_fish_sandwich_process())
123+
asyncio.run(use_prepare_fish_and_chips_process())
124+
asyncio.run(use_prepare_stateful_fried_fish_process_no_shared_state())
125+
asyncio.run(use_prepare_stateful_fried_fish_process_shared_state())
126+
asyncio.run(use_prepare_stateful_potato_fries_process_shared_state())
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
4+
import asyncio
5+
6+
from samples.getting_started_with_processes.step03.models.food_order_item import FoodItem
7+
from samples.getting_started_with_processes.step03.processes.single_food_item_process import SingleFoodItemProcess
8+
from semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion import OpenAIChatCompletion
9+
from semantic_kernel.kernel import Kernel
10+
from semantic_kernel.processes.kernel_process.kernel_process_event import KernelProcessEvent
11+
from semantic_kernel.processes.local_runtime.local_kernel_process import start
12+
13+
################################################################################################
14+
# Demonstrate the creation of KernelProcess and eliciting different food related events. #
15+
# For visual reference of the processes used here check the diagram in: #
16+
# https://github.com/microsoft/semantic-kernel/tree/main/python/samples/ #
17+
# getting_started_with_processes#step03b_food_ordering #
18+
################################################################################################
19+
20+
21+
def _create_kernel_with_chat_completion(service_id: str) -> Kernel:
22+
kernel = Kernel()
23+
kernel.add_service(OpenAIChatCompletion(service_id=service_id), overwrite=True)
24+
return kernel
25+
26+
27+
async def use_prepare_food_order_process_single_item(food_item: FoodItem):
28+
kernel = _create_kernel_with_chat_completion("sample")
29+
kernel_process = SingleFoodItemProcess.create_process().build()
30+
async with await start(
31+
kernel_process,
32+
kernel,
33+
KernelProcessEvent(id=SingleFoodItemProcess.ProcessEvents.SingleOrderReceived, data=food_item),
34+
) as running_process:
35+
return running_process
36+
37+
38+
async def use_single_order_fried_fish():
39+
await use_prepare_food_order_process_single_item(FoodItem.FRIED_FISH)
40+
41+
42+
async def use_single_order_potato_fries():
43+
await use_prepare_food_order_process_single_item(FoodItem.POTATO_FRIES)
44+
45+
46+
async def use_single_order_fish_sandwich():
47+
await use_prepare_food_order_process_single_item(FoodItem.FISH_SANDWICH)
48+
49+
50+
async def use_single_order_fish_and_chips():
51+
await use_prepare_food_order_process_single_item(FoodItem.FISH_AND_CHIPS)
52+
53+
54+
async def main():
55+
order_methods = [
56+
(use_single_order_fried_fish, "use_single_order_fried_fish"),
57+
(use_single_order_potato_fries, "use_single_order_potato_fries"),
58+
(use_single_order_fish_sandwich, "use_single_order_fish_sandwich"),
59+
(use_single_order_fish_and_chips, "use_single_order_fish_and_chips"),
60+
]
61+
62+
for method, name in order_methods:
63+
print(f"=== Start '{name}' ===")
64+
await method()
65+
print(f"=== End '{name}' ===\n")
66+
67+
68+
if __name__ == "__main__":
69+
asyncio.run(main())
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
from samples.getting_started_with_processes.step03.steps.cut_food_step import CutFoodStep
4+
from samples.getting_started_with_processes.step03.steps.cut_food_with_sharpening_step import CutFoodWithSharpeningStep
5+
from samples.getting_started_with_processes.step03.steps.external_step import ExternalStep
6+
from samples.getting_started_with_processes.step03.steps.fry_food_step import FryFoodStep
7+
from samples.getting_started_with_processes.step03.steps.gather_ingredients_step import (
8+
GatherIngredientsStep,
9+
GatherIngredientsWithStockStep,
10+
)
11+
12+
__all__ = [
13+
"ExternalStep",
14+
"CutFoodStep",
15+
"GatherIngredientsStep",
16+
"GatherIngredientsWithStockStep",
17+
"CutFoodWithSharpeningStep",
18+
"FryFoodStep",
19+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
from enum import Enum
4+
5+
from semantic_kernel.functions.kernel_function_decorator import kernel_function
6+
from semantic_kernel.processes.kernel_process.kernel_process_step import KernelProcessStep
7+
from semantic_kernel.processes.kernel_process.kernel_process_step_context import KernelProcessStepContext
8+
9+
10+
class CutFoodStep(KernelProcessStep):
11+
class Functions(Enum):
12+
ChopFood = "ChopFood"
13+
SliceFood = "SliceFood"
14+
15+
class OutputEvents(Enum):
16+
ChoppingReady = "ChoppingReady"
17+
SlicingReady = "SlicingReady"
18+
19+
def get_action_string(self, food: str, action: str) -> str:
20+
return f"{food}_{action}"
21+
22+
@kernel_function(name=Functions.ChopFood.value)
23+
async def chop_food(self, context: KernelProcessStepContext, food_actions: list[str]):
24+
food_to_be_cut = food_actions[0]
25+
food_actions.append(self.get_action_string(food_to_be_cut, "chopped"))
26+
print(f"CUTTING_STEP: Ingredient {food_to_be_cut} has been chopped!")
27+
await context.emit_event(process_event=CutFoodStep.OutputEvents.ChoppingReady.value, data=food_actions)
28+
29+
@kernel_function(name=Functions.SliceFood.value)
30+
async def slice_food(self, context: KernelProcessStepContext, food_actions: list[str]):
31+
food_to_be_cut = food_actions[0]
32+
food_actions.append(self.get_action_string(food_to_be_cut, "sliced"))
33+
print(f"CUTTING_STEP: Ingredient {food_to_be_cut} has been sliced!")
34+
await context.emit_event(process_event=CutFoodStep.OutputEvents.SlicingReady.value, data=food_actions)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
from enum import Enum
4+
5+
from semantic_kernel.functions.kernel_function_decorator import kernel_function
6+
from semantic_kernel.kernel_pydantic import KernelBaseModel
7+
from semantic_kernel.processes.kernel_process.kernel_process_step import KernelProcessStep
8+
from semantic_kernel.processes.kernel_process.kernel_process_step_context import KernelProcessStepContext
9+
from semantic_kernel.processes.kernel_process.kernel_process_step_state import KernelProcessStepState
10+
11+
12+
class CutFoodWithSharpeningState(KernelBaseModel):
13+
knife_sharpness: int = 5
14+
needs_sharpening_limit: int = 3
15+
sharpening_boost: int = 5
16+
17+
18+
class CutFoodWithSharpeningStep(KernelProcessStep[CutFoodWithSharpeningState]):
19+
class Functions(Enum):
20+
ChopFood = "ChopFood"
21+
SliceFood = "SliceFood"
22+
SharpenKnife = "SharpenKnife"
23+
24+
class OutputEvents(Enum):
25+
ChoppingReady = "ChoppingReady"
26+
SlicingReady = "SlicingReady"
27+
KnifeNeedsSharpening = "KnifeNeedsSharpening"
28+
KnifeSharpened = "KnifeSharpened"
29+
30+
state: CutFoodWithSharpeningState | None = None
31+
32+
async def activate(self, state: KernelProcessStepState[CutFoodWithSharpeningState]) -> None:
33+
self.state = state.state
34+
35+
def knife_needs_sharpening(self) -> bool:
36+
return self.state.knife_sharpness == self.state.needs_sharpening_limit
37+
38+
def get_action_string(self, food: str, action: str) -> str:
39+
return f"{food}_{action}"
40+
41+
@kernel_function(name=Functions.ChopFood.value)
42+
async def chop_food(self, context: KernelProcessStepContext, food_actions: list[str]):
43+
food_to_be_cut = food_actions[0]
44+
if self.knife_needs_sharpening():
45+
print(f"CUTTING_STEP: Dull knife, cannot chop {food_to_be_cut} - needs sharpening.")
46+
await context.emit_event(
47+
process_event=CutFoodWithSharpeningStep.OutputEvents.KnifeNeedsSharpening.value, data=food_actions
48+
)
49+
50+
return
51+
# Update knife sharpness
52+
self.state.knife_sharpness -= 1
53+
54+
food_actions.append(self.get_action_string(food_to_be_cut, "chopped"))
55+
print(
56+
f"CUTTING_STEP: Ingredient {food_to_be_cut} has been chopped! - knife sharpness: {self.state.knife_sharpness}" # noqa: E501
57+
)
58+
await context.emit_event(
59+
process_event=CutFoodWithSharpeningStep.OutputEvents.ChoppingReady.value, data=food_actions
60+
)
61+
62+
@kernel_function(name=Functions.SliceFood.value)
63+
async def slice_food(self, context: KernelProcessStepContext, food_actions: list[str]):
64+
food_to_be_cut = food_actions[0]
65+
if self.knife_needs_sharpening():
66+
print(f"CUTTING_STEP: Dull knife, cannot slice {food_to_be_cut} - needs sharpening.")
67+
await context.emit_event(
68+
process_event=CutFoodWithSharpeningStep.OutputEvents.KnifeNeedsSharpening.value, data=food_actions
69+
)
70+
71+
return
72+
# Update knife sharpness
73+
self.state.knife_sharpness -= 1
74+
75+
food_actions.append(self.get_action_string(food_to_be_cut, "sliced"))
76+
print(
77+
f"CUTTING_STEP: Ingredient {food_to_be_cut} has been sliced! - knife sharpness: {self.state.knife_sharpness}" # noqa: E501
78+
)
79+
await context.emit_event(
80+
process_event=CutFoodWithSharpeningStep.OutputEvents.SlicingReady.value, data=food_actions
81+
)
82+
83+
@kernel_function(name=Functions.SharpenKnife.value)
84+
async def sharpen_knife(self, context: KernelProcessStepContext, food_actions: list[str]):
85+
self.state.knife_sharpness += self.state.sharpening_boost
86+
print(f"KNIFE SHARPENED: Knife sharpness is now {self.state.knife_sharpness}!")
87+
await context.emit_event(
88+
process_event=CutFoodWithSharpeningStep.OutputEvents.KnifeSharpened.value, data=food_actions
89+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
from typing import Any
4+
5+
from semantic_kernel.functions.kernel_function_decorator import kernel_function
6+
from semantic_kernel.processes.kernel_process.kernel_process_event import (
7+
KernelProcessEventVisibility,
8+
)
9+
from semantic_kernel.processes.kernel_process.kernel_process_step import KernelProcessStep
10+
from semantic_kernel.processes.kernel_process.kernel_process_step_context import KernelProcessStepContext
11+
12+
13+
class ExternalStep(KernelProcessStep):
14+
def __init__(self, external_event_name: str):
15+
super().__init__(external_event_name=external_event_name)
16+
17+
@kernel_function()
18+
async def emit_external_event(self, context: KernelProcessStepContext, data: Any):
19+
await context.emit_event(
20+
process_event=self.external_event_name, data=data, visibility=KernelProcessEventVisibility.Public
21+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
from enum import Enum
4+
from random import Random
5+
6+
from pydantic import Field
7+
8+
from semantic_kernel.functions.kernel_function_decorator import kernel_function
9+
from semantic_kernel.processes.kernel_process.kernel_process_event import (
10+
KernelProcessEventVisibility,
11+
)
12+
from semantic_kernel.processes.kernel_process.kernel_process_step import KernelProcessStep
13+
from semantic_kernel.processes.kernel_process.kernel_process_step_context import KernelProcessStepContext
14+
15+
16+
class FryFoodStep(KernelProcessStep):
17+
class Functions(Enum):
18+
FryFood = "FryFood"
19+
20+
class OutputEvents(Enum):
21+
FoodRuined = "FoodRuined"
22+
FriedFoodReady = "FriedFoodReady"
23+
24+
random_seed: int = Field(default_factory=Random)
25+
26+
@kernel_function(name=Functions.FryFood.value)
27+
async def fry_food(self, context: KernelProcessStepContext, food_actions: list[str]):
28+
food_to_fry = food_actions[0]
29+
fryer_malfunction = self.random_seed.randint(0, 10)
30+
31+
# Oh no! Food got burnt :(
32+
if fryer_malfunction < 5:
33+
food_actions.append(f"{food_to_fry}_frying_failed")
34+
print(f"FRYING_STEP: Ingredient {food_to_fry} got burnt while frying :(")
35+
await context.emit_event(process_event=FryFoodStep.OutputEvents.FoodRuined.value, data=food_actions)
36+
return
37+
38+
food_actions.append(f"{food_to_fry}_frying_succeeded")
39+
print(f"FRYING_STEP: Ingredient {food_to_fry} is ready!")
40+
await context.emit_event(
41+
process_event=FryFoodStep.OutputEvents.FriedFoodReady.value,
42+
data=food_actions,
43+
visibility=KernelProcessEventVisibility.Public,
44+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
from enum import Enum
4+
5+
from samples.getting_started_with_processes.step03.models.food_ingredients import FoodIngredients
6+
from semantic_kernel.functions.kernel_function_decorator import kernel_function
7+
from semantic_kernel.kernel_pydantic import KernelBaseModel
8+
from semantic_kernel.processes.kernel_process.kernel_process_step import KernelProcessStep
9+
from semantic_kernel.processes.kernel_process.kernel_process_step_context import KernelProcessStepContext
10+
from semantic_kernel.processes.kernel_process.kernel_process_step_state import KernelProcessStepState
11+
12+
13+
class GatherIngredientsStep(KernelProcessStep):
14+
class Functions(Enum):
15+
GatherIngredients = "GatherIngredients"
16+
17+
class OutputEvents(Enum):
18+
IngredientsGathered = "IngredientsGathered"
19+
IngredientsOutOfStock = "IngredientsOutOfStock"
20+
21+
ingredient: FoodIngredients
22+
23+
def __init__(self, ingredient: FoodIngredients):
24+
super().__init__(ingredient=ingredient)
25+
26+
@kernel_function(name=Functions.GatherIngredients.value)
27+
async def gather_ingredients(self, context: KernelProcessStepContext, food_actions: list[str]):
28+
ingredient = self.ingredient.to_friendly_string()
29+
updated_food_actions = []
30+
updated_food_actions.extend(food_actions)
31+
if len(updated_food_actions) == 0:
32+
updated_food_actions.append(ingredient)
33+
updated_food_actions.append(f"{ingredient}_gathered")
34+
35+
print(f"GATHER_INGREDIENT: Gathered ingredient {ingredient}")
36+
await context.emit_event(
37+
process_event=GatherIngredientsStep.OutputEvents.IngredientsGathered.value, data=updated_food_actions
38+
)
39+
40+
41+
class GatherIngredientsState(KernelBaseModel):
42+
ingredients_stock: int = 5
43+
44+
45+
class GatherIngredientsWithStockStep(KernelProcessStep[GatherIngredientsState]):
46+
class Functions(Enum):
47+
GatherIngredients = "GatherIngredients"
48+
49+
class OutputEvents(Enum):
50+
IngredientsGathered = "IngredientsGathered"
51+
IngredientsOutOfStock = "IngredientsOutOfStock"
52+
53+
ingredient: FoodIngredients
54+
state: GatherIngredientsState | None = None
55+
56+
def __init__(self, ingredient: FoodIngredients):
57+
super().__init__(ingredient=ingredient)
58+
59+
async def activate(self, state: KernelProcessStepState[GatherIngredientsState]) -> None:
60+
self.state = state.state
61+
62+
@kernel_function(name=Functions.GatherIngredients.value)
63+
async def gather_ingredients(self, context: KernelProcessStepContext, food_actions: list[str]):
64+
ingredient = self.ingredient.to_friendly_string()
65+
updated_food_actions = []
66+
updated_food_actions.extend(food_actions)
67+
68+
if self.state.ingredients_stock == 0:
69+
print(f"GATHER_INGREDIENT: Could not gather {ingredient} - OUT OF STOCK!")
70+
await context.emit_event(
71+
process_event=GatherIngredientsWithStockStep.OutputEvents.IngredientsOutOfStock.value,
72+
data=updated_food_actions,
73+
)
74+
return
75+
76+
if len(updated_food_actions) == 0:
77+
updated_food_actions.append(ingredient)
78+
updated_food_actions.append(f"{ingredient}_gathered")
79+
80+
self.state.ingredients_stock -= 1
81+
print(f"GATHER_INGREDIENT: Gathered ingredient {ingredient} - remaining: {self.state.ingredients_stock}")
82+
await context.emit_event(
83+
process_event=GatherIngredientsWithStockStep.OutputEvents.IngredientsGathered.value,
84+
data=updated_food_actions,
85+
)

‎python/semantic_kernel/processes/kernel_process/kernel_process_message_channel.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ class KernelProcessMessageChannel(ABC):
1111
"""Abstract base class for emitting events from a step."""
1212

1313
@abstractmethod
14-
def emit_event(self, process_event: "KernelProcessEvent") -> None:
14+
async def emit_event(self, process_event: "KernelProcessEvent") -> None:
1515
"""Emits the specified event from the step."""
1616
pass

‎python/semantic_kernel/processes/kernel_process/kernel_process_step_context.py

+17-3
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,22 @@ def __init__(self, channel: KernelProcessMessageChannel):
1818
"""Initialize the step context."""
1919
super().__init__(step_message_channel=channel)
2020

21-
def emit_event(self, process_event: "KernelProcessEvent") -> None:
22-
"""Emit an event from the current step."""
21+
async def emit_event(self, process_event: "KernelProcessEvent | str", **kwargs) -> None:
22+
"""Emit an event from the current step.
23+
24+
It is possible to either specify a `KernelProcessEvent` object or the ID of the event
25+
along with the `data` and optional `visibility` keyword arguments.
26+
27+
Args:
28+
process_event (KernelProcessEvent | str): The event to emit.
29+
**kwargs: Additional keyword arguments to pass to the event.
30+
"""
31+
from semantic_kernel.processes.kernel_process.kernel_process_event import KernelProcessEvent
32+
2333
if process_event is None:
2434
raise ProcessEventUndefinedException("Process event cannot be None")
25-
self.step_message_channel.emit_event(process_event)
35+
36+
if not isinstance(process_event, KernelProcessEvent):
37+
process_event = KernelProcessEvent(id=process_event, **kwargs)
38+
39+
await self.step_message_channel.emit_event(process_event)

‎python/semantic_kernel/processes/local_runtime/local_process.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -218,9 +218,9 @@ async def enqueue_step_messages(self, step: LocalStep, message_channel: Queue[Lo
218218
for step_event in all_step_events:
219219
if step_event.visibility == KernelProcessEventVisibility.Public:
220220
if isinstance(step_event, KernelProcessEvent):
221-
self.emit_event(step_event) # type: ignore
221+
await self.emit_event(step_event) # type: ignore
222222
elif isinstance(step_event, LocalEvent):
223-
self.emit_local_event(step_event) # type: ignore
223+
await self.emit_local_event(step_event) # type: ignore
224224

225225
for edge in step.get_edge_for_event(step_event.id):
226226
message = LocalMessageFactory.create_from_edge(edge, step_event.data)

‎python/semantic_kernel/processes/local_runtime/local_step.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ async def handle_message(self, message: LocalMessage):
163163
event_name = f"{target_function}.OnError"
164164
event_value = str(ex)
165165
finally:
166-
self.emit_event(KernelProcessEvent(id=event_name, data=event_value))
166+
await self.emit_event(KernelProcessEvent(id=event_name, data=event_value))
167167

168168
# Reset the inputs for the function that was just executed
169169
self.inputs[target_function] = self.initial_inputs.get(target_function, {}).copy()
@@ -172,11 +172,11 @@ async def invoke_function(self, function: "KernelFunction", kernel: "Kernel", ar
172172
"""Invokes the function."""
173173
return await kernel.invoke(function, **arguments)
174174

175-
def emit_event(self, process_event: KernelProcessEvent):
175+
async def emit_event(self, process_event: KernelProcessEvent):
176176
"""Emits an event from the step."""
177-
self.emit_local_event(LocalEvent.from_kernel_process_event(process_event, self.event_namespace))
177+
await self.emit_local_event(LocalEvent.from_kernel_process_event(process_event, self.event_namespace))
178178

179-
def emit_local_event(self, local_event: "LocalEvent"):
179+
async def emit_local_event(self, local_event: "LocalEvent"):
180180
"""Emits an event from the step."""
181181
scoped_event = self.scoped_event(local_event)
182182
self.outgoing_event_queue.put(scoped_event)

‎python/tests/unit/processes/kernel_process/test_kernel_process_step_context.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Copyright (c) Microsoft. All rights reserved.
22

33

4-
from unittest.mock import AsyncMock, MagicMock
4+
from unittest.mock import AsyncMock
55

66
import pytest
77

@@ -29,15 +29,15 @@ async def test_initialization():
2929

3030

3131
@pytest.mark.asyncio
32-
def test_emit_event():
32+
async def test_emit_event():
3333
# Arrange
3434
channel = MockKernelProcessMessageChannel()
35-
channel.emit_event = MagicMock()
35+
channel.emit_event = AsyncMock()
3636
context = KernelProcessStepContext(channel=channel)
3737
event = KernelProcessEvent(id="event_001", data={"key": "value"})
3838

3939
# Act
40-
context.emit_event(event)
40+
await context.emit_event(event)
4141

4242
# Assert
4343
channel.emit_event.assert_called_once_with(event)

0 commit comments

Comments
 (0)
Please sign in to comment.