takataka430’s blog

.NET系を中心に勉強したことのまとめを書きます

C#を使ってOpenAIのAPIからの結果をChatGPTのように順次表示する方法

はじめに

ChatGPTは回答が一気に表示されるのではなく、少しずつ表示されます。これができたらユーザーは待機時間が短く感じらますよね。
今回はChatGPTのAPIを利用してどのように実装するのかを調査してみました。

環境

.NET 6 コンソールアプリ
OpenAIのモデル:gpt-3.5-turbo

ポイント

結果を順次受け取るようにする際のポイントをまとめました。

streamパラメータの設定

以下記事の通り、APIにリクエストを送る際のパラメータでstreamをtrueにする必要があります。

https://platform.openai.com/docs/api-reference/chat/create#chat/create-stream

結果の受け取り

上記のようにstreamを有効にした場合、結果は以下のようなJSONになります。

data: {
    "id": "...",
    "object": "...",
    "created": ...,
    "model": "gpt-3.5-turbo-0301",
    "choices": [
        {
            "delta": {
                "content": "..."
            },
            "index": 0,
            "finish_reason": null
        }
    ]
}

data: {
    "id": "...",
    "object": "...",
    "created": ...,
    "model": "gpt-3.5-turbo-0301",
    "choices": [
        {
            "delta": {
                "content": "..."
            },
            "index": 0,
            "finish_reason": null
        }
    ]
}

(省略)

data: [DONE
]

上記はすべての結果をまとめて表示していますが、コードを実行した際にはdata:[JSON形式のデータ]という結果を繰り返し取得できます。このままではJSONに変換しにくいので「data:」の部分は削除すると扱いやすいです。
また、回答はcontentの中にあるのでJSONの解析でこの値を取得するといいでしょう。

コード

では実際にコードを見てみましょう。C#のコンソールアプリでの例です。

using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json.Nodes;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        string key = "[OpenAIのAPIキー]";

        Console.WriteLine("質問を入力してください。");
        string question = Console.ReadLine();
        
        Console.WriteLine("--------------------------------------");

        var client = new HttpClient();
        var request = new HttpRequestMessage(HttpMethod.Post, "https://api.openai.com/v1/chat/completions");
        request.Headers.Add("Authorization", $"Bearer {key}");

        var content = JsonContent.Create(new
        {
            model = "gpt-3.5-turbo",
            messages= new List<message> { new message("user", question) },
            stream = true
        });

        request.Content = content;

        var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
        using var streamReader = new StreamReader(await response.Content.ReadAsStreamAsync());

        while (!streamReader.EndOfStream)
        {
            var line = await streamReader.ReadLineAsync();
            if (string.IsNullOrEmpty(line))
                continue;

            //冒頭の[data]を削除
            line = line.Remove(0, 6);
            
            //[DONE]の場合は終了
            if (line == "[DONE]")
                continue;

            var resultContent = JsonNode.Parse(line)?["choices"]?[0]?["delta"]?["content"]?.ToString();
            if(resultContent != null)
                Console.Write(resultContent);  //結果をコンソールに表示
        }
    }
}

class message
{
    public message(string _role, string _content)
    {
        role = _role;
        content = _content;
    }
    public string role { get; set; }
    public string content { get; set; }
}

実行結果

以下のように結果が順次表示されました。

youtu.be