Skip to content

Commit

Permalink
.Net Processes - Refactoring/expanding Process Sample02 (#9811)
Browse files Browse the repository at this point in the history
### Description

- Updating Sample 02 to expand the usage and show case how to refactor
the same process by using subprocesses as steps.
- Improving README file with more details on this sample
- Add more unit tests for Process testing/improving comments

Fixes #9836 
Fixes #9837 

### 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: Estefania Tenorio <estenori@microsoft.com>
  • Loading branch information
esttenorio and estenori authored Dec 4, 2024
1 parent 30b67ec commit 3c13912
Show file tree
Hide file tree
Showing 23 changed files with 686 additions and 87 deletions.
99 changes: 98 additions & 1 deletion dotnet/samples/GettingStartedWithProcesses/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ 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.
[Step02a_AccountOpening](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithProcesses/Step02/Step02a_AccountOpening.cs)|Showcasing processes cycles, fan in, fan out for opening an account.
[Step02b_AccountOpening](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/GettingStartedWithProcesses/Step02/Step02b_AccountOpening.cs)|How to refactor processes and make use of smaller processes as steps in larger processes.
[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_.
Expand All @@ -49,6 +50,27 @@ flowchart LR

### Step02_AccountOpening

The account opening sample has 2 different implementations covering the same scenario, it just uses different SK components to achieve the same goal.

In addition, the sample introduces the concept of using smaller process as steps to maintain the main process readable and manageble for future improvements and unit testing.
Also introduces the use of SK Event Subscribers.

A process for opening an account for this sample has the following steps:
- Fill New User Account Application Form
- Verify Applicant Credit Score
- Apply Fraud Detection Analysis to the Application Form
- Create New Entry in Core System Records
- Add new account to Marketing Records
- CRM Record Creation
- Mail user a user a notification about:
- Failure to open a new account due to Credit Score Check
- Failure to open a new account due to Fraud Detection Alert
- Welcome package including new account details

A SK process that only connects the steps listed above as is (no use of subprocesses as steps) for opening an account look like this:

#### Step02a_AccountOpening

```mermaid
flowchart LR
User(User) -->|Provides user details| FillForm(Fill New <br/> Customer <br/> Form)
Expand Down Expand Up @@ -79,6 +101,81 @@ flowchart LR
Mailer -->|End of Interaction| User
```

#### Step02b_AccountOpening

After grouping steps that have a common theme/dependencies, and creating smaller subprocesses and using them as steps,
the root process looks like this:

```mermaid
flowchart LR
User(User)
FillForm(Chat With User <br/> to Fill New <br/> Customer Form)
NewAccountVerification[[New Account Verification<br/> Process]]
NewAccountCreation[[New Account Creation<br/> Process]]
Mailer(Mail <br/> Service)
User<-->|Provides user details|FillForm
FillForm-->|New User Form|NewAccountVerification
NewAccountVerification-->|Account Credit Check<br/> Verification Failed|Mailer
NewAccountVerification-->|Account Fraud<br/> Detection Failed|Mailer
NewAccountVerification-->|Account Verification <br/> Succeeded|NewAccountCreation
NewAccountCreation-->|Account Creation <br/> Succeeded|Mailer
```

Where processes used as steps, which are reusing the same steps used [`Step02a_AccountOpening`](#step02a_accountopening), are:

```mermaid
graph LR
NewUserForm([New User Form])
NewUserFormConv([Form Filling Interaction])
subgraph AccountCreation[Account Creation Process]
direction LR
AccountValidation([Account Verification Passed])
NewUser1([New User Form])
NewUserFormConv1([Form Filling Interaction])
CoreSystem(Core System <br/> Record <br/> Creation)
Marketing(New Marketing <br/> Record Creation)
CRM(CRM Record <br/> Creation)
Welcome(Welcome <br/> Packet)
NewAccountCreation([New Account Success])
NewUser1-->CoreSystem
NewUserFormConv1-->CoreSystem
AccountValidation-->CoreSystem
CoreSystem-->CRM-->|Success|Welcome
CoreSystem-->Marketing-->|Success|Welcome
CoreSystem-->|Account Details|Welcome
Welcome-->NewAccountCreation
end
subgraph AccountVerification[Account Verification Process]
direction LR
NewUser2([New User Form])
CreditScoreCheck[Credit Check <br/> Step]
FraudCheck[Fraud Detection <br/> Step]
AccountVerificationPass([Account Verification Passed])
AccountCreditCheckFail([Credit Check Failed])
AccountFraudCheckFail([Fraud Check Failed])
NewUser2-->CreditScoreCheck-->|Credit Score <br/> Check Passed|FraudCheck
FraudCheck-->AccountVerificationPass
CreditScoreCheck-->AccountCreditCheckFail
FraudCheck-->AccountFraudCheckFail
end
AccountVerificationPass-->AccountValidation
NewUserForm-->NewUser1
NewUserForm-->NewUser2
NewUserFormConv-->NewUserFormConv1
```

### Step03a_FoodPreparation

This tutorial contains a set of food recipes associated with the Food Preparation Processes of a restaurant.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace Step02.Models;

/// <summary>
/// Represents the data structure for a form capturing details of a new customer, including personal information, contact details, account id and account type.<br/>
/// Class used in <see cref="Step02_AccountOpening"/> samples
/// Class used in <see cref="Step02a_AccountOpening"/>, <see cref="Step02b_AccountOpening"/> samples
/// </summary>
public class AccountDetails : NewCustomerForm
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ namespace Step02.Models;

/// <summary>
/// Processes Events related to Account Opening scenarios.<br/>
/// Class used in <see cref="Step02_AccountOpening"/> samples
/// Class used in <see cref="Step02a_AccountOpening"/>, <see cref="Step02b_AccountOpening"/> samples
/// </summary>
public static class AccountOpeningEvents
{
Expand All @@ -14,6 +14,8 @@ public static class AccountOpeningEvents
public static readonly string NewCustomerFormNeedsMoreDetails = nameof(NewCustomerFormNeedsMoreDetails);
public static readonly string CustomerInteractionTranscriptReady = nameof(CustomerInteractionTranscriptReady);

public static readonly string NewAccountVerificationCheckPassed = nameof(NewAccountVerificationCheckPassed);

public static readonly string CreditScoreCheckApproved = nameof(CreditScoreCheckApproved);
public static readonly string CreditScoreCheckRejected = nameof(CreditScoreCheckRejected);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace Step02.Models;
/// <summary>
/// Represents the details of interactions between a user and service, including a unique identifier for the account,
/// a transcript of conversation with the user, and the type of user interaction.<br/>
/// Class used in <see cref="Step02_AccountOpening"/> samples
/// Class used in <see cref="Step02a_AccountOpening"/>, <see cref="Step02b_AccountOpening"/> samples
/// </summary>
public record AccountUserInteractionDetails
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace Step02.Models;

/// <summary>
/// Holds details for a new entry in a marketing database, including the account identifier, contact name, phone number, and email address.<br/>
/// Class used in <see cref="Step02_AccountOpening"/> samples
/// Class used in <see cref="Step02a_AccountOpening"/>, <see cref="Step02b_AccountOpening"/> samples
/// </summary>
public record MarketingNewEntryDetails
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace Step02.Models;

/// <summary>
/// Represents the data structure for a form capturing details of a new customer, including personal information and contact details.<br/>
/// Class used in <see cref="Step02_AccountOpening"/> samples
/// Class used in <see cref="Step02a_AccountOpening"/>, <see cref="Step02b_AccountOpening"/> samples
/// </summary>
public class NewCustomerForm
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright (c) Microsoft. All rights reserved.

using Microsoft.SemanticKernel;
using Step02.Models;
using Step02.Steps;

namespace Step02.Processes;

/// <summary>
/// Demonstrate creation of <see cref="KernelProcess"/> and
/// eliciting its response to five explicit user messages.<br/>
/// For each test there is a different set of user messages that will cause different steps to be triggered using the same pipeline.<br/>
/// For visual reference of the process check the <see href="https://github.com/microsoft/semantic-kernel/tree/main/dotnet/samples/GettingStartedWithProcesses/README.md#step02b_accountOpening" >diagram</see>.
/// </summary>
public static class NewAccountCreationProcess
{
public static ProcessBuilder CreateProcess()
{
ProcessBuilder process = new("AccountCreationProcess");

var coreSystemRecordCreationStep = process.AddStepFromType<NewAccountStep>();
var marketingRecordCreationStep = process.AddStepFromType<NewMarketingEntryStep>();
var crmRecordStep = process.AddStepFromType<CRMRecordCreationStep>();
var welcomePacketStep = process.AddStepFromType<WelcomePacketStep>();

// When the newCustomerForm is completed...
process
.OnInputEvent(AccountOpeningEvents.NewCustomerFormCompleted)
// The information gets passed to the core system record creation step
.SendEventTo(new ProcessFunctionTargetBuilder(coreSystemRecordCreationStep, functionName: NewAccountStep.Functions.CreateNewAccount, parameterName: "customerDetails"));

// When the newCustomerForm is completed, the user interaction transcript with the user is passed to the core system record creation step
process
.OnInputEvent(AccountOpeningEvents.CustomerInteractionTranscriptReady)
.SendEventTo(new ProcessFunctionTargetBuilder(coreSystemRecordCreationStep, functionName: NewAccountStep.Functions.CreateNewAccount, parameterName: "interactionTranscript"));

// When the fraudDetectionCheck step passes, the information gets to core system record creation step to kickstart this step
process
.OnInputEvent(AccountOpeningEvents.NewAccountVerificationCheckPassed)
.SendEventTo(new ProcessFunctionTargetBuilder(coreSystemRecordCreationStep, functionName: NewAccountStep.Functions.CreateNewAccount, parameterName: "previousCheckSucceeded"));

// When the coreSystemRecordCreation step successfully creates a new accountId, it will trigger the creation of a new marketing entry through the marketingRecordCreation step
coreSystemRecordCreationStep
.OnEvent(AccountOpeningEvents.NewMarketingRecordInfoReady)
.SendEventTo(new ProcessFunctionTargetBuilder(marketingRecordCreationStep, functionName: NewMarketingEntryStep.Functions.CreateNewMarketingEntry, parameterName: "userDetails"));

// When the coreSystemRecordCreation step successfully creates a new accountId, it will trigger the creation of a new CRM entry through the crmRecord step
coreSystemRecordCreationStep
.OnEvent(AccountOpeningEvents.CRMRecordInfoReady)
.SendEventTo(new ProcessFunctionTargetBuilder(crmRecordStep, functionName: CRMRecordCreationStep.Functions.CreateCRMEntry, parameterName: "userInteractionDetails"));

// ParameterName is necessary when the step has multiple input arguments like welcomePacketStep.CreateWelcomePacketAsync
// When the coreSystemRecordCreation step successfully creates a new accountId, it will pass the account information details to the welcomePacket step
coreSystemRecordCreationStep
.OnEvent(AccountOpeningEvents.NewAccountDetailsReady)
.SendEventTo(new ProcessFunctionTargetBuilder(welcomePacketStep, parameterName: "accountDetails"));

// When the marketingRecordCreation step successfully creates a new marketing entry, it will notify the welcomePacket step it is ready
marketingRecordCreationStep
.OnEvent(AccountOpeningEvents.NewMarketingEntryCreated)
.SendEventTo(new ProcessFunctionTargetBuilder(welcomePacketStep, parameterName: "marketingEntryCreated"));

// When the crmRecord step successfully creates a new CRM entry, it will notify the welcomePacket step it is ready
crmRecordStep
.OnEvent(AccountOpeningEvents.CRMRecordInfoEntryCreated)
.SendEventTo(new ProcessFunctionTargetBuilder(welcomePacketStep, parameterName: "crmRecordCreated"));

return process;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (c) Microsoft. All rights reserved.

using Microsoft.SemanticKernel;
using Step02.Models;
using Step02.Steps;

namespace Step02.Processes;

/// <summary>
/// Demonstrate creation of <see cref="KernelProcess"/> and
/// eliciting its response to five explicit user messages.<br/>
/// For each test there is a different set of user messages that will cause different steps to be triggered using the same pipeline.<br/>
/// For visual reference of the process check the <see href="https://github.com/microsoft/semantic-kernel/tree/main/dotnet/samples/GettingStartedWithProcesses/README.md#step02b_accountOpening" >diagram</see>.
/// </summary>
public static class NewAccountVerificationProcess
{
public static ProcessBuilder CreateProcess()
{
ProcessBuilder process = new("AccountVerificationProcess");

var customerCreditCheckStep = process.AddStepFromType<CreditScoreCheckStep>();
var fraudDetectionCheckStep = process.AddStepFromType<FraudDetectionStep>();

// When the newCustomerForm is completed...
process
.OnInputEvent(AccountOpeningEvents.NewCustomerFormCompleted)
// The information gets passed to the core system record creation step
.SendEventTo(new ProcessFunctionTargetBuilder(customerCreditCheckStep, functionName: CreditScoreCheckStep.Functions.DetermineCreditScore, parameterName: "customerDetails"))
// The information gets passed to the fraud detection step for validation
.SendEventTo(new ProcessFunctionTargetBuilder(fraudDetectionCheckStep, functionName: FraudDetectionStep.Functions.FraudDetectionCheck, parameterName: "customerDetails"));

// When the creditScoreCheck step results in Approval, the information gets to the fraudDetection step to kickstart this step
customerCreditCheckStep
.OnEvent(AccountOpeningEvents.CreditScoreCheckApproved)
.SendEventTo(new ProcessFunctionTargetBuilder(fraudDetectionCheckStep, functionName: FraudDetectionStep.Functions.FraudDetectionCheck, parameterName: "previousCheckSucceeded"));

return process;
}
}
Loading

0 comments on commit 3c13912

Please sign in to comment.