🙆

普通と違う感じの Semantic Kernel 入門 001「関数」

に公開

はじめに

ここでは Semantic Kernel の自分的な入門を書いていこうと思います。
一般的な入門記事というよりは、私が Semantic Kernel を触ったりコードを読んだり、サンプルコードを読んだり、登壇して発表したりして得た知見をまとめていく感じです。「Semantic Kernel とは」とかについては公式ドキュメントの セマンティック カーネルの概要 を見ていただくか、先日書かれた AI エージェントフレームワーク Semantic Kernel 超入門!! (コード例は Python ですが) などに纏まっているので、そちらを参照してください。

Semanic Kernel の関数

Semantic Kernel で個人的に一番ベースとなる機能は何かと考えたときに浮かんでくるのは「関数」です。
Semantic Kernel の関数は AI を呼び出したり、C# のコードを実行したり、Web API を呼び出したり、MCP のツールを呼び出したりといった様々な処理を行う部分です。

この関数は KernelFunction というクラスで表現されています。コンソールアプリを作成して Microsoft.SemanticKernel パッケージをインストールして、以下のようなコードを書いてみましょう。

using Microsoft.SemanticKernel;

// デリゲートをラップした KernelFunction を作成
KernelFunction hello = KernelFunctionFactory.CreateFromMethod((string name) => $"Hello, {name}!");

// 関数に渡す引数を定義 
// IDictionary<string, object?> を実装しているため普通の Dictionary のように使える
KernelArguments arguments = new()
{
    ["name"] = "Kazuki"
};

// 引数を渡して関数を呼び出す
object? result = await hello.InvokeAsync(arguments);

// Hello, Kazuki! が出力されるはず
Console.WriteLine(result);

このコードでは、KernelFunctionFactory.CreateFromMethod メソッドを使って、デリゲートをラップした KernelFunction を作成しています。ここでは string 型の引数を受け取り、string 型の結果を返す関数を定義しています。KernelArguments を使って引数を渡し、InvokeAsync メソッドで関数を呼び出しています。結果は Hello, Kazuki! という文字列になります。

InvokeAsync には Semantic Kernel のコアのサービスの Kernel を渡すオーバーロードがあります。
どちらかというと Semantic Kernel では、こちらのオーバーロードを使うことが多いです。
以下のように書き換えてみましょう。

using Microsoft.SemanticKernel;

// デリゲートをラップした KernelFunction を作成
KernelFunction hello = KernelFunctionFactory.CreateFromMethod((string name) => $"Hello, {name}!");

// 関数に渡す引数を定義 
// IDictionary<string, object?> を実装しているため普通の Dictionary のように使える
KernelArguments arguments = new()
{
    ["name"] = "Kazuki"
};

// Kernel を作成
var kernel = new Kernel();

// Kernel を渡す場合は FunctionResult が返ってくる
FunctionResult result1 = await hello.InvokeAsync(kernel, arguments);
// GetValue で戻り値を取得
// Hello, Kazuki! が出力される
Console.WriteLine(result1.GetValue<string>());

// Kernel から関数を呼び出すことも可能
FunctionResult result2 = await kernel.InvokeAsync(hello, arguments);
// GetValue で戻り値を取得
// Hello, Kazuki! が出力される
Console.WriteLine(result1.GetValue<string>());

KernelFunctinoInvokeAsync を呼ぶ方法と KernelInvokeAsync を呼ぶ方法の2つがありますが、どちらも同じ結果を得ることができます。Kernel クラスの InvokeAsync メソッドは KernelFunctionInvokeAsync メソッドを内部で呼び出しているだけです。

ちなみに複数の引数を受け取る関数にも対応しています。その場合は KernelArguments で複数の引数を定義するだけです。
以下のように書いてみましょう。

using Microsoft.SemanticKernel;

// 複数引数の関数を定義
KernelFunction add = KernelFunctionFactory.CreateFromMethod((int x, int y) => x + y);

// 関数に渡す引数を定義 
KernelArguments arguments = new()
{
    ["x"] = 5,
    ["y"] = 10
};

// Kernel を作成
var kernel = new Kernel();

// Kernel を渡す場合は FunctionResult が返ってくる
FunctionResult result1 = await add.InvokeAsync(kernel, arguments);
// GetValue で戻り値を取得
// 15 が出力される
Console.WriteLine(result1.GetValue<int>());

関数の引数に指定できるもの

Semantic Kernel の関数の引数に渡す値は KernelArguments を使って定義します。しかし、その他にもいくつか特別な型を受け取ることが出来ます。
以下のようなコードを書いてみましょう。関数の第一引数に Kernel を指定しています。

using Microsoft.SemanticKernel;

KernelFunction f = KernelFunctionFactory.CreateFromMethod(
    // Kernel を引数に受け取れる
    (Kernel kernel, string name) => $"Hello, {name} from {kernel}!");

Kernel kernel = new();

// KernelArguments では name 引数だけを渡す
var result = await f.InvokeAsync(kernel, new KernelArguments
{
    ["name"] = "Kazuki"
});

// 結果を表示
Console.WriteLine(result.GetValue<string>());

実行すると以下のような結果になります。ちゃんと Kernel 型の引数が渡されていることがわかります。

Hello, Kazuki from Microsoft.SemanticKernel.Kernel!

その他にも Kernel に登録されているサービスを引数に受け取ることが出来ます。
その場合には Kernel のサービスから引数を受け取ることを明示するために FromKernelServicesAttribute を指定します。
Kernel にサービスを登録するには、一般的なサンプルであるように KernelBuilder を使ってサービスを登録して Kernel を作成するか、Kernel のコンストラクタに IServiceProvider を渡して作成します。今回は後者の方法でやってみましょう。TimeProvider をサービスに登録して、それを関数の引数で受け取るようにしたいと思います。

using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;

KernelFunction f = KernelFunctionFactory.CreateFromMethod(
    // Kernel と TimeProvider と string を引数に受け取る
    (Kernel kernel, [FromKernelServices]TimeProvider timeProvider, string name) =>
        $"Hello, {name} from {kernel} at {timeProvider.GetLocalNow()}!");

// TimeProvider を登録した IServiceProvider を作成
var services = new ServiceCollection()
    .AddSingleton(TimeProvider.System)
    .BuildServiceProvider();

// Kernel を作成するときに IServiceProvider を渡すことができる
var kernel = new Kernel(services);

// KernelArguments では name 引数だけを渡す
var r = await f.InvokeAsync(kernel, new KernelArguments
{
    ["name"] = "Kazuki"
});

// 結果を表示
Console.WriteLine(r.GetValue<string>());

実行すると以下のような結果になります。TimeProvider が正しく引数に渡されていることがわかります。

Hello, Kazuki from Microsoft.SemanticKernel.Kernel at 2025/05/26 19:45:24 +09:00!

まとめ

ここでは Semantic Kernel の関数について、基本的な使い方を紹介しました。
ポイントとしてはデリゲートから作られた関数には、KernelArguments で引数を渡す他に Kernel を受け取ることが出来たり、Kernel に登録されているサービスを引数に受け取ることが出来るという点です。この特性があるため、関数でかなり色々なことが出来ます。ここではやりませんでしたが、一般的には Kernel には AI サービスが登録されています。そのため関数で AI を呼び出すためのサービスを引数として受け取り AI を呼び出すことが出来ます。

AI Agent を作るためのフレームワークの Semantic Kernel の入門の最初で AI を一度も呼び出していませんが次回は AI をよびだすための関数について書いていきたいと思います。

目次

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

Microsoft (有志)

Discussion