Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions dotnet/SK-dotnet.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,10 @@
</Folder>
<Folder Name="/samples/Demos/ProcessWithCloudEvents/">
<File Path="samples/Demos/ProcessWithCloudEvents/README.md" />
<Project Path="samples/Demos/ProcessWithCloudEvents/ProcessWithCloudEvents.Grpc.LocalRuntime/ProcessWithCloudEvents.Grpc.LocalRuntime.csproj" />
<Project Path="samples/Demos/ProcessWithCloudEvents/ProcessWithCloudEvents.Grpc/ProcessWithCloudEvents.Grpc.csproj" />
<Project Path="samples/Demos/ProcessWithCloudEvents/ProcessWithCloudEvents.Processes/ProcessWithCloudEvents.Processes.csproj" />
<Project Path="samples/Demos/ProcessWithCloudEvents/ProcessWithCloudEvents.Tests.LocalRuntime/ProcessWithCloudEvents.Tests.LocalRuntime.csproj" />
</Folder>
<Folder Name="/src/">
<Project Path="src/IntegrationTests/IntegrationTests.csproj" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft. All rights reserved.

using System.Text.Json.Serialization;
using Microsoft.SemanticKernel;
using ProcessWithCloudEvents.Processes.Models;
using ProcessWithCloudEvents.Processes.Steps;

namespace ProcessWithCloudEvents.Processes;
Expand Down Expand Up @@ -34,6 +36,13 @@ public static class DocGenerationEvents
public const string UserApprovedDocument = nameof(UserApprovedDocument);
}

public static class ProcessInputEvents
{
public static readonly KernelProcessEventDescriptor<ProductInfo> StartDocumentGeneration = new(nameof(StartDocumentGeneration));
public static readonly KernelProcessEventDescriptor<bool> UserApprovedDocument = new(nameof(UserApprovedDocument));
public static readonly KernelProcessEventDescriptor<string> UserRejectedDocument = new(nameof(UserRejectedDocument));
}

