ChatGPT API でチャットの返答を Stream 受信して最速表示する方法
ChatGPT API(以下 OpenAI API と呼びます)で遊んでいますか?楽しいですよね。私も楽しくて色々と開発しています。OpenAI API Key だけで実行できる ChatGPT のようなチャットサービス「Chat Code」を作りました。ChatGPT PLUS より圧倒的に安く使えるかなと思います。私が安く使いたく開発しました。
リポジトリはこちらです。何か機能が欲しい場合は issue をどんどん書いてください。全員で最高のアプリにできたらなと思います。ちなみにエンジニアがターゲットです。
その中の技術で以下のような動作があります。
普通の実装であれば、返答が全て完了した後に表示されますよね。しかし、それだと長い返答になった時に完了まで待ち時間が発生してしまいます。そうならないよう、今回の Stream 技術を使用します。返答が少しづつ表示されるため待ち時間がほとんどありません。
ブラウザの JavaScript の話です。 Node.js ではありません。
通常は npm openai を使用してチャットを開始します。
const result = await openai.createChatCompletion({
model: model,
messages: messages
})
stream を開始したい場合は、 stream を有効にする必要があります。
const result = await openai.createChatCompletion({
model: model,
messages: messages,
stream: true
}, { requestType: "stream" })
ただしこれは ブラウザのJS
では動作しません。なぜか。
それは openai の内部では、Axios が使用されているからです。Axios は Node.js での Stream は対応していますが、ブラウザの Stream には対応していません。なのでブラウザの JS で Stream をするには npm openai は現時点では使えないと言うことです。
対策はあります。 Fetch API を使うと Stream に対応することができます。
// 直接 POST する。そうすることで、即 completion が帰ってきます。
const completion = await fetch('https://api.openai.com/v1/chat/completions', {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${apiKey}`
},
method: 'POST',
body: JSON.stringify({
// eslint-disable-next-line @typescript-eslint/no-unused-vars
messages: messages,
model: 'gpt-3.5-turbo',
stream: true // ここで stream を有効にする
})
});
// ReadableStream として使用する
const reader = completion.body?.getReader();
if (completion.status !== 200 || !reader) {
return "error";
}
const decoder = new TextDecoder('utf-8');
try {
// この read で再起的にメッセージを待機して取得します
const read = async (): Promise<any> => {
const { done, value } = await reader.read();
if (done) return reader.releaseLock();
const chunk = decoder.decode(value, { stream: true });
// この chunk には以下のようなデータ格納されている。複数格納されることもある。
// data: { ... }
// これは Event stream format と呼ばれる形式
// https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#event_stream_format
console.log(chunk);
const jsons = chunk
// 複数格納されていることもあるため split する
.split('data:')
// data を json parse する
// [DONE] は最後の行にくる
.map((data) => {
const trimData = data.trim();
if (trimData === '') return undefined;
if (trimData === '[DONE]') return undefined;
return JSON.parse(data.trim());
})
.filter((data) => data);
// あとはこの jsons を好きに使用すれば良いだけ。
console.log(jsons);
return read();
};
await read();
} catch (e) {
console.error(e);
}
// ReadableStream を最後は解放する
reader.releaseLock();
上記の実装で ChatGPT のような少しづつメッセージが表示されるようになると思います。最高のサービスを作りましょう!
Discussion