普通と違う感じの Semantic Kernel 入門 006「Kernel」

に公開

これまでの記事

本文

Semantic Kernel の入門の 6 回目は「Kernel」です。
Kernel は Semantic Kernel の中心的なクラスで、AI を呼び出すための関数やプラグインを管理します。Kernel を使うことで、AI を呼び出すための関数を簡単に定義し、実行することができます。

Kernel クラスは、以下のようなインターフェースになっています。

public sealed class Kernel
{
    // プロパティ
    public static IKernelBuilder CreateBuilder();
    public KernelPluginCollection Plugins { get; }
    public IList<IFunctionInvocationFilter> FunctionInvocationFilters { get; }
    public IList<IPromptRenderFilter> PromptRenderFilters { get; }
    public IList<IAutoFunctionInvocationFilter> AutoFunctionInvocationFilters { get; }
    public IServiceProvider Services { get; }
    public CultureInfo Culture { get; set; }
    public ILoggerFactory LoggerFactory { get; }
    public IAIServiceSelector ServiceSelector { get; }
    public IDictionary<string, object?> Data { get; }

    // メソッド
    public Kernel Clone();
    public T GetRequiredService<T>(object? serviceKey = null) where T : class;
    public IEnumerable<T> GetAllServices<T>() where T : class;

    public Task<FunctionResult> InvokeAsync(KernelFunction function, KernelArguments? arguments = null, CancellationToken cancellationToken = default);
    public Task<FunctionResult> InvokeAsync(string? pluginName, string functionName, KernelArguments? arguments = null, CancellationToken cancellationToken = default);
    public Task<TResult?> InvokeAsync<TResult>(KernelFunction function, KernelArguments? arguments = null, CancellationToken cancellationToken = default);
    public Task<TResult?> InvokeAsync<TResult>(string? pluginName, string functionName, KernelArguments? arguments = null, CancellationToken cancellationToken = default);

    public IAsyncEnumerable<StreamingKernelContent> InvokeStreamingAsync(KernelFunction function, KernelArguments? arguments = null, CancellationToken cancellationToken = default);
    public IAsyncEnumerable<StreamingKernelContent> InvokeStreamingAsync(string? pluginName, string functionName, KernelArguments? arguments = null, CancellationToken cancellationToken = default);
    public IAsyncEnumerable<T> InvokeStreamingAsync<T>(KernelFunction function, KernelArguments? arguments = null, CancellationToken cancellationToken = default);
    public IAsyncEnumerable<T> InvokeStreamingAsync<T>(string? pluginName, string functionName, KernelArguments? arguments = null, CancellationToken cancellationToken = default);
}

沢山ありますが、一番全体を支えている根っこの機能は以下の部分だと思います。

public sealed class Kernel
{
    // プロパティ
    public IServiceProvider Services { get; }

    // メソッド
    public T GetRequiredService<T>(object? serviceKey = null) where T : class;
    public IEnumerable<T> GetAllServices<T>() where T : class;
}

KernelIServiceProvider を内部に持っていて Kernel 自体が DI コンテナのような役割を果たしています。基本的に、この IServiceProvider に登録されているサービスを使って Kernel の機能を実現しています。

この超汎用的な機能をもう少し個別の機能特化にしたようなプロパティには以下のものがあります。

public sealed class Kernel
{
    public KernelPluginCollection Plugins { get; }
    public IList<IFunctionInvocationFilter> FunctionInvocationFilters { get; }
    public IList<IPromptRenderFilter> PromptRenderFilters { get; }
    public IList<IAutoFunctionInvocationFilter> AutoFunctionInvocationFilters { get; }
    public IAIServiceSelector ServiceSelector { get; }
}

Plugins4 回目 で紹介したプラグインのコレクションです。FunctionInvocationFilters は関数呼び出しのフィルター、PromptRenderFilters はプロンプトレンダリングのフィルター、AutoFunctionInvocationFilters は自動関数呼び出しのフィルターです。これらは Semantic Kernel の機能を拡張するために使われます。フィルターは、今までプロンプトのレンダリングや関数の呼び出しについて前に紹介しましたが、その前後に処理を挟むことが出来る機能です。こちらについては、また別の機会に詳しく紹介したいと思います。

IAIServiceSelector は、AI サービスを選択するためのインターフェースです。これまでの記事で紹介した KernelFunction で AI を呼び出す際に、どの AI サービスを使うかを決定するために使われます。Semantic Kernel では、複数の AI サービスを登録しておき、必要に応じて切り替えることができます。

