From e4b59646c68628146e52120cb0577095bb6c29ab Mon Sep 17 00:00:00 2001 From: Ben Thomas Date: Tue, 15 Oct 2024 15:02:28 -0700 Subject: [PATCH 1/5] Updated to process abstractions and core builders in preparation for Dapr runtime support. --- .../SharedSteps/ScriptedUserInputStep.cs | 3 +- .../Process.Abstractions/KernelProcessEdge.cs | 16 ++++++--- .../KernelProcessFunctionTarget.cs | 7 ++++ .../KernelProcessState.cs | 5 ++- .../KernelProcessStepState.cs | 35 +++++++++++++++++-- .../Process.Core/Internal/EndStep.cs | 2 +- .../Process.Core/ProcessBuilder.cs | 33 +++++++++++++++++ .../Process.Core/ProcessStepBuilder.cs | 17 ++++++++- .../Process.LocalRuntime/LocalProcess.cs | 2 +- .../Processes/ProcessTests.cs | 14 ++++---- 10 files changed, 113 insertions(+), 21 deletions(-) diff --git a/dotnet/samples/GettingStartedWithProcesses/SharedSteps/ScriptedUserInputStep.cs b/dotnet/samples/GettingStartedWithProcesses/SharedSteps/ScriptedUserInputStep.cs index c1908b8e8cdf..8723fe2548b0 100644 --- a/dotnet/samples/GettingStartedWithProcesses/SharedSteps/ScriptedUserInputStep.cs +++ b/dotnet/samples/GettingStartedWithProcesses/SharedSteps/ScriptedUserInputStep.cs @@ -41,8 +41,7 @@ public virtual void PopulateUserInputs(UserInputState state) /// A public override ValueTask ActivateAsync(KernelProcessStepState state) { - state.State ??= new(); - _state = state.State; + _state = state.State ?? new(); PopulateUserInputs(_state); diff --git a/dotnet/src/Experimental/Process.Abstractions/KernelProcessEdge.cs b/dotnet/src/Experimental/Process.Abstractions/KernelProcessEdge.cs index e474df9658c6..2e722172a1a9 100644 --- a/dotnet/src/Experimental/Process.Abstractions/KernelProcessEdge.cs +++ b/dotnet/src/Experimental/Process.Abstractions/KernelProcessEdge.cs @@ -1,31 +1,37 @@ // Copyright (c) Microsoft. All rights reserved. +using System.Runtime.Serialization; + namespace Microsoft.SemanticKernel; /// /// A serializable representation of an edge between a source and a . /// +[DataContract] +[KnownType(typeof(KernelProcessFunctionTarget))] public sealed class KernelProcessEdge { /// /// The unique identifier of the source Step. /// - public string SourceStepId { get; } + [DataMember(Name = "sourceStepId")] + public string SourceStepId { get; init; } /// /// The collection of s that are the output of the source Step. /// - public KernelProcessFunctionTarget OutputTarget { get; } + [DataMember(Name = "outputTarget")] + public KernelProcessFunctionTarget OutputTarget { get; init; } /// /// Creates a new instance of the class. /// - public KernelProcessEdge(string sourceStepId, KernelProcessFunctionTarget outputTargets) + public KernelProcessEdge(string sourceStepId, KernelProcessFunctionTarget outputTarget) { Verify.NotNullOrWhiteSpace(sourceStepId); - Verify.NotNull(outputTargets); + Verify.NotNull(outputTarget); this.SourceStepId = sourceStepId; - this.OutputTarget = outputTargets; + this.OutputTarget = outputTarget; } } diff --git a/dotnet/src/Experimental/Process.Abstractions/KernelProcessFunctionTarget.cs b/dotnet/src/Experimental/Process.Abstractions/KernelProcessFunctionTarget.cs index 8a388347f35c..5d6607d7e88a 100644 --- a/dotnet/src/Experimental/Process.Abstractions/KernelProcessFunctionTarget.cs +++ b/dotnet/src/Experimental/Process.Abstractions/KernelProcessFunctionTarget.cs @@ -1,10 +1,13 @@ // Copyright (c) Microsoft. All rights reserved. +using System.Runtime.Serialization; + namespace Microsoft.SemanticKernel; /// /// A serializable representation of a specific parameter of a specific function of a specific Step. /// +[DataContract] public record KernelProcessFunctionTarget { /// @@ -24,20 +27,24 @@ public KernelProcessFunctionTarget(string stepId, string functionName, string? p /// /// The unique identifier of the Step being targeted. /// + [DataMember(Name = "stepId")] public string StepId { get; init; } /// /// The name if the Kernel Function to target. /// + [DataMember(Name = "functionName")] public string FunctionName { get; init; } /// /// The name of the parameter to target. This may be null if the function has no parameters. /// + [DataMember(Name = "parameterName")] public string? ParameterName { get; init; } /// /// The unique identifier for the event to target. This may be null if the target is not a sub-process. /// + [DataMember(Name = "targetEventId")] public string? TargetEventId { get; init; } } diff --git a/dotnet/src/Experimental/Process.Abstractions/KernelProcessState.cs b/dotnet/src/Experimental/Process.Abstractions/KernelProcessState.cs index bb09b2068195..dc0641c24030 100644 --- a/dotnet/src/Experimental/Process.Abstractions/KernelProcessState.cs +++ b/dotnet/src/Experimental/Process.Abstractions/KernelProcessState.cs @@ -1,14 +1,17 @@ // Copyright (c) Microsoft. All rights reserved. +using System.Runtime.Serialization; + namespace Microsoft.SemanticKernel; /// /// Represents the state of a process. /// +[DataContract] public sealed record KernelProcessState : KernelProcessStepState { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The name of the associated /// The Id of the associated diff --git a/dotnet/src/Experimental/Process.Abstractions/KernelProcessStepState.cs b/dotnet/src/Experimental/Process.Abstractions/KernelProcessStepState.cs index fb90d70f8d2a..569a617713ae 100644 --- a/dotnet/src/Experimental/Process.Abstractions/KernelProcessStepState.cs +++ b/dotnet/src/Experimental/Process.Abstractions/KernelProcessStepState.cs @@ -1,22 +1,41 @@ // Copyright (c) Microsoft. All rights reserved. +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + namespace Microsoft.SemanticKernel; /// /// Represents the state of an individual step in a process. /// +[DataContract] +[KnownType(nameof(GetKnownTypes))] public record KernelProcessStepState { + /// + /// A set of known types that may be used in serialization. + /// + private readonly static HashSet s_knownTypes = []; + + /// + /// Used to dynamically provide the set of known types for serialization. + /// + /// + private static HashSet GetKnownTypes() => s_knownTypes; + /// /// The identifier of the Step which is required to be unique within an instance of a Process. /// This may be null until a process containing this step has been invoked. /// + [DataMember(Name = "id")] public string? Id { get; init; } /// - /// The name of the Step. This is itended to be human readable and is not required to be unique. If + /// The name of the Step. This is intended to be human readable and is not required to be unique. If /// not provided, the name will be derived from the steps .NET type. /// + [DataMember(Name = "name")] public string Name { get; init; } /// @@ -31,18 +50,30 @@ public KernelProcessStepState(string name, string? id = null) this.Id = id; this.Name = name; } + + /// + /// Registers a derived type for serialization. Types registered here are used by the KnownType attribute + /// to support DataContractSerialization of derived types as required to support Dapr. + /// + /// A Type that derives from + public static void RegisterDerivedType(Type derivedType) + { + s_knownTypes.Add(derivedType); + } } /// /// Represents the state of an individual step in a process that includes a user-defined state object. /// /// The type of the user-defined state. +[DataContract] public sealed record KernelProcessStepState : KernelProcessStepState where TState : class, new() { /// /// The user-defined state object associated with the Step. /// - public TState? State { get; set; } + [DataMember] + public TState? State { get; init; } /// /// Initializes a new instance of the class. diff --git a/dotnet/src/Experimental/Process.Core/Internal/EndStep.cs b/dotnet/src/Experimental/Process.Core/Internal/EndStep.cs index 287a2b488f1d..1eefc586bdc2 100644 --- a/dotnet/src/Experimental/Process.Core/Internal/EndStep.cs +++ b/dotnet/src/Experimental/Process.Core/Internal/EndStep.cs @@ -9,7 +9,7 @@ namespace Microsoft.SemanticKernel; /// internal sealed class EndStep : ProcessStepBuilder { - private const string EndStepValue = "END"; + private const string EndStepValue = "Microsoft.SemanticKernel.Process.EndStep"; /// /// The name of the end step. diff --git a/dotnet/src/Experimental/Process.Core/ProcessBuilder.cs b/dotnet/src/Experimental/Process.Core/ProcessBuilder.cs index fa509daebfd5..93af3215f6e9 100644 --- a/dotnet/src/Experimental/Process.Core/ProcessBuilder.cs +++ b/dotnet/src/Experimental/Process.Core/ProcessBuilder.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.SemanticKernel.Process; namespace Microsoft.SemanticKernel; @@ -109,12 +110,44 @@ internal override KernelProcessStepInfo BuildStep() /// An instance of public ProcessStepBuilder AddStepFromType(string? name = null) where TStep : KernelProcessStep { + // If the step has a user-defined state then we need to register it as a know type for the DataContractSerialization used by Dapr. + if (typeof(TStep).TryGetSubtypeOfStatefulStep(out Type? genericStepType) && genericStepType is not null) + { + // The step is a subclass of KernelProcessStep<>, so we need to extract the generic type argument + // and create an instance of the corresponding KernelProcessStepState<>. + var userStateType = genericStepType.GetGenericArguments()[0]; + Verify.NotNull(userStateType); + + var stateType = typeof(KernelProcessStepState<>).MakeGenericType(userStateType); + KernelProcessState.RegisterDerivedType(stateType); + } + var stepBuilder = new ProcessStepBuilder(name); this._steps.Add(stepBuilder); return stepBuilder; } + /// + /// Adds a step to the process and define it's initial user-defined state. + /// + /// The step Type. + /// The state Type. + /// The initial state of the step. + /// The name of the step. This parameter is optional. + /// An instance of + public ProcessStepBuilder AddStepFromType(TState initialState, string? name = null) where TStep : KernelProcessStep where TState : class, new() + { + // The step has a user-defined state so we need to register it as a know type for the DataContractSerialization used by Dapr. + var stateType = typeof(KernelProcessStepState); + KernelProcessState.RegisterDerivedType(stateType); + + var stepBuilder = new ProcessStepBuilder(name, initialState); + this._steps.Add(stepBuilder); + + return stepBuilder; + } + /// /// Adds a sub process to the process. /// diff --git a/dotnet/src/Experimental/Process.Core/ProcessStepBuilder.cs b/dotnet/src/Experimental/Process.Core/ProcessStepBuilder.cs index 4434a81c76f8..841875e164a1 100644 --- a/dotnet/src/Experimental/Process.Core/ProcessStepBuilder.cs +++ b/dotnet/src/Experimental/Process.Core/ProcessStepBuilder.cs @@ -194,13 +194,21 @@ protected ProcessStepBuilder(string name) /// public sealed class ProcessStepBuilder : ProcessStepBuilder where TStep : KernelProcessStep { + /// + /// The initial state of the step. This may be null if the step does not have any state. + /// + private readonly object? _initialState; + /// /// Creates a new instance of the class. If a name is not provided, the name will be derived from the type of the step. /// - public ProcessStepBuilder(string? name = null) + /// Optional: The name of the step. + /// Optional: The initial state of the step. + public ProcessStepBuilder(string? name = null, object? initialState = default) : base(name ?? typeof(TStep).Name) { this.FunctionsDict = this.GetFunctionMetadataMap(); + this._initialState = initialState; } /// @@ -221,7 +229,14 @@ internal override KernelProcessStepInfo BuildStep() var stateType = typeof(KernelProcessStepState<>).MakeGenericType(userStateType); Verify.NotNull(stateType); + // If the step has a user-defined state then we need to validate that the initial state is of the correct type. + if (this._initialState is not null && this._initialState.GetType() != userStateType) + { + throw new KernelException($"The initial state provided for step {this.Name} is not of the correct type. The expected type is {userStateType.Name}."); + } + stateObject = (KernelProcessStepState?)Activator.CreateInstance(stateType, this.Name, this.Id); + stateType.GetProperty(nameof(KernelProcessStepState.State))?.SetValue(stateObject, this._initialState); } else { diff --git a/dotnet/src/Experimental/Process.LocalRuntime/LocalProcess.cs b/dotnet/src/Experimental/Process.LocalRuntime/LocalProcess.cs index cab0d77e81f1..d26fd7ec3fed 100644 --- a/dotnet/src/Experimental/Process.LocalRuntime/LocalProcess.cs +++ b/dotnet/src/Experimental/Process.LocalRuntime/LocalProcess.cs @@ -15,7 +15,7 @@ namespace Microsoft.SemanticKernel; internal sealed class LocalProcess : LocalStep, IDisposable { - private const string EndProcessId = "END"; + private const string EndProcessId = "Microsoft.SemanticKernel.Process.EndStep"; private readonly JoinableTaskFactory _joinableTaskFactory; private readonly JoinableTaskContext _joinableTaskContext; private readonly Channel _externalEventChannel; diff --git a/dotnet/src/IntegrationTests/Processes/ProcessTests.cs b/dotnet/src/IntegrationTests/Processes/ProcessTests.cs index c1c426f66679..cd98a9321ad0 100644 --- a/dotnet/src/IntegrationTests/Processes/ProcessTests.cs +++ b/dotnet/src/IntegrationTests/Processes/ProcessTests.cs @@ -285,12 +285,11 @@ public string Echo(string message) /// private sealed class RepeatStep : KernelProcessStep { - private readonly StepState _state = new(); + private StepState? _state; public override ValueTask ActivateAsync(KernelProcessStepState state) { - state.State ??= this._state; - + this._state ??= state.State ?? new(); return default; } @@ -299,7 +298,7 @@ public async Task RepeatAsync(string message, KernelProcessStepContext context, { var output = string.Join(" ", Enumerable.Repeat(message, count)); Console.WriteLine($"[REPEAT] {output}"); - this._state.LastMessage = output; + this._state!.LastMessage = output; // Emit the OnReady event with a public visibility and an internal visibility to aid in testing await context.EmitEventAsync(new() { Id = ProcessTestsEvents.OutputReadyPublic, Data = output, Visibility = KernelProcessEventVisibility.Public }); @@ -330,12 +329,11 @@ await context.EmitEventAsync(new() /// private sealed class FanInStep : KernelProcessStep { - private readonly StepState _state = new(); + private StepState? _state = new(); public override ValueTask ActivateAsync(KernelProcessStepState state) { - state.State ??= this._state; - + this._state ??= state.State ?? new(); return default; } @@ -344,7 +342,7 @@ public async Task EmitCombinedMessageAsync(KernelProcessStepContext context, str { var output = $"{firstInput}-{secondInput}"; Console.WriteLine($"[EMIT_COMBINED] {output}"); - this._state.LastMessage = output; + this._state!.LastMessage = output; await context.EmitEventAsync(new() { From ca501729de7b201b249beb5cc970ec3ca01f3e4c Mon Sep 17 00:00:00 2001 From: Ben Thomas Date: Tue, 15 Oct 2024 15:31:03 -0700 Subject: [PATCH 2/5] Fixed for usnit tests. --- dotnet/src/Experimental/Process.LocalRuntime/LocalStep.cs | 1 + dotnet/src/IntegrationTests/Processes/ProcessTests.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/dotnet/src/Experimental/Process.LocalRuntime/LocalStep.cs b/dotnet/src/Experimental/Process.LocalRuntime/LocalStep.cs index 3d6ee8b21cf7..abc4659125d8 100644 --- a/dotnet/src/Experimental/Process.LocalRuntime/LocalStep.cs +++ b/dotnet/src/Experimental/Process.LocalRuntime/LocalStep.cs @@ -255,6 +255,7 @@ protected virtual async ValueTask InitializeStepAsync() } stateObject = (KernelProcessStepState?)Activator.CreateInstance(stateType, this.Name, this.Id); + stateType.GetProperty(nameof(KernelProcessStepState.State))?.SetValue(stateObject, Activator.CreateInstance(userStateType)); } else { diff --git a/dotnet/src/IntegrationTests/Processes/ProcessTests.cs b/dotnet/src/IntegrationTests/Processes/ProcessTests.cs index cd98a9321ad0..5d7800490827 100644 --- a/dotnet/src/IntegrationTests/Processes/ProcessTests.cs +++ b/dotnet/src/IntegrationTests/Processes/ProcessTests.cs @@ -329,7 +329,7 @@ await context.EmitEventAsync(new() /// private sealed class FanInStep : KernelProcessStep { - private StepState? _state = new(); + private StepState? _state; public override ValueTask ActivateAsync(KernelProcessStepState state) { From b743ebd8f7ee525520c01d4c3e8a0229ee27b5a2 Mon Sep 17 00:00:00 2001 From: Ben Thomas Date: Tue, 15 Oct 2024 15:52:33 -0700 Subject: [PATCH 3/5] Updating local process to respect initial state. --- .../Experimental/Process.LocalRuntime/LocalStep.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/dotnet/src/Experimental/Process.LocalRuntime/LocalStep.cs b/dotnet/src/Experimental/Process.LocalRuntime/LocalStep.cs index abc4659125d8..f5257415ef21 100644 --- a/dotnet/src/Experimental/Process.LocalRuntime/LocalStep.cs +++ b/dotnet/src/Experimental/Process.LocalRuntime/LocalStep.cs @@ -231,7 +231,7 @@ protected virtual async ValueTask InitializeStepAsync() this._inputs = this._initialInputs.ToDictionary(kvp => kvp.Key, kvp => kvp.Value?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value)); // Activate the step with user-defined state if needed - KernelProcessStepState? stateObject = null; + KernelProcessStepState stateObject = this._stepInfo.State; Type? stateType = null; if (TryGetSubtypeOfStatefulStep(this._stepInfo.InnerStepType, out Type? genericStepType) && genericStepType is not null) @@ -254,14 +254,17 @@ protected virtual async ValueTask InitializeStepAsync() throw new KernelException(errorMessage); } - stateObject = (KernelProcessStepState?)Activator.CreateInstance(stateType, this.Name, this.Id); - stateType.GetProperty(nameof(KernelProcessStepState.State))?.SetValue(stateObject, Activator.CreateInstance(userStateType)); + var userState = stateType.GetProperty(nameof(KernelProcessStepState.State))?.GetValue(stateObject); + if (userState is null) + { + stateType.GetProperty(nameof(KernelProcessStepState.State))?.SetValue(stateObject, Activator.CreateInstance(userStateType)); + } } else { // The step is a KernelProcessStep with no user-defined state, so we can use the base KernelProcessStepState. stateType = typeof(KernelProcessStepState); - stateObject = new KernelProcessStepState(this.Name, this.Id); + //stateObject = new KernelProcessStepState(this.Name, this.Id); } if (stateObject is null) From 8705225e506625152eea49f647bc1d84d7e8caec Mon Sep 17 00:00:00 2001 From: Ben Thomas Date: Tue, 15 Oct 2024 18:35:20 -0700 Subject: [PATCH 4/5] Updates per PR feedback: Removing unneeded initialization of state objects in tests. --- dotnet/src/IntegrationTests/Processes/ProcessTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dotnet/src/IntegrationTests/Processes/ProcessTests.cs b/dotnet/src/IntegrationTests/Processes/ProcessTests.cs index 5d7800490827..df8847d89620 100644 --- a/dotnet/src/IntegrationTests/Processes/ProcessTests.cs +++ b/dotnet/src/IntegrationTests/Processes/ProcessTests.cs @@ -289,7 +289,7 @@ private sealed class RepeatStep : KernelProcessStep public override ValueTask ActivateAsync(KernelProcessStepState state) { - this._state ??= state.State ?? new(); + this._state = state.State; return default; } @@ -333,7 +333,7 @@ private sealed class FanInStep : KernelProcessStep public override ValueTask ActivateAsync(KernelProcessStepState state) { - this._state ??= state.State ?? new(); + this._state = state.State; return default; } From 0ac0ae02fb2a4d195ba2a696851a5bfffe4e1833 Mon Sep 17 00:00:00 2001 From: Ben Thomas Date: Wed, 16 Oct 2024 11:01:22 -0700 Subject: [PATCH 5/5] Updates per PR feedback. --- .vscode/settings.json | 31 +++++++++---------- .../SharedSteps/ScriptedUserInputStep.cs | 4 +-- .../Process.Abstractions/KernelProcessEdge.cs | 4 +-- .../KernelProcessFunctionTarget.cs | 8 ++--- .../KernelProcessStepInfo.cs | 3 ++ .../KernelProcessStepState.cs | 24 +++++++------- .../Process.Core/ProcessBuilder.cs | 17 ---------- .../Process.Core/ProcessStepBuilder.cs | 2 +- .../Process.LocalRuntime/LocalProcess.cs | 4 +-- .../Process.LocalRuntime/LocalStep.cs | 1 - 10 files changed, 40 insertions(+), 58 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 3dc48d0f6e75..2372e2822947 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,8 +1,6 @@ { "prettier.enable": true, - "css.lint.validProperties": [ - "composes" - ], + "css.lint.validProperties": ["composes"], "editor.formatOnType": true, "editor.formatOnSave": true, "editor.formatOnPaste": true, @@ -15,13 +13,9 @@ "editor.bracketPairColorization.enabled": true, "editor.guides.bracketPairs": "active", "python.formatting.provider": "autopep8", - "python.formatting.autopep8Args": [ - "--max-line-length=120" - ], + "python.formatting.autopep8Args": ["--max-line-length=120"], "notebook.output.textLineLimit": 500, - "python.analysis.extraPaths": [ - "./python/src" - ], + "python.analysis.extraPaths": ["./python/src"], "javascript.updateImportsOnFileMove.enabled": "always", "search.exclude": { "**/node_modules": true, @@ -71,31 +65,34 @@ "**/Thumbs.db": true }, "cSpell.words": [ + "Dapr", "Partitioner", + "Pregel", "Prompty", - "SKEXP" + "SKEXP", + "superstep", + "Supersteps", + "typeref" ], "[java]": { "editor.formatOnSave": false, "editor.tabSize": 4, "editor.codeActionsOnSave": { "source.fixAll": "never" - }, + } }, "emeraldwalk.runonsave": { "commands": [ { "match": "\\.java$", "cmd": "java -Xmx128m -jar ${workspaceFolder}/java/utilities/google-java-format-1.17.0-all-deps.jar --replace --aosp ${file}" - }, - ], + } + ] }, "java.debug.settings.onBuildFailureProceed": true, "java.compile.nullAnalysis.mode": "disabled", "dotnet.defaultSolution": "dotnet\\SK-dotnet.sln", - "python.testing.pytestArgs": [ - "python/tests" - ], + "python.testing.pytestArgs": ["python/tests"], "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true -} \ No newline at end of file +} diff --git a/dotnet/samples/GettingStartedWithProcesses/SharedSteps/ScriptedUserInputStep.cs b/dotnet/samples/GettingStartedWithProcesses/SharedSteps/ScriptedUserInputStep.cs index 8723fe2548b0..8ece418f3070 100644 --- a/dotnet/samples/GettingStartedWithProcesses/SharedSteps/ScriptedUserInputStep.cs +++ b/dotnet/samples/GettingStartedWithProcesses/SharedSteps/ScriptedUserInputStep.cs @@ -41,9 +41,9 @@ public virtual void PopulateUserInputs(UserInputState state) /// A public override ValueTask ActivateAsync(KernelProcessStepState state) { - _state = state.State ?? new(); + _state = state.State; - PopulateUserInputs(_state); + PopulateUserInputs(_state!); return ValueTask.CompletedTask; } diff --git a/dotnet/src/Experimental/Process.Abstractions/KernelProcessEdge.cs b/dotnet/src/Experimental/Process.Abstractions/KernelProcessEdge.cs index 2e722172a1a9..224d5b67bb56 100644 --- a/dotnet/src/Experimental/Process.Abstractions/KernelProcessEdge.cs +++ b/dotnet/src/Experimental/Process.Abstractions/KernelProcessEdge.cs @@ -14,13 +14,13 @@ public sealed class KernelProcessEdge /// /// The unique identifier of the source Step. /// - [DataMember(Name = "sourceStepId")] + [DataMember] public string SourceStepId { get; init; } /// /// The collection of s that are the output of the source Step. /// - [DataMember(Name = "outputTarget")] + [DataMember] public KernelProcessFunctionTarget OutputTarget { get; init; } /// diff --git a/dotnet/src/Experimental/Process.Abstractions/KernelProcessFunctionTarget.cs b/dotnet/src/Experimental/Process.Abstractions/KernelProcessFunctionTarget.cs index 5d6607d7e88a..025382352709 100644 --- a/dotnet/src/Experimental/Process.Abstractions/KernelProcessFunctionTarget.cs +++ b/dotnet/src/Experimental/Process.Abstractions/KernelProcessFunctionTarget.cs @@ -27,24 +27,24 @@ public KernelProcessFunctionTarget(string stepId, string functionName, string? p /// /// The unique identifier of the Step being targeted. /// - [DataMember(Name = "stepId")] + [DataMember] public string StepId { get; init; } /// /// The name if the Kernel Function to target. /// - [DataMember(Name = "functionName")] + [DataMember] public string FunctionName { get; init; } /// /// The name of the parameter to target. This may be null if the function has no parameters. /// - [DataMember(Name = "parameterName")] + [DataMember] public string? ParameterName { get; init; } /// /// The unique identifier for the event to target. This may be null if the target is not a sub-process. /// - [DataMember(Name = "targetEventId")] + [DataMember] public string? TargetEventId { get; init; } } diff --git a/dotnet/src/Experimental/Process.Abstractions/KernelProcessStepInfo.cs b/dotnet/src/Experimental/Process.Abstractions/KernelProcessStepInfo.cs index 7273ccf875ea..7665a5bdcc58 100644 --- a/dotnet/src/Experimental/Process.Abstractions/KernelProcessStepInfo.cs +++ b/dotnet/src/Experimental/Process.Abstractions/KernelProcessStepInfo.cs @@ -54,5 +54,8 @@ public KernelProcessStepInfo(Type innerStepType, KernelProcessStepState state, D this.InnerStepType = innerStepType; this._outputEdges = edges; this._state = state; + + // Register the state as a know type for the DataContractSerialization used by Dapr. + KernelProcessState.RegisterDerivedType(state.GetType()); } } diff --git a/dotnet/src/Experimental/Process.Abstractions/KernelProcessStepState.cs b/dotnet/src/Experimental/Process.Abstractions/KernelProcessStepState.cs index 569a617713ae..f38eb23da397 100644 --- a/dotnet/src/Experimental/Process.Abstractions/KernelProcessStepState.cs +++ b/dotnet/src/Experimental/Process.Abstractions/KernelProcessStepState.cs @@ -24,18 +24,28 @@ public record KernelProcessStepState /// private static HashSet GetKnownTypes() => s_knownTypes; + /// + /// Registers a derived type for serialization. Types registered here are used by the KnownType attribute + /// to support DataContractSerialization of derived types as required to support Dapr. + /// + /// A Type that derives from + internal static void RegisterDerivedType(Type derivedType) + { + s_knownTypes.Add(derivedType); + } + /// /// The identifier of the Step which is required to be unique within an instance of a Process. /// This may be null until a process containing this step has been invoked. /// - [DataMember(Name = "id")] + [DataMember] public string? Id { get; init; } /// /// The name of the Step. This is intended to be human readable and is not required to be unique. If /// not provided, the name will be derived from the steps .NET type. /// - [DataMember(Name = "name")] + [DataMember] public string Name { get; init; } /// @@ -50,16 +60,6 @@ public KernelProcessStepState(string name, string? id = null) this.Id = id; this.Name = name; } - - /// - /// Registers a derived type for serialization. Types registered here are used by the KnownType attribute - /// to support DataContractSerialization of derived types as required to support Dapr. - /// - /// A Type that derives from - public static void RegisterDerivedType(Type derivedType) - { - s_knownTypes.Add(derivedType); - } } /// diff --git a/dotnet/src/Experimental/Process.Core/ProcessBuilder.cs b/dotnet/src/Experimental/Process.Core/ProcessBuilder.cs index 93af3215f6e9..13c020150bc2 100644 --- a/dotnet/src/Experimental/Process.Core/ProcessBuilder.cs +++ b/dotnet/src/Experimental/Process.Core/ProcessBuilder.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Microsoft.SemanticKernel.Process; namespace Microsoft.SemanticKernel; @@ -110,18 +109,6 @@ internal override KernelProcessStepInfo BuildStep() /// An instance of public ProcessStepBuilder AddStepFromType(string? name = null) where TStep : KernelProcessStep { - // If the step has a user-defined state then we need to register it as a know type for the DataContractSerialization used by Dapr. - if (typeof(TStep).TryGetSubtypeOfStatefulStep(out Type? genericStepType) && genericStepType is not null) - { - // The step is a subclass of KernelProcessStep<>, so we need to extract the generic type argument - // and create an instance of the corresponding KernelProcessStepState<>. - var userStateType = genericStepType.GetGenericArguments()[0]; - Verify.NotNull(userStateType); - - var stateType = typeof(KernelProcessStepState<>).MakeGenericType(userStateType); - KernelProcessState.RegisterDerivedType(stateType); - } - var stepBuilder = new ProcessStepBuilder(name); this._steps.Add(stepBuilder); @@ -138,10 +125,6 @@ public ProcessStepBuilder AddStepFromType(string? name = null) where TSte /// An instance of public ProcessStepBuilder AddStepFromType(TState initialState, string? name = null) where TStep : KernelProcessStep where TState : class, new() { - // The step has a user-defined state so we need to register it as a know type for the DataContractSerialization used by Dapr. - var stateType = typeof(KernelProcessStepState); - KernelProcessState.RegisterDerivedType(stateType); - var stepBuilder = new ProcessStepBuilder(name, initialState); this._steps.Add(stepBuilder); diff --git a/dotnet/src/Experimental/Process.Core/ProcessStepBuilder.cs b/dotnet/src/Experimental/Process.Core/ProcessStepBuilder.cs index 841875e164a1..d9dc5098429f 100644 --- a/dotnet/src/Experimental/Process.Core/ProcessStepBuilder.cs +++ b/dotnet/src/Experimental/Process.Core/ProcessStepBuilder.cs @@ -204,7 +204,7 @@ public sealed class ProcessStepBuilder : ProcessStepBuilder where TStep : /// /// Optional: The name of the step. /// Optional: The initial state of the step. - public ProcessStepBuilder(string? name = null, object? initialState = default) + internal ProcessStepBuilder(string? name = null, object? initialState = default) : base(name ?? typeof(TStep).Name) { this.FunctionsDict = this.GetFunctionMetadataMap(); diff --git a/dotnet/src/Experimental/Process.LocalRuntime/LocalProcess.cs b/dotnet/src/Experimental/Process.LocalRuntime/LocalProcess.cs index d26fd7ec3fed..9b284d697b7d 100644 --- a/dotnet/src/Experimental/Process.LocalRuntime/LocalProcess.cs +++ b/dotnet/src/Experimental/Process.LocalRuntime/LocalProcess.cs @@ -304,7 +304,7 @@ private async Task Internal_ExecuteAsync(Kernel? kernel = null, int maxSuperstep /// /// Processes external events that have been sent to the process, translates them to s, and enqueues - /// them to the provided message channel so that they can be processesed in the next superstep. + /// them to the provided message channel so that they can be processed in the next superstep. /// /// The message channel where messages should be enqueued. private void EnqueueExternalMessages(Queue messageChannel) @@ -324,7 +324,7 @@ private void EnqueueExternalMessages(Queue messageChannel) /// /// Processes events emitted by the given step in the last superstep, translates them to s, and enqueues - /// them to the provided message channel so that they can be processesed in the next superstep. + /// them to the provided message channel so that they can be processed in the next superstep. /// /// The step containing outgoing events to process. /// The message channel where messages should be enqueued. diff --git a/dotnet/src/Experimental/Process.LocalRuntime/LocalStep.cs b/dotnet/src/Experimental/Process.LocalRuntime/LocalStep.cs index f5257415ef21..acdd66c556a1 100644 --- a/dotnet/src/Experimental/Process.LocalRuntime/LocalStep.cs +++ b/dotnet/src/Experimental/Process.LocalRuntime/LocalStep.cs @@ -264,7 +264,6 @@ protected virtual async ValueTask InitializeStepAsync() { // The step is a KernelProcessStep with no user-defined state, so we can use the base KernelProcessStepState. stateType = typeof(KernelProcessStepState); - //stateObject = new KernelProcessStepState(this.Name, this.Id); } if (stateObject is null)