ブログに戻る

APIとSDKガイド

OpenAI Python SDKのタイムアウトとリトライ:CheapTokens用に設定する

OpenAI Python SDKでtimeout、max_retries、max_tokensを設定し、適切なエラーをキャッチし、スタックしたリクエストが10分間実行されないようにします。

投稿者: CheapTokens Team公開日 読了時間 6 分

暗い背景にtimeout=30.0とmax_retries=2を持つPythonコード
このページの内容
  1. 何も設定しない場合のSDKの動作
  2. クライアントでtimeoutとmax_retriesを設定する
  3. 1つのリクエストに対して上書きする
  4. 重要なエラーをキャッチする
  5. max_tokensを制限して長い回答が暴走しないようにする
  6. リトライが請求に与える影響
  7. 出荷前に

スクリプトがモデルを呼び出したのに何も返ってこず、10分後もまだ待っている。これはOpenAI Python SDKのデフォルトで、タイムアウトは10分、リトライは2回です。変更するには、timeout=30.0とmax_retries=2をOpenAI(...)に渡し、呼び出しごとにclient.with_options(...)で上書きし、max_tokensで回答を制限します。

SDKのREADME(2026年10月27日確認)にこれらすべてが記載されています。以下では、OpenAI Chat Completions形式を話すCheapTokensエンドポイントhttps://api.cheaptokens.dev/v1に適用します。その設定の詳細はYour first API request with CheapTokensとdocsにあります。

何も設定しない場合のSDKの動作#

重要なデフォルトが2つあります。タイムアウトは10分です。リトライは2回で、短い指数バックオフを伴い、接続エラーと408、409、429、5xxレスポンスに対して行われ、リクエストボディを安全に再送できる場合に限られます。タイムアウトしたリクエストもリトライされます。

つまり、各試行には独自のタイムアウトがあり、最悪のケースはおおよそタイムアウト×試行回数に、試行間のポーズを加えたものになります:

  • デフォルト:600 × 3 = 1800秒、30分
  • timeout=30.0、max_retries=2:30 × 3 = 90秒

Webエンドポイントが30秒以内に応答しなければならないとします。デフォルトでは、コードがエラーを認識するまでに30分間ハングする可能性があります。読み進める前に別の設定で試してみてください。

数字を当てる

クライアントがtimeout=45.0とmax_retries=3を持っています。例外が発生するまでの最悪の待ち時間は秒単位でいくらですか(試行間のポーズは数えません)?

答えを表示

答え: 180

45 × (3リトライ + 1初回試行) = 180秒。試行間のポーズが少し追加されます。

クライアントでtimeoutとmax_retriesを設定する#

最も簡単な修正は、クライアントを作成するときに両方を一度設定することです:

python
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.cheaptokens.dev/v1",
    api_key=os.environ["CHEAPTOKENS_API_KEY"],
    timeout=30.0,      # seconds per attempt
    max_retries=2,     # 0 turns retries off
)

resp = client.chat.completions.create(
    model="deepseek-ai/DeepSeek-V4-Flash-0731",
    messages=[{"role": "user", "content": "Summarize this ticket in two sentences: ..."}],
    max_tokens=800,
)
print(resp.choices[0].message.content)
print(resp.usage.prompt_tokens, resp.usage.completion_tokens)

max_retriesは非負の整数でなければなりません。timeoutはfloat、または接続と読み取りに別々の制限が必要な場合はhttpx Timeoutオブジェクトを受け取ります。現在のmainブランチのREADMEではそのためにhttpx2をインポートしていますが、古いバージョンではhttpxを使用していたので、pip show openaiを実行してインストールがどちらを使用しているか確認してください。単純なfloatはインポート不要でどちらでも動作します。

The request path with timeout=30 and two retries
One request: an attempt, a timeout, up to two retries, then an exception

CheapTokens docsの完全なモデルIDを使用してください。ここではdeepseek-ai/DeepSeek-V4-Flash-0731です。IDはプロバイダー間で異なります。このモデルは回答あたり最大32,768出力トークンを受け付け、380Kのコンテキストを持つため、上記のmax_tokens行が意図的にあります。詳細は以下と、モデルについてはcheap DeepSeekにあります。

1つのリクエストに対して上書きする#

