Skip to content

Commit

Permalink
.Net: Augment pipelining example with named outputs (microsoft#4154)
Browse files Browse the repository at this point in the history
  • Loading branch information
stephentoub committed Dec 11, 2023
1 parent b55d433 commit 4b820dc
Showing 1 changed file with 101 additions and 29 deletions.
130 changes: 101 additions & 29 deletions dotnet/samples/KernelSyntaxExamples/Example74_Pipelining.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,34 +21,55 @@ public static class Example74_Pipelining
/// </summary>
public static async Task RunAsync()
{
// Create a pipeline of functions that will parse a string into an int, multiply it by a double, truncate it to an int, and then humanize it.
KernelFunction parseInt32 = KernelFunctionFactory.CreateFromMethod((string s) => double.Parse(s, CultureInfo.InvariantCulture), "parseInt32");
KernelFunction multiplyByN = KernelFunctionFactory.CreateFromMethod((double i, double n) => i * n, "multiplyByN");
KernelFunction truncate = KernelFunctionFactory.CreateFromMethod((double d) => (int)d, "truncate");
KernelFunction humanize = KernelFunctionFactory.CreateFromPrompt(new PromptTemplateConfig()
{
Template = "Spell out this number in English: {{$number}}",
InputVariables = new() { new() { Name = "number" } },
});
KernelFunction pipeline = KernelFunctionCombinators.Pipe(new[] { parseInt32, multiplyByN, truncate, humanize }, "pipeline");

IKernelBuilder builder = Kernel.CreateBuilder();
builder.AddOpenAIChatCompletion(
TestConfiguration.OpenAI.ChatModelId,
TestConfiguration.OpenAI.ApiKey);
builder.Services.AddLogging(c => c.AddConsole().SetMinimumLevel(LogLevel.Trace));
Kernel kernel = builder.Build();

KernelArguments args = new()
Console.WriteLine("================ PIPELINE ================");
{
["s"] = "123.456",
["n"] = (double)78.90,
};

// - The parseInt32 function will be invoked, read "123.456" from the arguments, and parse it into (double)123.456.
// - The multiplyByN function will be invoked, with i=123.456 and n=78.90, and return (double)9740.6784.
// - The truncate function will be invoked, with d=9740.6784, and return (int)9740, which will be the final result.
Console.WriteLine(await pipeline.InvokeAsync(kernel, args));
// Create a pipeline of functions that will parse a string into an int, multiply it by a double, truncate it to an int, and then humanize it.
KernelFunction parseInt32 = KernelFunctionFactory.CreateFromMethod((string s) => double.Parse(s, CultureInfo.InvariantCulture), "parseInt32");
KernelFunction multiplyByN = KernelFunctionFactory.CreateFromMethod((double i, double n) => i * n, "multiplyByN");
KernelFunction truncate = KernelFunctionFactory.CreateFromMethod((double d) => (int)d, "truncate");
KernelFunction humanize = KernelFunctionFactory.CreateFromPrompt(new PromptTemplateConfig()
{
Template = "Spell out this number in English: {{$number}}",
InputVariables = new() { new() { Name = "number" } },
});
KernelFunction pipeline = KernelFunctionCombinators.Pipe(new[] { parseInt32, multiplyByN, truncate, humanize }, "pipeline");

KernelArguments args = new()
{
["s"] = "123.456",
["n"] = (double)78.90,
};

// - The parseInt32 function will be invoked, read "123.456" from the arguments, and parse it into (double)123.456.
// - The multiplyByN function will be invoked, with i=123.456 and n=78.90, and return (double)9740.6784.
// - The truncate function will be invoked, with d=9740.6784, and return (int)9740, which will be the final result.
Console.WriteLine(await pipeline.InvokeAsync(kernel, args));
}

Console.WriteLine("================ GRAPH ================");
{
KernelFunction rand = KernelFunctionFactory.CreateFromMethod(() => Random.Shared.Next(), "GetRandomInt32");
KernelFunction mult = KernelFunctionFactory.CreateFromMethod((int i, int j) => i * j, "Multiply");

// - Invokes rand and stores the random number into args["i"]
// - Invokes rand and stores the random number into args["j"]
// - Multiplies arg["i"] and args["j"] to produce the final result
KernelFunction graph = KernelFunctionCombinators.Pipe(new[]
{
(rand, "i"),
(rand, "j"),
(mult, "")
}, "graph");

Console.WriteLine(await graph.InvokeAsync(kernel));
}
}
}

