👌

普通と違う感じの Semantic Kernel 入門 009「Microsoft.Extensions.AI との統合」

に公開

これまでの記事

はじめに

ここでは、Microsoft.Extensions.AI と Semantic Kernel の統合について解説します。
Microsoft.Extensions.AI は .NET のチームの方で開発している AI (Chat Completion API や Embedding API) を使うための API の抽象化レイヤーを提供するものです。この他に Microsoft.Extesnsions.VectorData というベクトルストアを使うための抽象化レイヤーや Microsoft.Extensions.AI.Evaluation という評価のためのライブラリもあります。簡単に言いうと .NET で AI を使うための準公式ライブラリです。

各々のライブラリの特徴を箇条書きにすると以下のようになります。

  • Microsoft.Extensions.AI (.NET チームにより開発)
    • Chat Completions API や Embedding API を使うための抽象化レイヤー
    • OpenAI や Azure OpenAI Service などのプロバイダーをサポート
    • DI コンテナとの統合が容易
  • Microsoft.Extensions.VectorData (Semantic Kernel チームにより開発)
    • ベクトルストアを使うための抽象化レイヤー
    • 様々なベクトルストアをサポート
    • DI コンテナとの統合が容易
  • Microsoft.Extensions.AI.Evaluation (.NET チームにより開発)
    • AI の応答を評価するためのライブラリ

オーナーとなるチームが VectorData だけ Semantic Kernel チームで、他の 2 つは .NET チームが開発しています。もしかしたら将来的には全部 .NET チームに移管されるかもしれませんが、現在リポジトリの所在的には VectorData だけが Semantic Kernel チームの管轄となっています。

この記事では、この中の Microsoft.Extensions.AI のみに焦点を当てて、Semantic Kernel との統合について解説します。

Microsoft.Extensions.AI で提供されている機能

Microsoft.Extensions.AI は、AI を使うための抽象化レイヤーを提供します。
もう少し細かく説明を行うと Microsoft.Extensions.AI.Abstractions パッケージに抽象化レイヤーが実装されています。
具体的には、以下のような機能を提供しています。

  • IChatClient インターフェース
    • Chat Completions API を使うためのインターフェース
  • IEmbeddingClient インターフェース
    • Embedding API を使うためのインターフェース
  • ISpeechToTextClient インターフェース
    • 音声をテキストに変換するためのインターフェース
    • 現時点では実験的な機能

この他に AI が呼び出すためのツールの抽象化の AITool も提供しています。

そして、その抽象化レイヤーを使った共通的な機能が Microsoft.Extensions.AI パッケージに実装されています。
具体的には、以下のような機能を提供しています。

  • キャッシュ機能
  • ログ機能
  • OpenTelemetry
  • 関数の自動呼出し

これらの機能はミドルウェアとして実装されており、任意の IChatClientIEmbeddingClient の実装に対して適用することができます。ミドルウェア以外の機能としては、各クライアントにこれらのミドルウェアを追加するためのビルダークラスや、Structured Output を使った際に JSON のパースまでしてくれる機能などがあります。

Microsoft.Extensions.AI の実装

ここまでは、基本的に特定の LLM プロバイダーに依存しない部分の話になります。
各 LLM プロバイダーは .NET 向けの SDK を提供する際に Microsoft.Extensions.Abstractions の抽象化レイヤーに対して実装を提供する形になります。

Microsoft.Extensions.AI を中心としてエコシステム

Microsoft.Extensions.AI は .NET チームが提供する抽象化レイヤーのため .NET のエコシステムの中で広く使われることを想定しています。
現時点でも OpenAI 用の実装や Ollama 用の実装や Azure AI Foundry 用の実装などが提供されています。
開発者は IChatCLient などの抽象化レイヤーを使って開発をしている限り、基本的には LLM のプロバイダーに依存しないコードを書くことができます。

また、AI を使うための各種ライブラリも Microsoft.Extensions.AI で提供されている抽象化レイヤーに対して機能を実装することで Microsoft.Extensions.AI のエコシステムの一部として機能します。1 つ実装するだけで全てのエコシステム内のライブラリや LLM プロバイダーと連携できるようになります。

