📌

Semantic Kernel のテンプレートをプログラムでレンダリングする方法とテンプレートの文法

2023/05/04に公開

今まで Semantic Kernel のテンプレートをいくつか書いてきましたが、このテンプレートの文法(というほど大げさなものではないですが…)を簡単に見て見ようと思います。

テンプレートの実行方法

さて、まずは簡単に試せるようにテンプレートをプログラムでレンダリングする方法を見てみようと思います。テンプレートをレンダリングするには PromptTemplateEngine クラスの RenderAsync メソッドを呼び出します。このメソッドはプロンプトの文字列と SKContext を渡してあげると、レンダリングされた文字列を返してくれます。

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Orchestration;
using Microsoft.SemanticKernel.SkillDefinition;
using Microsoft.SemanticKernel.TemplateEngine;

var kernel = Kernel.Builder.Build();
string prompt = """
    これはテンプレートです。
    {{$input}}
    """;

var templateEngine = new PromptTemplateEngine();
var context = kernel.CreateNewContext();
context.Variables.Update("にゅうりょく!");
var renderedPrompt = await templateEngine.RenderAsync(prompt, context);
Console.WriteLine(renderedPrompt);

このコードを実行すると、次のような出力が得られます。

これはテンプレートです。
にゅうりょく!

簡単ですね。

テンプレートの文法

今まで特に説明をせずに使ってきましたがテンプレートは {{}} に囲まれた部分に $変数名 と記載すると変数がその場所に展開されるというルールになっています。
実は変数を展開する以外にも別のスキルを呼び出して、その結果を展開するといったこともサポートされています。例えば、以下のようなネイティブ スキルを定義してみます。

class Skill
{
    [SKFunction("Greeting")]
    public string Greeting(string input, SKContext context) => $"Hello, {input}.";
}

このネイティブ スキルを MySkill という名前で Kernel に登録しておきます。

var kernel = Kernel.Builder.Build();
kernel.ImportSkill(new Skill(), "MySkill");

このスキルの Greeting 関数を呼び出すにはテンプレート内で {{MySkill.Greeting}} と記載します。試してみましょう。

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Orchestration;
using Microsoft.SemanticKernel.SkillDefinition;
using Microsoft.SemanticKernel.TemplateEngine;

var kernel = Kernel.Builder.Build();
kernel.ImportSkill(new Skill(), "MySkill");

string prompt = """
    これはテンプレートです。
    {{$input}}

    スキルを呼び出します。
    {{MySkill.Greeting}}
    """;

var templateEngine = new PromptTemplateEngine();
var context = kernel.CreateNewContext();
context.Variables.Update("にゅうりょく!");
var renderedPrompt = await templateEngine.RenderAsync(prompt, context);
Console.WriteLine(renderedPrompt);


class Skill
{
    [SKFunction("Greeting")]
    public string Greeting(string input, SKContext context) => $"Hello, {input}.";
}

実行すると以下のように出力されます。

これはテンプレートです。
にゅうりょく!

スキルを呼び出します。
Hello, にゅうりょく!.

ちゃんと MySkillGreeting が呼び出されていることが確認できます。この他に {{MySkill.Greeting 'Kazuki Ota'}} のように Greetinginput に値を渡すこともできます。

コードを少し変えて試してみましょう。

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Orchestration;
using Microsoft.SemanticKernel.SkillDefinition;
using Microsoft.SemanticKernel.TemplateEngine;

var kernel = Kernel.Builder.Build();
kernel.ImportSkill(new Skill(), "MySkill");

string prompt = """
    これはテンプレートです。
    {{$input}}

    スキルを呼び出します。
    {{MySkill.Greeting 'Kazuki Ota'}}
    """;

var templateEngine = new PromptTemplateEngine();
var context = kernel.CreateNewContext();
context.Variables.Update("にゅうりょく!");
var renderedPrompt = await templateEngine.RenderAsync(prompt, context);
Console.WriteLine(renderedPrompt);


class Skill
{
    [SKFunction("Greeting")]
    public string Greeting(string input, SKContext context) => $"Hello, {input}.";
}

実行すると以下のように出力されます。

これはテンプレートです。
にゅうりょく!

スキルを呼び出します。
Hello, Kazuki Ota.

ちゃんと Greeting 関数の Kazuki Ota が渡されていることが確認できます。この他にリテラルではなく変数を渡すことも出来ます。例えば {{MySkill.Greeting $name}} のように書くと name 変数が Greeting 関数に渡されます。

これも、コードを少し変えて試してみましょう。

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Orchestration;
using Microsoft.SemanticKernel.SkillDefinition;
using Microsoft.SemanticKernel.TemplateEngine;

var kernel = Kernel.Builder.Build();
kernel.ImportSkill(new Skill(), "MySkill");

string prompt = """
    これはテンプレートです。
    {{$input}}

    スキルを呼び出します。
    {{MySkill.Greeting $name}}
    """;

var templateEngine = new PromptTemplateEngine();
var context = kernel.CreateNewContext();
context.Variables.Update("にゅうりょく!");
context.Variables["name"] = "田中 太郎"; // コンテキストに変数を追加
var renderedPrompt = await templateEngine.RenderAsync(prompt, context);
Console.WriteLine(renderedPrompt);


class Skill
{
    [SKFunction("Greeting")]
    public string Greeting(string input, SKContext context) => $"Hello, {input}.";
}

実行すると以下のように出力されます。

これはテンプレートです。
にゅうりょく!

スキルを呼び出します。
Hello, 田中 太郎.

ちゃんと name 変数が Greeting 関数にわたっていることが確認できます。

今までは MySkill という名前でスキルを登録していましたが ImportSkill メソッドでスキル名を指定しなかった場合はグローバル スキルという特殊なところに関数が登録されます。この場合はスキル名を省略して、いきなり関数名を指定することで呼び出すことが出来ます。

これも、プログラムを少し書き換えて試してみましょう。

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Orchestration;
using Microsoft.SemanticKernel.SkillDefinition;
using Microsoft.SemanticKernel.TemplateEngine;

var kernel = Kernel.Builder.Build();
// スキル名を指定せずにインポート
kernel.ImportSkill(new Skill());

// 関数呼び出し時にスキル名の指定が不要
string prompt = """
    これはテンプレートです。
    {{$input}}

    スキルを呼び出します。
    {{Greeting $name}}
    """;

var templateEngine = new PromptTemplateEngine();
var context = kernel.CreateNewContext();
context.Variables.Update("にゅうりょく!");
context.Variables["name"] = "田中 太郎"; // コンテキストに変数を追加
var renderedPrompt = await templateEngine.RenderAsync(prompt, context);
Console.WriteLine(renderedPrompt);


class Skill
{
    [SKFunction("Greeting")]
    public string Greeting(string input, SKContext context) => $"Hello, {input}.";
}

実行結果は先ほどと同じなので割愛します。

まとめ

ということでテンプレートをプログラム内でレンダリングする方法と、簡単なテンプレートの文法として変数の参照と関数の呼び出しについて解説しました。
プログラム内でテンプレートを評価したくなるケースは少ないと思いますが、アイデア次第では何か出来ると思う(何処かで AI にテンプレートを吐かせるようにプロンプトを組んで実際にあぷr内で評価している例を見た記憶がある…)ので記憶の片隅にでもとどめておこうと思います。

Microsoft (有志)

Discussion