-
Notifications
You must be signed in to change notification settings - Fork 4k
.Net: AutoFunctionInvocation IChatClient Support #11536
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
RogerBarreto
merged 27 commits into
microsoft:feature-msextensions-ai
from
RogerBarreto:issues/10730-autoinvocationfilter-ichatclient-support
Apr 16, 2025
Merged
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
fa1776f
AddChatClient OpenAI WIP
RogerBarreto 69ff2c2
Adding UT and Extension Methods
RogerBarreto dbd0aab
Function Call impl
RogerBarreto 9c892e1
Auto vs KernelFIC
RogerBarreto be5583e
AutoFunctionInvocationContext as KFIC
RogerBarreto 8663ab0
AutoFunctionInvocation WIP
RogerBarreto 704c80b
AutoContext losing result
RogerBarreto e4ae494
FilterCanOverrideArguments
RogerBarreto 7a5fc08
Fix failing UT + Aded PromptExecutionSettings to be propagated into A…
RogerBarreto 11de110
Resolving UT for FilterCanHandleException
RogerBarreto 85c8cc4
Adjust for HandleExceptionONStreaming
RogerBarreto ead8061
Added all behavior except Skipping
RogerBarreto df4b108
Fix warnings
RogerBarreto ffc01d9
AutoINvocation Skipping and ChatReducer passing
RogerBarreto f7ee7a2
Fix CallId optionality
RogerBarreto 7344afd
Fix warnings
RogerBarreto de4aa18
Merge branch 'feature-msextensions-ai' of https://github.com/microsof…
RogerBarreto 196fa98
Fix 9.4.0 conflicts and errors
RogerBarreto 83c2e18
Internalizing components
RogerBarreto c3b89d8
Starting update of FunctionInvokingChatClient
RogerBarreto 3d56b95
Using Extensions AI + Latest logic for FunctionINvokingChatClient
RogerBarreto 6d7a374
Removing KernelFunctionINvocationContext in favor of Microsoft.Extens…
RogerBarreto 7a38027
Fix reference
RogerBarreto c989c14
Typo fix
RogerBarreto 4316651
Fix typos + virtual to private
RogerBarreto cb636e9
Address PR comments
RogerBarreto 7043c72
Address PR feedback
RogerBarreto File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
167 changes: 167 additions & 0 deletions
167
dotnet/samples/Concepts/Filtering/ChatClient_AutoFunctionInvocationFiltering.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
|
||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.SemanticKernel; | ||
using Microsoft.SemanticKernel.Connectors.OpenAI; | ||
|
||
namespace Filtering; | ||
|
||
public class ChatClient_AutoFunctionInvocationFiltering(ITestOutputHelper output) : BaseTest(output) | ||
{ | ||
/// <summary> | ||
/// Shows how to use <see cref="IAutoFunctionInvocationFilter"/>. | ||
/// </summary> | ||
[Fact] | ||
public async Task UsingAutoFunctionInvocationFilter() | ||
{ | ||
var builder = Kernel.CreateBuilder(); | ||
|
||
builder.AddOpenAIChatClient("gpt-4", TestConfiguration.OpenAI.ApiKey); | ||
|
||
// This filter outputs information about auto function invocation and returns overridden result. | ||
builder.Services.AddSingleton<IAutoFunctionInvocationFilter>(new AutoFunctionInvocationFilter(this.Output)); | ||
|
||
var kernel = builder.Build(); | ||
|
||
var function = KernelFunctionFactory.CreateFromMethod(() => "Result from function", "MyFunction"); | ||
|
||
kernel.ImportPluginFromFunctions("MyPlugin", [function]); | ||
|
||
var executionSettings = new OpenAIPromptExecutionSettings | ||
{ | ||
FunctionChoiceBehavior = FunctionChoiceBehavior.Required([function], autoInvoke: true) | ||
}; | ||
|
||
var result = await kernel.InvokePromptAsync("Invoke provided function and return result", new(executionSettings)); | ||
|
||
Console.WriteLine(result); | ||
|
||
// Output: | ||
// Request sequence number: 0 | ||
// Function sequence number: 0 | ||
// Total number of functions: 1 | ||
// Result from auto function invocation filter. | ||
} | ||
|
||
/// <summary> | ||
/// Shows how to get list of function calls by using <see cref="IAutoFunctionInvocationFilter"/>. | ||
/// </summary> | ||
[Fact] | ||
public async Task GetFunctionCallsWithFilterAsync() | ||
{ | ||
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 | ||
{ | ||
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() | ||
}; | ||
|
||
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 | ||
var functionName = context.Function.Name; | ||
|
||
// Example: get chat history | ||
var chatHistory = context.ChatHistory; | ||
|
||
// 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 | ||
output.WriteLine($"Request sequence index: {context.RequestSequenceIndex}"); | ||
|
||
// Example: get function sequence index | ||
output.WriteLine($"Function sequence index: {context.FunctionSequenceIndex}"); | ||
|
||
// Example: get total number of functions which will be called | ||
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. | ||
await next(context); | ||
|
||
// Example: get function result | ||
var result = context.Result; | ||
|
||
// Example: override function result value | ||
context.Result = new FunctionResult(context.Result, "Result from auto function invocation filter"); | ||
|
||
// Example: Terminate function invocation | ||
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); | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.