例えば、Microsoft.Extensions.AI.EvaluationMicrosoft.Extensions.AI.Abstractions を使って AI の応答を評価するためのライブラリです。これにより、AI の応答を評価するためのコードは LLM プロバイダーに依存しない形で書くことができます。さらに Model Context Protocol (MCP) も Microsoft.Extensions.AI の抽象化レイヤーを使って実装されているため、個別の LLM のプロバイダーが MCP をサポートする必要はありません。

そして、Semantic Kernel も Microsoft.Extensions.AI を使って実装されたマルチエージェント システムを作るためのライブラリです。Semantic Kernel も .NET の Microsoft.Extensions.AI のエコシステムの一部として機能します。

図で表すと以下のようなイメージです。

Semantic Kernel 内での Microsoft.Extensions.AI

Semantic Kernel は Microsoft.Extensions.AI の登場以前から AI を使うための抽象化レイヤーを提供していました。
Chat Completions API のようなタイプの API のための抽象化レイヤーとして IChatCompletionService インターフェースがありました。この IChatCompletionService を実装して AzureOpenAIChatCompletionService などの各種 LLM 向けの実装があるという形でした。そして Semantic Kernel の各種機能は IChatCompletionService を使って実装することで LLM に依存しないかたちを実現していました。

この IChatCompletionServiceMicrosoft.Extensions.AIIChatClient に相当するものです。現在、この IChatCompletionService を使える箇所で IChatClient も使えるように変更が進められています。主要な機能は、大体 IChatClient を使うように変更されているので、Semantic Kernel の機能を使う際には IChatClient を使うことができます。

例えば KernelFunction は、Microsoft.Extensions.AI 対応の過程で以下のように AITool を継承する形に変更されました。以下のような継承関係になっています。internals の部分は Semantic Kernel の内部で使われるクラスです。

AIFunction から継承しているのに AIFunction をラップする AIFunctionKernelFunction があるのが個人的には面白いです。AIFunctionKernelFunction として扱うときに必要とは言え、初見では少し混乱するような構造になっているなと思いました。

このような構造になっているため、AITool を受け取る箇所には KernelFunction を渡すことが出来ます。一点注意点として、KernelFunction を実行するには多くのケースで Kernel が必要になります。そのため KernelFunction を、そのまま渡すと実行時にエラーになることがあります。

例えば以下のような Kernel がまったく必要ない処理の場合は問題なく動作します。

using Microsoft.Extensions.AI;
using Microsoft.SemanticKernel;

// Kernel がなくても動く関数
var simpleFunction = KernelFunctionFactory.CreateFromMethod(TimeProvider.System.GetLocalNow, "GetLocalNow");
await InvokeAIFunctionAndWriteOutputAsync(simpleFunction);

async Task InvokeAIFunctionAndWriteOutputAsync(AIFunction function)
{
    var result = await function.InvokeAsync();
    Console.WriteLine(result);
}

TimeProvider.System.GetLocalNow を呼び出すだけの関数なので、Kernel は必要ありません。そのまま AIFunction を受け取る処理に渡して実行することができます。

上記のコードを実行すると、以下のように現在のローカル時間が出力されます。

2025-06-03T09:57:17.864371+09:00

ただし、以下のように Kernel が必要な処理を行う場合は、Kernel を渡す必要があります。

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

// 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();

// プロンプトから関数を作成 (実行するには AI サービスの登録された Kernel が必要)
var greetingFunction = KernelFunctionFactory.CreateFromPrompt("こんにちは");

// そのまま実行するとエラーになる
await InvokeAIFunctionAndWriteOutputAsync(greetingFunction);
async Task InvokeAIFunctionAndWriteOutputAsync(AIFunction function)
{
    var result = await function.InvokeAsync();
    Console.WriteLine(result);
}

このコードを実行すると、以下のようなメッセージの KernelException が発生します。

No service was found for any of the supported types: Microsoft.SemanticKernel.ChatCompletion.IChatCompletionService, Microsoft.SemanticKernel.TextGeneration.ITextGenerationService, Microsoft.Extensions.AI.IChatClient.

