Azure版ChatGPT こと Azure OpenAI Service を C# に組み込んでみた!

こんにちは、太田です。 最近はAIの波に乗って、生成AIに手を出しています。

今回は、そんなAIの中でも1番ホットと言える「ChatGPT」をC#に組み込んだ話です。 正確に言うと、「Azure OpenAI Service」を使って「gpt-35-turbo」モデルをC#で使えるようにしました。

Azure OpenAI Serviceの簡単な説明、OpenAI社提供のAPIとの違い、C#への組み込み方法をご紹介していきます。

※この記事は 2023/06/22 現在の情報を元にしています

Azure OpenAI Serviceとは

Azure OpenAI Serviceとは、Azureのクローズド環境・セキュリティの機能を利用して安全にChatGPTを利用することができるサービスです。 同サービスでは多くの言語モデルが利用可能で、その中の1つにChatGPT(正確には、ChatGPTで利用されているGPT-3.5モデル)が含まれています。

OpenAI社のChatGPT API との違い

多くの皆さんが「ChatGPT」と聞いて頭に浮かぶのは、ブラウザ上で質問する画面ではないでしょうか。

これはOpenAI社が提供しているWEBサービスです。また、OpenAI社はその他にChatGPTを利用できるAPIも提供しています。

一方、Microsoft社はOpenAIとのパートナーシップを通じ、Azureのインフラストラクチャを利用して、ChatGPTを利用できるAPIを提供しています。 つまり、「OpenAI社が提供するChatGPTのAPI」「Azure OpenAI Serviceが提供するChatGPTのAPI」の2つが存在します。

この2つは大きな機能差異はありませんが、細かな点で違いがあります。ここではいくつか主要な違いを紹介します。

OpenAI社のAPI

  • 新機能がリリースされた場合、いち早く利用可能
  • セキュリティ関連の機能が充実していない
  • 混雑時は著しくパフォーマンスが低下する

Azure版のAPI

  • OpenAI社で新機能がリリースされてもすぐには利用できない
  • AzureのプライベートエンドポイントやKeyVaultを利用して、安全にAPIを利用することができる
  • Azureのインフラストラクチャを利用しており、OpenAI社のAPIに比べてパフォーマンスが安定している

Azure OpenAI Service の利用準備

利用申請

現在、Azure OpenAI Serviceは利用申請をすることで使用可能になります。 申請フォーム に必要事項を入力して承認されると、申請したサブスクリプションでAzure OpenAI Serviceが利用可能となります。指示に従って必要な情報を記入して送信しましょう。

リソースの作成

Azure Portal で「Cognitive Services」の画面に行くと「Azure OpenAI」が表示されています。「作成」を押してリソースの作成に進みましょう。



リソースの作成に必要な情報を記入します。 リージョンは現在「East US」「France Central」「UK South」「West Europe」の4つだけで、日本のリージョンはありません。 価格レベルは「Standard S0」しか選択肢がありませんが、今のところは使った分だけの請求になるのであまり関係ありません。



その後、「ネットワーク」「タグ」の設定を行いリソースの作成を行います。 これらは他のAzureのサービスと基本的に同じです。 特定のネットワークのみアクセス可能とするには「ネットワーク」の設定をしておきましょう。

キーとエンドポイントを取得

作成したリソースを開き、左のメニューから「キーとエンドポイント」を選択して以下の画面を開きます。 後で利用するために キー(キー1とキー2のどちらか)・エンドポイントのURL、をそれぞれ取得し、控えておいて下さい。
※本来は「Azure KeyVault」を用いてキーを安全に利用したほうがよいのですが、今回の説明では割愛させていただきます。

モデルのデプロイ

Azure Portalで作成したAzure OpenAI Serviceの画面から、「Azure OpenAI Studio」を開きましょう。



ChatGPTを利用するには、「モデル」をデプロイする必要があります。モデルのデプロイの詳細な手順は、詳しく説明している 公式ドキュメント があるのでこちらを参照ください。ChatGPTを利用するには、「gpt-35-turbo」モデルをデプロイしましょう。


APIで利用するために、ここで作成したモデルの「デプロイ名」は控えておいて下さい。

C#への組み込み

NuGet パッケージの導入

C#で利用するためには、「Azure.AI.OpenAI」のNuGet パッケージをインストールして利用するのが簡単です。Visual Studioをお使いの方は、Nuget パッケージマネージャーから「Azure.AI.OpenAI」を探してインストールしましょう。

ソースコード

では、早速C#に組み込んでいきましょう。

まず、OpenAIのクライアントを作成します。 その際、必要なusingも追加しましょう。

using Azure.AI.OpenAI;
using Azure;

OpenAIClient client = new OpenAIClient(
    new Uri("【ここにエンドポイントURLを入れる】"),
    new AzureKeyCredential("【ここにキーを入れる】"));

エンドポイントのURLやキーは、実環境では設定ファイルや環境変数に入れると良いでしょう。

次に、リクエストを送る際のChatCompletionsOptionsを作成します。

var options = new ChatCompletionsOptions()
{
    Messages =
    {
        new ChatMessage(ChatRole.System, @"You are an AI assistant that helps people find information."),
        new ChatMessage(ChatRole.User, @"日本の首都は?"),
    },
    Temperature = 0.7F,
    MaxTokens = 800,
};

