🙆

普通と違う感じの Semantic Kernel 入門 007「フィルター」

に公開

これまでの記事

本文

今回はフィルターについて見ていこうと思います。
フィルターは、ASP.NET Core のミドルウェアのようなものです。何かの処理の前後に任意の処理を挟むことが出来ます。
Semantic Kernel では以下の 3 つのフィルターが用意されています。

  • IFunctionInvocationFilter
    • 関数の呼び出しの前後に処理を挟むことが出来ます。
  • IPromptRenderFilter
    • プロンプトのレンダリングの前後に処理を挟むことが出来ます。
  • IAutoFunctionInvocationFilter
    • 自動関数呼び出しの前後に処理を挟むことが出来ます。

自分が処理を割り込ませたいタイミングに応じて、適切なフィルターを実装します。
各フィルターは以下のように定義されています。

IFunctionInvocationFilter

/// <summary>
/// Interface for filtering actions during function invocation.
/// </summary>
public interface IFunctionInvocationFilter
{
    /// <summary>
    /// Method which is called asynchronously before function invocation.
    /// </summary>
    /// <param name="context">Instance of <see cref="FunctionInvocationContext"/> with function invocation details.</param>
    /// <param name="next">Delegate to the next filter in pipeline or function itself. If it's not invoked, next filter or function won't be invoked.</param>
    Task OnFunctionInvocationAsync(FunctionInvocationContext context, Func<FunctionInvocationContext, Task> next);
}

IPromptRenderFilter

/// <summary>
/// Interface for filtering actions during prompt rendering.
/// </summary>
public interface IPromptRenderFilter
{
    /// <summary>
    /// Method which is called asynchronously before prompt rendering.
    /// </summary>
    /// <param name="context">Instance of <see cref="PromptRenderContext"/> with prompt rendering details.</param>
    /// <param name="next">Delegate to the next filter in pipeline or prompt rendering operation itself. If it's not invoked, next filter or prompt rendering won't be invoked.</param>
    Task OnPromptRenderAsync(PromptRenderContext context, Func<PromptRenderContext, Task> next);
}

IAutoFunctionInvocationFilter

/// <summary>
/// Interface for filtering actions during automatic function invocation.
/// </summary>
public interface IAutoFunctionInvocationFilter
{
    /// <summary>
    /// Method which is called asynchronously before automatic function invocation.
    /// </summary>
    /// <param name="context">Instance of <see cref="AutoFunctionInvocationContext"/> with automatic function invocation details.</param>
    /// <param name="next">Delegate to the next filter in pipeline or function invocation itself. If it's not invoked, next filter won't be invoked and function invocation will be skipped.</param>
    Task OnAutoFunctionInvocationAsync(AutoFunctionInvocationContext context, Func<AutoFunctionInvocationContext, Task> next);
}

それぞれ On処理名Async メソッドが定義されていて第一引数に処理に必要なコンテキストが渡されます。第二引数は次のフィルターや処理を呼び出すためのデリゲートです。何もしないフィルターの定義は以下のような感じになります。

class NoOpFunctionInvocationFilter : IFunctionInvocationFilter
{
    public async Task OnFunctionInvocationAsync(
        FunctionInvocationContext context, 
        Func<FunctionInvocationContext, Task> next)
    {
        // 次のフィルターを呼び出す
        await next(context);
    }
}

IFunctionInvocationFilter の使い方

IFunctionInvocationFilter の引数の FunctionInvocationContext は関数の呼び出しに関する情報を持っています。
基本的には Function プロパティと Arguments プロパティを見て関数の呼び出しの可否の判定や引数の加工を行います。

public class FunctionInvocationContext
{
    public CancellationToken CancellationToken { get; init; }
    public bool IsStreaming { get; init; }
    public Kernel Kernel { get; }
    public KernelFunction Function { get; }
    public KernelArguments Arguments { get; }
    public FunctionResult Result { get; set; }
}

関数呼び出しの結果は Result プロパティに設定します。通常は next デリゲートを呼び出して、その先で Result が設定されますが、フィルターの中で next を呼び出さずに Result を設定することも可能です。例えば関数呼び出し時に人間に確認を求めるようなフィルターを実装することも可能です。Foo 関数を呼び出すときに人間に確認を求めるフィルターを実装してみましょう。以下のような感じです。

