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 c1908b8e8cdf..8ece418f3070 100644
--- a/dotnet/samples/GettingStartedWithProcesses/SharedSteps/ScriptedUserInputStep.cs
+++ b/dotnet/samples/GettingStartedWithProcesses/SharedSteps/ScriptedUserInputStep.cs
@@ -41,10 +41,9 @@ public virtual void PopulateUserInputs(UserInputState state)
/// A
public override ValueTask ActivateAsync(KernelProcessStepState 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 e474df9658c6..224d5b67bb56 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]
+ public string SourceStepId { get; init; }
///
/// The collection of s that are the output of the source Step.
///
- public KernelProcessFunctionTarget OutputTarget { get; }
+ [DataMember]
+ 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..025382352709 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]
public string StepId { get; init; }
///
/// The name if the Kernel Function to target.
///
+ [DataMember]
public string FunctionName { get; init; }
///
/// The name of the parameter to target. This may be null if the function has no parameters.
///
+ [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]
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/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 fb90d70f8d2a..f38eb23da397 100644
--- a/dotnet/src/Experimental/Process.Abstractions/KernelProcessStepState.cs
+++ b/dotnet/src/Experimental/Process.Abstractions/KernelProcessStepState.cs
@@ -1,22 +1,51 @@
// 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;
+
+ ///
+ /// 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]
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]
public string Name { get; init; }
///
@@ -37,12 +66,14 @@ public KernelProcessStepState(string name, string? id = null)
/// 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..13c020150bc2 100644
--- a/dotnet/src/Experimental/Process.Core/ProcessBuilder.cs
+++ b/dotnet/src/Experimental/Process.Core/ProcessBuilder.cs
@@ -115,6 +115,22 @@ public ProcessStepBuilder AddStepFromType(string? name = null) where TSte
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()
+ {
+ 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..d9dc5098429f 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.
+ internal 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
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;
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;
public override ValueTask ActivateAsync(KernelProcessStepState state)
{
- state.State ??= this._state;
-
+ this._state = state.State;
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()
{