Skip to content

Latest commit

 

History

History
166 lines (124 loc) · 7.18 KB

example-human-in-loop.md

File metadata and controls

166 lines (124 loc) · 7.18 KB
title description zone_pivot_groups author ms.topic ms.author ms.date ms.service
How-To: Human-in-the-Loop
A step-by-step walk-through for Human-in-the-Loop in Processes
programming-languages
bentho
tutorial
bentho
01/13/2025
semantic-kernel

How-To: Human-in-the-Loop

Warning

The Semantic Kernel Process Framework is experimental, still in development and is subject to change.

Overview

In the previous sections we built a Process to help us automate the creation of documentation for our new product. Our process can now generate documentation that is specific to our product, and can ensure it meets our quality bar by running it through a proofread and edit cycle. In this section we will improve on that process again by requiring a human to approve or reject the documentation before it's published. The flexibility of the process framework means that there are several ways that we could go about doing this but in this example we will demonstrate integration with an external pubsub system for requesting approval.

Flow diagram for our process with a human-in-the-loop pattern.

Make publishing wait for approval

The first change we need to make to the process is to make the publishing step wait for the approval before it publishes the documentation. One option is to simply add a second parameter for the approval to the PublishDocumentation function in the PublishDocumentationStep. This works because a KernelFunction in a step will only be invoked when all of its required parameters have been provided.

::: zone pivot="programming-language-csharp"

// A process step to publish documentation
public class PublishDocumentationStep : KernelProcessStep
{
    [KernelFunction]
    public void PublishDocumentation(string docs, bool isApproved) // added the isApproved parameter
    {
        // Only publish the documentation if it has been approved
        if (isApproved)
        {
            // For example purposes we just write the generated docs to the console
            Console.WriteLine($"{nameof(PublishDocumentationStep)}:\n\tPublishing product documentation:\n\n{docs}");
        }
    }
}

::: zone-end

::: zone pivot="programming-language-python"

Support for Python Human-in-the-loop Process behavior is coming soon. ::: zone-end

::: zone pivot="programming-language-java" ::: zone-end

With the code above, the PublishDocumentation function in the PublishDocumentationStep will only be invoked when the generated documentation has been sent to the docs parameter and the result of the approval has been sent to the isApproved parameter.

We can now update the logic in ProofreadStep step to additionally emit an event to our external pubsub system which will notify the human approver that there is a new request.

::: zone pivot="programming-language-csharp"

// A process step to publish documentation
public class PublishDocumentationStep : KernelProcessStep
{
    ...
    if (formattedResponse.MeetsExpectations)
    {
        await context.EmitEventAsync("DocumentationApproved", data: documentation);

        // Emit event to external pubsub to trigger human in the loop approval.
        await context.EmitExternalEventAsync("HumanApprovalRequired", data: documentation);
    }
    else
    {
        await context.EmitEventAsync("DocumentationRejected", data: new { Explanation = formattedResponse.Explanation, Suggestions = formattedResponse.Suggestions});
    }
    ...
}

Now whenever the newly generated documentation is approved by the proofread agent, the approved documents will be queued on the publishing step, and a human will be notified via our external pubsub system. Let's update the process flow to match this new design.

::: zone-end

::: zone pivot="programming-language-python"

Support for Python Human-in-the-loop Process behavior is coming soon. ::: zone-end

::: zone pivot="programming-language-java" ::: zone-end

::: zone pivot="programming-language-csharp"

// Create the process builder
ProcessBuilder processBuilder = new("DocumentationGeneration");

// Add the steps
var infoGatheringStep = processBuilder.AddStepFromType<GatherProductInfoStep>();
var docsGenerationStep = processBuilder.AddStepFromType<GenerateDocumentationStepV2>();
var docsProofreadStep = processBuilder.AddStepFromType<ProofreadStep>();
var docsPublishStep = processBuilder.AddStepFromType<PublishDocumentationStep>();

// Orchestrate the events
processBuilder
    .OnInputEvent("Start")
    .SendEventTo(new(infoGatheringStep));

// When external human approval event comes in, route it to the 'isApproved' parameter of the docsPublishStep
processBuilder
    .OnInputEvent("HumanApprovalResponse")
    .SendEventTo(new(docsPublishStep, parameterName: "isApproved"));

infoGatheringStep
    .OnFunctionResult()
    .SendEventTo(new(docsGenerationStep, functionName: "GenerateDocumentation"));

docsGenerationStep
    .OnEvent("DocumentationGenerated")
    .SendEventTo(new(docsProofreadStep));

docsProofreadStep
    .OnEvent("DocumentationRejected")
    .SendEventTo(new(docsGenerationStep, functionName: "ApplySuggestions"));

// When the proofreader approves the documentation, send it to the 'docs' parameter of the docsPublishStep
docsProofreadStep
    .OnEvent("DocumentationApproved")
    .SendEventTo(new(docsPublishStep, parameterName: "docs"));

var process = processBuilder.Build();
return process;

::: zone-end

::: zone pivot="programming-language-python"

Support for Python Human-in-the-loop Process behavior is coming soon. ::: zone-end

::: zone pivot="programming-language-java" ::: zone-end

Two changes have been made to the process flow:

  • Added an input event named HumanApprovalResponse that will be routed to the isApproved parameter of the docsPublishStep step.
  • Since the KernelFunction in docsPublishStep now has two parameters, we need to update the existing route to specify the parameter name of docs.

Run the process as you did before and notice that this time when the proofreader approves the generated documentation and sends it to the docs parameter of the docPublishStep step, the step is no longer invoked because it is waiting for the isApproved parameter. At this point the process goes idle because there are no steps ready to be invoked and the call that we made to start the process returns. The process will remain in this idle state until our "human-in-the-loop" takes action to approve or reject the publish request. Once this has happened and the result has been communicated back to our program, we can restart the process with the result.

::: zone pivot="programming-language-csharp"

// Restart the process with approval for publishing the documentation.
await process.StartAsync(kernel, new KernelProcessEvent { Id = "HumanApprovalResponse", Data = true });

::: zone-end

::: zone pivot="programming-language-python"

Support for Python Human-in-the-loop Process behavior is coming soon. ::: zone-end

::: zone pivot="programming-language-java" ::: zone-end

When the process is started again with the HumanApprovalResponse it will pick up from where it left off and invoke the docsPublishStep with isApproved set to true and our documentation will be published.