public class HumanInTheLoopFilter : IFunctionInvocationFilter
{
    public Task OnFunctionInvocationAsync(FunctionInvocationContext context, Func<FunctionInvocationContext, Task> next)
    {
        if (context.Function.Name == "Foo")
        {
            if (!GetUserApproval())
            {
                context.Result = new FunctionResult(context.Result, "ユーザーによりキャンセルされました。");
                return Task.CompletedTask;
            }
        }

        return next(context);
    }

    private static bool GetUserApproval()
    {
        // ここで人間に確認を求める処理を実装する
        Console.WriteLine("Foo 関数の実行を続けますか? (yes/no)");
        return Console.ReadLine()?.Trim()?.ToLower() == "yes";
    }
}

このようにフィルターを実装して KernelBuilderServicesIFunctionInvocationFilter として登録することで、関数呼び出しの前後に処理を挟むことが出来ます。実際に試してみましょう。

using Azure.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;

// User Secrets から設定を読み込む
var configuration = new ConfigurationBuilder()
    .AddUserSecrets<Program>()
    .Build();
// AOAI にデプロイしているモデル名
var modelDeploymentName = configuration["AOAI:ModelDeploymentName"]
    ?? throw new ArgumentNullException("AOAI:ModelDeploymentName is not set in the configuration.");
// AOAI のエンドポイント
var endpoint = configuration["AOAI:Endpoint"]
    ?? throw new ArgumentNullException("AOAI:Endpoint is not set in the configuration.");

// Builder を作成
var builder = Kernel.CreateBuilder();

// フィルターを登録
builder.Services.AddSingleton<IFunctionInvocationFilter, HumanInTheLoopFilter>();

// AOAI 用の Chat Client を登録
builder.AddAzureOpenAIChatClient(
    modelDeploymentName,
    endpoint,
    new AzureCliCredential());

// AI サービスが登録された Kernel を作成
var kernel = builder.Build();

// Foo 関数と Bar 関数を作成
var fooFunction = kernel.CreateFunctionFromMethod(
    () =>
    {
        Console.WriteLine("Foo 関数が呼ばれました。");
        return "Foo result";
    },
    functionName: "Foo");
var barFunction = kernel.CreateFunctionFromMethod(
    () =>
    {
        Console.WriteLine("Bar 関数が呼ばれました。");
        return "Bar result";
    },
    functionName: "Bar");

// Foo 関数を呼び出す。フィルターで確認が求められる。
var fooResult = await fooFunction.InvokeAsync(kernel);
Console.WriteLine($"Foo result: {fooResult.GetValue<string>()}");

// Bar 関数を呼び出す。フィルターで確認は求められない。
var barResult = await barFunction.InvokeAsync(kernel);
Console.WriteLine($"Bar result: {barResult.GetValue<string>()}");

実行すると、Foo 関数を呼び出すときに人間に確認が求められます。
Foo 関数の実行を続ける場合は yes と入力します。そうでない場合は no と入力します。no を入力した際の出力は以下のようになります。

Foo 関数の実行を続けますか? (yes/no)
no
Foo result: ユーザーによりキャンセルされました。
Bar 関数が呼ばれました。
Bar result: Bar result

yes を入力した場合は以下のようになります。

Foo 関数の実行を続けますか? (yes/no)
yes
Foo 関数が呼ばれました。
Foo result: Foo result
Bar 関数が呼ばれました。
Bar result: Bar result

このようにフィルターを使うことで、関数呼び出しの前後に任意の処理を挟むことが出来ます。この処理は、KernelFunction 内部で実装されているので KernelFunction の呼び出し時に渡される Kernel に登録されている IFunctionInvocationFilter が呼び出されます。

IPromptRenderFilter の使い方

IPromptRenderFilter はプロンプトのレンダリングの前後に処理を挟むことが出来ます。このプロンプトのレンダリングは KernelFunction を呼び出す際に行われます。KernelFunctionFactoryKernelCreateFunctionFromPrompt メソッドを使って作成した関数の呼び出しや、KernelInvokePromptAsync メソッドを使ってプロンプトを直接呼び出す際に、プロンプトのレンダリングが行われます。逆に言うと、自分で IPromptTemplateFactory を使ってプロンプトのレンダリング処理を実装している場合は、IPromptRenderFilter は呼び出されません。この点には注意してください。あくまで KernelFunction を使ってプロンプトを呼び出す際 (KernelInvokePromptAsync も内部で KernelFunctin が作成されています) のプロンプトのレンダリング時にフィルターが呼び出されます。

