Skip to content

Commit

Permalink
.Net: Plans using FunctionRunner + RequestSettings (#3264)
Browse files Browse the repository at this point in the history
### Motivation and Context

Plans now fully utilize Kernel.RunAsync thru FunctionRunner interface,
this will allow Plan steps to trigger Pre/Post Hooks defined in the
Kernel that the Plan was ran.

Option to override the RequestSettings in the Kernel.RunAsync call. 
This is was required while running Plan steps that by default will use
the current Plan settings.

One more step towards using RunAsync as the main entry point + Pre/Post
hooks phase 2.

Resolves Partially #2324 

### Description

- Kernel.RunAsync optionally accepts `AIRequestSettings`
- PlanSteps now run in the Kernel and can trigger Pre/Post Hooks 

### Contribution Checklist

- [x] The code builds clean without any errors or warnings
- [x] The PR follows the [SK Contribution
Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md)
and the [pre-submission formatting
script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts)
raises no violations
- [x] All unit tests pass, and I have added new tests where possible
- [x] I didn't break anyone 😄
  • Loading branch information
RogerBarreto authored Oct 24, 2023
1 parent ba40209 commit 0c1b669
Show file tree
Hide file tree
Showing 10 changed files with 97 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ public async Task ItCanCreatePlanAsync(string goal)
// Arrange
var kernel = new Mock<IKernel>();
kernel.Setup(x => x.LoggerFactory).Returns(new Mock<ILoggerFactory>().Object);
kernel.Setup(x => x.RunAsync(It.IsAny<ContextVariables>(), It.IsAny<CancellationToken>(), It.IsAny<ISKFunction>()))
.Returns<ContextVariables, CancellationToken, ISKFunction[]>(async (vars, cancellationToken, functions) =>
kernel.Setup(x => x.RunAsync(It.IsAny<ContextVariables>(), It.IsAny<AIRequestSettings?>(), It.IsAny<CancellationToken>(), It.IsAny<ISKFunction>()))
.Returns<ContextVariables, AIRequestSettings, CancellationToken, ISKFunction[]>(async (vars, requestSettings, cancellationToken, functions) =>
{
var functionResult = await functions[0].InvokeAsync(kernel.Object, vars, cancellationToken: cancellationToken);
return KernelResult.FromFunctionResults(functionResult.GetValue<string>(), new List<FunctionResult> { functionResult });
Expand Down Expand Up @@ -173,8 +173,8 @@ public async Task InvalidXMLThrowsAsync()

// Mock Plugins
kernel.Setup(x => x.Functions).Returns(functions.Object);
kernel.Setup(x => x.RunAsync(It.IsAny<ContextVariables>(), It.IsAny<CancellationToken>(), It.IsAny<ISKFunction>()))
.Returns<ContextVariables, CancellationToken, ISKFunction[]>(async (vars, cancellationToken, functions) =>
kernel.Setup(x => x.RunAsync(It.IsAny<ContextVariables>(), It.IsAny<AIRequestSettings?>(), It.IsAny<CancellationToken>(), It.IsAny<ISKFunction>()))
.Returns<ContextVariables, AIRequestSettings, CancellationToken, ISKFunction[]>(async (vars, requestSettings, cancellationToken, functions) =>
{
var functionResult = await functions[0].InvokeAsync(kernel.Object, vars, cancellationToken: cancellationToken);
return KernelResult.FromFunctionResults(functionResult.GetValue<string>(), new List<FunctionResult> { functionResult });
Expand Down
5 changes: 4 additions & 1 deletion dotnet/src/SemanticKernel.Abstractions/IKernel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel.AI;
using Microsoft.SemanticKernel.Events;
using Microsoft.SemanticKernel.Http;
using Microsoft.SemanticKernel.Memory;
Expand Down Expand Up @@ -52,12 +53,14 @@ public interface IKernel
/// Run a pipeline composed of synchronous and asynchronous functions.
/// </summary>
/// <param name="variables">Input to process</param>
/// <param name="requestSettings">Request settings to use in the functions call</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <param name="pipeline">List of functions</param>
/// <returns>Result of the function composition</returns>
Task<KernelResult> RunAsync(
ContextVariables variables,
CancellationToken cancellationToken,
AIRequestSettings? requestSettings = null,
CancellationToken cancellationToken = default,
params ISKFunction[] pipeline);

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

using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.SemanticKernel.Orchestration;

/// <summary>
/// Function runner extensions.
/// </summary>
public static class FunctionRunnerExtensions
{
/// <summary>
/// Execute a function using the resources loaded in the context.
/// </summary>
/// <param name="functionRunner">Target function runner</param>
/// <param name="skFunction">Target function to run</param>
/// <param name="variables">Input to process</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>Result of the function composition</returns>
public static Task<FunctionResult> RunAsync(
this IFunctionRunner functionRunner,
ISKFunction skFunction,
ContextVariables? variables = null,
CancellationToken cancellationToken = default)
{
return functionRunner.RunAsync(skFunction, variables, null, cancellationToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

using System.Threading;
using System.Threading.Tasks;
using Microsoft.SemanticKernel.AI;

namespace Microsoft.SemanticKernel.Orchestration;

Expand All @@ -15,11 +16,13 @@ public interface IFunctionRunner
/// </summary>
/// <param name="skFunction">Target function to run</param>
/// <param name="variables">Input to process</param>
/// <param name="requestSettings">Request settings to override</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>Result of the function composition</returns>
Task<FunctionResult> RunAsync(
ISKFunction skFunction,
ContextVariables? variables = null,
AIRequestSettings? requestSettings = null,
CancellationToken cancellationToken = default);

/// <summary>
Expand Down
5 changes: 3 additions & 2 deletions dotnet/src/SemanticKernel.Core/Kernel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.SemanticKernel.AI;
using Microsoft.SemanticKernel.Diagnostics;
using Microsoft.SemanticKernel.Events;
using Microsoft.SemanticKernel.Http;
Expand Down Expand Up @@ -97,7 +98,7 @@ public ISKFunction RegisterCustomFunction(ISKFunction customFunction)
}

/// <inheritdoc/>
public async Task<KernelResult> RunAsync(ContextVariables variables, CancellationToken cancellationToken, params ISKFunction[] pipeline)
public async Task<KernelResult> RunAsync(ContextVariables variables, AIRequestSettings? requestSettings, CancellationToken cancellationToken = default, params ISKFunction[] pipeline)
{
var context = this.CreateNewContext(variables);

Expand Down Expand Up @@ -128,7 +129,7 @@ public async Task<KernelResult> RunAsync(ContextVariables variables, Cancellatio
continue;
}

functionResult = await skFunction.InvokeAsync(context, cancellationToken: cancellationToken).ConfigureAwait(false);
functionResult = await skFunction.InvokeAsync(context, requestSettings, cancellationToken).ConfigureAwait(false);

context = functionResult.Context;

Expand Down
27 changes: 23 additions & 4 deletions dotnet/src/SemanticKernel.Core/KernelExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,26 @@ public static Task<KernelResult> RunAsync(
CancellationToken cancellationToken = default)
{
Verify.NotNull(kernel);
return kernel.RunAsync(variables ?? new(), cancellationToken, skFunction);
return kernel.RunAsync(variables ?? new(), null, cancellationToken, skFunction);
}

/// <summary>
/// Run a pipeline composed of synchronous and asynchronous functions.
/// </summary>
/// <param name="kernel">The kernel.</param>
/// <param name="variables">Variables to process</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <param name="pipeline">List of functions</param>
/// <returns>Result of the function composition</returns>
public static Task<KernelResult> RunAsync(
this IKernel kernel,
ContextVariables variables,
CancellationToken cancellationToken = default,
params ISKFunction[] pipeline)
{
Verify.NotNull(kernel);

return kernel.RunAsync(variables, null, cancellationToken, pipeline);
}

/// <summary>
Expand Down Expand Up @@ -131,7 +150,7 @@ public static Task<KernelResult> RunAsync(
params ISKFunction[] pipeline)
{
Verify.NotNull(kernel);
return kernel.RunAsync(variables, CancellationToken.None, pipeline);
return kernel.RunAsync(variables, null, CancellationToken.None, pipeline);
}

/// <summary>
Expand All @@ -147,7 +166,7 @@ public static Task<KernelResult> RunAsync(
params ISKFunction[] pipeline)
{
Verify.NotNull(kernel);
return kernel.RunAsync(new ContextVariables(), cancellationToken, pipeline);
return kernel.RunAsync(new ContextVariables(), null, cancellationToken, pipeline);
}

/// <summary>
Expand All @@ -165,6 +184,6 @@ public static Task<KernelResult> RunAsync(
params ISKFunction[] pipeline)
{
Verify.NotNull(kernel);
return kernel.RunAsync(new ContextVariables(input), cancellationToken, pipeline);
return kernel.RunAsync(new ContextVariables(input), null, cancellationToken, pipeline);
}
}
11 changes: 8 additions & 3 deletions dotnet/src/SemanticKernel.Core/Orchestration/FunctionRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.SemanticKernel.AI;

namespace Microsoft.SemanticKernel.Orchestration;

Expand All @@ -23,16 +24,20 @@ public FunctionRunner(IKernel kernel)
}

/// <inheritdoc/>
public async Task<FunctionResult> RunAsync(ISKFunction skFunction, ContextVariables? variables = null, CancellationToken cancellationToken = default)
public async Task<FunctionResult> RunAsync(
ISKFunction skFunction,
ContextVariables? variables = null,
AIRequestSettings? requestSettings = null,
CancellationToken cancellationToken = default)
{
return (await this._kernel.RunAsync(skFunction, variables, cancellationToken).ConfigureAwait(false))
return (await this._kernel.RunAsync(variables ?? new(), requestSettings, cancellationToken, skFunction).ConfigureAwait(false))
.FunctionResults.First();
}

/// <inheritdoc/>
public Task<FunctionResult> RunAsync(string pluginName, string functionName, ContextVariables? variables = null, CancellationToken cancellationToken = default)
{
var function = this._kernel.Functions.GetFunction(pluginName, functionName);
return this.RunAsync(function, variables, cancellationToken);
return this.RunAsync(function, variables, null, cancellationToken);
}
}
12 changes: 8 additions & 4 deletions dotnet/src/SemanticKernel.Core/Planning/Plan.cs
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ public async Task<Plan> InvokeNextStepAsync(SKContext context, CancellationToken
var functionVariables = this.GetNextStepVariables(context.Variables, step);

// Execute the step
var result = await context.Runner.RunAsync(step, functionVariables, cancellationToken).ConfigureAwait(false);
var result = await context.Runner.RunAsync(step, functionVariables, null, cancellationToken).ConfigureAwait(false);

var resultValue = result.Context.Result.Trim();

Expand Down Expand Up @@ -336,10 +336,14 @@ public async Task<FunctionResult> InvokeAsync(
var functionContext = context.Clone(functionVariables, context.Functions);

// Execute the step
result = await this.Function
.WithInstrumentation(context.LoggerFactory)
.InvokeAsync(functionContext, requestSettings, cancellationToken)

result = await context.Runner.RunAsync(
this.Function.WithInstrumentation(context.LoggerFactory),
functionVariables,
requestSettings,
cancellationToken)
.ConfigureAwait(false);

this.UpdateFunctionResultWithOutputs(result);
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.AI;
using Microsoft.SemanticKernel.Diagnostics;
using Microsoft.SemanticKernel.Orchestration;
using Microsoft.SemanticKernel.Planning;
Expand Down Expand Up @@ -276,8 +277,8 @@ public async Task CanStepAndSerializePlanWithStepsAsync()
returnContext.Variables.Update(returnContext.Variables.Input + c.Variables.Input))
.Returns(() => Task.FromResult(new FunctionResult("functionName", "pluginName", returnContext)));

this._functionRunner.Setup(k => k.RunAsync(It.IsAny<ISKFunction>(), It.IsAny<ContextVariables>(), It.IsAny<CancellationToken>()))
.Returns<ISKFunction, ContextVariables, CancellationToken>((function, variables, ct) =>
this._functionRunner.Setup(k => k.RunAsync(It.IsAny<ISKFunction>(), It.IsAny<ContextVariables>(), It.IsAny<AIRequestSettings?>(), It.IsAny<CancellationToken>()))
.Returns<ISKFunction, ContextVariables, AIRequestSettings, CancellationToken>((function, variables, settings, ct) =>
{
var c = new SKContext(new Mock<IFunctionRunner>().Object, this._serviceProvider.Object, variables);
returnContext.Variables.Update(returnContext.Variables.Input + c.Variables.Input);
Expand Down Expand Up @@ -355,8 +356,8 @@ public async Task CanStepAndSerializePlanWithStepsAndContextAsync()

mockFunction.Setup(x => x.Describe()).Returns(new FunctionView("functionName", "pluginName"));

this._functionRunner.Setup(k => k.RunAsync(It.IsAny<ISKFunction>(), It.IsAny<ContextVariables>(), It.IsAny<CancellationToken>()))
.Returns<ISKFunction, ContextVariables, CancellationToken>((function, variables, ct) =>
this._functionRunner.Setup(k => k.RunAsync(It.IsAny<ISKFunction>(), It.IsAny<ContextVariables>(), It.IsAny<AIRequestSettings?>(), It.IsAny<CancellationToken>()))
.Returns<ISKFunction, ContextVariables, AIRequestSettings, CancellationToken>((function, variables, settings, ct) =>
{
var c = new SKContext(new Mock<IFunctionRunner>().Object, this._serviceProvider.Object, variables);
c.Variables.TryGetValue("variables", out string? v);
Expand Down Expand Up @@ -397,7 +398,7 @@ public async Task CanStepAndSerializePlanWithStepsAndContextAsync()
// Assert
Assert.NotNull(plan);
Assert.Equal($"{stepOutput}{planInput}foo{stepOutput}{planInput}foobar", plan.State.ToString());
this._functionRunner.Verify(x => x.RunAsync(It.IsAny<ISKFunction>(), It.IsAny<ContextVariables>(), It.IsAny<CancellationToken>()), Times.Exactly(2));
this._functionRunner.Verify(x => x.RunAsync(It.IsAny<ISKFunction>(), It.IsAny<ContextVariables>(), It.IsAny<AIRequestSettings?>(), It.IsAny<CancellationToken>()), Times.Exactly(2));

// Act
var serializedPlan2 = plan.ToJson();
Expand Down Expand Up @@ -458,8 +459,8 @@ public async Task CanStepAndSerializeAndDeserializePlanWithStepsAndContextAsync(

plan.AddSteps(mockFunction.Object, mockFunction.Object);

this._functionRunner.Setup(k => k.RunAsync(It.IsAny<ISKFunction>(), It.IsAny<ContextVariables>(), It.IsAny<CancellationToken>()))
.Returns<ISKFunction, ContextVariables, CancellationToken>((function, variables, ct) =>
this._functionRunner.Setup(k => k.RunAsync(It.IsAny<ISKFunction>(), It.IsAny<ContextVariables>(), It.IsAny<AIRequestSettings?>(), It.IsAny<CancellationToken>()))
.Returns<ISKFunction, ContextVariables, AIRequestSettings, CancellationToken>((function, variables, settings, ct) =>
{
var c = new SKContext(new Mock<IFunctionRunner>().Object, this._serviceProvider.Object, variables);
c.Variables.TryGetValue("variables", out string? v);
Expand Down Expand Up @@ -498,7 +499,7 @@ public async Task CanStepAndSerializeAndDeserializePlanWithStepsAndContextAsync(
// Assert
Assert.NotNull(plan);
Assert.Equal($"{stepOutput}{planInput}foo{stepOutput}{planInput}foobar", plan.State.ToString());
this._functionRunner.Verify(x => x.RunAsync(It.IsAny<ISKFunction>(), It.IsAny<ContextVariables>(), It.IsAny<CancellationToken>()), Times.Exactly(2));
this._functionRunner.Verify(x => x.RunAsync(It.IsAny<ISKFunction>(), It.IsAny<ContextVariables>(), It.IsAny<AIRequestSettings?>(), It.IsAny<CancellationToken>()), Times.Exactly(2));

// Act
var serializedPlan2 = plan.ToJson();
Expand Down
12 changes: 6 additions & 6 deletions dotnet/src/SemanticKernel.UnitTests/Planning/PlanTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -506,8 +506,8 @@ public async Task CanExecutePlanWithStateAsync()
// Arrange
var (kernel, functionRunner, serviceProvider) = this.SetupKernelMock();

functionRunner.Setup(k => k.RunAsync(It.IsAny<ISKFunction>(), It.IsAny<ContextVariables>(), It.IsAny<CancellationToken>()))
.Returns<ISKFunction, ContextVariables, CancellationToken>(async (function, variables, ct) =>
functionRunner.Setup(k => k.RunAsync(It.IsAny<ISKFunction>(), It.IsAny<ContextVariables>(), It.IsAny<AIRequestSettings?>(), It.IsAny<CancellationToken>()))
.Returns<ISKFunction, ContextVariables, AIRequestSettings, CancellationToken>(async (function, variables, settings, ct) =>
{
var c = new SKContext(functionRunner.Object, serviceProvider.Object, variables);
var functionResult = await function.InvokeAsync(c, cancellationToken: ct);
Expand Down Expand Up @@ -668,8 +668,8 @@ public async Task CanExecutePlanWithJoinedResultAsync()
// Arrange
var (kernel, functionRunner, serviceProvider) = this.SetupKernelMock();

functionRunner.Setup(k => k.RunAsync(It.IsAny<ISKFunction>(), It.IsAny<ContextVariables>(), It.IsAny<CancellationToken>()))
.Returns<ISKFunction, ContextVariables, CancellationToken>(async (function, variables, ct) =>
functionRunner.Setup(k => k.RunAsync(It.IsAny<ISKFunction>(), It.IsAny<ContextVariables>(), It.IsAny<AIRequestSettings?>(), It.IsAny<CancellationToken>()))
.Returns<ISKFunction, ContextVariables, AIRequestSettings, CancellationToken>(async (function, variables, settings, ct) =>
{
var c = new SKContext(functionRunner.Object, serviceProvider.Object, variables);
var functionResult = await function.InvokeAsync(c, cancellationToken: ct);
Expand Down Expand Up @@ -842,8 +842,8 @@ public async Task CanExecutePlanWithExpandedAsync()
return new SKContext(functionRunner.Object, serviceProvider.Object, contextVariables, functions);
});

functionRunner.Setup(k => k.RunAsync(It.IsAny<ISKFunction>(), It.IsAny<ContextVariables>(), It.IsAny<CancellationToken>()))
.Returns<ISKFunction, ContextVariables, CancellationToken>(async (function, variables, ct) =>
functionRunner.Setup(k => k.RunAsync(It.IsAny<ISKFunction>(), It.IsAny<ContextVariables>(), It.IsAny<AIRequestSettings?>(), It.IsAny<CancellationToken>()))
.Returns<ISKFunction, ContextVariables, AIRequestSettings, CancellationToken>(async (function, variables, settings, ct) =>
{
var c = new SKContext(functionRunner.Object, serviceProvider.Object, variables);
var functionResult = await function.InvokeAsync(c, cancellationToken: ct);
Expand Down

0 comments on commit 0c1b669

Please sign in to comment.