Azure OpenAIのStreamで回答をスムーズに表示する方法

Azure OpenAIのStream機能を利用すると全ての回答生成の完了を待つことなく少しづつ結果を表示することができます。ChatGPTのような感じですね。

ただ、実際に使ってみると1文字ずつ表示されるのではなく、ある程度の文字の塊ごとに表示されるため、少し見栄えが良くないと感じました。

これを解消する方法を探していたところ、以下の記事を見つけました。

qiita.com

記事内ではJavaScriptを利用しているため、C#での実装方法について考えてみたいと思います。

環境

  • C# コンソールアプリ(.NET 8)
  • Azure.AI.OpenAI 2.0.0

実装のポイント

今回の実装をするにあたり、ポイントとなるクラスは以下の通りです。

Queueクラス

Streamで受け取った文字を一時的に格納して、表示するときに取り出します。

learn.microsoft.com

Timerクラス

指定した時間ごとに処理を行う際に利用できます。

learn.microsoft.com

ソースコード

以下のように実装します。

using Azure.AI.OpenAI;
using OpenAI.Chat;
using System.ClientModel;
using Timer = System.Timers.Timer;

string endpoint = "[Azure OpenAIのエンドポイント]";
string key = "[Azure OpenAIのAPIキー]";

AzureOpenAIClient azureClient = new(
    new Uri(endpoint),
    new ApiKeyCredential(key));

//チャットクライアントを取得
ChatClient chatClient = azureClient.GetChatClient("gpt-4-o");

//キューを作成
Queue<string> queue = new Queue<string>();

//33msごとにキューから取り出して表示
var timer = new Timer(33);
timer.Elapsed += (s, e) =>
{
    if (queue.Count > 0)
    {
        Console.Write(queue.Dequeue());
    }
};

//タイマーを開始
timer.Start();

//Azure OpenAIにリクエストを送信してStreamで受信
CollectionResult<StreamingChatCompletionUpdate> completionUpdates = chatClient.CompleteChatStreaming(
    [
        new UserChatMessage("Blazorについて小学生でも分かるように説明してください。"),
    ]);

//受信したストリーミングデータをキューに追加
foreach (StreamingChatCompletionUpdate completionUpdate in completionUpdates)
{
    foreach (ChatMessageContentPart contentPart in completionUpdate.ContentUpdate)
    {
        //テキストを1文字ずつキューに追加
        foreach (var c in contentPart.Text)
        {
            queue.Enqueue(c.ToString());
        }
    }
}

//コンソールアプリが終了しないように、キューが空になるまで待機
while (queue.Count > 0)
{
}

//タイマーを停止
timer.Stop();

これを実装すると、以下のようにスムーズに表示することができます。

youtu.be

終わりに

今回は、C#のコンソールアプリでStreamの結果をスムーズに表示する方法を考えました。

実装がコンソールアプリに最適化されているため、今後はBlazorなどでどのような実装にすればいいかを考えてみようと思います。

執筆担当者プロフィール
古川 貴浩

古川 貴浩(日本ビジネスシステムズ株式会社)

アプリケーション開発をしています。.NETやAI関連が好きです。

担当記事一覧