LoginSignup
2
1

C#でChatGPT APIを触りたい 2 ~ ReadableStream ~

Last updated at Posted at 2023-06-14

この記事は :apple:

:small_red_triangle_down:結果
ChatStream.gif

対象読者 :whale:

  • C#でChatGPT APIを叩きたい人へ
  • ChatGPTの画面のように、少しずつレスポンスを返したい方へ

初めに :seedling:

以前、 C#でChatGPT APIを触りたい with Betalgo.OpenAI.GPT3という記事の中でBetalgo.OpenAI.GPT3の利用方法について紹介しました。
時がたち、名前空間や機能に変更がありましたので、試してみました。

実装 :star:

レスポンスをReadableStream - Web API|MDNとして扱ってくれる、CreateCompletionAsStreamメソッドを利用し、画面描画までを行います。

公式のサンプルはこちら

まずはCreateCompletionAsStreamメソッドの呼び出しです。

StreamChatService.cs
public async IAsyncEnumerable<string> StreamChat(string prompt)
{
    var messages = new List<ChatMessage>()
    {
        ChatMessage.FromSystem("The following is a conversation with an AI assistant. The assistant is helpful, creative, clever, and very friendly."),
        ChatMessage.FromUser(prompt)
    };

    var completionResult = openAIService.ChatCompletion.CreateCompletionAsStream(
        new ChatCompletionCreateRequest()
        {
            Messages = messages,
            Model = Models.ChatGpt3_5Turbo
        });

    await foreach (var completion in completionResult)
    {
        if (completion.Successful)
        {
            yield return completion.Choices.FirstOrDefault()?.Message.Content ?? string.Empty;
        }
        else
        {
            if (completion.Error == null)
            {
                throw new Exception("Unknown Error");
            }
            Console.WriteLine($"{completion.Error.Code}: {completion.Error.Message}");
        }
    }
    Console.WriteLine("Complete");
}

注目すべきは、StreamChatメソッドの戻り値が IAsyncEnumerable<string> 型である点です。
これは非同期ストリームを表しており、レスポンスを受け取る度にその中身を呼び出し元に返すことが可能です。

参考文献: 非同期ストリーム - C# によるプログラミング入門

Blazor Serverで非同期ストリームを受け取る:love_letter:

非同期ストリームから受けとった文字列を画面に表示させる場合、注意が必要です。

StreamChat.razor

private async void GetChatCompletion()
{
    isChatting = true;
    responseMessage = string.Empty;
    
    var response = StreamChatService.StreamChat(chatMessage);
    await foreach(var blob in response)
    {
        responseMessage += blob;
        await InvokeAsync(StateHasChanged);
        
        // Allowing control to return to the renderer to update UI.
        await Task.Delay(1);
    }

    isChatting = false;
    await InvokeAsync(StateHasChanged);
}

コメントでも補足していますが、await Task.Delay(1);を実行し、制御をメインのイベントループに戻す必要があります。

ComponentBase.csがその実装箇所であり、新しいレンダリングをキューに入れ、実行を待機します。
どうやら処理を譲るだけで良さそうですので、Task.Delay(1)の長さは問題にはならなさそうですが、このあたりに詳しい方、是非ご教授いただきたいです。

まとめ :feet:

Betalgo.OpenAIを用いて少しずつレスポンスを返す画面を実装しました。
IAsyncEnumerableの使い方やBlazorのレンダリングの仕組みについての理解が進んだのであれば幸いです。
最近ではChatGPTにFunction Calingの機能が追加されたりと、新しいことばかりですね。
また進展があれば記事にします:blush:
では:raised_hand:

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1