Expand All @@ -57,7 +78,7 @@ public static class KernelFunctionCombinators
/// <summary>
/// Invokes a pipeline of functions, running each in order and passing the output from one as the first argument to the next.
/// </summary>
/// <param name="functions">The functions to invoke.</param>
/// <param name="functions">The pipeline of functions to invoke.</param>
/// <param name="kernel">The kernel to use for the operations.</param>
/// <param name="arguments">The arguments.</param>
/// <param name="cancellationToken">The cancellation token to monitor for a cancellation request.</param>
Expand All @@ -66,6 +87,18 @@ public static class KernelFunctionCombinators
IEnumerable<KernelFunction> functions, Kernel kernel, KernelArguments arguments, CancellationToken cancellationToken) =>
Pipe(functions).InvokeAsync(kernel, arguments, cancellationToken);

/// <summary>
/// Invokes a pipeline of functions, running each in order and passing the output from one as the named argument to the next.
/// </summary>
/// <param name="functions">The sequence of functions to invoke, along with the name of the argument to assign to the result of the function's invocation.</param>
/// <param name="kernel">The kernel to use for the operations.</param>
/// <param name="arguments">The arguments.</param>
/// <param name="cancellationToken">The cancellation token to monitor for a cancellation request.</param>
/// <returns></returns>
public static Task<FunctionResult> InvokePipelineAsync(
IEnumerable<(KernelFunction Function, string OutputVariable)> functions, Kernel kernel, KernelArguments arguments, CancellationToken cancellationToken) =>
Pipe(functions).InvokeAsync(kernel, arguments, cancellationToken);

/// <summary>
/// Creates a function whose invocation will invoke each of the supplied functions in sequence.
/// </summary>
Expand All @@ -83,22 +116,61 @@ public static class KernelFunctionCombinators
{
ArgumentNullException.ThrowIfNull(functions);

KernelFunction[] arr = functions.ToArray();
Array.ForEach(arr, f => ArgumentNullException.ThrowIfNull(f));
KernelFunction[] funcs = functions.ToArray();
Array.ForEach(funcs, f => ArgumentNullException.ThrowIfNull(f));

var funcsAndVars = new (KernelFunction Function, string OutputVariable)[funcs.Length];
for (int i = 0; i < funcs.Length; i++)
{
string p = "";
if (i < funcs.Length - 1)
{
var parameters = funcs[i + 1].Metadata.Parameters;
if (parameters.Count > 0)
{
p = parameters[0].Name;
}
}

funcsAndVars[i] = (funcs[i], p);
}

return Pipe(funcsAndVars, functionName, description);
}

/// <summary>
/// Creates a function whose invocation will invoke each of the supplied functions in sequence.
/// </summary>
/// <param name="functions">The pipeline of functions to invoke, along with the name of the argument to assign to the result of the function's invocation.</param>
/// <param name="functionName">The name of the combined operation.</param>
/// <param name="description">The description of the combined operation.</param>
/// <returns>The result of the final function.</returns>
/// <remarks>
/// The result from one function will be fed into the first argument of the next function.
/// </remarks>
public static KernelFunction Pipe(
IEnumerable<(KernelFunction Function, string OutputVariable)> functions,
string? functionName = null,
string? description = null)
{
ArgumentNullException.ThrowIfNull(functions);

(KernelFunction Function, string OutputVariable)[] arr = functions.ToArray();
Array.ForEach(arr, f =>
{
ArgumentNullException.ThrowIfNull(f.Function);
ArgumentNullException.ThrowIfNull(f.OutputVariable);
});

return KernelFunctionFactory.CreateFromMethod(async (Kernel kernel, KernelArguments arguments) =>
{
FunctionResult? result = null;
for (int i = 0; i < arr.Length; i++)
{
result = await arr[i].InvokeAsync(kernel, arguments).ConfigureAwait(false);
result = await arr[i].Function.InvokeAsync(kernel, arguments).ConfigureAwait(false);
if (i < arr.Length - 1)
{
if (arr[i + 1].Metadata.Parameters is { Count: > 0 } parameters)
{
arguments[parameters[0].Name] = result.GetValue<object>();
}
arguments[arr[i].OutputVariable] = result.GetValue<object>();
}
}
Expand Down

0 comments on commit 4b820dc

Please sign in to comment.