普通と違う感じの Semantic Kernel 入門 008「DI コンテナとの統合」
これまでの記事
- 普通と違う感じの Semantic Kernel 入門 001「関数」
- 普通と違う感じの Semantic Kernel 入門 002「テンプレートエンジン」
- 普通と違う感じの Semantic Kernel 入門 003「AI を呼ぶ関数」
- 普通と違う感じの Semantic Kernel 入門 004「プラグイン」
- 普通と違う感じの Semantic Kernel 入門 005「Chat Completions API を使おう」
- 普通と違う感じの Semantic Kernel 入門 006「Kernel」
- 普通と違う感じの Semantic Kernel 入門 007「フィルター」
本文
ここでは、Semantic Kernel の DI コンテナとの統合について解説します。
これまでは KernelBuilder
を使って Kernel
を構築していました。そして、KernelBuilder
が IServiceCollection
を持っているため、そこにサービスを登録していました。しかし、一般的な .NET のアプリケーションでは DI コンテナである IServiceCollection
は汎用ホストから提供されます。このようなアプリケーションで、どのように Kernel
を構築するのかといったことを解説します。
Kernel
を構築する
手組で DI コンテナでオブジェクトを組み立てるには、まずそのオブジェクトを組み立てるために何が必要なのかを知る必要があります。Kernel
クラスをインスタンス化するだけであれば new Kernel()
と書くことでインスタンスの作成が出来ます。AI などのサービスを使わない場合はプラグインの追加やプラグインに登録された関数の呼び出しも可能です。
Kernel
を作成してプラグインを登録し、関数を呼び出すコードは以下のようになります。
using Microsoft.SemanticKernel;
// AI が不要なら Kernel の作成は簡単
var kernel = new Kernel();
// Kernel 作成後にプラグインを登録することもできる
kernel.Plugins.AddFromFunctions("TimePlugin",
[
KernelFunctionFactory.CreateFromMethod(
() => TimeProvider.System.GetLocalNow(),
functionName: "GetLocalNow",
description: "現在のローカル時間を取得します。"),
]);
// プラグインの関数を呼び出す
var result = await kernel.InvokeAsync("TimePlugin", "GetLocalNow");
Console.WriteLine(result.GetValue<DateTimeOffset>());
実行結果は、現在時刻が表示されるだけなので割愛します。
AI などのサービスを使う場合は、Kernel
のコンストラクタに明示的に渡す必要があります。実際に Kernel
のコンストラクタは 2 つの引数を受け取ります。どちらのパラメーターにもデフォルト値に null
が設定されているため引数無しでインスタンス化できているのです。実際の Kernel
のコンストラクタは以下のようになっています。
Kernel(IServiceProvider? services = null, KernelPluginCollection? plugins = null)
services
パラメーターは DI コンテナの IServiceProvider
を受け取ります。plugins
パラメーターはプラグインのコレクションを受け取ります。プラグインのコレクションは KernelPluginCollection
クラスのインスタンスです。KernelPluginCollection
は KernelPlugin
のコレクションで、プラグインの名前から KernelPlugin
を取得する機能がある以外は普通のコレクションです。
実際には KernelPluginCollection
を明示的に指定することはレアケースです。plugins
パラメーターが null
の場合は KernelPluginCollection
を services
パラメーターから取得します。services
にも KernelPluginCollection
が登録されていない場合は、services
から IEnumerable<KernelPlugin>
を取得し、KernelPluginCollection
を作成します。つまり、IServiceProvider
に KernelPlugin
を登録しておけば、よしなに KernelPluginCollection
が作成されます。
実際に KernelPlugin
を DI コンテナに登録して動きを確認してみましょう。まずは以下のようなプラグインのクラスを作成します。どちらも TimeProvider
を DI コンテナから取得して、現在のローカル時間を取得したり天気予報を返す関数を持っています。
// Kernel に登録するプラグイン
class TimePlugin(TimeProvider timerProvider)
{
[KernelFunction]
public DateTimeOffset GetLocalNow() => timerProvider.GetLocalNow();
}
class WeatherForecastPlugin(TimeProvider timerProvider)
{
[KernelFunction]
public string GetWeatherForecast(string location)
{
var now = timerProvider.GetLocalNow();
return $"[{now:yyyy-MM-dd HH:mm}] The weather in {location} is sunny.";
}
[KernelFunction]
public string GetWeatherAdvice(string location)
{
var now = timerProvider.GetLocalNow();
return $"[{now:yyyy-MM-dd HH:mm}] It's a great day to be outside in {location}!";
}
}
このプラグインを DI コンテナに KernelPlugin
として登録します。以下のように IServiceCollection
に登録するコードを書きます。
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
var services = new ServiceCollection();
// プラグインで使う TimeProvider を登録
services.AddSingleton(TimeProvider.System);
// KernelPluginFactory を使ってプラグインを登録
services.AddSingleton(sp =>
KernelPluginFactory.CreateFromType<TimePlugin>("TimePlugin", sp));
services.AddSingleton(sp =>
KernelPluginFactory.CreateFromType<WeatherForecastPlugin>("WeatherForecastPlugin", sp));
登録が終わったら、IServiceProvider
を使って Kernel
を作成します。以下のように Kernel
をインスタンス化し、プラグインの関数を呼び出すコードを書きます。呼び出した結果は標準出力に出力しましょう。
// IServiceProvider を使って Kernel を作成
var kernel = new Kernel(services.BuildServiceProvider());
// プラグインの関数を呼び出す
var now = await kernel.InvokeAsync("TimePlugin", "GetLocalNow");
var arguments = new KernelArguments
{
["location"] = "Tokyo",
};
var weatherForecast = await kernel.InvokeAsync("WeatherForecastPlugin", "GetWeatherForecast", arguments);
var weatherAdvice = await kernel.InvokeAsync("WeatherForecastPlugin", "GetWeatherAdvice", arguments);
// 結果を出力
Console.WriteLine(now.GetValue<DateTimeOffset>());
Console.WriteLine(weatherForecast.GetValue<string>());
Console.WriteLine(weatherAdvice.GetValue<string>());
このコードを実行すると以下のような結果が得られます。
2025/06/01 12:15:10 +09:00
[2025-06-01 12:15] The weather in Tokyo is sunny.
[2025-06-01 12:15] It's a great day to be outside in Tokyo!
このように、DI コンテナを使って Kernel
を構築し、プラグインの関数を呼び出すことができます。さらに Kernel
も DI コンテナに登録することが出来ます。このための拡張メソッドとして AddKernel
メソッドが提供されています。これを使うと以下のように書けます。
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
var services = new ServiceCollection();
// プラグインで使う TimeProvider を登録
services.AddSingleton(TimeProvider.System);
// KernelPluginFactory を使ってプラグインを登録
services.AddSingleton(sp =>
KernelPluginFactory.CreateFromType<TimePlugin>("TimePlugin", sp));
services.AddSingleton(sp =>
KernelPluginFactory.CreateFromType<WeatherForecastPlugin>("WeatherForecastPlugin", sp));
// DI コンテナに Kernel を登録
services.AddKernel();
var sp = services.BuildServiceProvider();
// DI コンテナから Kernel を作成
var kernel = sp.GetRequiredService<Kernel>();
// プラグインの関数を呼び出す
var now = await kernel.InvokeAsync("TimePlugin", "GetLocalNow");
var arguments = new KernelArguments
{
["location"] = "Tokyo",
};
var weatherForecast = await kernel.InvokeAsync("WeatherForecastPlugin", "GetWeatherForecast", arguments);
var weatherAdvice = await kernel.InvokeAsync("WeatherForecastPlugin", "GetWeatherAdvice", arguments);
// 結果を出力
Console.WriteLine(now.GetValue<DateTimeOffset>());
Console.WriteLine(weatherForecast.GetValue<string>());
Console.WriteLine(weatherAdvice.GetValue<string>());
// Kernel に登録するプラグイン
class TimePlugin(TimeProvider timerProvider)
{
[KernelFunction]
public DateTimeOffset GetLocalNow() => timerProvider.GetLocalNow();
}
class WeatherForecastPlugin(TimeProvider timerProvider)
{
[KernelFunction]
public string GetWeatherForecast(string location)
{
var now = timerProvider.GetLocalNow();
return $"[{now:yyyy-MM-dd HH:mm}] The weather in {location} is sunny.";
}
[KernelFunction]
public string GetWeatherAdvice(string location)
{
var now = timerProvider.GetLocalNow();
return $"[{now:yyyy-MM-dd HH:mm}] It's a great day to be outside in {location}!";
}
}
実行結果は先ほどと同じなので割愛します。
AI サービスを使う場合
ここまでは AI を呼び出さない場合の Kernel
の構築方法を解説しました。Semantic Kernel は AI を呼び出してなんぼのフレームワークです。ということで、AI サービスを使う場合の Kernel
の構築方法を解説します。AI サービスを呼び出す場合も所定のサービスを DI コンテナに登録しておくだけです。若干厄介なのが 2025 年 6 月 1 日時点では、新しい IChatClient
を使用する場合のメソッドがプレビュー版であることです。IChatClient
の前に使われていた(現在の現役ですが) IChatCompletionService
はプレビュー版ではないので過渡期だなぁと感じますね。
AI 系のサービスを DI コンテナに登録する場合も、Semantic Kernel から提供されている IServiceCollection
に対する拡張メソッドを使うことで簡単に登録できます。さらに、適切なサービスを自分で登録をすれば Semantic Kernel で提供されている拡張メソッドを使わなくても AI サービスを使うことができます。例えばチャット系機能は以下のように IChatClient
を DI コンテナに登録することで使えるようになります。
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
var services = new ServiceCollection();
services.AddSingleton<IChatClient>(new EchoChatClient("default"));
var kernel = new Kernel(services.BuildServiceProvider());
var promptFunction = kernel.CreateFunctionFromPrompt("Hello");
// デフォルトの IChatClient が使われる
var result = await kernel.InvokeAsync(promptFunction);
Console.WriteLine(result.GetValue<string>());
// ダミーの IChatClient 実装
class EchoChatClient(string name) : IChatClient
{
public void Dispose()
{
throw new NotImplementedException();
}
public Task<ChatResponse> GetResponseAsync(IEnumerable<ChatMessage> messages, ChatOptions? options = null, CancellationToken cancellationToken = default)
{
var lastMessage = messages.LastOrDefault()?.Text ?? "";
return Task.FromResult(
new ChatResponse(new ChatMessage(ChatRole.Assistant, $"Echo: {lastMessage} by {name}")));
}
public object? GetService(Type serviceType, object? serviceKey = null)
{
if (serviceType == typeof(ChatClientMetadata))
{
return new ChatClientMetadata("mock");
}
return null;
}
public IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(IEnumerable<ChatMessage> messages, ChatOptions? options = null, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
}
ダミーのエコーを返す IChatClient
を実装して、IServiceCollection
に登録しています。この IChatClient
が Semantic Kernel が AI (Chat Completions API) を使う場所で使われます。今回の例では Hello
というプロンプトを実行する関数を作成して呼び出しています。実行結果は以下のようになります。
Echo: Hello by default
DI コンテナに AddKeyedSingleton
を使って serviceKey
を指定して登録することで、複数の IChatClient
を登録することもできます。以下のように serviceKey
を指定して登録します。
services.AddKeyedSingleton<IChatClient>("key1", new EchoChatClient("key1"));
services.AddKeyedSingleton<IChatClient>("key2", new EchoChatClient("key2"));
この key1
や key2
は PromptExecutionSettings
の ServiceId
で指定します。
以下のようなコードになります。
// キーを指定して使用する IChatClient を指定する
var result1 = await kernel.InvokeAsync(promptFunction,
arguments: new(new PromptExecutionSettings { ServiceId = "key1" }));
Console.WriteLine(result1.GetValue<string>());
var result2 = await kernel.InvokeAsync(promptFunction,
arguments: new(new PromptExecutionSettings { ServiceId = "key2" }));
Console.WriteLine(result2.GetValue<string>());
実行結果は以下のようになります。
Echo: Hello by key1
Echo: Hello by key2
その他の AI サービスも同様に DI コンテナに登録して使う形になります。現時点では、まだプレビューですがベクトルを生成するような場合には IEmbeddingGenerator
を DI コンテナに登録して使います。このパターンを覚えておくと Semantic Kernel を使う際に使い方の当たりをつけやすいと思います。
次は Semantic Kernel で提供されているメソッドを使っていこうと思います。こちらはプレビュー機能なので気を付けてください。
IServiceCollection
にも Semantic Kernel の拡張メソッドの AddAzureOpenAIChatClient
があります。これを使うと Azure OpenAI Service の Chat Completions API を簡単に使えるようになります。試しに先ほどやった Hello
を送るコードの IChatClient
を Azure OpenAI Service の Chat Completions API を使うように変更してみます。
コードは以下のようになります。
using Azure.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
var configuration = new ConfigurationBuilder()
.AddUserSecrets<Program>()
.Build();
// AOAI にデプロイしているモデル名
var modelDeploymentName = configuration["AOAI:ModelDeploymentName"]
?? throw new ArgumentNullException("AOAI:ModelDeploymentName is not set in the configuration.");
// AOAI のエンドポイント
var endpoint = configuration["AOAI:Endpoint"]
?? throw new ArgumentNullException("AOAI:Endpoint is not set in the configuration.");
var services = new ServiceCollection();
#pragma warning disable SKEXP0010 // preview なので警告が出るが無視する
services.AddAzureOpenAIChatClient(modelDeploymentName, endpoint, new AzureCliCredential());
#pragma warning restore SKEXP0010
var kernel = new Kernel(services.BuildServiceProvider());
var promptFunction = kernel.CreateFunctionFromPrompt("Hello");
// デフォルトの IChatClient が使われる
var result = await kernel.InvokeAsync(promptFunction);
Console.WriteLine(result.GetValue<string>());
実行すると以下のようになります。
Hello! How can I help you today?
ASP.NET Core での使用方法
ASP.NET Core のように汎用ホストを使う場合は DI コンテナがフレームワーク側で提供されます。そのため、KernelBuilder
を使うよりも IServiceCollection
に各種サービスを登録して使うほうが自然に使えます。適当な GET リクエストの message
パラメーターを受け取って AI に送信し、結果を返すような ASP.NET Core の Web API を作成してみます。
以下のようなコードになります。
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.SemanticKernel;
var builder = WebApplication.CreateBuilder(args);
// AOAI にアクセスする IChatClient を登録する
#pragma warning disable SKEXP0010
builder.Services.AddAzureOpenAIChatClient(
builder.Configuration["AOAI:ModelDeploymentName"]!,
new AzureOpenAIClient(new(builder.Configuration["AOAI:Endpoint"]!),
new AzureCliCredential()));
#pragma warning restore SKEXP0010
// Kernel を DI コンテナに登録する
builder.Services.AddKernel();
// Kernel のプラグインを登録する
builder.Services.AddSingleton(sp =>
KernelPluginFactory.CreateFromFunctions("AI",
[
KernelFunctionFactory.CreateFromPrompt(promptConfig: new("""
<message role="system">
あなたは猫型アシスタントです。ユーザーの問題解決を行ってください。
猫らしく振舞うために語尾は「にゃん」にしてください。
</message>
<message>
{{$message}}
</message>
""")
{
Name = "InvokeCat",
}),
]));
var app = builder.Build();
app.MapGet("/ai", async ([FromQuery]string message, [FromServices]Kernel kernel) =>
{
// DI コンテナから Kernel を取して処理を行う
var result = await kernel.InvokeAsync(
"AI",
"InvokeCat",
arguments: new()
{
["message"] = message,
});
return result.GetValue<string>();
});
app.Run();
このコードでは、AOAI の Chat Completions API を使うために AddAzureOpenAIChatClient
を使って IChatClient
を DI コンテナに登録しています。さらに、Kernel
を DI コンテナに登録し、プラグインを登録しています。プラグインには、AOAI の Chat Completions API を使うための関数を登録しています。InvokeCat
という関数は、ユーザーからのメッセージを受け取り、猫型アシスタントとして応答するように設定されています。
このコードを実行して、/ai?message=こんにちは
のようにリクエストを送ると、以下のような応答が得られます。
にゃん!こんにちはにゃん。どんなお手伝いができるかにゃん?
このように、ASP.NET Core の Web API と Semantic Kernel を組み合わせて、AI を使ったアプリケーションを簡単に作成することができます。
Kernel の DI コンテナのライフタイム
Semantic Kernel の Kernel
クラスを DI コンテナに登録する際には一般的に Transient
(DI コンテナから取得されるたびにインスタンス化される) で登録します。これは、Kernel
が Plugins
プロパティなどに可変なコレクションを持っていて、それに要素を追加することで処理ごとに異なるプラグインを登録するような使い方をするケースがあるためです。このような使い方をする場合に Kernel
を Singleton
で登録してしまうと、プラグインの登録が上書きされてしまい、意図しない動作をする可能性があります。
まとめ
ここでは、Semantic Kernel の DI コンテナとの統合について解説しました。Kernel
を手組で構築する方法や、DI コンテナを使って Kernel
を構築する方法、AI サービスを使う場合の Kernel
の構築方法などを紹介しました。また、ASP.NET Core での使用方法や Kernel
の DI コンテナのライフタイムについても触れました。
Semantic Kernel は ASP.NET Core などの .NET のアプリケーションとの親和性が高くなるように作られていることがなんとなく感じ取ってもらえたかと思います。
次回は「Microsoft.Extensions.AI と Semantic Kernel の統合」について解説します。
Discussion