これは Kernel を指定せずに KernelFunction を実行したときにはデフォルトで空の Kernel が使われるためです。今回のような AI サービスを使う関数を実行するには、ちゃんとセットアップされた Kernel を指定して実行する必要があります。Kernel を渡すための WithKernel という拡張メソッドが定義されています。これを使うと関数のクローンを作成して、その関数に Kernel を設定することができます。
このメソッドを使うと上記コードは以下のように書き換えることができます。(Kernel を作成する手前のコードは同じため省略しています)

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

// プロンプトから関数を作成 (実行するには AI サービスの登録された Kernel が必要)
var greetingFunction = KernelFunctionFactory.CreateFromPrompt("こんにちは");

// WithKernel メソッドを使うと Kernel を持った関数の Clone を作成できる
#pragma warning disable SKEXP0001
await InvokeAIFunctionAndWriteOutputAsync(greetingFunction.WithKernel(kernel));
#pragma warning restore SKEXP0001

async Task InvokeAIFunctionAndWriteOutputAsync(AIFunction function)
{
    var result = await function.InvokeAsync();
    Console.WriteLine(result);
}

実行すると以下のような結果になります。AIFunction として KernelFunction を実行すると実行結果の値が System.Text.Json.JsonElement に変換されて帰ってきます。そのため上記のメソッドの実行結果は以下のようになります。

{
  "messages": [
    {
      "role": "assistant",
      "contents": [
        {
          "$type": "text",
          "text": "こんにちは!\uD83D\uDE0A 何かお手伝いできることがありますか?"
        }
      ],
      "messageId": "chatcmpl-BeAXMBxRf4cw8M7Z8lIg7DVAVXDjM"
    }
  ],
  "responseId": "chatcmpl-BeAXMBxRf4cw8M7Z8lIg7DVAVXDjM",
  "modelId": "gpt-4.1-2025-04-14",
  "createdAt": "2025-06-03T01:15:24+00:00",
  "finishReason": "stop",
  "usage": {
    "inputTokenCount": 8,
    "outputTokenCount": 17,
    "totalTokenCount": 25,
    "additionalCounts": {
      "InputTokenDetails.AudioTokenCount": 0,
      "InputTokenDetails.CachedTokenCount": 0,
      "OutputTokenDetails.ReasoningTokenCount": 0,
      "OutputTokenDetails.AudioTokenCount": 0,
      "OutputTokenDetails.AcceptedPredictionTokenCount": 0,
      "OutputTokenDetails.RejectedPredictionTokenCount": 0
    }
  }
}

今回は、プロンプトの実行が IChatClient を使って実行されるため生の戻り値は Microsoft.Extensions.AI.ChatResponse になります。それが JsonElement に変換されているため上記のような結果になっています。ここから応答のメッセージだけ抜き出したい場合は一度 ChatResponse にデシリアライズするか、めんどくさいですが FunctionResult に変換してから GetValue メソッドを使ってメッセージを取得することができます。

例えば以下のように InvokeAIFunctionAndWriteOutputAsync メソッドを変更すると、応答のメッセージだけを出力することができます。

async Task InvokeAIFunctionAndWriteOutputAsync(AIFunction function)
{
    var result = await function.InvokeAsync();
    // 結果は JSON 形式で返されるので、デシリアライズする
    // デシリアライズの時のオプションは Microsoft.Extensions.AI でデフォルトで使われるオプションが
    // 内部でも使われているので、それを指定する
    var chatResponse = JsonSerializer.Deserialize<ChatResponse>(
        (JsonElement) result!, 
        AIJsonUtilities.DefaultOptions);
#pragma warning disable SKEXP0001 // AsKernelFunction はプレビュー機能なので警告の抑止が必要
    // KernelFunction と戻り値を使って FunctionResult を作成
    var functionResult = new FunctionResult(function.AsKernelFunction(), chatResponse);
#pragma warning restore SKEXP0001
    // FunctionResult から値を取得
    var answer = functionResult.GetValue<string>();
    // 結果を出力
    Console.WriteLine(answer);
}

