💎

chatGPT APIの結果をchatGPT(web版)のようにちょっとずつ受け取る方法

2023/03/13に公開

ruby-openaiだと

RubyでchatGPTを使う場合には ruby-openaiなる神Gemがあるんですが、これを使うと結果は一つのresponseとして返ってきます。
Web版のchatGPTがそうであるように仕組みとしてはresponse bodyをstreamで受け取る手段があるっぽく、実際にruby-openaiのリクエストパラメータに stream: trueを入れるとレスポンスが変化すしたりします。

response = client.chat(
    parameters: {
        model: "gpt-3.5-turbo", # Required.
        messages: [{ role: "user", content: "Hello!"}], # Required.
        temperature: 0.7,
	stream: true # <- これつけるとレスポンスの中身がちょっと変わる
    })

ruby-openaiはHTTP通信のライブラリにhttpartyちゃんを使ってるんですが、調べてみた感じこのGemにはresponse bodyをstreamで処理する手段が無いらしい。
Rubyだとnet/httpとかfaradayは対応しているらしい。ruby-openaiをフォークしてソースコードを追跡した時に概ねどうAPIを叩けば良いのか理解したのでそんじゃ自前で書いてみようかと

faradayもってこーい

faraday、非常に便利なGemであります。
どこのRubyのプロジェクトでもAPI叩くなら使われてるんじゃないかってくらい見るfaradayさん。今回もお力をお借りするマス。

def post(path, params)
  client.post(path) do |request|
    request.headers = headers
    request.body = params.to_json
    request.options.on_data = proc do |chunk, overall_received_bytes|
      yield(chunk, overall_received_bytes)
    end
  end
end

headerとか書いていくと長くなるんで割愛するんですが、だいたいこんな感じ。
postのリクエストをする時にparamsに { stream: true }を忘れないことが大切!
そうするとproc do ~ endでchunkの中に小間切されたレスポンスが入ってるのでそれを取り出します。

post(
  '/v1/chat/completions',
  {
    model: 'gpt-3.5-turbo',
    messages: [{ role: 'user', content: 'おすすめのゲーミングPCメーカー' }],
    stream: true
  }
) do |chunk, _|
  unless chunk.match?(/data: \[DONE\]/)
    text = chunk.split('data: ')[1]
    data = JSON.parse(text)
    print(data['choices'][0]['delta']['content'])
  end
end

どう見てもJSON形式なんですが data: {中身(JSON)}で返ってくるので不要な部分を落としてからJSON.parseします。
ちなみに通信の最後には [DONE]というのが入ってくるんですが、当然JSONでパースすると死ぬので除外してます。ここの良い取扱を知ってたら知りたい気持ちありますあります。

とはいえこれで結果をちょっとずつ受け取ることができまます!

補足

基本的なfaradayの使い方を解説するのは趣旨と違うので省きました。

Discussion