Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Advertência
O Semantic Kernel Process Framework é experimental, ainda está em desenvolvimento e está sujeito a alterações.
Visão geral
Nas seções anteriores, construímos um Processo para nos ajudar a automatizar a criação de documentação para nosso novo produto. Nosso processo agora pode gerar documentação específica para o nosso produto e pode garantir que ele atenda aos nossos padrões de qualidade, passando-o por um ciclo de revisão e edição. Nesta seção, melhoraremos esse processo novamente, exigindo que um humano aprove ou rejeite a documentação antes que ela seja publicada. A flexibilidade da estrutura do processo significa que há várias maneiras de fazer isso, mas neste exemplo demonstraremos a integração com um sistema pubsub externo para solicitar aprovação.
Fazer com que a publicação aguarde aprovação
A primeira mudança que precisamos fazer no processo é fazer com que a etapa de publicação aguarde a aprovação antes de publicar a documentação. Uma opção é simplesmente adicionar um segundo parâmetro para a aprovação à função PublishDocumentation no PublishDocumentationStep. Isso funciona porque uma KernelFunction em uma etapa só será invocada quando todos os seus parâmetros necessários tiverem sido fornecidos.
// A process step to publish documentation
public class PublishDocumentationStep : KernelProcessStep
{
[KernelFunction]
public DocumentInfo PublishDocumentation(DocumentInfo document, bool userApproval) // added the userApproval parameter
{
// Only publish the documentation if it has been approved
if (userApproval)
{
// For example purposes we just write the generated docs to the console
Console.WriteLine($"[{nameof(PublishDocumentationStep)}]:\tPublishing product documentation approved by user: \n{document.Title}\n{document.Content}");
}
return document;
}
}
O suporte para o comportamento Python Human-in-the-loop Process estará disponível em breve.
Com o código acima, a função PublishDocumentation no PublishDocumentationStep só será invocada quando a documentação gerada tiver sido enviada para o parâmetro document e o resultado da aprovação tiver sido enviado para o parâmetro userApproval.
Agora podemos reutilizar a lógica de ProofreadStep passo existente para emitir adicionalmente um evento para o nosso sistema pubsub externo que notificará o aprovador humano de que há uma nova solicitação.
// A process step to publish documentation
public class ProofReadDocumentationStep : KernelProcessStep
{
...
if (formattedResponse.MeetsExpectations)
{
// Events that are getting piped to steps that will be resumed, like PublishDocumentationStep.OnPublishDocumentation
// require events to be marked as public so they are persisted and restored correctly
await context.EmitEventAsync("DocumentationApproved", data: document, visibility: KernelProcessEventVisibility.Public);
}
...
}
Como queremos publicar a documentação recém-gerada quando ela for aprovada pelo revisor, os documentos aprovados serão enfileirados na etapa de publicação. Além disso, uma pessoa será notificada através do nosso sistema de publicação e subscrição externo com uma atualização sobre o documento mais recente. Vamos atualizar o fluxo do processo para corresponder a esse novo design.
O suporte para o comportamento Python Human-in-the-loop Process estará disponível em breve.
// 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>();
// internal component that allows emitting SK events externally, a list of topic names
// is needed to link them to existing SK events
var proxyStep = processBuilder.AddProxyStep(["RequestUserReview", "PublishDocumentation"]);
// Orchestrate the events
processBuilder
.OnInputEvent("StartDocumentGeneration")
.SendEventTo(new(infoGatheringStep));
processBuilder
.OnInputEvent("UserRejectedDocument")
.SendEventTo(new(docsGenerationStep, functionName: "ApplySuggestions"));
// When external human approval event comes in, route it to the 'isApproved' parameter of the docsPublishStep
processBuilder
.OnInputEvent("UserApprovedDocument")
.SendEventTo(new(docsPublishStep, parameterName: "userApproval"));
// Hooking up the rest of the process steps
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 'document' parameter of the docsPublishStep
// Additionally, the generated document is emitted externally for user approval using the pre-configured proxyStep
docsProofreadStep
.OnEvent("DocumentationApproved")
// [NEW] addition to emit messages externally
.EmitExternalEvent(proxyStep, "RequestUserReview") // Hooking up existing "DocumentationApproved" to external topic "RequestUserReview"
.SendEventTo(new(docsPublishStep, parameterName: "document"));
// When event is approved by user, it gets published externally too
docsPublishStep
.OnFunctionResult()
// [NEW] addition to emit messages externally
.EmitExternalEvent(proxyStep, "PublishDocumentation");
var process = processBuilder.Build();
return process;
Finalmente, uma implementação da interface IExternalKernelProcessMessageChannel deve ser fornecida, uma vez que é usado internamente pelo novo ProxyStep. Esta interface é usada para emitir mensagens externamente. A implementação desta interface dependerá do sistema externo que estiver a utilizar. Neste exemplo, usaremos um cliente personalizado que criamos para enviar mensagens para um sistema pubsub externo.
// Example of potential custom IExternalKernelProcessMessageChannel implementation
public class MyCloudEventClient : IExternalKernelProcessMessageChannel
{
private MyCustomClient? _customClient;
// Example of an implementation for the process
public async Task EmitExternalEventAsync(string externalTopicEvent, KernelProcessProxyMessage message)
{
// logic used for emitting messages externally.
// Since all topics are received here potentially
// some if else/switch logic is needed to map correctly topics with external APIs/endpoints.
if (this._customClient != null)
{
switch (externalTopicEvent)
{
case "RequestUserReview":
var requestDocument = message.EventData.ToObject() as DocumentInfo;
// As an example only invoking a sample of a custom client with a different endpoint/api route
this._customClient.InvokeAsync("REQUEST_USER_REVIEW", requestDocument);
return;
case "PublishDocumentation":
var publishedDocument = message.EventData.ToObject() as DocumentInfo;
// As an example only invoking a sample of a custom client with a different endpoint/api route
this._customClient.InvokeAsync("PUBLISH_DOC_EXTERNALLY", publishedDocument);
return;
}
}
}
public async ValueTask Initialize()
{
// logic needed to initialize proxy step, can be used to initialize custom client
this._customClient = new MyCustomClient("http://localhost:8080");
this._customClient.Initialize();
}
public async ValueTask Uninitialize()
{
// Cleanup to be executed when proxy step is uninitialized
if (this._customClient != null)
{
await this._customClient.ShutdownAsync();
}
}
}
Finalmente, para permitir que o processo ProxyStep faça uso da IExternalKernelProcessMessageChannel implementação, neste caso MyCloudEventClient, precisamos canalizá-lo corretamente.
Ao usar o Local Runtime, a classe implementada pode ser passada ao invocar StartAsync a KernelProcess classe.
KernelProcess process;
IExternalKernelProcessMessageChannel myExternalMessageChannel = new MyCloudEventClient();
// Start the process with the external message channel
await process.StartAsync(kernel, new KernelProcessEvent
{
Id = inputEvent,
Data = input,
},
myExternalMessageChannel)
Ao usar o Dapr Runtime, o encanamento deve ser feito por meio de injeção de dependência na configuração do programa do projeto.
var builder = WebApplication.CreateBuilder(args);
...
// depending on the application a singleton or scoped service can be used
// Injecting SK Process custom client IExternalKernelProcessMessageChannel implementation
builder.Services.AddSingleton<IExternalKernelProcessMessageChannel, MyCloudEventClient>();
O suporte para o comportamento Python Human-in-the-loop Process estará disponível em breve.
Duas alterações foram feitas no fluxo do processo:
- Adicionado um evento de entrada chamado
HumanApprovalResponseque será roteado para o parâmetrouserApprovalda etapadocsPublishStep. - Como o KernelFunction em
docsPublishStepagora tem dois parâmetros, precisamos atualizar a rota existente para especificar o nome do parâmetro dedocument.
Execute o processo como você fez antes e observe que, desta vez, quando o revisor aprova a documentação gerada e a envia para o parâmetro document da etapa docPublishStep, a etapa não é mais invocada porque está aguardando o parâmetro userApproval. Neste ponto, o processo fica ocioso porque não há etapas prontas para serem invocadas e a chamada que fizemos para iniciar o processo retorna. O processo permanecerá nesse estado inativo até que o nosso "humano no circuito" intervenha para aprovar ou rejeitar o pedido de publicação. Uma vez que isso tenha acontecido e o resultado tenha sido comunicado de volta ao nosso programa, podemos reiniciar o processo com o resultado.
// Restart the process with approval for publishing the documentation.
await process.StartAsync(kernel, new KernelProcessEvent { Id = "UserApprovedDocument", Data = true });
O suporte para o comportamento Python Human-in-the-loop Process estará disponível em breve.
Quando o processo for reiniciado com o UserApprovedDocument ele retomará de onde parou e invocará o docsPublishStep com userApproval definido para true e nossa documentação será publicada. Se for iniciado novamente com o evento UserRejectedDocument, o processo iniciará a função ApplySuggestions na etapa docsGenerationStep e continuará como antes.
O processo está agora concluído e adicionámos uma etapa human-in-the-loop ao nosso processo. O processo agora pode ser usado para gerar documentação para o nosso produto, revisá-lo e publicá-lo depois de ter sido aprovado por um ser humano.