IPromptRenderFilter の引数の PromptRenderContext はプロンプトのレンダリングに関する情報を持っています。
定義は以下のような感じです。

public sealed class PromptRenderContext
{
    public bool IsStreaming { get; init; }
    public Kernel Kernel { get; }
    public KernelFunction Function { get; }
    public KernelArguments Arguments { get; }
    public PromptExecutionSettings? ExecutionSettings { get; init; }
    public string? RenderedPrompt { get; set;}
    public FunctionResult? Result { get; set; }
}

プロンプトを実行する KernelFunction で実行されるフィルターなので呼び出される KernelFunction と、その引数が KernelArguments として渡されています。この値を見て色々な処理を行うことが出来ます。主な使用方法としては await next(context); を呼び出した後に RenderedPrompt プロパティにレンダリングされたプロンプトが設定されるので、それをチェックしたり加工したりすることが出来ます。
その他にレンダリング後のプロンプトのログを取ったり、状況によってはプロンプトのレンダリングをキャンセルすることもできます。キャンセルする場合は await next(context); を呼び出さずに context.Result = new FunctionResult(context.Function, "キャンセルされました。"); のように Result プロパティに関数が呼び出されたときの結果を設定します。

では、フィルターを使ってログを取る、キャンセルする、プロンプトを置き換えるという 3 つのフィルターを実装して動作確認をしてみましょう。
まずは、今回の動作確認では AI を直接呼び出す必要はないので簡単なダミーの AzureOpenAIClient を実装します。

// 渡されたテキストに対して「Echo: テキスト」と応答するモッククライアント
class MockAOAIClient : AzureOpenAIClient
{
    public override ChatClient GetChatClient(string deploymentName) =>
        new MockChatClient();

    class MockChatClient : ChatClient
    {
        public override Task<ClientResult<ChatCompletion>> CompleteChatAsync(
            IEnumerable<ChatMessage> messages, 
            ChatCompletionOptions? options = null, 
            CancellationToken cancellationToken = default)
        {
            var text = messages.Last().Content[0].Text;
            var clientResult = ClientResult.FromValue(
                OpenAIChatModelFactory.ChatCompletion(
                    role: ChatMessageRole.Assistant,
                    content: new ($"Echo: {text}")),
                Mock.Of<PipelineResponse>());
            return Task.FromResult(clientResult);
        }
    }
}

次にフィルターを実装します。ログを取るフィルターは以下のような感じです。

// プロンプトのログをとるフィルター
class LoggingPromptFilter : IPromptRenderFilter
{
    public async Task OnPromptRenderAsync(PromptRenderContext context, Func<PromptRenderContext, Task> next)
    {
        Console.WriteLine($"開始: {nameof(LoggingPromptFilter)}#{nameof(OnPromptRenderAsync)}({context.Function.Name})");
        await next(context);
        Console.WriteLine($"終了: {nameof(LoggingPromptFilter)}#{nameof(OnPromptRenderAsync)}({context.Function.Name}) => {context.RenderedPrompt}");
    }
}

キャンセルするフィルターは以下のような感じです。今回の例では Argumentscancel という引数がある場合にキャンセルします。

// 引数に cancel がるとキャンセルするフィルター
class CancelPromptFilter : IPromptRenderFilter
{
    public async Task OnPromptRenderAsync(PromptRenderContext context, Func<PromptRenderContext, Task> next)
    {
        if (context.Arguments.ContainsKey("cancel"))
        {
            context.Result = new(context.Function, "キャンセルされました");
            return;
        }

        await next(context);
    }
}

プロンプトを置き換えるフィルターは以下のような感じです。無条件にプロンプトを置き換えます。

// プロンプトを置き換えるフィルター
class ReplacePromptFilter : IPromptRenderFilter
{
    public Task OnPromptRenderAsync(PromptRenderContext context, Func<PromptRenderContext, Task> next)
    {
        context.RenderedPrompt = "フィルターで置き換えられたプロンプト";
        return Task.CompletedTask;
    }
}

これらのフィルターを KernelBuilder に登録して、プロンプトを呼び出してみましょう。
セットするフィルターに応じて動きの違いを確認したいので、セットするフィルターを引数をとして受け取るメソッドを作成して、それを呼び出して動作確認をするようにします。

