掲載内容は個人の見解であり、所属する企業を代表するものではありません.
私は Azure OpenAI Service(AOAI) を利用するアプリを作るときは C# でコーディングするのですが、AOAI 用のライブラリっていくつか存在するんですよね。 書き分けてたら若干混乱してきたのでちょっと整理がてらブログにしてみました。 後日コピぺするためのメモとも言います。 まあ最近は GitHub Copilot さんが勝手に書いてくれるんですが、選択肢が複数あることは理解しておいた方がいいと思いますし、どれを選ぶかは人間の仕事だと思いますので。
まずは Azure OpenAI Serivce のドキュメント のリファレンスにも記載のある Azure.AI.OpenAI
名前空間のライブラリです。
コーディングに使用する参考情報としては以下のあたりでしょうか。
実際に AOAI を呼び出すコードは以下のようになります。 なんのライブラリを使ってるか確認するために、わざと using せずに名前空間をべた書きしています。 バージョンは本記事執筆時点で新しいのを選んだだけなので他意はありません。
#r "nuget: Azure.Identity, 1.13.1"
#r "nuget: Azure.AI.OpenAI, 2.1.0-beta.2"
string aoaiEndpoint = "https://aoaiAccountName.openai.azure.com";
string modelDeploy = "gpt-4o";
System.Uri endpoint = new (aoaiEndpoint);
Azure.Core.TokenCredential credential = new Azure.Identity.DefaultAzureCredential();
Azure.AI.OpenAI.AzureOpenAIClient aoaiClient = new (endpoint, credential);
OpenAI.Chat.ChatClient chatClient = aoaiClient.GetChatClient(modelDeploy);
System.ClientModel.ClientResult<OpenAI.Chat.ChatCompletion> res1 = await chatClient.CompleteChatAsync("こんにちわ");
OpenAI.Chat.ChatCompletion chatcompletion = res1.Value;
chatcompletion.Display();
Azure.AI.OpenAI
パッケージを取り寄せます。System.Uri
クラスのインスタンスを生成しています。これは基本ライブラリなので特に言及することはありません。Azure.Identity.DefaultAzureCredential
を使用しています。機微情報のハードコードは避けるべきです。ここまでは事前準備みたいなものですね。
AzureOpenAIClient
クラス のインスタンスを生成しています。
Azure.AI.OpenAI.dll
に含まれるクラスですね。わかりやすい。OpenAI.OpanAIClient
クラスで、Azure.AI.OpenAI
パッケージが依存する OpenAI
というパッケージに含まれていますGetChatClient
するのですが、ここで取得できるのは OpenAI.Chat.ChatClient
クラスのインスタンスです。
OpenAI
パッケージに含まれる OpenAI.Chat.ChatClient
クラスになります。System.ClientModel.ClientResult<T>
になります
System.ClientModel
というパッケージに含まれており、さまざまなサービスを利用するクライアントライブラリを構築するための汎用ツールセットの1つですAzure.AI.OpenAI
は Azure サービスを利用する汎用的なクラスライブラリである Azure.Core
に依存しており、それがこの System.ClientModel
に依存するという推移的な依存関係になっています。OpenAI.Chat.ChatCompletion
が取得できています。
おおざっぱに言えば本家 OpenAI とのなるべくインタフェース的な意味での互換性を持たせつつ、内部実装としては Azure SDK for .NET を使っている、といったところでしょうか。 とはいえ様々なパッケージのインタフェースが思いっきり見えてしまっているので、AOAI を抽象化できてはいないといったところでしょうか。
Polyglot Notebook の Display を使用して結果を表示すると以下のようになります。 AOAI らしさは全くなく、本家というか REST API のインタフェースを素直に実装したようなイメージでしょうか。
次に先日発表された Microsoft.Extensions.AI (MEAI) になります。 Microsoft.Extensions は Dependency Injection, Logging, Configuration といった処理を抽象化するためのライブラリですので、それが AI にも適用されたといったところでしょうか。
コーディングに使用する参考情報としては以下のあたりでしょうか。
それでは MEAI を使用して AOAI を呼び出すコードは以下のようになります。 なんのライブラリを使ってるか確認するために、わざと using せずに名前空間をべた書きしています。 ただ拡張メソッドを使うのでどうしても一部 using は必要。
#r "nuget: Azure.Identity, 1.13.1"
#r "nuget: Azure.AI.OpenAI, 2.1.0-beta.2"
#r "nuget: Microsoft.Extensions.AI.OpenAI, 9.0.0-preview.9.24556.5"
using Microsoft.Extensions.AI;
System.Uri endpoint = new (aoaiEndpoint);
Azure.Core.TokenCredential credential = new Azure.Identity.DefaultAzureCredential();
Azure.AI.OpenAI.AzureOpenAIClient aoaiClient = new (endpoint, credential);
Microsoft.Extensions.AI.IChatClient chatClient = aoaiClient.AsChatClient(modelDeploy);
Microsoft.Extensions.AI.ChatCompletion chatCompletion = await chatClient.CompleteAsync("こんにちわ");
chatCompletion.GetType().Display();
chatCompletion.Display();
割とすっきりしましたね。
Azure.AI.OpenAI
に加えて Microsoft.Extensions.AI.OpenAI
パッケージを取り寄せています。
Microsoft.Extensons.AI
自体は汎用的なインタフェースなどを定めたフレームワーク的なもので、実装部分を含んでいません。Microsoft.Extensions.AI.OpenAI
パッケージを使用します。Microsoft.Extensions.AI
パッケージはその依存関係として同時に取り寄せられています。Microsoft.Extensions.AI.OpenAI
は名前の通り本家 OpenAI 用なので、追加で AOAI 用のパッケージも必要になりますAzure.AI.OpenAI.AzureOpenAIClient
を生成しています。
Microsoft.Extensions.AI.IChatClient
を取得しています。ここからMEAI。
Microsoft.Extensions.AI
名前空間直下の汎用インタフェースであり、本家 OAI や AOAI にも依存していません。AsChatClient
拡張メソッドは、Microsoft.Extensions.AI.OpenAI.OpenAIClientExtensions クラスで定義されていますAzureOpenAIClient
クラスが OpenAIClient
から派生しているので OAI でも AOAI でも同じ処理になるということですねMicrosoft.Extensions.AI
名前空間は以下の ChatCompletion
クラスになります
Polyglot Notebook の Display を使用して結果を表示すると以下のようになります。
インタフェース的には抽象化されているのですが、RawRepresentation
プロパティで OpenAI.Chat.ChatCompletion
が取得できるようなので、インタフェースで抽象化しつつ、内部的には OpenAI
や Azure.AI.OpenAI
を使っているということでしょう。
さて、なぜ MEAI なんて存在が必要になったのか、多分こういう設計思想と想定利用なんだろうなという個人的な考えを整理してみました。
Microsoft.Extensions
の設計思想としては抽象化がキモだと思うので、Microsoft.Extensions.AI.Abstractions
を見てみましょう。
パッケージやアセンブリ名としては Microsoft.Extensions.AI.Abstractions
なんですが、
格納されているクラスやインタフェースの名前空間は Microsoft.Extensions.AI
になっているものばかりです。
Microsoft.Extensions.AI
パッケージが依存しているパッケージでもあるので Abstractions
も自動的に取り寄せられており、
でも名前空間は一緒なのでソースコードには表れてこないってわけですね。
これらに加えて Microsoft.Extensions.AI.OpenAI
や Microsoft.Extensions.AI.Ollama
といった拡張用のパッケージを独立して用意しておき、
これらを組み合わせることでアプリケーションからは AI モデルを利用できる、という仕組みになっています。
AOAI なら AOAI 用の、本家 OAI なら OAI 専用に用意されたライブラリではなく、汎用的で拡張可能になっているというのがポイントでしょうか。
上記の例ではインタフェース名は IChatClient でしたが、
IEmbeddingGenerator なんてものも存在します。
OpenAI のようなサービスの粒度ではなく、利用したい機能でインタフェースが切られているわけです。
確かに AOAI は推論系の API として Chat Completions だけでなく Completions、Embeddings、Image generations、Transcriptions、などなど様々なタスクを処理することができます。
アプリを作るユーザー目線で言えば AOAI が使いたいわけではなく(中の人としては使ってほしいですが)、
そもそもどういった推論を使いたいかが重要です。
利用したい機能として統一されたインタフェースが切り出されているというのは、開発者にとっては大きなメリットではないでしょうか。
今後の期待としては Image Generation や TTS/STT のためのインタフェースが提供され、統一したプログラミングモデルで利用できるようになってほしいものです。
前述のインタフェースで表現される Chat
や Embeddings
は何も OpenAI や ChatGPT でしか提供できない推論ではありません。
有名どころで言えば Ollama や Phi3 などがありますよね。
現状の MEAI は
これらの異なるモデルを統一したインタフェースで扱えるということは、アナウンス記事にもあったように開発時と本番運用ではモデルを変えたり、
運用中に新しいモデルに切り替えたりということが柔軟に出来るということです。
またこれらの AI モデルを提供するベンダー視点で言えば、同じインタフェースとして抽象化できるものであれば、
MEAI を拡張するパッケージを提供すればこのエコシステムに参入することが出来ることになります。
これは何も Microsoft の開発を待つことなく、IChatCompletion
や IEmbeddingGenerator
実装を提供して NuGet に公開すればよいわけです。
同じモデルを使用するにしても GPT-4o なら Azure OpenAI と本家 OpenAI というホスティング上の選択肢があります。
これらを柔軟に切り替えたり同時に束ねて利用したい場合などもあるでしょうから、その意味でもインタフェースが一緒というのは大きいです。
Azure.AI.OpenAI.AzureOpenAIClient
は OpenAI.OpenAIClient
を継承し、リクエスト・レスポンスは同じクラスをそのまま利用することで実現していました。
が、このアプローチではサービスが増えれば増えるほど拡張性に限界が出てくると思われます。
Phi3 なら Azure AI Service としてクラウドでホストすることも出来れば、ローカルマシン上にダウンロードしたモデルを使用することも出来るわけです。 AI デプロイの組み合わせに幅が出てきますが、同時に様々な考慮事項が出てきます。
これらの問題を投下的に扱う上ではインタフェースだけでは足りてないですね。
インタフェースが統一されていたとしても、エンドポイント System.Uri
や認証方式 Azure.Core.TokenCredential
などどうしてもホスティングサービス依存の部分が出てきます。
前述のサンプルコードで言うと Azure.AI.OpenAI.AzureOpenAIClient
を new
するところが該当します。
サンプルコードではシンプルにするために直接インスタンス生成していますが、実際のプログラミングの現場では Dependency Injection を使うことになるでしょう。
つまりこのホスティングサービスごとの差分は DI コンテナを組み立てるスタートアップコードに集約することができます。
アプリケーションコードでは IChatClient
を挿入してもらう実装と DI で生成することを意識すればよいでしょう。
以上まとめると、MEAI を使うアプリのコードは以下のようになると思います。 ダミーコードなのでこのままでは動きませんが、イメージとして(MEAI を DI するお作法についてはまだ調査中・・・)
// アプリからすれば AOAI か OAI かは関係ない、Phi3 か GPT4 かも関係ない、チャットが使えればよい
public class YourSpecialService(IChatClient chatClient)
{
public async Task ExecuteAnyInference()
{
await chatClient.CompleteAsync("your prompt");
}
}
// モデルやホスティングサービスは DI コンテナで吸収
var builder = Host.CreateApplicationBuilder();
builder.Services.AddTransient<IChatClient>( new AzureOpenAIClient().AsChatClient() );
.AddTransient<YourSpecialService>();
builder.Build().Run();
さて Azure OpenAI を C# から呼び出すもう一つの選択肢、Semantic Kernel です。 前述の2つに比べるとオーケストレーションなども含むため責務のレイヤーが異なるわけですが、ここでは AOAI を呼び出すためだけのライブラリとして考えます。 なお今後は Semantic Kernel でも MEAI を使うようになる ようですので、上記の MEAI と同じような世界観になっていくでしょう。
コーディングに使用する参考情報としては以下のあたりでしょうか。
実際に Semantic Kernel (SK)で ChatCompletion するサンプルコードは以下のようになります。
#r "nuget: Azure.Identity, 1.13.1"
#r "nuget: Microsoft.SemanticKernel, 1.29.0"
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
System.Uri endpoint = new (aoaiEndpoint);
Azure.Core.TokenCredential credential = new Azure.Identity.DefaultAzureCredential();
Microsoft.SemanticKernel.IKernelBuilder builder = Microsoft.SemanticKernel.Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion(modelDeploy, aoaiEndpoint, credential);
Microsoft.SemanticKernel.Kernel kernel = builder.Build();
Microsoft.SemanticKernel.ChatCompletion.IChatCompletionService chatClient = kernel.GetRequiredService<Microsoft.SemanticKernel.ChatCompletion.IChatCompletionService>();
Microsoft.SemanticKernel.ChatMessageContent chatCompletion = await chatClient.GetChatMessageContentAsync("こんにちわ");
chatCompletion.GetType().Display();
chatCompletion.Display();
Microsoft.SemanticKernel
パッケージだけを取り寄せればよい
Microsoft.SemanticKernel.Connectors.Hogehoge
)という形で拡張可能な仕組みなっているようです。Microsoft.SemanticKernel.Connectors.AzureOpenAI
という別パッケージなのですが、本体の依存関係に入っているので推移的に取り寄せられているMicrosoft.SemanticKernel.Connectors.AzureOpenAI
はさらに Azure.AI.OpenAI
や Microsoft.SemanticKernel.Connectors.OpenAI
に依存しており、内部的には上記と同じものMicrosoft.SemanticKernel.ChatCompletion.IChatCompletionService
DI コンテナから取得
Polyglot Notebook の Display を使用して結果を表示すると以下のようになります。
応答のインタフェースは Microsoft.SemanticKernel.ChatMessageContent
ですが、実体は Microsoft.SemanticKernel.Connectors.OpenAI.OpenAIChatMessageContent
という OAI 固有の実装が返ってきています。
内部的に使っているオリジナルのデータも OpenAI.Chat.ChatCompletion
なので前述の2つと一緒ですね。
MEAI と同じ AOAI を呼び出すライブラリというレイヤーで考えると、機能的なインターフェースは IChatCompletionService
で統一されているようです。
拡張性の観点という意味では各コネクタでこのインタフェースの実装を提供しているはずなので、ここの品揃えが気になるところです。
クラスライブラリドキュメントで確認したところ、AOAI を含めて 8 つに対応しています。
MEAI に比べて歴史が長い(といってもまだ2年も経ってないのですが)だけあって、ここは流石といったところですね。
HuggingFace、Gemini、MistralAI といった有名どころや Onnx にも対応しているので Phi3 の Chat Completion も使えそうですね。
こういった多種多様なモデルやホスティングサービスは、スタートアップコードで Microsoft.SemanticKernel.IKernelBuilder
に対して各パッケージで提供している拡張メソッド AddHogeHogeChatCompletion()
を使用することで切り替えられるようです。
このため SK の場合も MEAI と同様に使いたい推論に特化したインタフェースだけ意識してアプリを実装することが出来そうです。
SK の場合は Chat Completion 以外にも以下のサービスに対応しています。 まだまだ試験段階のものが多いですが。
またプロンプトのテンプレートエンジンが使えたり Function Calling が使いやすいなどのメリットも嬉しいところです。
まとめといっても 3 種の SDK の簡単なサンプルコードを書き連ねただけなので感想レベルでしかないのですが。。。
OpenAI の ChatGPT がリリースされて以来、急速に各社様々な AI モデルをリリースして百花繚乱といった状況が続いている印象です。 その中で話題になるのはマルチモーダル、パフォーマンス、パラメータ数といった「スペック」を中心にした議論が多かったと思います。 もちろんそれはそれで大事なのですが、AI そのものの開発や研究まで全く分からない私は若干ついていけていなかったんですよね(これは僕個人の問題)
で、そういう難しいことが分からないので、結局それ使って何が出来るの?ってのを考えてしまうわけです。 こんなに多種多様な AI のモデルごとベンダーごとにプログラミングを切り替えて調査・検証なんてやってられっかって正直思ってました(やってる人ごめんなさい) ただこうやってインタフェースとしての標準化や抽象化のレイヤが進むことで以下の効果が生まれると思ってますので、この流れは大歓迎しているところです
以下はざっと調べただけで検証はしていません
そういえば Phi-3 ファミリーのようなローカルで動く SLM を MEAI や SK から呼び出すとどうなるんでしょうか。
Microsoft.Extensions.AI.IChatCompletion
や Microsoft.SemanticKernel.ChatCompletion.IChatCompletionService
の実装がどうなっているかという話です。
MEAI では Microsoft.Extensions.AI.AzureAIInferenceChatClient が該当すると思うのですが、
内部実装が Azure.AI.Inference.ChatCompletionsClient を中心に組み立てられていました。
このコンストラクターを見ると System.Uri
を引数に取るので、HTTP な Web API 呼び出しが前提になっています。
確かに AI Toolkit for Visual Studio Code などを使えば Phi3 を localhost でホストして Chat Completion API を提供することが可能です。 みんな大好き PowerShell で呼ぶとこんな感じです。
$model = 'Phi-3.5-mini-cpu-int4-awq-block-128-acc-level-4-onnx'
$url = 'http://127.0.0.1:5272/v1/chat/completions'
$body = ConvertTo-Json @{
messages = @(
@{ role='user'; content='Hello' }
);
model = $model
}
$ret = Invoke-RestMethod -Uri $url -Method Post -Body $body -ContentType 'application/json'
$ret.choices[0].message
おそらく Microsoft.Extensions.AI.AzureAIInference
パッケージを組み込んで、そのエンドポイントだけ localhost に向けるか、そのパスを調整してやれば良さそうです。
ただこのアプローチ、せっかくアプリと同じマシン上にモデルがあるにも関わらず、localhost とはいえアプリとモデルの間に HTTP やシリアライズのオーバーヘッドを支払うのは釈然としません。 そもそもプロセス分離することになりそうなので、その起動制御や生存監視とかも必要になっちゃいますし。 検証用途なら便利なんですけどね REST。
というわけで現状は IChatCompletion を自力実装して直接モデルを呼ぶコードを書くか、Microsoft オフィシャルのアップデートを待つしかないかなと思っていたら、既にしばやん先生がやられてました。すごい。
SK の場合は Microsoft.SemanticKernel.Connectors.Onnx.OnnxRuntimeGenAIChatCompletionService が該当しそうです。
内部実装的にも Microsoft.ML.OnnxRuntimeGenAI
を使用していますので、これであれば Semantic Kernel を使用するアプリケーションが直接ローカルの Onnx モデルを読み込んで推論させることが出来そうです。
すばらしい。
内容的には IChatCompletionService
インタフェースを、内部的に Phi-3 のプロンプト テンプレート と相互に変換する処理を実装して、
Microsoft.ML.OnnxRuntimeGenAI
の Tokenizer や Generator を使って、ローカルに保存された Phi-3 モデルとやり取りする感じになっています。