🔥
Semantic Kernel で Plan を進捗情報も含めて永続化したい
Semantic Kernel の Plan
は複数ステップのプランを 1 ステップずつ実行することが出来ます。
その時に Plan
をいったん途中で止めて後で続きをやりたいというケースがあると思います。Semantic Kernel のサンプルでは JSON 形式にシリアライズしているケースがありますが実はデシリアライズもサポートされています。
シリアライズは plan.ToJson()
を使い、デシリアライズは Plan Plan.FromJson(string json, SKContext context)
を使います。JSON から Plan
オブジェクトを組み立てた後に context
から ISKFunction
を取得して設定してくれます。context
は省略可能ですが、きちんとデシリアライズしたい場合は context
を渡してください。
1 つ前の記事で作ったお手製の Plan
を少し書き換えてシリアライズ、デシリアライズを試してみたいと思います。
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Orchestration;
using Microsoft.SemanticKernel.Planning;
using Microsoft.SemanticKernel.SkillDefinition;
var kernel = KernelBuilder.Create();
// デバッグ用のネイティブスキルを登録
kernel.ImportSkill(new DebugSkill("test1", "Test1 result"), "test1");
kernel.ImportSkill(new DebugSkill("test2", "Test2 result"), "test2");
kernel.ImportSkill(new DebugSkill("test3", "Test3 result"), "test3");
// import したスキルの関数をラップしたプランを作成
var test1 = new Plan(kernel.Func("test1", "invoke"))
{
// 出力を test1Result という名前で Context に格納する
Outputs = { "test1Result" },
};
var test2 = new Plan(kernel.Func("test2", "invoke"))
{
// 出力を test2Result という名前で Context に格納する
Outputs = { "test2Result" },
// 入力として test1Result を受け取る
Parameters = new ContextVariables()
{
["test1Result"] = "",
},
};
var test3 = new Plan(kernel.Func("test3", "invoke"))
{
// 入力として test1Result と test2Result を受け取る。
// test2Result は customNameParameter という名前で受け取る
Parameters = new ContextVariables()
{
["test1Result"] = "",
["customNameParameter"] = "$test2Result",
},
};
// その 3 つのプランを順番に実行するプランを作成
var plan = new Plan("Planの動きを確認する。", test1, test2, test3);
var context = kernel.CreateNewContext();
while (plan.HasNextStep)
{
await plan.InvokeNextStepAsync(context);
var json = plan.ToJson();
plan = Plan.FromJson(json, context);
}
Console.WriteLine($"Result: {plan.State.Input}");
// ログを出力して指定した結果を返すだけのデバッグ目的のスキル
class DebugSkill
{
private readonly string _name;
private readonly string _skillResult;
public DebugSkill(string name, string skillResult)
{
_name = name;
_skillResult = skillResult;
}
[SKFunction("テスト用関数")]
public string Invoke(string input, SKContext context)
{
Console.WriteLine($"{_name} was invoked with {input}.");
foreach (var variable in context.Variables)
{
Console.WriteLine($" {variable.Key}: {variable.Value}");
}
Console.WriteLine($" Result value: {_skillResult}");
return _skillResult;
}
}
実行結果は以下のようになります。
test1 was invoked with Planの動きを確認する。.
INPUT: Planの動きを確認する。
Result value: Test1 result
test2 was invoked with Test1 result.
INPUT: Test1 result
test1Result: Test1 result
Result value: Test2 result
test3 was invoked with Test2 result.
INPUT: Test2 result
test1Result: Test1 result
customNameParameter: Test2 result
Result value: Test3 result
Result: Test3 result
Plan
の実行を InvokeNextStepAsync
で 1 ステップずつ進めています。1 ステップ進めるたびに ToJson
でシリアライズして Plan.FromJson
でデシリアライズしています。
その状態でも、ちゃんと状態を保持した状態で Plan
を進めることが出来ています。
まとめ
非常に簡単にですが Plan
のシリアライズとデシリアライズについて紹介しました。
これを使うと Plan
を DB に保存しておいて後でやるということも出来ますし、色々つかいみちがありそうです。
Discussion