Skip to content

Commit 983da69

Browse files
joslatcrickman
andauthored
.Net: process framework - Simplest step implementation and simplification of Function event resolution for steps with 1 function. (#9650)
… ### Motivation and Context **Required:** I would like to have a process framework with a simplified process and steps, showing how simple, easy and straightforward it can be. I want this example to take away the fear and reduce the cognitive load as a "first super-easy step". **Problem solved:** Excessive cognitive load on the first example. There Should be a super-easy step that is and feels easy. I believe this gets close to it :) **Scenario:** Learning, taking the fear out. Helping in making it easier. **Simplification added:** Also to simplify it even further i added the feature that the Function event name gets resolved when there is a single function When declaring the process, instead of: ``` startStep .OnEvent(StartStep.OutputEvents.Executed) .SendEventTo(new ProcessFunctionTargetBuilder(doSomeWorkStep)); doSomeWorkStep .OnEvent(DoSomeWorkStep.OutputEvents.Executed) .SendEventTo(new ProcessFunctionTargetBuilder(doMoreWorkStep)); doMoreWorkStep .OnEvent(DoMoreWorkStep.OutputEvents.Executed) .SendEventTo(new ProcessFunctionTargetBuilder(endStep)); endStep .OnEvent(EndStep.OutputEvents.Executed) .StopProcess(); ``` I would like to do the following: ``` startStep .OnFunctionResult() .SendEventTo(new ProcessFunctionTargetBuilder(doSomeWorkStep)); doSomeWorkStep .OnFunctionResult() .SendEventTo(new ProcessFunctionTargetBuilder(doMoreWorkStep)); doMoreWorkStep .OnFunctionResult() .SendEventTo(new ProcessFunctionTargetBuilder(endStep)); endStep .OnFunctionResult() .StopProcess(); ``` So that the Function is inferred by the framework, reducing verbosity and cognitive load. Fixes: #9647 Fixes: #9648 ### Description - Added example Step00 (process, steps ) - Added documentation - Added the small refactoring on the StepProcessBuilder to support Function resolution when "there is only one" ### 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 😄 --------- Co-authored-by: Chris <[email protected]>
1 parent 76052b6 commit 983da69

File tree

8 files changed

+172
-5
lines changed

8 files changed

+172
-5
lines changed

dotnet/samples/GettingStartedWithProcesses/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,22 @@ The getting started with agents examples include:
2121

2222
Example|Description
2323
---|---
24+
[Step00_Processes](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithProcesses/Step00/Step00_Processes.cs)|How to create the simplest process with minimal code and event wiring
2425
[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
2526
[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.
2627
[Step03a_FoodPreparation](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithProcesses/Step03/Step03a_FoodPreparation.cs)|Showcasing reuse of steps, creation of processes, spawning of multiple events, use of stateful steps with food preparation samples.
2728
[Step03b_FoodOrdering](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithProcesses/Step03/Step03b_FoodOrdering.cs)|Showcasing use of subprocesses as steps, spawning of multiple events conditionally reusing the food preparation samples.
2829
[Step04_AgentOrchestration](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithProcesses/Step04/Step04_AgentOrchestration.cs)|Showcasing use of process steps in conjunction with the _Agent Framework_.
2930

31+
### Step00_Processes
32+
33+
```mermaid
34+
flowchart LR
35+
Start(Start)--> DoSomeWork(DoSomeWork)
36+
DoSomeWork--> DoMoreWork(DoMoreWork)
37+
DoMoreWork--> End(End)
38+
```
39+
3040
### Step01_Processes
3141

3242
```mermaid
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using Microsoft.SemanticKernel;
4+
using Step00.Steps;
5+
6+
namespace Step00;
7+
8+
/// <summary>
9+
/// Demonstrate creation of the simplest <see cref="KernelProcess"/> and
10+
/// eliciting its response to three explicit user messages.
11+
/// </summary>
12+
public class Step00_Processes(ITestOutputHelper output) : BaseTest(output, redirectSystemConsoleOutput: true)
13+
{
14+
public static class ProcessEvents
15+
{
16+
public const string StartProcess = nameof(StartProcess);
17+
}
18+
19+
/// <summary>
20+
/// Demonstrates the creation of the simplest possible process with multiple steps
21+
/// </summary>
22+
/// <returns>A <see cref="Task"/></returns>
23+
[Fact]
24+
public async Task UseSimplestProcessAsync()
25+
{
26+
// Create a simple kernel
27+
Kernel kernel = Kernel.CreateBuilder()
28+
.Build();
29+
30+
ProcessBuilder processBuilder = new(nameof(Step00_Processes));
31+
32+
// Create a process that will interact with the chat completion service
33+
ProcessBuilder process = new("ChatBot");
34+
var startStep = processBuilder.AddStepFromType<StartStep>();
35+
var doSomeWorkStep = processBuilder.AddStepFromType<DoSomeWorkStep>();
36+
var doMoreWorkStep = processBuilder.AddStepFromType<DoMoreWorkStep>();
37+
var lastStep = processBuilder.AddStepFromType<LastStep>();
38+
39+
// Define the process flow
40+
processBuilder
41+
.OnInputEvent(ProcessEvents.StartProcess)
42+
.SendEventTo(new ProcessFunctionTargetBuilder(startStep));
43+
44+
startStep
45+
.OnFunctionResult()
46+
.SendEventTo(new ProcessFunctionTargetBuilder(doSomeWorkStep));
47+
48+
doSomeWorkStep
49+
.OnFunctionResult()
50+
.SendEventTo(new ProcessFunctionTargetBuilder(doMoreWorkStep));
51+
52+
doMoreWorkStep
53+
.OnFunctionResult()
54+
.SendEventTo(new ProcessFunctionTargetBuilder(lastStep));
55+
56+
lastStep
57+
.OnFunctionResult()
58+
.StopProcess();
59+
60+
// Build the process to get a handle that can be started
61+
KernelProcess kernelProcess = process.Build();
62+
63+
// Start the process with an initial external event
64+
using var runningProcess = await kernelProcess.StartAsync(
65+
kernel,
66+
new KernelProcessEvent()
67+
{
68+
Id = ProcessEvents.StartProcess,
69+
Data = null
70+
});
71+
}
72+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using Microsoft.SemanticKernel;
4+
5+
namespace Step00.Steps;
6+
7+
public sealed class DoMoreWorkStep : KernelProcessStep
8+
{
9+
[KernelFunction]
10+
public async ValueTask ExecuteAsync(KernelProcessStepContext context)
11+
{
12+
Console.WriteLine("Step 3 - Doing Yet More Work...\n");
13+
}
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using Microsoft.SemanticKernel;
4+
5+
namespace Step00.Steps;
6+
7+
public sealed class DoSomeWorkStep : KernelProcessStep
8+
{
9+
[KernelFunction]
10+
public async ValueTask ExecuteAsync(KernelProcessStepContext context)
11+
{
12+
Console.WriteLine("Step 2 - Doing Some Work...\n");
13+
}
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using Microsoft.SemanticKernel;
4+
5+
namespace Step00.Steps;
6+
7+
public sealed class LastStep : KernelProcessStep
8+
{
9+
[KernelFunction]
10+
public async ValueTask ExecuteAsync(KernelProcessStepContext context)
11+
{
12+
Console.WriteLine("Step 4 - This is the Final Step...\n");
13+
}
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using Microsoft.SemanticKernel;
4+
5+
namespace Step00.Steps;
6+
7+
public sealed class StartStep : KernelProcessStep
8+
{
9+
[KernelFunction]
10+
public async ValueTask ExecuteAsync(KernelProcessStepContext context)
11+
{
12+
Console.WriteLine("Step 1 - Start\n");
13+
}
14+
}

dotnet/samples/GettingStartedWithProcesses/Step01/Step01_Processes.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public async Task UseSimpleProcessAsync()
4343

4444
// When the intro is complete, notify the userInput step
4545
introStep
46-
.OnFunctionResult(nameof(IntroStep.PrintIntroMessage))
46+
.OnFunctionResult()
4747
.SendEventTo(new ProcessFunctionTargetBuilder(userInputStep));
4848

4949
// When the userInput step emits an exit event, send it to the end step

dotnet/src/Experimental/Process.Core/ProcessStepBuilder.cs

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,20 +47,30 @@ public ProcessStepEdgeBuilder OnEvent(string eventId)
4747
/// <summary>
4848
/// Define the behavior of the step when the specified function has been successfully invoked.
4949
/// </summary>
50-
/// <param name="functionName">The name of the function of interest.</param>
50+
/// <param name="functionName">Optional: The name of the function of interest.</param>
51+
/// If the function name is not provided, it will be inferred if there's exactly one function in the step.
5152
/// <returns>An instance of <see cref="ProcessStepEdgeBuilder"/>.</returns>
52-
public ProcessStepEdgeBuilder OnFunctionResult(string functionName)
53+
public ProcessStepEdgeBuilder OnFunctionResult(string? functionName = null)
5354
{
55+
if (string.IsNullOrWhiteSpace(functionName))
56+
{
57+
functionName = this.ResolveFunctionName();
58+
}
5459
return this.OnEvent($"{functionName}.OnResult");
5560
}
5661

5762
/// <summary>
5863
/// Define the behavior of the step when the specified function has thrown an exception.
64+
/// If the function name is not provided, it will be inferred if there's exactly one function in the step.
5965
/// </summary>
60-
/// <param name="functionName">The name of the function of interest.</param>
66+
/// <param name="functionName">Optional: The name of the function of interest.</param>
6167
/// <returns>An instance of <see cref="ProcessStepEdgeBuilder"/>.</returns>
62-
public ProcessStepEdgeBuilder OnFunctionError(string functionName)
68+
public ProcessStepEdgeBuilder OnFunctionError(string? functionName = null)
6369
{
70+
if (string.IsNullOrWhiteSpace(functionName))
71+
{
72+
functionName = this.ResolveFunctionName();
73+
}
6474
return this.OnEvent($"{functionName}.OnError");
6575
}
6676

@@ -85,6 +95,25 @@ public ProcessStepEdgeBuilder OnFunctionError(string functionName)
8595
/// <returns>an instance of <see cref="KernelProcessStepInfo"/>.</returns>
8696
internal abstract KernelProcessStepInfo BuildStep(KernelProcessStepStateMetadata? stateMetadata = null);
8797

98+
/// <summary>
99+
/// Resolves the function name for the step.
100+
/// </summary>
101+
/// <returns></returns>
102+
/// <exception cref="KernelException"></exception>
103+
private string ResolveFunctionName()
104+
{
105+
if (this.FunctionsDict.Count == 0)
106+
{
107+
throw new KernelException($"The step {this.Name} has no functions.");
108+
}
109+
else if (this.FunctionsDict.Count > 1)
110+
{
111+
throw new KernelException($"The step {this.Name} has more than one function, so a function name must be provided.");
112+
}
113+
114+
return this.FunctionsDict.Keys.First();
115+
}
116+
88117
/// <summary>
89118
/// Links the output of the current step to the an input of another step via the specified event type.
90119
/// </summary>

0 commit comments

Comments
 (0)