async Task RunAsync(KernelArguments arguments, params IEnumerable<IPromptRenderFilter> filters)
{
    // Kernel を作成
    var builder = Kernel.CreateBuilder();
    foreach (var filter in filters)
    {
        builder.Services.AddSingleton(filter);
    }
    // EchoChatClient を登録
    builder.AddAzureOpenAIChatClient("dummy", new MockAOAIClient());
    var kernel = builder.Build();
    // プロンプトから関数を作成
    var function = kernel.CreateFunctionFromPrompt(
        promptConfig: new("オリジナルプロンプト")
        {
            Name = "Foo",
            InputVariables = [new() { Name = "cancel" }],
        });
    // 関数を実行
    var result = await kernel.InvokeAsync(function, arguments);
    // 結果を表示
    Console.WriteLine($"結果: {result.GetValue<string>()}");
    Console.WriteLine();
}

では、これを使ってフィルターの動作確認をしてみましょう。まずはログを取るフィルターだけをセットして実行してみます。

Console.WriteLine("=== ReplacePromptFilter ===");
await RunAsync(new (), new ReplacePromptFilter());

実行すると以下のような出力が得られます。関数で設定しているオリジナルのプロンプトがログに出力されて、そのまま AI に渡されていることがわかります。

=== LoggingPromptFilter ===
開始: LoggingPromptFilter#OnPromptRenderAsync(Foo)
終了: LoggingPromptFilter#OnPromptRenderAsync(Foo) => オリジナルプロンプト
結果: Echo: オリジナルプロンプト

次にプロンプトを置き換えるフィルターをセットして実行してみます。

Console.WriteLine("=== ReplacePromptFilter ===");
await RunAsync(new(), new ReplacePromptFilter());

実行すると以下のような出力が得られます。プロンプトが置き換えられていることがわかります。

=== ReplacePromptFilter ===
結果: Echo: フィルターで置き換えられたプロンプト

次にキャンセルするフィルターをセットして実行してみます。cancel 引数の有無で動作確認をするため引数無しと有りの 2 回実行します。

Console.WriteLine("=== CancelPromptFilter ===");
await RunAsync(new(), new CancelPromptFilter());

Console.WriteLine("=== CancelPromptFilter with cancel param ===");
await RunAsync(new() { ["cancel"] = "" }, new CancelPromptFilter());

実行すると以下のような出力が得られます。引数無しの場合はプロンプトがそのまま AI に渡されて、引数有りの場合はキャンセルされていることがわかります。

=== CancelPromptFilter ===
結果: Echo: オリジナルプロンプト

=== CancelPromptFilter with cancel param ===
結果: キャンセルされました

最後にログ、キャンセル、置き換えの 3 つのフィルターをセットして実行してみます。キャンセルする場合としない場合で動作確認をします。
注意点は、ReplacePromptFilter は後続のフィルターを呼び出さないため末端に持ってこないと他のフィルターが呼び出されないため最後に持ってくる必要があります。もちろん、後続のフィルターをキャンセルするのが目的なのであれば、ReplacePromptFilter のような後続のフィルターを呼び出さないフィルターを前のほうに持ってくることも可能です。

Console.WriteLine("=== LoggingPromptFilter, CancelPromptFilter, ReplacePromptFilter ===");
await RunAsync(new(), new LoggingPromptFilter(), new CancelPromptFilter(), new ReplacePromptFilter());

Console.WriteLine("=== LoggingPromptFilter, CancelPromptFilter, ReplacePromptFilter with cancel param ===");
await RunAsync(new() { ["cancel"] = "" }, new LoggingPromptFilter(), new CancelPromptFilter(), new ReplacePromptFilter());

実行すると以下のような出力が得られます。ログが出力されて、置き換えられたプロンプトが AI に渡されていることがわかります。また、キャンセルされた場合はキャンセルされた旨のメッセージが出力されます。

=== LoggingPromptFilter, CancelPromptFilter, ReplacePromptFilter ===
開始: LoggingPromptFilter#OnPromptRenderAsync(Foo)
終了: LoggingPromptFilter#OnPromptRenderAsync(Foo) => フィルターで置き換えられたプロンプト
結果: Echo: フィルターで置き換えられたプロンプト

=== LoggingPromptFilter, CancelPromptFilter, ReplacePromptFilter with cancel param ===
開始: LoggingPromptFilter#OnPromptRenderAsync(Foo)
終了: LoggingPromptFilter#OnPromptRenderAsync(Foo) =>
結果: キャンセルされました

