一言で言うと:1回の呼び出しで数万文字(エピソードのアウトライン、長編フィクション、大規模な翻訳、大きなコードなど)を生成するようモデルに依頼する場合は、レスポンスをストリーミングし、非ストリーミングは使用しないでください。クライアントの読み取りタイムアウトは、生成全体の時間ではなく、データイベント間の間隔(数十秒 — 90~120秒が安全な値です)に設定してください。
max_tokens に余裕を持たせ、テキストを使用する前に stop_reason を確認してください。この4つを実行すれば、長い出力の呼び出しで「何も返ってこない」ことはなくなります。/v1/messages を使用しています。OpenAI互換フォーマットとの違いについては、別途説明します。
最初に知っておくべき3つのこと
- 10k語の出力では、実際の生成に10~20分かかるのは正常です。 モデルは数万文字をtokenごとに出力し、さらに推論/thinkingフェーズもあるため、エンドツーエンドのレイテンシーは実際に長くなります。これはゲートウェイが遅いのではなく、生成そのものに時間がかかっているためです。
-
非ストリーミングでは、すべてをバッファリングしてから送信します。 非ストリーミング(
streamが省略されている、またはfalseの場合)では、サーバーはモデルが生成全体を完了するまで待機し、その後にレスポンスボディ全体を一度に返す必要があります。その数分間、クライアントの読み取りタイムアウトとの競争になり、生成が長いほど結果が到着する前に切断される可能性が高くなります。また、例外は空であることが多く(httpx.ReadErrorのstr(e)が空欄)、原因を確認できません。 - 接続が切断されても課金されるため、無計画なリトライは二重課金になります。 サーバーが出力を生成した時点で、結果が届かなかった場合でも呼び出しは課金されます。すでにボディの一部を受信した後にリトライすると、モデルは再度実行され、再び料金が発生します。
ストリーミングを使用し、非ストリーミングは使用しない
ストリーミング(stream: true)では、最初のバイトが数秒以内に到着し、その後は数十秒ごとにデータイベントが到着します。読み取りタイムアウトは、何分も実行される生成全体ではなく、イベント間の間隔だけをカバーすればよいため、ストリーミングは長い出力を確実に配信できます。
2つのプロトコルには異なる終端記号があります。混同しないでください。
アダプティブthinkingが有効な場合、Claude ネイティブは最初に
type: "thinking" ブロック(その増分は thinking_delta)を出力し、その後に text ブロックを出力します。レンダリング時は、thinking_delta と text_delta を別々にルーティングし、thinkingを本文テキストに連結しないでください。
最小限のClaude ネイティブ /v1/messages ストリーミング例(プレーンなhttpx、行ごとのSSE解析):
読み取りタイムアウトは総時間ではなくイベント間隔に合わせて設定する
多くの人は、生成全体をカバーするために読み取りタイムアウトを非常に大きな値(たとえば 1800 秒)に設定しますが、それでもタイムアウトします。これは、非ストリーミングではその値が生成全体と競合し、わずかな中断でも失敗するためです。正しいアプローチは、ストリーミングとイベント間隔に合わせた読み取りタイムアウトの組み合わせです。 測定参照値(claude-opus-5 が約 15k 文字の入力から約 20k 文字のエピソード概要を生成した場合):
90~120 秒の読み取りタイムアウトであれば、最大のイベント間隔を余裕を持ってカバーできます。数分に及ぶ値は不要です。3 部構成のタイムアウトではフェーズを分割し、それぞれに適切な値を設定できます。
max_tokens に余裕を持たせ、stop_reason を確認する
長い出力はmax_tokens の上限に達して切り詰められやすくなります。これは特に Claude のような thinking を有効化したモデルで顕著です — thinking 自体が max_tokens の予算を消費します。長い文章では、その予算を使い切ることがあります。
max_tokensは 64000 から開始してください(高い effort / 深い thinking ではさらに多く指定してください。claude-opus-5は 128K 出力に対応しています)。- レスポンスを使用する前に
stop_reasonを確認してください:end_turn— 正常に完了しており、テキストは完全です。これが唯一の成功です。max_tokens— 切り詰められており、テキストが不完全、または空の場合もあります。これは切り詰めであり、「空の結果」ではありません —max_tokensを増やして再試行してください。refusal— 安全ポリシーにより拒否されました。別途処理してください。
str(e) だけ、または「テキストが空」であることだけで成功を判断するのは誤解を招きます — 空の本文は通常、max_tokens による切り詰めです。
リトライ戦略
長い出力に対するリトライは慎重に行い、「失敗時にリトライ」が「二重課金と2回目の長時間実行」にならないようにします。- レスポンスヘッダーの到着前に発生した失敗、および
5xx/429に対してのみリトライします(バックオフを使用し、最大2回まで)。これらは接続または一時的な問題であり、リトライが合理的です。 - ボディの一部をすでに受信した後に切断されたstreamを、無条件にリトライしないでください。 サーバーはすでに生成と課金を行っており、リトライすると再度実行され、再度支払うことになります。
- 照合とトラブルシューティングのために、レスポンスヘッダー内のリクエストIDをログに記録します。
シナリオ早見表
常に stream を使用してください。
api.apiyi.com(中国本土で推奨)またはvip.apiyi.com(海外で推奨)を使用し、api-cf.apiyi.comは使用しないでください(CDNノードは約100秒で524を返し、長いリクエストを処理できません)。
関連
APIのタイムアウトを回避する方法
シナリオ別のタイムアウト値
ストリーミングと非ストリーミングの比較
トレードオフと選択方法
Claudeの推論とエフォート
適応型推論、エフォート層、max_tokens、切り詰め
Claudeのレスポンス処理
ネイティブレスポンス形式、SSEイベント、stop_reason