実は KernelBuilderAddAzureOpenAIChatClient には、いくつかオプションの引数があります。これを使うことで、複数の AI サービスを登録することが出来ます。以下の serviceIdmodelId の引数を使うことで、AI サービスの識別子とモデルの識別子を指定できます。

public static IKernelBuilder AddAzureOpenAIChatClient(
    this IKernelBuilder builder, 
    string deploymentName, 
    string endpoint, 
    TokenCredential credentials, 
    string? serviceId = null, 
    string? modelId = null, 
    string? apiVersion = null, 
    HttpClient? httpClient = null, 
    string? openTelemetrySourceName = null, 
    Action<OpenTelemetryChatClient>? openTelemetryConfig = null)

ここで指定した serviceIdmodelIdPromptExecutionSettingsServiceIdModelId に値を設定することで、AI サービスを選択することが出来ます。試してみましょう。今回は、ダミーの AzureOpenAIClient を実装して、それを使ってクラウドに繋げずに動作確認をしたいと思います。

.NET のモックライブラリの Moq を追加した状態で以下のようにダミーの AzureOpenAIClient を実装します。

class DummyOpenAIClient(string id) : AzureOpenAIClient
{
    public override ChatClient GetChatClient(string deploymentName) => 
        new DummyChatClient($"{id}-{deploymentName}");

    class DummyChatClient(string id) : ChatClient
    {
        public override Task<ClientResult<ChatCompletion>> CompleteChatAsync(IEnumerable<ChatMessage> messages, ChatCompletionOptions options = null, CancellationToken cancellationToken = default) =>
            Task.FromResult(ClientResult.FromValue(
                OpenAIChatModelFactory.ChatCompletion(
                    role: ChatMessageRole.Assistant,
                    content: new OpenAI.Chat.ChatMessageContent(
                        $"reply message: {messages.Last().Content[0].Text} generated by {id}.")),
                Mock.Of<PipelineResponse>()));
    }
}

このダミーのクライアントを使って、Kernel を作成します。

var builder = Kernel.CreateBuilder();
builder.AddAzureOpenAIChatClient(
    "model1",
    new DummyOpenAIClient("service1"),
    serviceId: "service1",
    modelId: "model1");
builder.AddAzureOpenAIChatClient(
    "model2",
    new DummyOpenAIClient("service2"),
    serviceId: "service2",
    modelId: "model2");

var kernel = builder.Build();

これで、service1 & model1service2 & model2 という 2 つの AI サービスを登録しました。次に、KernelFunction を使って AI を呼び出してみましょう。KernelFunction 作成時に渡せる PromptExecutionSettingsServiceIdModelId を使うことで、どの AI サービスを使うかを指定できます。今回は func1service1 & model1 を、func2service2 & model2 を指定してみます。

var func1 = kernel.CreateFunctionFromPrompt("Test prompt 1",
    new PromptExecutionSettings
    {
        ServiceId = "service1",
        ModelId = "model1",
    });
var func2 = kernel.CreateFunctionFromPrompt("Test prompt 2",
    new PromptExecutionSettings
    {
        ServiceId = "service2",
        ModelId = "model2",
    });

そして、この関数を呼び出して結果を表示してみます。

var result1 = await func1.InvokeAsync(kernel);
var result2 = await func2.InvokeAsync(kernel);

Console.WriteLine($"result1: {result1.GetValue<string>()}");
Console.WriteLine($"result2: {result2.GetValue<string>()}");

実行すると、以下のような結果が得られます。

result1: reply message: Test prompt 1 generated by service1-model1.
result2: reply message: Test prompt 2 generated by service2-model2.

ちゃんと使用する AI サービスを切り替えて、異なる応答を得ることができました。このように、使用した AI サービスを切り替えることができるのが Semantic Kernel の特徴の 1 つです。例えば、o3gpt-4.1 を処理に応じて使い分けたい時などに便利です。

残りのメソッドは、KernelFunction を使って AI を呼び出すためのメソッドです。InvokeAsync メソッドは、指定した関数を非同期で呼び出し、結果を返します。InvokeStreamingAsync メソッドは、ストリーミングで結果を返します。これらのメソッドは、AI を呼び出す際に非常に便利です。

特に、これまではストリーミング系の呼び出しを使ってきていませんでしたがストリーミングも IAsyncEnumerable<StreamingKernelContent> を返すので簡単に使うことが出来ます。 チャットの返信は StreamingChatMessageContent という型になっているので、その型の場合はテキストを出してあげることで、ストリーミングの内容を確認することが出来ます。