/// <summary>
/// SK Process topics emitted by <see cref="DocumentGenerationProcess"/>
/// Topics are used to emit events to external systems
Expand All @@ -57,8 +66,8 @@ public static class DocGenerationTopics
/// <returns>instance of <see cref="ProcessBuilder"/></returns>
public static ProcessBuilder CreateProcessBuilder(string processName = "DocumentationGeneration")
{
// Create the process builder
ProcessBuilder processBuilder = new(processName);
// Create the process builder
ProcessBuilder processBuilder = new(processName, processOptions: new() { JsonSerializerAdditionalContexts = [DocumentJsonSerializerContext.Default] });

// Add the steps
var infoGatheringStep = processBuilder.AddStepFromType<GatherProductInfoStep>();
Expand All @@ -70,7 +79,7 @@ public static ProcessBuilder CreateProcessBuilder(string processName = "Document

// Orchestrate the external input events
processBuilder
.OnInputEvent(DocGenerationEvents.StartDocumentGeneration)
.OnInputEvent(ProcessInputEvents.StartDocumentGeneration)
.SendEventTo(new(infoGatheringStep));

processBuilder
Expand All @@ -87,39 +96,45 @@ public static ProcessBuilder CreateProcessBuilder(string processName = "Document
.SendEventTo(new ProcessFunctionTargetBuilder(docsGenerationStep, functionName: GenerateDocumentationStep.ProcessFunctions.GenerateDocs));

docsGenerationStep
.OnEvent(GenerateDocumentationStep.OutputEvents.DocumentationGenerated)
.OnEvent(GenerateDocumentationStep.StepEvents.DocumentationGenerated)
.SendEventTo(new ProcessFunctionTargetBuilder(docsProofreadStep));

docsProofreadStep
.OnEvent(ProofReadDocumentationStep.OutputEvents.DocumentationRejected)
.OnEvent(ProofReadDocumentationStep.StepEvents.DocumentationRejected)
.SendEventTo(new ProcessFunctionTargetBuilder(docsGenerationStep, functionName: GenerateDocumentationStep.ProcessFunctions.ApplySuggestions));

// When the proofreader approves the documentation, send it to the 'docs' parameter of the docsPublishStep
// Additionally, the generated document is emitted externally for user approval using the pre-configured proxyStep
docsProofreadStep
.OnEvent(ProofReadDocumentationStep.OutputEvents.DocumentationApproved)
.EmitExternalEvent(proxyStep, DocGenerationTopics.RequestUserReview);
.OnEvent(ProofReadDocumentationStep.StepEvents.DocumentationApproved)
.EmitExternalEvent(proxyStep, DocGenerationTopics.RequestUserReview)
.EmitAsPublicEvent();
//.SendEventTo(new ProcessFunctionTargetBuilder(docsPublishStep, parameterName: "document"));

processBuilder
.ListenFor()
.AllOf([
new(messageType: ProofReadDocumentationStep.OutputEvents.DocumentationApproved, docsProofreadStep),
new(messageType: DocGenerationEvents.UserApprovedDocument, processBuilder),
new TypedMessageSourceBuilder<DocumentInfo>(messageType: ProofReadDocumentationStep.StepEvents.DocumentationApproved, docsProofreadStep),
new TypedMessageSourceBuilder<bool>(messageType: ProcessInputEvents.UserApprovedDocument, processBuilder),
])
.SendEventTo(new ProcessStepTargetBuilder(docsPublishStep, inputMapping: (inputEvents) =>
{
return new() {
{ "document", inputEvents[docsProofreadStep.GetFullEventId(ProofReadDocumentationStep.OutputEvents.DocumentationApproved)] },
{ "userApproval", inputEvents[processBuilder.GetFullEventId(DocGenerationEvents.UserApprovedDocument)] },
{ "document", inputEvents[docsProofreadStep.GetFullEventId(ProofReadDocumentationStep.StepEvents.DocumentationApproved)] },
{ "userApproval", inputEvents[processBuilder.GetFullEventId(ProcessInputEvents.UserApprovedDocument)] },
};
}));

// When event is approved by user, it gets published externally too
docsPublishStep
.OnFunctionResult()
.EmitExternalEvent(proxyStep, DocGenerationTopics.PublishDocumentation);
.EmitExternalEvent(proxyStep, DocGenerationTopics.PublishDocumentation)
.EmitAsPublicEvent();

return processBuilder;
}
}

[JsonSerializable(typeof(ProductInfo))]
[JsonSerializable(typeof(DocumentInfo))]
public partial class DocumentJsonSerializerContext : JsonSerializerContext;
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ public static class ProcessFunctions
/// <summary>
/// Output events of the step, using this since 2 steps emit the same output event
/// </summary>
public static class OutputEvents
public static new class StepEvents
{
/// <summary>
/// Document Generated output event
/// </summary>
public const string DocumentationGenerated = nameof(DocumentationGenerated);
public static readonly KernelProcessEventDescriptor<DocumentInfo> DocumentationGenerated = new(nameof(DocumentationGenerated));
}

internal GenerateDocumentationState _state = new();
Expand Down Expand Up @@ -84,7 +84,7 @@ public async Task GenerateDocumentationAsync(Kernel kernel, KernelProcessStepCon

this._state!.LastGeneratedDocument = generatedContent;

await context.EmitEventAsync(OutputEvents.DocumentationGenerated, generatedContent);
await context.EmitEventAsync(StepEvents.DocumentationGenerated, generatedContent);
}

/// <summary>
Expand Down Expand Up @@ -115,7 +115,7 @@ public async Task ApplySuggestionsAsync(Kernel kernel, KernelProcessStepContext

this._state!.LastGeneratedDocument = updatedContent;

await context.EmitEventAsync(OutputEvents.DocumentationGenerated, updatedContent);
await context.EmitEventAsync(StepEvents.DocumentationGenerated, updatedContent);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,16 @@ public class ProofReadDocumentationStep : KernelProcessStep
/// <summary>
/// SK Process Events emitted by <see cref="ProofReadDocumentationStep"/>
/// </summary>
public static class OutputEvents
public static new class StepEvents
{
/// <summary>
/// Document has errors and needs to be revised event
/// </summary>
public const string DocumentationRejected = nameof(DocumentationRejected);
public static readonly KernelProcessEventDescriptor<string> DocumentationRejected = new(nameof(DocumentationRejected));
/// <summary>
/// Document looks ok and can be processed by the next step
/// </summary>
public const string DocumentationApproved = nameof(DocumentationApproved);
public static readonly KernelProcessEventDescriptor<DocumentInfo> DocumentationApproved = new(nameof(DocumentationApproved));
}

private readonly string _systemPrompt = """"
Expand Down Expand Up @@ -70,16 +70,13 @@ public async Task ProofreadDocumentationAsync(Kernel kernel, KernelProcessStepCo
{
// 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(OutputEvents.DocumentationApproved, data: document, visibility: KernelProcessEventVisibility.Public);
await context.EmitEventAsync(StepEvents.DocumentationApproved, document);
}
else
{
await context.EmitEventAsync(new()
{
Id = OutputEvents.DocumentationRejected,
await context.EmitEventAsync(StepEvents.DocumentationRejected,
// This event is getting piped to the GenerateDocumentationStep.ApplySuggestionsAsync step which expects a string with suggestions for the document
Data = $"Explanation = {formattedResponse.Explanation}, Suggestions = {string.Join(",", formattedResponse.Suggestions)} ",
});
$"Explanation = {formattedResponse.Explanation}, Suggestions = {string.Join(",", formattedResponse.Suggestions)} ");
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<AssemblyName>ProcessWithCloudEventsLocalRuntimeTests</AssemblyName>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<RootNamespace></RootNamespace>
<!-- Suppress: "Declare types in namespaces", "Require ConfigureAwait", "Experimental" -->
<NoWarn>
$(NoWarn);CS8618,IDE0005,IDE0009,IDE1006,CA1051,CA1050,CA1707,CA1812,CA1054,CA2007,VSTHRD111,CS1591,RCS1110,RCS1243,CA5394,SKEXP0001,SKEXP0010,SKEXP0040,SKEXP0050,SKEXP0060,SKEXP0070,SKEXP0080,SKEXP0081,SKEXP0101,SKEXP0110,OPENAI001
</NoWarn>
<OutputType>Library</OutputType>
<UserSecretsId>5ee045b0-aea3-4f08-8d31-32d1a6f8fed0</UserSecretsId>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
<PackageReference Include="Microsoft.Extensions.Configuration" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Microsoft.Extensions.Http" />
<PackageReference Include="Microsoft.Extensions.Http.Resilience" />
<PackageReference Include="Microsoft.Extensions.Logging" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio" />
</ItemGroup>

<Import Project="$(RepoRoot)/dotnet/src/InternalUtilities/process/LocalStorageComponents.props" />

<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Connectors\Connectors.OpenAI\Connectors.OpenAI.csproj" />
<ProjectReference Include="..\..\..\..\src\Experimental\Process.Abstractions\Process.Abstractions.csproj" />
<ProjectReference Include="..\..\..\..\src\Experimental\Process.Core\Process.Core.csproj" />
<ProjectReference Include="..\..\..\..\src\Experimental\Process.LocalRuntime\Process.LocalRuntime.csproj" />
<ProjectReference Include="..\ProcessWithCloudEvents.Processes\ProcessWithCloudEvents.Processes.csproj" />
</ItemGroup>

<ItemGroup>
<Using Include="Xunit" />
<Using Include="Xunit.Abstractions" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) Microsoft. All rights reserved.

using Microsoft.SemanticKernel;
using ProcessWithCloudEvents.Processes;
using static ProcessWithCloudEvents.Processes.DocumentGenerationProcess;

namespace ProcessWithCloudEvents.Tests.LocalRuntime;

public class ProcessWithCloudEvents_DocumentGenerationProcess
{
[Fact]
public void StartDocumentGenerationOnly()
{
var processBuilder = DocumentGenerationProcess.CreateProcessBuilder();

var t = "";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,11 @@ public static ProcessBuilder CreateProcess(string processName = "FishAndChipsPro
}));

addCondimentsStep
.OnEvent(AddFishAndChipsCondimentsStep.OutputEvents.CondimentsAdded)
.OnEvent(AddFishAndChipsCondimentsStep.StepEvents.CondimentsAdded)
.SendEventTo(new ProcessFunctionTargetBuilder(externalStep));

externalStep.OnEvent(ExternalFishAndChipsStep.StepEvents.FishAndChipsReady).EmitAsPublicEvent();

return processBuilder;
}

Expand Down Expand Up @@ -79,9 +81,11 @@ public static ProcessBuilder CreateProcessWithStatefulSteps(string processName =
}));

addCondimentsStep
.OnEvent(AddFishAndChipsCondimentsStep.OutputEvents.CondimentsAdded)
.OnEvent(AddFishAndChipsCondimentsStep.StepEvents.CondimentsAdded)
.SendEventTo(new ProcessFunctionTargetBuilder(externalStep));

externalStep.OnEvent(ExternalFishAndChipsStep.StepEvents.FishAndChipsReady).EmitAsPublicEvent();

return processBuilder;
}

Expand All @@ -92,9 +96,9 @@ public static class ProcessFunctions
public const string AddCondiments = nameof(AddCondiments);
}

public static class OutputEvents
public static new class StepEvents
{
public const string CondimentsAdded = nameof(CondimentsAdded);
public static readonly KernelProcessEventDescriptor<List<string>> CondimentsAdded = new(nameof(CondimentsAdded));
}

[KernelFunction(ProcessFunctions.AddCondiments)]
Expand All @@ -103,12 +107,17 @@ public async Task AddCondimentsAsync(KernelProcessStepContext context, List<stri
Console.WriteLine($"ADD_CONDIMENTS: Added condiments to Fish & Chips - Fish: {JsonSerializer.Serialize(fishActions)}, Potatoes: {JsonSerializer.Serialize(potatoActions)}");
fishActions.AddRange(potatoActions);
fishActions.Add(FoodIngredients.Condiments.ToFriendlyString());
await context.EmitEventAsync(new() { Id = OutputEvents.CondimentsAdded, Data = fishActions });
await context.EmitEventAsync(StepEvents.CondimentsAdded, fishActions);
}
}

private sealed class ExternalFishAndChipsStep : ExternalStep
{
public static new class StepEvents
{
public static readonly KernelProcessEventDescriptor<List<string>> FishAndChipsReady = new(nameof(ProcessEvents.FishAndChipsReady));
}

public ExternalFishAndChipsStep() : base(ProcessEvents.FishAndChipsReady) { }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ public static ProcessBuilder CreateProcess(string processName = "FishSandwichPro
.OnEvent(AddSpecialSauceStep.OutputEvents.SpecialSauceAdded)
.SendEventTo(new ProcessFunctionTargetBuilder(externalStep));

externalStep.OnEvent(ProcessEvents.FishSandwichReady).EmitAsPublicEvent();

return processBuilder;
}

Expand Down Expand Up @@ -99,6 +101,8 @@ public static ProcessBuilder CreateProcessWithStatefulStepsV2(string processName
.OnEvent(AddSpecialSauceStep.OutputEvents.SpecialSauceAdded)
.SendEventTo(new ProcessFunctionTargetBuilder(externalStep));

externalStep.OnEvent(ExternalFriedFishStep.StepEvents.FishSandwichReady).EmitAsPublicEvent();

return processBuilder;
}

Expand Down Expand Up @@ -146,6 +150,10 @@ public async Task SliceFoodAsync(KernelProcessStepContext context, List<string>

private sealed class ExternalFriedFishStep : ExternalStep
{
public static new class StepEvents
{
public static readonly KernelProcessEventDescriptor<List<string>> FishSandwichReady = new(nameof(ProcessEvents.FishSandwichReady));
}
public ExternalFriedFishStep() : base(ProcessEvents.FishSandwichReady) { }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public static class ProcessEvents
// When multiple processes use the same final step, the should event marked as public
// so that the step event can be used as the output event of the process too.
// In these samples both fried fish and potato fries end with FryStep success
public const string FriedFishReady = FryFoodStep.OutputEvents.FriedFoodReady;
public const string FriedFishReady = nameof(FryFoodStep.StepEvents.FriedFoodReady);
}

/// <summary>
Expand All @@ -39,15 +39,15 @@ public static ProcessBuilder CreateProcess(string processName = "FriedFishProces
.SendEventTo(new ProcessFunctionTargetBuilder(gatherIngredientsStep));

gatherIngredientsStep
.OnEvent(GatherFriedFishIngredientsStep.OutputEvents.IngredientsGathered)
.OnEvent(GatherFriedFishIngredientsStep.StepEvents.IngredientsGathered)
.SendEventTo(new ProcessFunctionTargetBuilder(chopStep, functionName: CutFoodStep.ProcessStepFunctions.ChopFood));

chopStep
.OnEvent(CutFoodStep.OutputEvents.ChoppingReady)
.OnEvent(CutFoodStep.StepEvents.ChoppingReady)
.SendEventTo(new ProcessFunctionTargetBuilder(fryStep));

fryStep
.OnEvent(FryFoodStep.OutputEvents.FoodRuined)
.OnEvent(FryFoodStep.StepEvents.FoodRuined)
.SendEventTo(new ProcessFunctionTargetBuilder(gatherIngredientsStep));

return processBuilder;
Expand Down Expand Up @@ -75,9 +75,11 @@ public static ProcessBuilder CreateProcessWithStatefulStepsV1(string processName
.SendEventTo(new ProcessFunctionTargetBuilder(fryStep));

fryStep
.OnEvent(FryFoodStep.OutputEvents.FoodRuined)
.OnEvent(FryFoodStep.StepEvents.FoodRuined)
.SendEventTo(new ProcessFunctionTargetBuilder(gatherIngredientsStep));

fryStep.OnEvent(FryFoodStep.StepEvents.FriedFoodReady).EmitAsPublicEvent();

return processBuilder;
}

Expand Down Expand Up @@ -121,7 +123,7 @@ public static ProcessBuilder CreateProcessWithStatefulStepsV2(string processName
.SendEventTo(new ProcessFunctionTargetBuilder(chopStep, functionName: CutFoodWithSharpeningStep.ProcessStepFunctions.ChopFood));

fryStep
.OnEvent(FryFoodStep.OutputEvents.FoodRuined)
.OnEvent(FryFoodStep.StepEvents.FoodRuined)
.SendEventTo(new ProcessFunctionTargetBuilder(gatherIngredientsStep));

return processBuilder;
Expand Down
Loading
Loading