Skip to content

Commit

Permalink
Add Stop Strategy (#14)
Browse files Browse the repository at this point in the history
* update

* add stop strategy

* update

* update

* update

* update
  • Loading branch information
LittleLittleCloud authored Sep 12, 2024
1 parent ced2f12 commit 0108322
Show file tree
Hide file tree
Showing 15 changed files with 332 additions and 123 deletions.
10 changes: 10 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,16 @@ csharp_style_var_for_built_in_types = false:suggestion
csharp_style_var_when_type_is_apparent = false:none
csharp_style_var_elsewhere = false:suggestion

dotnet_diagnostic.IDE0035.severity = warning # Remove unreachable code
dotnet_diagnostic.IDE0161.severity = warning # Use file-scoped namespace

# Header template
file_header_template = Copyright (c) LittleLittleCloud. All rights reserved.\n{fileName}
dotnet_diagnostic.IDE0073.severity = error

# enable format error
dotnet_diagnostic.IDE0055.severity = error

# Visual Basic files
[*.vb]
# Modifier preferences
Expand Down
9 changes: 6 additions & 3 deletions example/CodeInterpreter/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using System.Text;
// Copyright (c) LittleLittleCloud. All rights reserved.
// Program.cs

using System.Text;
using System.Text.Json;
using AutoGen.Core;
using AutoGen.DotnetInteractive;
Expand Down Expand Up @@ -186,7 +189,7 @@ Fix the code according to the review.

return reply.GetContent();
}

return null;
}

Expand Down Expand Up @@ -225,7 +228,7 @@ Fix the code according to the review.
return reply is not null ? (code, reply) : (code, "disapproved");
}
}

[Step]
[DependOn(nameof(InputTask))]
public async Task<bool> IsTask(
Expand Down
7 changes: 5 additions & 2 deletions example/GetWeather/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using System.Text.Json;
// Copyright (c) LittleLittleCloud. All rights reserved.
// Program.cs

using System.Text.Json;
using Microsoft.Extensions.Logging;
using StepWise.Core;

Expand All @@ -14,7 +17,7 @@
{ "cities", StepVariable.Create(new string[] { "Seattle", "Redmond" }) }
};