前に KernelFunction を試したコードをストリームに変更してみましょう。


using Azure.Identity;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.PromptTemplates.Liquid;

// User Secrets から設定を読み込む
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.");

// Builder を作成
var builder = Kernel.CreateBuilder();

// AOAI 用の Chat Client を登録
builder.AddAzureOpenAIChatClient(
    modelDeploymentName,
    endpoint,
    new AzureCliCredential());

// AI サービスが登録された Kernel を作成
var kernel = builder.Build();

// プロンプトから関数を作成
var introductionFunction = KernelFunctionFactory.CreateFromPrompt(
    // Liquid テンプレートはループや分岐をサポートしているので複数のタグを生成可能
    new PromptTemplateConfig("""
        <message role="system">
          あなたは猫型アシスタントです。
          猫らしく振舞うために語尾は「にゃん」にしてください。
        </message>
        {% for message in messages %}
        <message role="{{message.role}}">
            {{message.text}}
        </message>
        {% endfor %}
        """)
    {
        TemplateFormat = LiquidPromptTemplateFactory.LiquidTemplateFormat,
    },
    // テンプレートファクトリを指定して Liquid テンプレートを使用する
    promptTemplateFactory: new LiquidPromptTemplateFactory());

// パラメーターとして渡す ChatMessage の配列を作成
ChatMessage[] messages = [
    new (ChatRole.User, "こんにちは!私の名前は Kazuki です。どうぞよろしくお願いします。"),
    new (ChatRole.Assistant, "こんにちは、Kazukiさん!よろしくお願いします。にゃん!"),
    new (ChatRole.User, "実は私は犬なんです…、黙っててごめんなさい…。猫の敵です…。"),
];

// ストリーム対応の API で呼び出す
bool isFirst = true;
await foreach (var streamChunk in introductionFunction.InvokeStreamingAsync(
    kernel,
    new KernelArguments
    {
        ["messages"] = messages,
    }))
{
    // ストリームのチャンクが StreamingChatMessageContent であればそのままメッセージを取得
    var message = streamChunk switch
    {
        StreamingChatMessageContent text => text.Content,
        _ => Convert.ToBase64String(streamChunk.ToByteArray()),
    };

    // 最初のメッセージかどうかを判定して、出力形式を変える
    if (isFirst)
    {
        Console.Write("Assistant: ");
        isFirst = false;
    }

    Console.Write($"{message}|");
}

Console.WriteLine();

結構長いコードですが、最後の await foreach の部分がストリーミングの部分です。StreamingChatMessageContent の場合はそのままメッセージを取得し、そうでない場合は Base64 エンコードされたバイナリデータを出力しています。ストリームで返ってきている 1 つ 1つのチャンクを | で区切って出力しています。
実行すると、以下のような出力が得られます。

Assistant: ||え|っ|、|犬|さん|だった|の|に|ゃ|ん|!?|び|っ|く|り|した|け|ど|、大|丈夫|に|ゃ|ん|よ|!|犬|さん|も|お|友|だ|ち|に|ゃ|ん|。|これ|から|も|仲|良|く|して|ほ|しい|に|ゃ|ん|!|||

このように、ストリーミングで AI の応答を受け取ることができます。ストリーミングを使うことで、AI の応答をリアルタイムで受け取ることができ、ユーザーにとってよりインタラクティブな体験を提供することができます。

その他の API

Data プロパティは使ったことがありませんが、Kernel にデータを保存するための辞書です。これを使うことで、Kernel を経由してデータを共有することができます。こういう系の機能は出来れば使いたくない奴ですね…。なんとなく保守性が低下しそうな気がします。

LoggerFactory は、Kernel のロギングを行うためのファクトリです。Semantic Kernel の内部でログを出力するために使われます。これを使うことで、Semantic Kernel の動作をデバッグすることができます。LoggerFactory を設定する方法は KernelBuilderServicesAddLogging を使って登録するだけです。

やってみましょう。以下のように Kernel を作成する前に AddLogging を使ってロガーを登録します。
そして、InvokePromptAsync を呼び出して AI を呼び出すと、コンソールにログが出力されます。


using Azure.Identity;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
using Microsoft.Extensions.Configuration;

// User Secrets から設定を読み込む
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.");

// Builder を作成
var builder = Kernel.CreateBuilder();

// AOAI 用の Chat Client を登録
builder.AddAzureOpenAIChatClient(
    modelDeploymentName,
    endpoint,
    new AzureCliCredential());