このように、IPromptRenderFilter を使うことでプロンプトのレンダリングの前後に任意の処理を挟むことが出来ます。プロンプトのログを取ったり、プロンプトを置き換えたり、キャンセルしたりといった処理を実装することが出来ます。しかし、あくまで KernelFunction を使ってプロンプトを呼び出す際のフィルターであることに注意してください。自分でプロンプトのレンダリング処理を実装している場合は、IPromptRenderFilter は呼び出されません。

IAutoFunctionInvocationFilter の使い方

最後に IAutoFunctionInvocationFilter の使い方を見ていきます。これは、自動関数呼び出しの前後に処理を挟むことが出来ます。自動関数呼び出しとは、Semantic Kernel が自動的に関数を呼び出す機能です。この機能を使うには PromptExecutionSettingsFunctionInvocationBehavior プロパティに FunctionInvocationBehavior.Auto() を設定します。これにより、Semantic Kernel は OpenAI などのモデルのツール呼び出しのレスポンスを自動的にハンドリングをして関数の呼び出しを行います。
関数の自動呼出しは便利で強力な機能ですが、勝手に呼び出されては困るような処理が呼び出される可能性があるというリスクがあります。また、呼び出しても良いが呼び出し前に人間による確認を行いたい、AI が自動的に呼び出した処理の記録を取りたいといった要件もあるでしょう。そういった場合に IAutoFunctionInvocationFilter を使うことが出来ます。個人的には一番使う機会の多いフィルターになるのではないかと思います。

IAutoFunctionInvocationFilter の引数の AutoFunctionInvocationContext は自動関数呼び出しに関する情報を持っています。これを利用することで、呼び出される関数やその引数、呼び出し元の情報などを取得することができます。AutoFunctionInvocationContext の定義は以下のようになっています。

public class AutoFunctionInvocationContext : Microsoft.Extensions.AI.FunctionInvocationContext
{
    public CancellationToken CancellationToken { get; init; }
    public KernelArguments? Arguments { get; init; }
    public int RequestSequenceIndex { get; init; }
    public int FunctionSequenceIndex { get; init; }
    public string? ToolCallId { get; init; }
    public ChatMessageContent ChatMessageContent { get; }
    public PromptExecutionSettings? ExecutionSettings { get; init; }
    public ChatHistory ChatHistory { get; }
    public KernelFunction Function { get; }
    public Kernel Kernel { get; }
    public FunctionResult Result { get; set; }
}

このように、AutoFunctionInvocationContext も他の Context クラスと同様に、フィルター処理に必要な情報をプロパティとして持っています。これを使って自動関数呼び出しの前後に処理を挟むフィルターを実装することが出来ます。例えば、自動関数呼び出しのログを取るフィルターは以下のようになります。

class LoggingAutoFunctionInvocationFilter : IAutoFunctionInvocationFilter
{
    public async Task OnAutoFunctionInvocationAsync(AutoFunctionInvocationContext context, Func<AutoFunctionInvocationContext, Task> next)
    {
        Console.WriteLine($"開始: {nameof(LoggingAutoFunctionInvocationFilter)}#{nameof(OnAutoFunctionInvocationAsync)}({context.Function.Name})");
        await next(context);
        Console.WriteLine($"終了: {nameof(LoggingAutoFunctionInvocationFilter)}#{nameof(OnAutoFunctionInvocationAsync)}({context.Function.Name})");
    }
}

このフィルターを使うと、自動関数呼び出しの前後にログを出力することが出来ます。実際に試してみましょう。

// User Secrets から設定を読み込む
using Azure.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;

var configuration = new ConfigurationBuilder()
    .AddUserSecrets<Program>()
    .Build();
// AOAI にデプロイしているモデル名
var modelDeploymentName = configuration["AOAI:ModelDeploymentName"]
    ?? throw new ArgumentNullException("AOAI:ModelDeploymentName is not set in the configuration.");
// AOAI のエンドポイント
var endpoint = configuration["AOAI:Endpoint"]
    ?? throw new ArgumentNullException("AOAI:Endpoint is not set in the configuration.");

// Builder を作成
var builder = Kernel.CreateBuilder();

// フィルターを登録
builder.Services.AddSingleton<IAutoFunctionInvocationFilter, LoggingAutoFunctionInvocationFilter>();
// 現在時間を取得するプラグイン
builder.Plugins.AddFromFunctions("TimePlugin",
    [
        KernelFunctionFactory.CreateFromMethod(
            () => TimeProvider.System.GetLocalNow(),
            functionName: "GetLocalNow",
            description: "現在のローカル時間を取得します。"),
    ]);