1つのクライアント、異なるジョブ。チャットの返信はすぐに諦めるべきで、夜間の要約は待つことができます。2つ目のクライアントを作成する代わりに、呼び出し用に派生させます:

python
quick = client.with_options(timeout=5.0, max_retries=0)
resp = quick.chat.completions.create(
    model="deepseek-ai/DeepSeek-V4-Flash-0731",
    messages=[{"role": "user", "content": "Reply with one word: ok"}],
    max_tokens=5,
)

with_optionsは新しい値を持つコピーを返し、clientはそのままにします。max_retries=0を設定するのは、自分のコードがすでにリトライしている場合、たとえばタスクを再エンキューするジョブキューに適切です。そうでなければ、2つの層が掛け算になります:3つのSDK試行が3つのキュー試行内にあると、1つのタスクに9つのリクエストになります。

重要なエラーをキャッチする#

リトライが尽きた後、SDKは例外を発生させます。すべてのエラークラスはopenai.APIErrorから継承し、失敗したリクエストはrequest_idを持ち、これをログに記録します:

python
import openai

def ask(prompt: str) -> str | None:
    try:
        r = client.chat.completions.create(
            model="deepseek-ai/DeepSeek-V4-Flash-0731",
            messages=[{"role": "user", "content": prompt}],
            max_tokens=800,
        )
        return r.choices[0].message.content
    except openai.APITimeoutError:
        print("timed out after retries")
    except openai.RateLimitError as e:
        print("429, slow down:", e.request_id)
    except openai.APIConnectionError:
        print("cannot reach the API")
    except openai.APIStatusError as e:
        print("HTTP", e.status_code, e.request_id)
    return None

順序は狭いものから広いものへ。APIStatusErrorは任意の4xxまたは5xxレスポンスをカバーするので、最初に来るとRateLimitErrorを飲み込んでしまいます。タイムアウトも同様で、一般的な接続エラーの上に位置します。バッチスクリプトでは、return Noneが要点です:プロンプトをログに記録し、次に進み、最後に失敗を再実行します。

タイムアウトが発生し、リトライが使い果たされると、例外は次のようになります:

The APITimeoutError message after retries are used up
What a caught timeout looks like in the log

429の場合の詳細、試行間の待機を含めて、429 Too Many Requests from an AI APIを参照してください。

max_tokensを制限して長い回答が暴走しないようにする#

タイムアウトは待機を停止します。モデルに書く量を減らさせるわけではありません。max_tokensがなければ、32,768トークンの回答制限を持つモデルは非常に長い返信を書くことができ、長い返信は遅いものです。ジョブに合った制限を設定してください:チケット要約には800、はい/いいえには5。

コストは回答が途中で切れる可能性があることです。resp.choices[0].finish_reasonを確認してください:lengthはモデルが終了する前に上限に達したことを意味します。Why does an AI answer stop mid-sentence?でカバーされています。代わりに回答をストリーミングすると、最初の単語が早く到着し、待ち時間が短く感じられます。Streaming responses in Pythonを参照してください。

リトライが請求に与える影響#

リトライは新しいリクエストです。試行が処理された場合、それは独自のトークンを持つ別のリクエストです。チケット要約が入力1,500トークン、出力400トークンだとします:1つのリクエストは今日の価格で$0.000063かかり、完全な価格表には他のモデルがあります。価格はモデルごとに固定で、ピーク時間はないので、午前3時のリトライは正午のものと同じコストです。

スライダーを自分のバッチに合わせて動かしてください:

コスト計算機

1,000件のチケット要約のバッチ

リクエストあたりの入力トークン数: 1,500 · リクエストあたりの出力トークン数: 400 · 1日あたりのリクエスト数: 1,000

モデル1日あたり1か月あたり
GLM-5.3-Flash$0.046$1.38
DeepSeek-V4-Flash$0.0625$1.88
MiniMax-M2.7$0.135$4.05

表は1回のパスを示しています。バッチの10分の1が2回目の試行を必要とする場合、10分の1を追加します。リトライは失敗が稀な場合には安価な保険であり、失敗し続けるエンドポイントに対してmax_retries=10を設定する場合には高価な習慣です。

出荷前に#

チェックリスト

タイムアウトとリトライ、本番環境対応