普通と違う感じの Semantic Kernel 入門 002「テンプレートエンジン」
これまでの記事
本文
前回の最後で AI を呼び出すための関数を説明をすると書きましたが、その前にテンプレートエンジンについてやろうと思います。
というのも、このテンプレートエンジンによって生成された文字列が AI の呼び出しに渡されるため切っても切れない関係だからです。
Semantic Kernel のテンプレートエンジン
Semantic Kernel では、AI を呼び出す関数経由では基本的にテンプレートエンジンを使用してプロンプトが生成されます。
Semantic Kernel のテンプレートエンジンは IPromptTemplateFactory
によって、なんのテンプレートエンジンを使うかを差し替えることが出来るようになっています。
例えば以下のようなテンプレートエンジンの実装が用意されています。
-
EchoPromptTemplateFactory
: 引数をそのまま返すテンプレートエンジン -
KernelPromptTemplateFactory
: Semantic Kernel の既定のテンプレートエンジン。基本的な変数の展開や関数呼び出し機能を持つ。 -
HandlebarsPromptTemplateFactory
: Handlebars のテンプレートエンジン。ループや分岐などもサポートしていて多機能。 -
LiquidPromptTemplateFactory
: Liquid のテンプレートエンジン。Handlebars と同様に多機能。この中では一番新しい。 -
AggregatorPromptTemplateFactory
: 複数のテンプレートエンジンを組み合わせて使うためのテンプレートエンジン。
昔は AI に送信するテンプレートを駆使して、システムプロンプトに外部のリソースからとってきた情報を埋め込むといったことをしていました。
そのために、ループや分岐といった機能を持つ Handlebars
を使って色々やることも多かったと思います。ですが、最近は AI の関数呼び出し機能を使って外部のリソースの情報を必要に応じて AI が取得してくることも増えてきました。そのため若干出番は減っていると思います。そのためここでは既定の KernelPromptTemplateFactory
のみを少し深堀していこうと思います。
KernelPromptTemplateFactory の使い方
では、KernelPromptTemplateFactory
の使い方を見ていきましょう。
まずは、KernelPromptTemplateFactory
を使ってテンプレートを作成し、変数を設定してレンダリングする基本的な例を見ていきます。
using Microsoft.SemanticKernel;
// テンプレートのファクトリを作成
IPromptTemplateFactory templateFactory = new KernelPromptTemplateFactory();
// テンプレートを作成
IPromptTemplate template = templateFactory.Create(new PromptTemplateConfig("""
Hello, {{$name}}!
"""));
// テンプレートに変数を設定してレンダリング
string prompt = await template.RenderAsync(new Kernel(), new KernelArguments
{
["name"] = "Kazuki",
});
// レンダリングされたプロンプトを表示
Console.WriteLine(prompt);
普通に Semantic Kernel を使っていると、ここらへんの API を直接使うことはありませんが、大体中でこんなことが行われています。
TemplateFactory
の Create
メソッドでテンプレートを作成し、RenderAsync
メソッドで変数を設定してレンダリングしています。
KernelArguments
を使って変数を設定し、テンプレート内で {{$name}}
のように変数を参照しています。
この例では、Hello, Kazuki!
という文字列が出力されます。
Create
メソッド以外にも TryCreate
メソッドもあります。こちらはよくある戻り値が bool
のパターンで、テンプレートの作成に失敗した場合は false
を返します。IPromptTemplate
は out
引数で返されます。
この単純なコード内にも色々と暗黙のルールがあったりします。例えば LiquidPromptTemplateFactory
を使うように書き換えると例外が出てしまいます。試してみましょう。
以下の NuGet パッケージをインストールします。
Microsoft.SemanticKernel.PromptTemplates.Liquid
そしてコードを以下のように書き換えます。
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.PromptTemplates.Liquid;
// テンプレートのファクトリを作成
IPromptTemplateFactory templateFactory = new LiquidPromptTemplateFactory();
// テンプレートを作成
IPromptTemplate template = templateFactory.Create(new PromptTemplateConfig("""
Hello, {{$name}}!
"""));
// テンプレートに変数を設定してレンダリング
string prompt = await template.RenderAsync(new Kernel(), new KernelArguments
{
["name"] = "Kazuki",
});
// レンダリングされたプロンプトを表示
Console.WriteLine(prompt);
実行すると以下のような例外が発生します。
Microsoft.SemanticKernel.KernelException: Prompt template format semantic-kernel is not supported.
at Microsoft.SemanticKernel.PromptTemplateFactoryExtensions.Create(IPromptTemplateFactory factory, PromptTemplateConfig templateConfig)
at Program.<Main>$(String[] args) in D:\Repos\runceel\sk-edu\SK-Edu\002-PromptTemplate\PromptTemplate001\Program.cs:line 7
このメッセージにある通りテンプレートを指定している PromptTemplateConfig
をデフォルトのまま使うと Semantic Kernel の既定のテンプレートエンジンである KernelPromptTemplateFactory
が指定されます。
LiquidPromptTemplateFactory
を使う場合は、テンプレートのフォーマットを明示的に指定する必要があります。これをするには PromptTemplateConfig
の TemplateFormat
プロパティを設定することで使用する必要があります。TemplateFormat
に指定する値は各 PromptTemplateFactory
の実装に static
変数として定義されています。LiquidPromptTemplateFactory
の場合は LiquidPromptTemplateFactory.LiquidTemplateFormat
になります。
ということで LiquidPromptTemplateFactory
を使うように以下のようにするには以下のように書き換えが必要です。ついでにテンプレートエンジンも Liquid
のものに書き換えます。変数の指定は単純に {{name}}
になります。$
を付ける必要はありません。
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.PromptTemplates.Liquid;
// テンプレートのファクトリを作成
IPromptTemplateFactory templateFactory = new LiquidPromptTemplateFactory();
// テンプレートを作成
IPromptTemplate template = templateFactory.Create(
new PromptTemplateConfig("""
Hello, {{name}}!
""")
{
// テンプレートのフォーマットをLiquidに設定
TemplateFormat = LiquidPromptTemplateFactory.LiquidTemplateFormat,
});
// テンプレートに変数を設定してレンダリング
string prompt = await template.RenderAsync(new Kernel(), new KernelArguments
{
["name"] = "Kazuki",
});
// レンダリングされたプロンプトを表示
Console.WriteLine(prompt);
今度は実行すると、ちゃんと以下のように表示されます。テンプレートエンジンが動いていることがわかります。
Hello, Kazuki!
テンプレートエンジンの切り替え
このままでは、1つのテンプレートにしか対応できません。不便です。
1つのアプリ内で使用するテンプレートエンジンが複数あるケースも考えられます。そのような状況に対応するためには AggregatorPromptTemplateFactory
を使います。これは複数のテンプレートエンジンをまとめて、1 つのテンプレートエンジンとして扱うことができます。PromptTemplateConfig
の TemplateFormat
プロパティに指定されたテンプレートエンジンのフォーマットに応じて、適切なテンプレートエンジンを選択してくれます。
では、LiquidPromptTemplateFactory
と KernelPromptTemplateFactory
と EchoPromptTemplateFactory
の 3 つを使えるように書き換えてみましょう。イメージとしては liquid
と Semantic Kernel のテンプレートエンジンを使いつつ、無効なフォーマットが指定された場合は EchoPromptTemplateFactory
を使うようにします。
以下のようにコードを書き換えます。
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.PromptTemplates.Liquid;
// テンプレートのファクトリを作成
IPromptTemplateFactory templateFactory = new AggregatorPromptTemplateFactory(
// 複数のテンプレートファクトリを登録
new LiquidPromptTemplateFactory(),
new KernelPromptTemplateFactory(),
new EchoPromptTemplateFactory());
// 何も指定していないので KernelPromptTemplateFactory がデフォルトで使用される
IPromptTemplate kernelTemplate = templateFactory.Create(
new PromptTemplateConfig("""
Kernel, {{$name}}!
"""));
// TemplateFormat が設定されているため LiquidPromptTemplateFactory が使用される
IPromptTemplate liquidTemplate = templateFactory.Create(
new PromptTemplateConfig("""
Liquid, {{name}}!
""")
{
// テンプレートのフォーマットをLiquidに設定
TemplateFormat = LiquidPromptTemplateFactory.LiquidTemplateFormat,
});
// TemplateFormat が設定されているため LiquidPromptTemplateFactory が使用される
IPromptTemplate echoTemplate = templateFactory.Create(
new PromptTemplateConfig("""
Echo, {{name}}!
""")
{
// 無効なテンプレートのフォーマットを設定
TemplateFormat = "犬派×きのこ派",
});
// テンプレートに渡す変数
var arguments = new KernelArguments
{
["name"] = "Kazuki",
};
// ダミーの空のカーネル
var kernel = new Kernel();
// レンダリングされたプロンプトを表示
Console.WriteLine($"""
{await kernelTemplate.RenderAsync(kernel, arguments)}
{await liquidTemplate.RenderAsync(kernel, arguments)}
{await echoTemplate.RenderAsync(kernel, arguments)}
""");
TemplateFormat
無し(デフォルト)の場合は KernelPromptTemplateFactory
が使用されます。
TemplateFormat
に LiquidPromptTemplateFactory.LiquidTemplateFormat
を指定した場合は LiquidPromptTemplateFactory
が使用されます。
TemplateFormat
に無効な値を指定した場合は EchoPromptTemplateFactory
が使用されます。
実行すると以下のように表示されます。
Kernel, Kazuki!
Liquid, Kazuki!
Echo, {{name}}!
Echo の場合は変数が展開されずにそのまま表示されます。ちゃんと思った通りに動いていますね。このようにして複数のテンプレートエンジンを使うことが出来ます。
KernelPromptTemplateFactory の機能
KernelPromptTemplateFactory
は Semantic Kernel の既定のテンプレートエンジンで、基本的な変数の展開や関数呼び出し機能を持っています。
変数の展開は、{{$name}}
のように {{
と }}
で囲んで $
を指定します。その他にテンプレート内から関数を呼ぶことも出来ます。$
が無いと関数として認識されます。
テンプレートから呼び出す関数は Kernel
の中にプラグインとして登録されている必要があります。プラグインについては別の記事で説明しますが、簡単に言うと関数の集合体がプラグインです。
とりあえず現在時刻を返す関数を登録して、テンプレートから呼び出してみましょう。
以下のようにコードを書き換えます。
using Microsoft.SemanticKernel;
var kernel = new Kernel();
// Kernel にも実は CreateFunctionFromMethod がある
// 中では KernelFunctionFactory が呼ばれているだけなので実質的には同じ
var getLocalNowFunction = kernel.CreateFunctionFromMethod(
TimeProvider.System.GetLocalNow,
"GetLocalNow");
// Kernel に関数をプラグインとして登録する
kernel.Plugins.AddFromFunctions("TimePlugin", [getLocalNowFunction]);
// テンプレートのファクトリを作成
IPromptTemplateFactory templateFactory = new KernelPromptTemplateFactory();
// テンプレートを作成
IPromptTemplate template = templateFactory.Create(new PromptTemplateConfig("""
Hello, {{$name}}!The current local time is {{TimePlugin.GetLocalNow}}.
"""));
// テンプレートに変数を設定してレンダリング
string prompt = await template.RenderAsync(kernel, new KernelArguments
{
["name"] = "Kazuki",
});
// レンダリングされたプロンプトを表示
Console.WriteLine(prompt);
最初の方のコードで関数を作成してプラグインとして登録しています。テンプレート内での関数呼び出しは {{TimePlugin.GetLocalNow}}
のようにプラグイン名と関数名をドットで区切って指定します。忘れてはいかないのが RenderAsync
メソッドの第一引数にプラグインが登録されている Kernel
を指定することです。これを忘れると関数が呼び出されません。
実行すると以下のように表示されます。ちゃんと時間が表示されるので関数が呼び出されていることが確認できます。
Hello, Kazuki!The current local time is 05/26/2025 23:09:23 +09:00.
関数には引数を渡すことも出来ます。引数は 1 つ目だけは引数名を指定しないで呼び出せます。2 つ目以降の引数は 名前=値
の形式で指定します。
例えば、フルネームを取得する関数を作成して、テンプレートから呼び出してみましょう。
以下のようにコードを書き換えます。
using Microsoft.SemanticKernel;
var kernel = new Kernel();
// 引数を受け取る関数を作成
var getFullNameFunction = kernel.CreateFunctionFromMethod(
(string firstName, string lastName) => $"{firstName} {lastName}",
"GetFullName");
// Kernel に関数をプラグインとして登録する
kernel.Plugins.AddFromFunctions("GreetingPlugin", [getFullNameFunction]);
// テンプレートのファクトリを作成
IPromptTemplateFactory templateFactory = new KernelPromptTemplateFactory();
// テンプレートを作成
IPromptTemplate template = templateFactory.Create(new PromptTemplateConfig("""
Hello, {{GreetingPlugin.GetFullName firstName=$firstName lastName='Ota'}}!
"""));
// テンプレートに変数を設定してレンダリング
string prompt = await template.RenderAsync(kernel, new KernelArguments
{
["firstName"] = "Kazuki",
});
// レンダリングされたプロンプトを表示
Console.WriteLine(prompt);
実行すると以下のように表示されます。ちゃんと GetFullName
関数が呼び出されていることが確認できます。
Hello, Kazuki Ota!
因みに Semantic Kernel のデフォルトのテンプレートエンジンでは関数の引数内で関数を呼び出すことは出来ません。あまり複雑なことは出来ないようになっています。
まとめ
ここでは Semantic Kernel のテンプレートエンジンについて、基本的な使い方を紹介しました。
テンプレートエンジンは AI を呼び出すためのプロンプトを生成するために使用されます。
Semantic Kernel では IPromptTemplateFactory
を使ってテンプレートエンジンを差し替えることが出来ます。
KernelPromptTemplateFactory
は Semantic Kernel の既定のテンプレートエンジンで、基本的な変数の展開や関数呼び出し機能を持っています。
テンプレートエンジンを使うことで、AI を呼び出すためのプロンプトを柔軟に生成することが出来ます。
次回は AI を呼び出すための関数について説明します。テンプレートエンジンと関数は密接に関連しているので、テンプレートエンジンを理解した上で関数を学ぶとより理解が深まると思います。
Discussion