Messagesは、ChatMessage型でSystem・User・Assistant(ChatGPT)のメッセージが入ります。Systemメッセージには、そのやり取りで守ってもらいたい制約を記述します。 Systemメッセージから始まり、UserとAssistantのメッセージが交互に続くイメージです。
今回のサンプルコードでは、まずSystemとUserのメッセージのみ指定しています。2回目のリクエストを送る場合は、System+User(1回目の質問)+Assistant(1回目の回答)+User(2回目の質問) をセットにして送ることになります。

TemperatureMaxTokens は、リクエストを送る際に指定可能なパラメータの1部です。これらのパラメータは指定しなくてもデフォルトの値が使用されます。
Temperatureは「0~1の範囲の値で、値が大きいほどバラエティに富んだ回答を返し、値が低いほど一貫性のある回答を返すパラメータ」です。
MaxTokensは回答を生成する際のトークン数の最大値を指定します。トークン数は、Tokenizer で簡単に調べることが可能です。
その他に指定できるパラメータは 公式ドキュメント のページで紹介されています。

最後に、作成したoptionsを利用して、リクエストを送り、回答を取得します。answer に、回答がstring?型で入ります。

var responseWithoutStream = await client.GetChatCompletionsAsync("【ここにモデルのデプロイ名を入れる】", options);
var answer = responseWithoutStream.Value.Choices[0].Message.Content;

Choices[0]の部分はマジックナンバーが入っているように見えますが、一般的にこの書き方をしている場合が多いです。というのも、リクエストを送る際のオプションで n に整数を指定することで、回答を複数生成することができ、その場合はChoicesに複数の回答が配列で入ります。オプション n を指定しなければ、回答が1つしか無いので0番目を取得することになります。しかし、複数回答を出力するユースケースはあまり想像できず、複数出力させて良い回答を選ぶよりは、プロンプトを工夫して1つの良い回答を出させる方が効率が良いと思います。

Console.WriteLine(answer); でコンソールに回答を出力した結果が以下のようになります。

これで、C#からChatGPT を利用できました!

応用例

回答の値はstring型で返却されますが、配列やリストとして回答を使用したいことがありますよね。そんなときは、プロンプトを工夫して回答の形式を指定します。

サンプルコード:

string systemMessage = @"回答は以下のようなフォーマットに従って下さい。
回答1,回答2,回答3,回答4,回答5";

string userMessage = @"質問:
果物の名前を5個教えてください。

果物の名前:";

var options = new ChatCompletionsOptions()
{
    Messages =
    {
        new ChatMessage(ChatRole.System, systemMessage),
        new ChatMessage(ChatRole.User, userMessage),
    }
};

var responseWithoutStream = await client.GetChatCompletionsAsync("【ここにモデルのデプロイ名を入れる】", options);
var answer = responseWithoutStream.Value.Choices[0].Message.Content;

// カンマ区切りで回答の文字列をListに変換
List<string> answerList = answer.Split(',').ToList();

// Listの中身を順番に出力
foreach (string item in answerList)
{
    Console.WriteLine(item);
}

出力結果:


ここでポイントになるのは systemMessageuserMessage です。

systemMessage では、回答のフォーマットを指定しています。カンマ区切りで出力してもらうようにすることで、出力結果をリストに変換して利用することが容易になります。
userMessage では質問をしていますが、ここにも工夫があります。改めてuserMessageの中身を見てみましょう

質問:
果物の名前を5個教えてください。

果物の名前:

最後に「果物の名前:」と入れていることがポイントです。この記述がなく、普通に「果物の名前を5個教えてください。」と質問すると、回答のはじめに「以下が果物の名前になります」などと余計な説明文が入ってしまう場合があります。

ChatGPT で使用しているGPTモデルは、文章の続きを「推論」しています。これを利用して、「果物の名前:」と質問の最後に付けておくことで、純粋に””果物の名前だけ””を含んだ回答が出力されやすくなります。そうすると、その後カンマ区切りで回答をリストに変換する場合も、不都合なく変換することが可能になります。

ただし、ここまで述べたプロンプトだけでは不十分です。カンマ区切りで例を示しましたが、カンマの後に半角スペースが入っていることもあります。果物の名前ではなくてマニアックな質問をすると回答を返してくれない場合もあります。もっと複雑な、構造化されたデータを返してほしい場合もあります。
一方で、あまり多くの指示をプロンプトに詰め込みすぎるとトークン数が増え、コストが増えてしまいます。トークン数を抑えるコツとして、プロンプトを英語にするとトークン数を少なくすることができますが、日本語とのニュアンスの違いには注意が必要です。

これからは、ユースケースによって最適なプロンプトを作る腕が試されると思います。

おわりに

今回は、「Azure OpenAI Service」をC#に組み込んだ話でした。 最初のセットアップさえ済ませてしまえば、後はプロンプトを工夫することで様々な回答を得ることができます。 これを応用して再帰的にChatGPTを呼び出すことで、無限大の可能性がありそうです。 今後もこのChatGPTをサービスに組み込むことで、よりよいプロダクトを開発していきたいです。

ecbeingでは、新技術をいち早くキャッチアップする好奇心旺盛な人を募集しています! careers.ecbeing.tech