// Logger を登録
builder.Services.AddLogging(builder =>
{
    builder.AddConsole();
    builder.SetMinimumLevel(LogLevel.Trace);
});

// AI サービスが登録された Kernel を作成
var kernel = builder.Build();

// プロンプトを AI に送信して応答を取得
var result = await kernel.InvokePromptAsync("こんにちは");
Console.WriteLine(result.GetValue<string>());

実行結果は以下のようになります。

info: Microsoft.SemanticKernel.KernelFunction[0]
      Function (null)-InvokePromptAsync_bd873ff192664f84bca2015f49a41bc4 invoking.
trce: Microsoft.SemanticKernel.KernelFunction[0]
      Function (null)-InvokePromptAsync_bd873ff192664f84bca2015f49a41bc4 arguments: {}
trce: Microsoft.SemanticKernel.KernelFunctionFactory[0]
      Rendered prompt: こんにちは
info: Microsoft.Extensions.AI.OpenTelemetryChatClient[1]
      {}
trce: Microsoft.Extensions.AI.LoggingChatClient[805843669]
      GetResponseAsync invoked: [
        {
          "role": "user",
          "contents": [
            {
              "$type": "text",
              "text": "こんにちは"
            }
          ]
        }
      ]. Options: null. Metadata: {
        "providerName": "openai",
        "providerUri": "https://xxxxxxxxxxxxxxxxxx.openai.azure.com/",
        "defaultModelId": "gpt-4.1"
      }.
trce: Microsoft.Extensions.AI.LoggingChatClient[384896670]
      GetResponseAsync completed: {
        "messages": [
          {
            "role": "assistant",
            "contents": [
              {
                "$type": "text",
                "text": "こんにちは!\uD83D\uDE0A  \n今日はどうされましたか?何かお手伝いできることがあれば教えてください。"
              }
            ],
            "messageId": "chatcmpl-BcBaMC2hi9ekT7UW24YHDmGdBGBQz"
          }
        ],
        "responseId": "chatcmpl-BcBaMC2hi9ekT7UW24YHDmGdBGBQz",
        "modelId": "gpt-4.1-2025-04-14",
        "createdAt": "2025-05-28T13:58:18+00:00",
        "finishReason": "stop",
        "usage": {
          "inputTokenCount": 8,
          "outputTokenCount": 27,
          "totalTokenCount": 35,
          "additionalCounts": {
            "InputTokenDetails.AudioTokenCount": 0,
            "InputTokenDetails.CachedTokenCount": 0,
            "OutputTokenDetails.ReasoningTokenCount": 0,
            "OutputTokenDetails.AudioTokenCount": 0,
            "OutputTokenDetails.AcceptedPredictionTokenCount": 0,
            "OutputTokenDetails.RejectedPredictionTokenCount": 0
          }
        }
      }.
info: Microsoft.Extensions.AI.OpenTelemetryChatClient[1]
      {"finish_reason":"stop","index":0,"message":{}}
info: Microsoft.SemanticKernel.KernelFunction[0]
      Function (null)-InvokePromptAsync_bd873ff192664f84bca2015f49a41bc4 succeeded.
trce: Microsoft.SemanticKernel.KernelFunction[0]
      Function (null)-InvokePromptAsync_bd873ff192664f84bca2015f49a41bc4 result: こんにちは!??
今日はどうされましたか?何かお手伝いできることがあれば教えてください。
info: Microsoft.SemanticKernel.KernelFunction[0]
      Function (null)-InvokePromptAsync_bd873ff192664f84bca2015f49a41bc4 completed. Duration: 5.2548557s
こんにちは!??
今日はどうされましたか?何かお手伝いできることがあれば教えてください。

このように、Kernel を使うことで、Semantic Kernel の内部の動作をログとして出力することができます。これにより、デバッグやトラブルシューティングが容易になります。本番時には必ず指定しましょう。

まとめ

今回は Kernel クラスについて紹介しました。Kernel は Semantic Kernel の中心的なクラスで、AI を呼び出すための関数やプラグインを管理します。個人的に抱いているイメージは Kernel クラスは Semantic Kernel 内の各機能を繋ぐためのハブのようなイメージです。様々な API で使用され、そこに登録されているサービスを使用して様々な機能が実装されています。
重要なクラスなので Semantic Kernel を使う場合は軽くコードに目を通しておいてもいいかもしれません。

次回は、フィルターについて紹介したいと思います。(予定は未定です)

目次

普通と違う感じの Semantic Kernel 入門の目次

Microsoft (有志)

Discussion