AIFunctionKernelFunction として使う場合は、AsKernelFunction 拡張メソッドを使うことで KernelFunction に変換することができます。これによってプラグインとして登録するなどの Semantic Kernel の機能を使うことができます。以下に AIFunctionKernelFunction として使う例を示します。

using System.Text.Json;
using Microsoft.Extensions.AI;
using Microsoft.SemanticKernel;

// AIFunction を作成
var aiFunction = AIFunctionFactory.Create((string format) =>
{
    return TimeProvider.System.GetLocalNow().ToString(format);
});

#pragma warning disable SKEXP0001 
// AIFunction から KernelFunction に変換
// プレビュー機能なので警告の抑制が必要
var kernelFunction = aiFunction.AsKernelFunction();
#pragma warning restore SKEXP0001 

// KernelFunction の API を使って関数を呼び出す
FunctionResult result = await kernelFunction.InvokeAsync(
    new Kernel(),
    new KernelArguments
    {
        ["format"] = "yyyy-MM-dd HH:mm:ss zzz",
    });
// デフォルトで戻り値は JsonElement になる点に注意
Console.WriteLine(result.GetValue<JsonElement>());

実行すると、以下のように現在のローカル時間が出力されます。

2025-06-03 15:58:10 +09:00

コメントにもありますが AIFunction の戻り値はデフォルトで JsonElement になるので、FuncionResultGetValue の型引数には JsonElement を指定する必要がある点に注意が必要です。JsonElement 以外にしたい場合は Semantic Kernel ではなく Microsoft.Extensions.AI の世界の話なのでここでは割愛します。

ここまで KernelFunction にフォーカスして Microsoft.Extensions.AI との統合機能について解説してきましたが、ちょっとここで Semantic Kernel のプロジェクト構造から Microsoft.Extensions.AI の統合機能について見ていこうと思います。一般的に Semantic Kernel を使う場合は、Microsoft.SemanticKernel パッケージを使うことが多いのですが、これはおそらくメタパッケージ(このパッケージ自体には DLL などは含まれていなくて関連するパッケージをとりまとめるようなもの)になっているように見えます。
Microsoft.SemanticKernel パッケージを参照すると Microsoft.SemanticKernel.CoreMicrosoft.SemanticKernel.Connectors.AzureOpenAI の 2 つのパッケージが参照されます。それぞれの依存関係から今回関係のあるものを抜き出すと以下のようになります。

  • Microsoft.SemanticKernel.Core
    • Microsoft.SemanticKernel.Abstractions
      • Microsoft.Extensions.AI
      • Microsoft.Extensions.VectorData
  • Microsoft.SemanticKernel.Connectors.AzureOpenAI

これを見ると Microsoft.SemanticKernel.Abstractions から Microsoft.Extensions.AIMicrosoft.Extensions.VectorData が参照されていることがわかります。Microsoft.SemanticKernel.Abstractions は Semantic Kernel の抽象化レイヤーで、Semantic Kernel の機能を使うためのインターフェースやクラスが定義されています。この部分から相互運用が可能な形になっています。

私が見つけられた範囲の相互運用のための型変換は以下のようなものがありました。

  • PromptExecutionSettings クラスと ChatOptions の相互変換
    • PromptExecutionSettingsToChatOptions 拡張メソッドで ChatOptions に変換
    • ChatOptionsToPromptExecutionSettings 拡張メソッドで PromptExecutionSettings に変換
  • KernelFunctionAIFunction の相互変換
    • AIFunctionAsKernelFunction 拡張メソッドで KernelFunction に変換
    • KernelFunctionAIFunction を継承しているため、そのまま AIFunction として使うことができる。Kernel が必要な場合は WithKernel メソッドを使う。
  • IChatClientGetResponseAsync メソッドに PromptExecutionSettingsKernel を受け取るオーバーロードがある
  • IChatClient と従来の抽象化レイヤーの IChatCompletionService の相互変換
    • IChatCompletionServiceAsChatClient 拡張メソッドで IChatClient に変換 (プレビュー機能)
    • IChatClientAsChatCompletionService 拡張メソッドで IChatCompletionService に変換 (プレビュー機能)