// AOAI 用の Chat Client を登録
builder.AddAzureOpenAIChatClient(
    modelDeploymentName,
    endpoint,
    new AzureCliCredential());

// AI サービスが登録された Kernel を作成
var kernel = builder.Build();

// 時間を聞いて自動関数呼び出しをしてもらう
var result = await kernel.InvokePromptAsync(
    "今何時?",
    arguments: new(new PromptExecutionSettings
    {
        // 自動関数呼び出しをオンに設定
        FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(),
    }));
// 結果を出力
Console.WriteLine(result.GetValue<string>());

実行すると、関数の自動呼び出しの前後にログが出力されます。

開始: LoggingAutoFunctionInvocationFilter#OnAutoFunctionInvocationAsync(GetLocalNow)
終了: LoggingAutoFunctionInvocationFilter#OnAutoFunctionInvocationAsync(GetLocalNow)
今の時刻は2025年5月31日18時12分(日本時間)です。

このように IAutoFunctionInvocationFilter を使うことで、自動関数呼び出しの前後に任意の処理を挟むことが出来ます。自動関数呼び出しのログを取ったり、呼び出し前に人間に確認を求めたりといったフィルターを実装することが出来ます。呼び出し前に人間の確認を求めるフィルターも試してみましょう。先ほどのコードに対して追加で以下のようなフィルターを実装します。

class HumanInTheLoopAutoFunctionInvocationFilter : IAutoFunctionInvocationFilter
{
    public async Task OnAutoFunctionInvocationAsync(AutoFunctionInvocationContext context, Func<AutoFunctionInvocationContext, Task> next)
    {
        if (!GetUserApproval(context.Function.Name))
        {
            context.Result = new FunctionResult(context.Function, "ユーザーによりキャンセルされました。");
            return;
        }

        await next(context);
    }

    private static bool GetUserApproval(string functionName)
    {
        // ここで人間に確認を求める処理を実装する
        Console.WriteLine($"{functionName} 関数の実行を続けますか? (yes/no)");
        return Console.ReadLine()?.Trim()?.ToLower() == "yes";
    }
}

フィルターの登録処理に、このフィルターを追加するコードを追加します。

// フィルターを登録
builder.Services.AddSingleton<IAutoFunctionInvocationFilter, LoggingAutoFunctionInvocationFilter>();
builder.Services.AddSingleton<IAutoFunctionInvocationFilter, HumanInTheLoopAutoFunctionInvocationFilter>(); // この行を追加

実行すると、関数の自動呼び出しの前に人間に確認が求められます。yes と入力すると関数が実行され、no と入力するとキャンセルされます。以下は yes を入力した場合の出力例です。

開始: LoggingAutoFunctionInvocationFilter#OnAutoFunctionInvocationAsync(GetLocalNow)
GetLocalNow 関数の実行を続けますか? (yes/no)
yes
終了: LoggingAutoFunctionInvocationFilter#OnAutoFunctionInvocationAsync(GetLocalNow)
今の時刻は2025年5月31日18時15分です。

no を入力した場合の出力例は以下のようになります。

開始: LoggingAutoFunctionInvocationFilter#OnAutoFunctionInvocationAsync(GetLocalNow)
GetLocalNow 関数の実行を続けますか? (yes/no)
だが断る!
終了: LoggingAutoFunctionInvocationFilter#OnAutoFunctionInvocationAsync(GetLocalNow)
現在時刻を確認しようとしましたが、リクエストがキャンセルされました。もう一度お試しいただくか、「今の時刻を教えて」とご入力ください。

まとめ

フィルターを使うことで、関数呼び出しやプロンプトのレンダリング、自動関数呼び出しの前後に任意の処理を挟むことが出来ます。
フィルターは、処理の前後に何らかの処理を挟みたい場合に非常に便利な機能です。
特に、IFunctionInvocationFilterIAutoFunctionInvocationFilter は非常に強力で、関数呼び出しの可否の判定や引数の加工、呼び出し前の確認、呼び出し履歴の記録など様々な用途に利用できます。
また、IPromptRenderFilter を使うことでプロンプトのレンダリングの前後に処理を挟むことが出来ます。プロンプトのログを取ったり、プロンプトを置き換えたり、キャンセルしたりといった処理を実装することが出来ます。
フィルターをうまく活用することで、Semantic Kernel の処理をより柔軟に制御することが出来るようになります。

目次

普通と違う感じの Semantic Kernel 入門の目次

Microsoft (有志)

Discussion