Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

.Net: process framework - Simplest step implementation and simplification of Function event resolution for steps with 1 function. #9650

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
10 changes: 10 additions & 0 deletions dotnet/samples/GettingStartedWithProcesses/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,22 @@ The getting started with agents examples include:

Example|Description
---|---
[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
[Step01_Processes](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithProcesses/Step01/Step01_Processes.cs)|How to create a simple process with a loop and a conditional exit
[Step02_AccountOpening](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithProcesses/Step02/Step02_AccountOpening.cs)|Showcasing processes cycles, fan in, fan out for opening an account.
[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.
[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.
[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_.

### Step00_Processes

```mermaid
flowchart LR
Start(Start)--> DoSomeWork(DoSomeWork)
DoSomeWork--> DoMoreWork(DoMoreWork)
DoMoreWork--> End(End)
```

### Step01_Processes

```mermaid
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright (c) Microsoft. All rights reserved.

using Microsoft.SemanticKernel;
using Step00.Steps;

namespace Step00;
joslat marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Demonstrate creation of the simplest <see cref="KernelProcess"/> and
/// eliciting its response to three explicit user messages.
/// </summary>
public class Step00_Processes(ITestOutputHelper output) : BaseTest(output, redirectSystemConsoleOutput: true)
{
public static class ProcessEvents
{
public const string StartProcess = nameof(StartProcess);
}

/// <summary>
/// Demonstrates the creation of the simplest possible process with multiple steps
/// </summary>
/// <returns>A <see cref="Task"/></returns>
[Fact]
public async Task UseSimplestProcessAsync()
{
// Create a simple kernel
Kernel kernel = Kernel.CreateBuilder()
.Build();

ProcessBuilder processBuilder = new(nameof(Step00_Processes));

// Create a process that will interact with the chat completion service
ProcessBuilder process = new("ChatBot");
var startStep = processBuilder.AddStepFromType<StartStep>();
var doSomeWorkStep = processBuilder.AddStepFromType<DoSomeWorkStep>();
var doMoreWorkStep = processBuilder.AddStepFromType<DoMoreWorkStep>();
var lastStep = processBuilder.AddStepFromType<LastStep>();

// Define the process flow
processBuilder
.OnInputEvent(ProcessEvents.StartProcess)
.SendEventTo(new ProcessFunctionTargetBuilder(startStep));

startStep
.OnFunctionResult()
joslat marked this conversation as resolved.
Show resolved Hide resolved
.SendEventTo(new ProcessFunctionTargetBuilder(doSomeWorkStep));

doSomeWorkStep
.OnFunctionResult()
.SendEventTo(new ProcessFunctionTargetBuilder(doMoreWorkStep));

doMoreWorkStep
.OnFunctionResult()
.SendEventTo(new ProcessFunctionTargetBuilder(lastStep));

lastStep
.OnFunctionResult()
.StopProcess();

// Build the process to get a handle that can be started
KernelProcess kernelProcess = process.Build();

// Start the process with an initial external event
using var runningProcess = await kernelProcess.StartAsync(
kernel,
new KernelProcessEvent()
{
Id = ProcessEvents.StartProcess,
Data = null
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) Microsoft. All rights reserved.

using Microsoft.SemanticKernel;

namespace Step00.Steps;

public sealed class DoMoreWorkStep : KernelProcessStep
{
[KernelFunction]
public async ValueTask ExecuteAsync(KernelProcessStepContext context)
{
Console.WriteLine("Step 3 - Doing Yet More Work...\n");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) Microsoft. All rights reserved.

using Microsoft.SemanticKernel;

namespace Step00.Steps;

public sealed class DoSomeWorkStep : KernelProcessStep
{
[KernelFunction]
public async ValueTask ExecuteAsync(KernelProcessStepContext context)
{
Console.WriteLine("Step 2 - Doing Some Work...\n");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) Microsoft. All rights reserved.

using Microsoft.SemanticKernel;

namespace Step00.Steps;

public sealed class LastStep : KernelProcessStep
{
[KernelFunction]
public async ValueTask ExecuteAsync(KernelProcessStepContext context)
{
Console.WriteLine("Step 4 - This is the Final Step...\n");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) Microsoft. All rights reserved.

using Microsoft.SemanticKernel;

namespace Step00.Steps;

public sealed class StartStep : KernelProcessStep
{
[KernelFunction]
public async ValueTask ExecuteAsync(KernelProcessStepContext context)
{
Console.WriteLine("Step 1 - Start\n");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public async Task UseSimpleProcessAsync()

// When the intro is complete, notify the userInput step
introStep
.OnFunctionResult(nameof(IntroStep.PrintIntroMessage))
.OnFunctionResult()
.SendEventTo(new ProcessFunctionTargetBuilder(userInputStep));

// When the userInput step emits an exit event, send it to the end step
Expand Down
37 changes: 33 additions & 4 deletions dotnet/src/Experimental/Process.Core/ProcessStepBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,20 +47,30 @@ public ProcessStepEdgeBuilder OnEvent(string eventId)
/// <summary>
/// Define the behavior of the step when the specified function has been successfully invoked.
/// </summary>
/// <param name="functionName">The name of the function of interest.</param>
/// <param name="functionName">Optional: The name of the function of interest.</param>
/// If the function name is not provided, it will be inferred if there's exactly one function in the step.
/// <returns>An instance of <see cref="ProcessStepEdgeBuilder"/>.</returns>
public ProcessStepEdgeBuilder OnFunctionResult(string functionName)
public ProcessStepEdgeBuilder OnFunctionResult(string? functionName = null)
{
if (string.IsNullOrWhiteSpace(functionName))
{
functionName = this.ResolveFunctionName();
}
return this.OnEvent($"{functionName}.OnResult");
}

/// <summary>
/// Define the behavior of the step when the specified function has thrown an exception.
/// If the function name is not provided, it will be inferred if there's exactly one function in the step.
/// </summary>
/// <param name="functionName">The name of the function of interest.</param>
/// <param name="functionName">Optional: The name of the function of interest.</param>
/// <returns>An instance of <see cref="ProcessStepEdgeBuilder"/>.</returns>
public ProcessStepEdgeBuilder OnFunctionError(string functionName)
public ProcessStepEdgeBuilder OnFunctionError(string? functionName = null)
{
if (string.IsNullOrWhiteSpace(functionName))
{
functionName = this.ResolveFunctionName();
}
return this.OnEvent($"{functionName}.OnError");
}

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

/// <summary>
/// Resolves the function name for the step.
/// </summary>
/// <returns></returns>
/// <exception cref="KernelException"></exception>
private string ResolveFunctionName()
{
if (this.FunctionsDict.Count == 0)
{
throw new KernelException($"The step {this.Name} has no functions.");
}
else if (this.FunctionsDict.Count > 1)
{
throw new KernelException($"The step {this.Name} has more than one function, so a function name must be provided.");
}

return this.FunctionsDict.Keys.First();
}

/// <summary>
/// Links the output of the current step to the an input of another step via the specified event type.
/// </summary>
Expand Down
Loading