await foreach(var stepResult in workflowEngine.ExecuteAsync(nameof(Workflow.GetWeatherAsync), input))
await foreach (var stepResult in workflowEngine.ExecuteAsync(nameof(Workflow.GetWeatherAsync), input))
{
if (stepResult.StepName == nameof(Workflow.GetWeatherAsync) && stepResult.Result?.As<Workflow.Weather[]>() is Workflow.Weather[] weathers)
{
Expand Down
5 changes: 4 additions & 1 deletion src/StepWise.Core/Attributes.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using System;
// Copyright (c) LittleLittleCloud. All rights reserved.
// Attributes.cs

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;
Expand Down
58 changes: 56 additions & 2 deletions src/StepWise.Core/Extension/StepWiseEngineExtension.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
using System;
// Copyright (c) LittleLittleCloud. All rights reserved.
// StepWiseEngineExtension.cs

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;

namespace StepWise.Core.Extension;
Expand All @@ -19,8 +23,22 @@ public static async Task<TResult> ExecuteAsync<TResult>(
int? maxSteps = null,
CancellationToken ct = default)
{
var stopStratgies = new List<IStepWiseEngineStopStrategy>();

if (earlyStop)
{
stopStratgies.Add(new EarlyStopStrategy(targetStepName));
}

if (maxSteps.HasValue)
{
stopStratgies.Add(new MaxStepsStopStrategy(maxSteps.Value));
}

var stopStrategy = stopStratgies.Count > 0 ? new StopStrategyPipeline(stopStratgies.ToArray()) : null;

maxSteps ??= int.MaxValue;
await foreach (var stepResult in engine.ExecuteAsync(targetStepName, inputs, earlyStop, maxSteps, ct))
await foreach (var stepResult in engine.ExecuteAsync(targetStepName, inputs, stopStrategy, ct))
{
if (stepResult.Result != null && stepResult.StepName == targetStepName)
{
Expand All @@ -30,4 +48,40 @@ public static async Task<TResult> ExecuteAsync<TResult>(

throw new Exception($"Step '{targetStepName}' did not return the expected result type.");
}

/// <summary>
/// Execute the workflow until the target step is reached or no further steps can be executed.
/// If the <paramref name="earlyStop"/> is true, the workflow will stop as soon as the target step is reached and completed.
/// Otherwise, the workflow will continue to execute until no further steps can be executed.
/// </summary>
public static async IAsyncEnumerable<StepRunAndResult> ExecuteAsync(
this IStepWiseEngine engine,
string targetStepName,
Dictionary<string, StepVariable>? inputs = null,
bool earlyStop = true,
int? maxSteps = null,
[EnumeratorCancellation]
CancellationToken ct = default)
{
var stopStratgies = new List<IStepWiseEngineStopStrategy>();

if (earlyStop)
{
stopStratgies.Add(new EarlyStopStrategy(targetStepName));
}

if (maxSteps.HasValue)
{
stopStratgies.Add(new MaxStepsStopStrategy(maxSteps.Value));
}

var stopStrategy = stopStratgies.Count > 0 ? new StopStrategyPipeline(stopStratgies.ToArray()) : null;

maxSteps ??= int.MaxValue;

await foreach (var stepResult in engine.ExecuteAsync(targetStepName, inputs, stopStrategy, ct))
{
yield return stepResult;
}
}
}
117 changes: 113 additions & 4 deletions src/StepWise.Core/IStepWiseEngine.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@

// Copyright (c) LittleLittleCloud. All rights reserved.
// IStepWiseEngine.cs

namespace StepWise.Core;

public interface IStepWiseEngine
Expand All @@ -7,10 +9,117 @@ public interface IStepWiseEngine
/// Execute the workflow until the target step is reached or no further steps can be executed.
/// If the <paramref name="earlyStop"/> is true, the workflow will stop as soon as the target step is reached and completed.
/// Otherwise, the workflow will continue to execute until no further steps can be executed.
IAsyncEnumerable<StepResult> ExecuteAsync(
IAsyncEnumerable<StepRunAndResult> ExecuteAsync(
string targetStep,
Dictionary<string, StepVariable>? inputs = null,
bool earlyStop = true,
int? maxSteps = null,
IStepWiseEngineStopStrategy? stopStrategy = null,
CancellationToken ct = default);
}

public interface IStepWiseEngineStopStrategy
{
string Name { get; }

bool ShouldStop(StepRunAndResult[] stepResult);
}

/// <summary>
/// Stops the workflow when any of the strategies in the pipeline returns true.
/// </summary>
public class StopStrategyPipeline : IStepWiseEngineStopStrategy
{
private readonly IStepWiseEngineStopStrategy[] _strategies;

public StopStrategyPipeline(params IStepWiseEngineStopStrategy[] strategies)
{
_strategies = strategies;
}

public string Name => this.ToString();

public bool ShouldStop(StepRunAndResult[] stepResult)
{
return _strategies.Any(x => x.ShouldStop(stepResult));
}

public override string ToString()
{
// [stragegy1]=>[strategy2]=>[strategy3]
return string.Join("=>", _strategies.Select(x => x.Name));
}
}

/// <summary>
/// Stop strategy that stops the workflow after a certain number of steps.
/// </summary>
public class MaxStepsStopStrategy : IStepWiseEngineStopStrategy
{
private readonly int _maxSteps;

public MaxStepsStopStrategy(int maxSteps)
{
_maxSteps = maxSteps;
}

public string Name => nameof(MaxStepsStopStrategy);

public bool ShouldStop(StepRunAndResult[] stepResult)
{
return stepResult.Length >= _maxSteps;
}
}

/// <summary>
/// Early stop strategy that stops the workflow as soon as the target step is reached and returns a result.
/// </summary>
public class EarlyStopStrategy : IStepWiseEngineStopStrategy
{
private readonly string _targetStep;

public EarlyStopStrategy(string targetStep)
{
_targetStep = targetStep;
}

public string Name => nameof(EarlyStopStrategy);

public bool ShouldStop(StepRunAndResult[] stepResult)
{
return stepResult.Any(x => x.StepName == _targetStep && x.Result != null);
}
}

public class DelegateStopStrategy : IStepWiseEngineStopStrategy
{
private readonly Func<StepRunAndResult[], bool> _shouldStop;

private DelegateStopStrategy(Func<StepRunAndResult[], bool> shouldStop)
{
_shouldStop = shouldStop;
}

public string Name => nameof(DelegateStopStrategy);

public static DelegateStopStrategy Create(Func<StepRunAndResult[], bool> shouldStop)
{
return new DelegateStopStrategy(shouldStop);
}

public bool ShouldStop(StepRunAndResult[] stepResult)
{
return _shouldStop(stepResult);
}
}

/// <summary>
/// Stop strategy that never stops the workflow.
/// </summary>
public class NeverStopStopStrategy : IStepWiseEngineStopStrategy
{
public string Name => nameof(NeverStopStopStrategy);

public bool ShouldStop(StepRunAndResult[] stepResult)
{
return false;
}
}
5 changes: 4 additions & 1 deletion src/StepWise.Core/Parameter.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using System;
// Copyright (c) LittleLittleCloud. All rights reserved.
// Parameter.cs

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
Expand Down
24 changes: 16 additions & 8 deletions src/StepWise.Core/Step.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using System.Reflection;
// Copyright (c) LittleLittleCloud. All rights reserved.
// Step.cs

using System.Reflection;

namespace StepWise.Core;

Expand Down Expand Up @@ -41,7 +44,7 @@ public static Step CreateFromMethod(Delegate stepMethod)
dependencies.Add(attr.Name);
}

foreach ( var param in parameters )
foreach (var param in parameters)
{
var sourceStep = param.GetCustomAttribute<FromStepAttribute>()?.Name;
var hasDefaultValue = param.HasDefaultValue;
Expand Down Expand Up @@ -211,25 +214,30 @@ public override string ToString()
}
}

public class StepResult
public class StepRunAndResult
{
public StepResult(StepRun stepBean, object? result)
public StepRunAndResult(StepRun stepBean, StepVariable? result)
{
StepRun = stepBean;
Result = result is null ? null : StepVariable.Create(result, stepBean.Generation);
Result = result;
}

public static StepResult Create(StepRun stepBean, object? result)
public static StepRunAndResult Create(StepRun stepBean, StepVariable? result = null)
{
return new StepResult(stepBean, result);
return new StepRunAndResult(stepBean, result);
}

public StepRun StepRun { get; }

public string StepName => StepRun.Step.Name;

/// <summary>
/// The result of the step.
/// The result of the step. It can be null if the step doesn't return a value.
/// </summary>
public StepVariable? Result { get; }

public override string ToString()
{
return $"{StepRun} => {Result?.Value}";
}
}
Loading

0 comments on commit 0108322

Please sign in to comment.