// Copyright (c) Microsoft. All rights reserved. using System.Runtime.Serialization; using Microsoft.AspNetCore.Mvc; using Microsoft.SemanticKernel; namespace ProcessWithDapr.Controllers; /// /// A controller for chatbot. /// [ApiController] public class ProcessController : ControllerBase { private readonly Kernel _kernel; /// /// Initializes a new instance of the class. /// /// An instance of public ProcessController(Kernel kernel) { this._kernel = kernel; } /// /// Start and run a process. /// /// The Id of the process. /// [HttpGet("processes/{processId}")] public async Task PostAsync(string processId) { var process = this.GetProcess(); var processContext = await process.StartAsync(new KernelProcessEvent() { Id = CommonEvents.StartProcess }, processId: processId); var finalState = await processContext.GetStateAsync(); return this.Ok(processId); } private KernelProcess GetProcess() { // Create the process builder. ProcessBuilder processBuilder = new("ProcessWithDapr"); // Add some steps to the process. var kickoffStep = processBuilder.AddStepFromType(); var myAStep = processBuilder.AddStepFromType(); var myBStep = processBuilder.AddStepFromType(); // ########## Configuring initial state on steps in a process ########### // For demonstration purposes, we add the CStep and configure its initial state with a CurrentCycle of 1. // Initializing state in a step can be useful for when you need a step to start out with a predetermines // configuration that is not easily accomplished with dependency injection. var myCStep = processBuilder.AddStepFromType(initialState: new() { CurrentCycle = 1 }); // Setup the input event that can trigger the process to run and specify which step and function it should be routed to. processBuilder .OnInputEvent(CommonEvents.StartProcess) .SendEventTo(new ProcessFunctionTargetBuilder(kickoffStep)); // When the kickoff step is finished, trigger both AStep and BStep. kickoffStep .OnEvent(CommonEvents.StartARequested) .SendEventTo(new ProcessFunctionTargetBuilder(myAStep)) .SendEventTo(new ProcessFunctionTargetBuilder(myBStep)); // When AStep finishes, send its output to CStep. myAStep .OnEvent(CommonEvents.AStepDone) .SendEventTo(new ProcessFunctionTargetBuilder(myCStep, parameterName: "astepdata")); // When BStep finishes, send its output to CStep also. myBStep .OnEvent(CommonEvents.BStepDone) .SendEventTo(new ProcessFunctionTargetBuilder(myCStep, parameterName: "bstepdata")); // When CStep has finished without requesting an exit, activate the Kickoff step to start again. myCStep .OnEvent(CommonEvents.CStepDone) .SendEventTo(new ProcessFunctionTargetBuilder(kickoffStep)); // When the CStep has finished by requesting an exit, stop the process. myCStep .OnEvent(CommonEvents.ExitRequested) .StopProcess(); var process = processBuilder.Build(); return process; } #pragma warning disable CA1812 // Avoid uninstantiated internal classes // These classes are dynamically instantiated by the processes used in tests. /// /// Kick off step for the process. /// private sealed class KickoffStep : KernelProcessStep { public static class Functions { public const string KickOff = nameof(KickOff); } [KernelFunction(Functions.KickOff)] public async ValueTask PrintWelcomeMessageAsync(KernelProcessStepContext context) { Console.WriteLine("##### Kickoff ran."); await context.EmitEventAsync(new() { Id = CommonEvents.StartARequested, Data = "Get Going" }); } } /// /// A step in the process. /// private sealed class AStep : KernelProcessStep { [KernelFunction] public async ValueTask DoItAsync(KernelProcessStepContext context) { Console.WriteLine("##### AStep ran."); await Task.Delay(TimeSpan.FromSeconds(1)); await context.EmitEventAsync(CommonEvents.AStepDone, "I did A"); } } /// /// A step in the process. /// private sealed class BStep : KernelProcessStep { [KernelFunction] public async ValueTask DoItAsync(KernelProcessStepContext context) { Console.WriteLine("##### BStep ran."); await Task.Delay(TimeSpan.FromSeconds(2)); await context.EmitEventAsync(new() { Id = CommonEvents.BStepDone, Data = "I did B" }); } } /// /// A stateful step in the process. This step uses as the persisted /// state object and overrides the ActivateAsync method to initialize the state when activated. /// private sealed class CStep : KernelProcessStep { private CStepState? _state; // ################ Using persisted state ################# // CStep has state that we want to be persisted in the process. To ensure that the step always // starts with the previously persisted or configured state, we need to override the ActivateAsync // method and use the state object it provides. public override ValueTask ActivateAsync(KernelProcessStepState state) { this._state = state.State!; Console.WriteLine($"##### CStep activated with Cycle = '{state.State?.CurrentCycle}'."); return base.ActivateAsync(state); } [KernelFunction] public async ValueTask DoItAsync(KernelProcessStepContext context, string astepdata, string bstepdata) { // ########### This method will restart the process in a loop until CurrentCycle >= 3 ########### this._state!.CurrentCycle++; if (this._state.CurrentCycle >= 3) { // Exit the processes Console.WriteLine("##### CStep run cycle 3 - exiting."); await context.EmitEventAsync(new() { Id = CommonEvents.ExitRequested }); return; } // Cycle back to the start Console.WriteLine($"##### CStep run cycle {this._state.CurrentCycle}."); await context.EmitEventAsync(new() { Id = CommonEvents.CStepDone }); } } /// /// A state object for the CStep. /// [DataContract] private sealed record CStepState { [DataMember] public int CurrentCycle { get; set; } } /// /// Common Events used in the process. /// private static class CommonEvents { public const string UserInputReceived = nameof(UserInputReceived); public const string CompletionResponseGenerated = nameof(CompletionResponseGenerated); public const string WelcomeDone = nameof(WelcomeDone); public const string AStepDone = nameof(AStepDone); public const string BStepDone = nameof(BStepDone); public const string CStepDone = nameof(CStepDone); public const string StartARequested = nameof(StartARequested); public const string StartBRequested = nameof(StartBRequested); public const string ExitRequested = nameof(ExitRequested); public const string StartProcess = nameof(StartProcess); } #pragma warning restore CA1812 // Avoid uninstantiated internal classes }