📑

Semantic Kernel のネイティブ スキルを試してみよう

2023/05/01に公開

Semantic Kernel はプロンプトをベースに作るセマンティック スキルの他に C# のコードで書いた処理をスキルとして登録するネイティブ スキルというものもあります。今回はこれを試してみようと思います。

ネイティブ スキル

ネイティブ スキルは、一定のお約束に従った C# のクラスです。以下のようなシグネチャーのメソッドをスキルの関数として利用できます。引数の名前は任意の名前を使うことが出来ます。実装したい処理にあわせて適切な名前を付けることが出来ます。

// 文字列を受け取って文字列を返す
string MySkill(string input);
// 非同期も OK
Task<string> MySkill(string input);

// 第二引数に SKContext を受け取ることで、プロンプトのコンテキストを参照できる
string MySkill(string input, SKContext context);
// 非同期も OK
Task<string> MySkill(string input, SKContext context);

// 戻り値を SKContext にすることも出来る
SKContext MySkill(string input, SKContext context);
// 非同期も OK
Task<SKContext> MySkill(string input, SKContext context);

// input も SKContext から取得するなら第一引数は不要
string MySkill(SKContext context);
Task<string> MySkill(SKContext context);
SKContext MySkill(SKContext context);
Task<SKContext> MySkill(SKContext context);

// 戻り値無し版
void MySkill(string input, SKContext context);
void MySkill(string input);

// 入力無しも OK
void MySkill();
string MySkill();
Task<string> MySkill();

上記のようなシグネチャーのメソッドに対して以下の属性を設定することで、ネイティブ スキルの関数として使えるようになります。

  • [SKFunction(description: "スキルの説明")]
    • この属性をつけるとスキルの関数として登録されます。
  • [SKFunctionName(name: "スキルの名前")]
    • 省略可能です。省略した場合はメソッド名がスキルの名前になります。
  • [SKFunctionInput(DefaultValue = "入力のデフォルト値", Description = "入力の説明")]
    • メソッドの第一引数の input の説明です。
  • [SKFunctionContextParameter(Name = "パラメーター名", DefaultValue = "パラメーターの説明", Description = "パラメーターの説明")]
    • context.Variables.Get("parameterName", out var parameterVar) で取得するパラメーターの名前やデフォルト値や説明です。
    • パラメーターの数だけこの属性を設定することが出来ます。

例えば人の名前を受け取って Hello, 名前!! のように加工して返すスキルを作ると以下のようになります。

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

namespace ConsoleApp9;
internal class SampleNativeSkill
{
    private const string s_defaultInput = "Tanaka Taro";
    private const string s_messageTemplateDefaultValue = "Hello, {0}!!";
    private const string s_messageTemplateName = "messageTemplate";

    [SKFunction("Say hello.")]
    [SKFunctionInput(DefaultValue = s_defaultInput, Description = "Your name.")]
    [SKFunctionContextParameter(DefaultValue = s_messageTemplateDefaultValue, Description = "Template for the greeting message.", Name = s_messageTemplateName)]
    public string SayHello(string name, SKContext context)
    {
        if (string.IsNullOrWhiteSpace(name))
        {
            name = s_defaultInput;
        }
        if (!context.Variables.Get(s_messageTemplateName, out var messageTemplate))
        {
            messageTemplate = s_messageTemplateDefaultValue;
        }

        return string.Format(messageTemplate, name);
    }
}

作成したネイティブ スキルは IKernelImportSkill メソッドで登録します。登録出来たら、後は IKernelRunAsync を呼び出すだけです。

using ConsoleApp9;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Orchestration;

var kernel = Kernel.Builder
    .WithLogger(LoggerFactory.Create(b =>
    {
        b.AddFilter(_ => true);
        b.AddConsole();
    }).CreateLogger<Program>())
    .Build();

var skill = kernel.ImportSkill(new SampleNativeSkill());

// 登録されたスキルの内容を確認
foreach (var f in skill)
{
    // 基本情報
    Console.WriteLine($"{f.Value.Name}: {f.Value.SkillName}, {f.Value.Description}, {f.Value.IsSemantic}");
    // パラメーターは Describe メソッドを呼ばないと取得できない
    var describe = f.Value.Describe();
    foreach (var p in describe.Parameters)
    {
        Console.WriteLine($"  {p.Name}: {p.DefaultValue}, {p.Description}");
    }
}

var input = new ContextVariables("Kazuki Ota");
var context = await kernel.RunAsync(input, skill["SayHello"]);
Console.WriteLine(context.Result);

Semantic Kernel の内部の動きがわかるようにログも出すようにしています。
実行すると以下のようなログが出力されます。

trce: Program[0]
      Importing skill ConsoleApp9.SampleNativeSkill in the global namespace
trce: Program[0]
      Importing skill name: _GLOBAL_FUNCTIONS_
trce: Program[0]
      Methods found 5
trce: Program[0]
      Method SayHello found
trce: Program[0]
      Method GetType doesn't have SKFunctionAttribute
trce: Program[0]
      Method ToString doesn't have SKFunctionAttribute
trce: Program[0]
      Method Equals doesn't have SKFunctionAttribute
trce: Program[0]
      Method GetHashCode doesn't have SKFunctionAttribute
trce: Program[0]
      Methods imported 1
SayHello: _GLOBAL_FUNCTIONS_, Say hello., False
  input: Tanaka Taro, Your name.
  messageTemplate: Hello, {0}!!, Template for the greeting message.
trce: Program[12]
      Executing function type 12: InStringAndContextOutString
Hello, Kazuki Ota!!

ログから _GLOBAL_FUNCTION_ というスキル名で登録されていることが確認できます。
スキル名を指定したい場合は以下のように ImportSkill メソッドの第二引数にスキル名を指定します。

var skill = kernel.ImportSkill(new SampleNativeSkill(), nameof(SampleNativeSkill));

この状態で実行するとログに以下のように表示されます。スキル名が SampleNativeSkill になっていることが確認できます。

trce: Program[0]
      Importing skill SampleNativeSkill
trce: Program[0]
      Importing skill name: SampleNativeSkill

まとめ

ネイティブ スキルについて確認しました。
ネイティブ スキルを使うと任意の C# のコードを実行できるようになるので、プロンプトと組み合わせて色々なことが出来るようになります。

ただ、プロンプトで出来ることはなるべくプロンプトでやってあげた方が良いです。
1 + 1 のようなカッチリとして計算や検索エンジンなどの外部リソースからデータを取ってくるケースではネイティブ スキルを使用することになると思います。

AI は進化してよりよい仕事が出来るようになるけど、プログラムにハードコーディングされた処理は進化しないので…。

Microsoft (有志)

Discussion