ChatGPT で話題になっている OpenAI からは API や SDK が提供されており、これらを使うことで、自作アプリケーションに ChatGPT のような機能を組み込むことができるようになります。API 自体は無料登録でも(バージョン 3.5 以下を)利用することができますが、有料版にすることで(精度が話題になっている)バージョン 4.0 の API を使うこともできるようになる、というものです。
・・・というものなのですが、実際に ChatGTP と同様に問い合わせをする API を実行してみるとかなりの確率で 429 という HTTP ステータスコードのエラーが返されます。この 429 は "Too many requests" を意味するエラーコードです。特に無料登録の場合は利用回数に制限があったり、API 実行時のリソース割り当て優先度が低く設定されてしまい、「(他に使われていて)混み合っている時は実行できない」ということが頻繁に発生するのでした。
この問題に関しては OpenAI の記事でも取り上げられていて、「(API 実行時に)"exponential backoff" で実行するようにしてほしい」と紹介されています:
https://help.openai.com/en/articles/5955604-how-can-i-solve-429-too-many-requests-errors
↑の記事では(exponential backoff 関数が標準で使える) Python での簡単なサンプルが紹介されています。個人的には JavaScript で実装することが多いので JavaScript のサンプルを探してみました。OpenAI API ではない例で exponential backoff を JavaScript で実装している例は見つけることができた(↓)のですが、OpenAI API のサンプルは軽く探した限りでは見つかりませんでした:
https://advancedweb.hu/how-to-implement-an-exponential-backoff-retry-strategy-in-javascript/
・・・というわけで自分で作ってみました、というブログエントリです。Node.js での実装サンプルはこちらの GitHub で公開しています:
https://github.com/dotnsf/openai-api
【そもそも exponential backoff とは?】
"exponential" は「指数的」という意味で、「指数的に増加する段階ウェイトをかけながら(何かを)繰り返し実行する」ことです。
今回の例だと(ChatGPT の)API を実行して 429 エラーになった場合、1秒待って再実行し、また 429 エラーになったら1秒待って再実行し、・・・ という方法ではありません。これだとウェイトは増加することなく、常に1秒待って再実行を繰り返しているだけです。
「指数的」という名前の通り、ウェイトに指数を使います。例えば「2の〇乗」という指数を使った場合で計算すると、まず API を実行して 429 エラーになった場合、まず(2の0乗=)1秒待って再実行し、また 429 エラーになった場合、次は(2の1乗=)2秒待って再実行し、また 429 エラーになった場合は次は(2の2乗=)4秒待って再実行し、また 429 エラーになったら(2の3乗=)8秒待って再実行し、・・・ といった具合にウェイト時間が指数関数で増えていくようなループ処理を行うことになります(上の GitHub 公開サンプルの場合、秒ではなくミリ秒の単位で計算しています)。これが exponential backoff です。
特定条件下で 429 エラーが高い確率で返される可能性が高いとわかっているような API を使う時の、ベストプラクティス的な実装方法のようです。
【実装例】
Node.js(JavaScript)での実装例を紹介します。例えば exponential backoff 実装前はこのような形で実行していました:
SDK を使って単純に createCompletion メソッドを実行しています。これだと 429 エラーが発生した時にそのまま 429 エラーを返すことになります。
ここを exponential backoff を実装して作り変えたものがこちらです(青字部分が変更箇所です):
この実装ではデフォルトで最大7回まで再実行する想定で、失敗した後に(2 の試行回数乗) * 10 ミリ秒待ってから再実行する、という形で実装しています。
・・・ただ、OpenAI の無料枠の混雑具合は相当なものらしく、この方法で API を実行しても結局 429 エラーとなるだけっぽい気がしています。10 回くらい試してもいいのかな?
・・・というものなのですが、実際に ChatGTP と同様に問い合わせをする API を実行してみるとかなりの確率で 429 という HTTP ステータスコードのエラーが返されます。この 429 は "Too many requests" を意味するエラーコードです。特に無料登録の場合は利用回数に制限があったり、API 実行時のリソース割り当て優先度が低く設定されてしまい、「(他に使われていて)混み合っている時は実行できない」ということが頻繁に発生するのでした。
この問題に関しては OpenAI の記事でも取り上げられていて、「(API 実行時に)"exponential backoff" で実行するようにしてほしい」と紹介されています:
https://help.openai.com/en/articles/5955604-how-can-i-solve-429-too-many-requests-errors
↑の記事では(exponential backoff 関数が標準で使える) Python での簡単なサンプルが紹介されています。個人的には JavaScript で実装することが多いので JavaScript のサンプルを探してみました。OpenAI API ではない例で exponential backoff を JavaScript で実装している例は見つけることができた(↓)のですが、OpenAI API のサンプルは軽く探した限りでは見つかりませんでした:
https://advancedweb.hu/how-to-implement-an-exponential-backoff-retry-strategy-in-javascript/
・・・というわけで自分で作ってみました、というブログエントリです。Node.js での実装サンプルはこちらの GitHub で公開しています:
https://github.com/dotnsf/openai-api
【そもそも exponential backoff とは?】
"exponential" は「指数的」という意味で、「指数的に増加する段階ウェイトをかけながら(何かを)繰り返し実行する」ことです。
今回の例だと(ChatGPT の)API を実行して 429 エラーになった場合、1秒待って再実行し、また 429 エラーになったら1秒待って再実行し、・・・ という方法ではありません。これだとウェイトは増加することなく、常に1秒待って再実行を繰り返しているだけです。
「指数的」という名前の通り、ウェイトに指数を使います。例えば「2の〇乗」という指数を使った場合で計算すると、まず API を実行して 429 エラーになった場合、まず(2の0乗=)1秒待って再実行し、また 429 エラーになった場合、次は(2の1乗=)2秒待って再実行し、また 429 エラーになった場合は次は(2の2乗=)4秒待って再実行し、また 429 エラーになったら(2の3乗=)8秒待って再実行し、・・・ といった具合にウェイト時間が指数関数で増えていくようなループ処理を行うことになります(上の GitHub 公開サンプルの場合、秒ではなくミリ秒の単位で計算しています)。これが exponential backoff です。
特定条件下で 429 エラーが高い確率で返される可能性が高いとわかっているような API を使う時の、ベストプラクティス的な実装方法のようです。
【実装例】
Node.js(JavaScript)での実装例を紹介します。例えば exponential backoff 実装前はこのような形で実行していました:
var { Configuration, OpenAIApi } = require( 'openai' );
var configuration = new Configuration({ apiKey: "(API KEY)", organization: "" });
var openai = new OpenAIApi( configuration );
:
:
var option = {
model: "gpt-3.5-turbo",
prompt: "海をテーマにしたおススメ映画を教えてください",
max_tokens: 4000
};
try{
var result = await openai.createCompletion( option );
}catch( err ){
}
SDK を使って単純に createCompletion メソッドを実行しています。これだと 429 エラーが発生した時にそのまま 429 エラーを返すことになります。
ここを exponential backoff を実装して作り変えたものがこちらです(青字部分が変更箇所です):
var { Configuration, OpenAIApi } = require( 'openai' ); var configuration = new Configuration({ apiKey: "(API KEY)", organization: "" }); var openai = new OpenAIApi( configuration ); : : const wait = ( ms ) => new Promise( ( res ) => setTimeout( res, ms ) ); const progressingOperation = async ( option ) => { await wait( 10 ); try{ var result = await openai.createCompletion( option ); return { status: true, result: result }; }catch( e ){ return { status: false, result: e }; } } const callWithProgress = async ( fn, option, maxdepth = 7, depth = 0 ) => { const result = await fn( option ); // check completion if( result.status ){ // finished return result.result; }else{ if( depth > maxdepth ){ throw result; } await wait( Math.pow( 2, depth ) * 10 ); return callWithProgress( fn, option, maxdepth, depth + 1 ); } } var option = { model: "gpt-3.5-turbo", prompt: "海をテーマにしたおススメ映画を教えてください", max_tokens: 4000 }; try{ //var result = await openai.createCompletion( option ); var result = await callWithProgress( progressingOperation, option, 5 ); }catch( err ){ }
この実装ではデフォルトで最大7回まで再実行する想定で、失敗した後に(2 の試行回数乗) * 10 ミリ秒待ってから再実行する、という形で実装しています。
・・・ただ、OpenAI の無料枠の混雑具合は相当なものらしく、この方法で API を実行しても結局 429 エラーとなるだけっぽい気がしています。10 回くらい試してもいいのかな?