Skip to content

Commit

Permalink
.Net: Added example how to get list of function calls in auto functio…
Browse files Browse the repository at this point in the history
…n invocation filter (#6800)

### Motivation and Context

<!-- Thank you for your contribution to the semantic-kernel repo!
Please help reviewers and future users, providing the following
information:
  1. Why is this change required?
  2. What problem does it solve?
  3. What scenario does it contribute to?
  4. If it fixes an open issue, please link to the issue here.
-->

Related: #6638
Closes: #6645

This example shows how to get information about all function calls
before executing any function in function calling.

### Contribution Checklist

<!-- Before submitting this PR, please make sure: -->

- [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
dmytrostruk committed Jun 18, 2024
1 parent e244dad commit 38f81cc
Showing 1 changed file with 92 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ namespace Filtering;

public class AutoFunctionInvocationFiltering(ITestOutputHelper output) : BaseTest(output)
{
/// <summary>
/// Shows how to use <see cref="IAutoFunctionInvocationFilter"/>.
/// </summary>
[Fact]
public async Task AutoFunctionInvocationFilterAsync()
{
Expand All @@ -16,7 +19,7 @@ public async Task AutoFunctionInvocationFilterAsync()
builder.AddOpenAIChatCompletion("gpt-4", TestConfiguration.OpenAI.ApiKey);

// This filter outputs information about auto function invocation and returns overridden result.
builder.Services.AddSingleton<IAutoFunctionInvocationFilter>(new AutoFunctionInvocationFilterExample(this.Output));
builder.Services.AddSingleton<IAutoFunctionInvocationFilter>(new AutoFunctionInvocationFilter(this.Output));

var kernel = builder.Build();

Expand All @@ -40,11 +43,56 @@ public async Task AutoFunctionInvocationFilterAsync()
// Result from auto function invocation filter.
}

/// <summary>Shows syntax for auto function invocation filter.</summary>
private sealed class AutoFunctionInvocationFilterExample(ITestOutputHelper output) : IAutoFunctionInvocationFilter
/// <summary>
/// Shows how to get list of function calls by using <see cref="IAutoFunctionInvocationFilter"/>.
/// </summary>
[Fact]
public async Task GetFunctionCallsWithFilterAsync()
{
private readonly ITestOutputHelper _output = output;
var builder = Kernel.CreateBuilder();

builder.AddOpenAIChatCompletion("gpt-3.5-turbo-1106", TestConfiguration.OpenAI.ApiKey);

builder.Services.AddSingleton<IAutoFunctionInvocationFilter>(new FunctionCallsFilter(this.Output));

var kernel = builder.Build();

kernel.ImportPluginFromFunctions("HelperFunctions",
[
kernel.CreateFunctionFromMethod(() => DateTime.UtcNow.ToString("R"), "GetCurrentUtcTime", "Retrieves the current time in UTC."),
kernel.CreateFunctionFromMethod((string cityName) =>
cityName switch
{
"Boston" => "61 and rainy",
"London" => "55 and cloudy",
"Miami" => "80 and sunny",
"Paris" => "60 and rainy",
"Tokyo" => "50 and sunny",
"Sydney" => "75 and sunny",
"Tel Aviv" => "80 and sunny",
_ => "31 and snowing",
}, "GetWeatherForCity", "Gets the current weather for the specified city"),
]);

var executionSettings = new OpenAIPromptExecutionSettings
{
ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions
};

await foreach (var chunk in kernel.InvokePromptStreamingAsync("Check current UTC time and return current weather in Boston city.", new(executionSettings)))
{
Console.WriteLine(chunk.ToString());
}

// Output:
// Request #0. Function call: HelperFunctions.GetCurrentUtcTime.
// Request #0. Function call: HelperFunctions.GetWeatherForCity.
// The current UTC time is {time of execution}, and the current weather in Boston is 61°F and rainy.
}

/// <summary>Shows available syntax for auto function invocation filter.</summary>
private sealed class AutoFunctionInvocationFilter(ITestOutputHelper output) : IAutoFunctionInvocationFilter
{
public async Task OnAutoFunctionInvocationAsync(AutoFunctionInvocationContext context, Func<AutoFunctionInvocationContext, Task> next)
{
// Example: get function information
Expand All @@ -56,14 +104,31 @@ public async Task OnAutoFunctionInvocationAsync(AutoFunctionInvocationContext co
// Example: get information about all functions which will be invoked
var functionCalls = FunctionCallContent.GetFunctionCalls(context.ChatHistory.Last());

// In function calling functionality there are two loops.
// Outer loop is "request" loop - it performs multiple requests to LLM until user ask will be satisfied.
// Inner loop is "function" loop - it handles LLM response with multiple function calls.

// Workflow example:
// 1. Request to LLM #1 -> Response with 3 functions to call.
// 1.1. Function #1 called.
// 1.2. Function #2 called.
// 1.3. Function #3 called.
// 2. Request to LLM #2 -> Response with 2 functions to call.
// 2.1. Function #1 called.
// 2.2. Function #2 called.

// context.RequestSequenceIndex - it's a sequence number of outer/request loop operation.
// context.FunctionSequenceIndex - it's a sequence number of inner/function loop operation.
// context.FunctionCount - number of functions which will be called per request (based on example above: 3 for first request, 2 for second request).

// Example: get request sequence index
this._output.WriteLine($"Request sequence index: {context.RequestSequenceIndex}");
output.WriteLine($"Request sequence index: {context.RequestSequenceIndex}");

// Example: get function sequence index
this._output.WriteLine($"Function sequence index: {context.FunctionSequenceIndex}");
output.WriteLine($"Function sequence index: {context.FunctionSequenceIndex}");

// Example: get total number of functions which will be called
this._output.WriteLine($"Total number of functions: {context.FunctionCount}");
output.WriteLine($"Total number of functions: {context.FunctionCount}");

// Calling next filter in pipeline or function itself.
// By skipping this call, next filters and function won't be invoked, and function call loop will proceed to the next function.
Expand All @@ -79,4 +144,24 @@ public async Task OnAutoFunctionInvocationAsync(AutoFunctionInvocationContext co
context.Terminate = true;
}
}

/// <summary>Shows how to get list of all function calls per request.</summary>
private sealed class FunctionCallsFilter(ITestOutputHelper output) : IAutoFunctionInvocationFilter
{
public async Task OnAutoFunctionInvocationAsync(AutoFunctionInvocationContext context, Func<AutoFunctionInvocationContext, Task> next)
{
var chatHistory = context.ChatHistory;
var functionCalls = FunctionCallContent.GetFunctionCalls(chatHistory.Last()).ToArray();

if (functionCalls is { Length: > 0 })
{
foreach (var functionCall in functionCalls)
{
output.WriteLine($"Request #{context.RequestSequenceIndex}. Function call: {functionCall.PluginName}.{functionCall.FunctionName}.");
}
}

await next(context);
}
}
}

0 comments on commit 38f81cc

Please sign in to comment.