Semantic Kernel の Planner を用い、複数 Plugin を束ねた処理をする
長いこと https://normalian.hatenablog.com/ でブログを書いていましたが、昨今の流行に従い Zenn 側でも記事を書いてみようとこちらでも記事を投稿していきます。初回となる今回は Semantic Kernel をテーマにしようと思っています。
Semantic Kernel の概要自体は方々のブログに記事があるので割愛しますが、Microsoft 版 LangChain と思って頂ければ大きな認識祖語はないと思います。以下の公式 GitHub サイトを参照して頂ければ概要自体はつかめることに加え、C# だけでなく Python や Java も言語としてサポートしていることが分かります(各言語ごとに進捗差異はありますが)。
Planner と Plugin って?
今回テーマにしたいのは掲題でもある Planner です。Semantic Kernel は OpenAI を活用して様々な複雑な問題を解決するオーケストレーションをしてくれますが、OpenAI 自体はファイル操作や HTTP リクエストを飛ばす等の IO 処理を含め、いくつかの処理はそのままではできません。
Semantic Kernel において、そういった個別の拡張的な機能を実現するのが Plugin となります。ファイル操作で有れば以下の様に FileIOPlugin が標準で提供されています。
FileIOPlugin 自体は標準で提供されている Plugin なので、NuGet で Microsoft.SemanticKernel.Plugins.Core をインストール後、以下の様に ImportPluginFromType メソッドを用いて Semantic Kernel の Kernel インスタンスに読み込むことが可能です。
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Planning.Handlebars;
using Microsoft.SemanticKernel.Plugins.Core;
using System.Text;
using ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddConsole();
builder.SetMinimumLevel(LogLevel.Warning);
});
#pragma warning disable SKEXP0050, SKEXP0060
var builder = Kernel.CreateBuilder();
builder.Services.AddSingleton(loggerFactory);
builder.AddAzureOpenAIChatCompletion(deployment, endpoint, apiKey);
var kernel = builder.Build();
kernel.ImportPluginFromType<FileIOPlugin>();
更にこの読み込んだ Plugin に Planner を活用して実際の処理を行います。上記のコードに加えて以下のソースコードを実行することでテキストファイルの読み込みが可能です。
string filename = @"test.txt ";
// This code as follows can read the file directly.
Console.WriteLine("========================================== Start plan");
{
// Create a plan
var planner = new HandlebarsPlanner(new HandlebarsPlannerOptions() { AllowLoops = true });
var plan = await planner.CreatePlanAsync(kernel, $"Please read {filename} file on current directory");
Console.WriteLine($"Plan: {plan}");
// Execute the plan
Console.WriteLine("========================================== Start execute plan");
var result = (await plan.InvokeAsync(kernel)).Trim();
Console.WriteLine($"Results: {result}");
}
上記のコードのうち二つ目の Console.WriteLine($"Plan: {plan}") でどの様な Plan が作成されたかを確認できます(この時点ではファイル操作等は行われません)。実行結果例としては以下の様になります。特に Plan 部分に関して、Planner がどの様な step で Plugin 等を利用して処理を行うかが確認できます。
========================================== Start applicaion
========================================== Start plan
Plan: {{!-- Step 0: Extract Key Values --}}
{{set "path" "test.txt"}}
{{!-- Step 1: Read the file --}}
{{set "fileContent" (FileIOPlugin-Read path=path)}}
{{!-- Step 2: Print the file content --}}
{{json fileContent}}
========================================== Start execute plan
Results: You can read this sentences if your Semantic Kernel application works well.
あなたの予想に反して、この文章が見えているでしょうか?
========================================== End of Application
こちらのコードの完全版は以下にあるので必要な場合は参照ください。
ちなみに ImportPluginFromType で FileIOPlugin を読み込まずにコードを実行した場合は以下の様な実行結果になります。以下の様にありもしない Example-ReadFile という Plugin を
========================================== Start applicaion
========================================== Start plan
Plan: {{!-- Step 0: Extract key values --}}
{{!-- No key values needed for this goal --}}
{{!-- Step 1: Read test.txt file --}}
{{set "fileContent" (Example-ReadFile path="test.txt")}}
{{!-- Step 2: Print the file content --}}
{{json fileContent}}
========================================== Start execute plan
fail: Microsoft.SemanticKernel.Planning.Handlebars.HandlebarsPlan[0]
Plan execution failed. Error: [HallucinatedHelpers] The plan references hallucinated helpers: Helper 'Example-ReadFile'
Microsoft.SemanticKernel.KernelException: [HallucinatedHelpers] The plan references hallucinated helpers: Helper 'Example-ReadFile'
---> HandlebarsDotNet.HandlebarsRuntimeException: Template references a helper that cannot be resolved. Helper 'Example-ReadFile'
at HandlebarsDotNet.Helpers.MissingHelperDescriptor.Invoke(HelperOptions& options, Context& context, Arguments& arguments)
at HandlebarsDotNet.Helpers.MissingHelperDescriptor.HandlebarsDotNet.Helpers.IHelperDescriptor<HandlebarsDotNet.HelperOptions>.Invoke(HelperOptions& options, Context& context, Arguments& arguments)
at HandlebarsDotNet.Helpers.LateBindHelperDescriptor.Invoke(HelperOptions& options, Context& context, Arguments& arguments)
Planner で複数の Plugin を呼ぶ
上記だけでは「単にファイルを読む操作を使っただけだよね?」になってしまうので、もう少し複雑なシナリオを考えてみましょう。次は以下を同時に行います。
- 特定の URL に HTTP リクエストを飛ばす
- 結果をテキストファイルに保存する
上記での二つの処理は、それぞれ OpenAI 単独では難しい処理で、自分でのソースコードで記載する処理と組み合わせてる必要があります。こちらに関しては以下のソースコードを参照下さい。
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Planning.Handlebars;
using Microsoft.SemanticKernel.Plugins.Core;
using System.Text;
Console.WriteLine("========================================== Start applicaion");
Console.OutputEncoding = Encoding.GetEncoding("utf-8");
var configuration = new ConfigurationBuilder()
.AddUserSecrets("90cdbb2e-9f8f-4322-8837-f59e9e4b4703")
.Build();
var deployment = configuration["AzureOpenAI:Deployment"];
var endpoint = configuration["AzureOpenAI:Endpoint"];
var apiKey = configuration["AzureOpenAI:ApiKey"];
using ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddConsole();
builder.SetMinimumLevel(LogLevel.Warning);
});
#pragma warning disable SKEXP0050, SKEXP0060
var builder = Kernel.CreateBuilder();
builder.Services.AddSingleton(loggerFactory);
builder.AddAzureOpenAIChatCompletion(deployment, endpoint, apiKey);
var kernel = builder.Build();
kernel.ImportPluginFromType<HttpPlugin>();
kernel.ImportPluginFromType<FileIOPlugin>();
var uri = "https://raw.githubusercontent.com/normalian/My-Azure-Portal-ChromeExtension/master/README.md";
var filename = "response_message.txt";
Console.WriteLine("========================================== Start make plan");
{
// Create a plan
var planner = new HandlebarsPlanner(new HandlebarsPlannerOptions() { AllowLoops = true });
var plan = await planner.CreatePlanAsync(kernel, $"Please send http request to {uri} and save the response as {filename} file on current directory.");
Console.WriteLine($"Plan: {plan}");
// Execute the plan
Console.WriteLine("========================================== Start execute plan");
var result = (await plan.InvokeAsync(kernel)).Trim();
Console.WriteLine($"Results: {result}");
}
#pragma warning restore SKEXP0050, SKEXP0060
Console.WriteLine("========================================== End of Application ");
こちらのソースコードの完全版は以下に配置したので、ご参考になれば幸いです。
Discussion