さらに、IChatClient のミドルウェアとして Semantic Kernel の KernelFunction の関数を自動で呼び出すようにするための KernelFunctionInvokingChatClient というものが実装されています。これは UseKernelFFunctionInvocation 拡張メソッドを IChatClient のビルダーに対して呼び出すことで追加することが出来ます。ただ、通常は Semantic Kernel の AddAzureOpenAIChatClient メソッドを使うと、内部で KernelFunctionInvokingChatClient が登録されるため、Semantic Kernel の機能を使う場合は特に意識する必要はありません。

Semantic Kernel の AddAzureOpenAIChatClient メソッドは以下のように実装されています。

var loggerFactory = serviceProvider.GetService<ILoggerFactory>();

var client = azureOpenAIClient ?? serviceProvider.GetRequiredService<AzureOpenAIClient>();

var builder = client.GetChatClient(deploymentName)
    .AsIChatClient()
    .AsBuilder()
    .UseKernelFunctionInvocation(loggerFactory)
    .UseOpenTelemetry(loggerFactory, openTelemetrySourceName, openTelemetryConfig);

if (loggerFactory is not null)
{
    builder.UseLogging(loggerFactory);
}

return builder.Build();

KernelFunction の自動呼出しと OpenTelemetry のミドルウェアが追加されていて、ILoggerFactory がある場合はロギングのミドルウェアも追加されます。Semantic Kernel で登録された IChatClient を使う場合は、このようなミドルウェアが追加されているということを頭に入れておくと良いでしょう。

Microsoft.Extensions.AI との統合のメリット

Microsoft.Extensions.AI と Semantic Kernel の統合によって、以下のようなメリットがあります。

  • 一貫したインターフェース: IChatClient を通じて、Microsoft.Extensions.AI の機能を簡単に利用できるようになります。
  • 拡張性: Semantic Kernel の機能を活用しながら、Microsoft.Extensions.AI のエコシステムに統合することができます。
  • 相互運用性: 両者の抽象化レイヤーが相互に変換可能なため、柔軟なアーキテクチャを構築できます。

この中の相互運用性が個人的には一番のメリットだと感じています。
例えば最初の方でも書いた内容になりますが Model Context Protocol (MCP) は MCP のツールを AITool として返すような API になっています。
つまり、これはシームレスに Semantic Kernel の KernelFunction として使うことが出来るということなので、Semantic Kernel のプラグインとして扱えるということになります。
Semantic Kernel 自体は特に MCP をサポートするための機能は提供していないにも関わらず Microsoft.Extensions.AI のエコシステムの一部として提供されているため、Semantic Kernel の機能を使うことができます。

このように Microsoft.Extensions.AI を中心として様々な機能が統合されているため、Semantic Kernel もその一部として機能することができます。これにより、Semantic Kernel の機能を使いながら、Microsoft.Extensions.AI のエコシステムの恩恵を受けることができるようになっています。

まとめ

Semantic Kernel は AI を使うための抽象化レイヤーとして独自の Microsoft.SemanticKernel.Abstractions を提供していましたが、現在は Microsoft.Extensions.AI にも対応を行い、IChatClient を使う方向に進んでいます。一度 GA させてしまった IChatCompletionService は Semantic Kernel の安定した API を提供するというポリシーがあるので、消えることはそうそうないと思いますが Semantic Kernel v2 のようなメジャーバージョンアップのタイミングで IChatCompletionService を廃止して IChatClient に統一される可能性はあるかもしれません。

現時点では Semantic Kernel の抽象化レイヤーと Microsoft.Extensions.AI の抽象化レイヤーが両方使えて、相互に変換可能な形になっています。これによって Microsoft.Extensions.AI のエコシステムの一部として Semantic Kernel が機能することが可能になっています。今後の広がりが楽しみですね。

次回は「Agent Framework」について書こうと思います。

目次

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

Microsoft (有志)

Discussion