Webは開くのにAPIはエラー:違いは3か所
ブラウザでAIのWeb画面を開く場合、1回のリクエストが終わればそこで完了し、接続はブラウザがまとめて再利用します。失敗してもリロードすれば済みます。API呼び出しはこのモデルとは違います。SDKはバックグラウンドでリクエストを送り続け、ストリーミング出力では数十秒、ときにはそれ以上の長い接続を維持する必要があり、複数のタスクが同時に流れ込みます。この3つの違いは見落とされやすく、バッチ処理が本格的に動き出してからまとめて表面化します。
| 観点 | Web画面 | API呼び出し | 問題が出たときの典型的な症状 |
|---|---|---|---|
| 出口IP | 変化はほとんど気にならない | できるだけ固定し、変化を少なくする | 401 / 403、地域制限のメッセージ |
| 同時接続 | ブラウザのコネクションプールが一括管理 | タスク数に応じて複数の接続を同時に張る | 接続タイムアウト、429、EOF |
| タイムアウト設定 | ページの読み込みが終われば完了 | ストリーミング出力の全体をカバーする必要がある | read timeout、接続のリセット |
切り分けは出口から始めるのがおすすめです。出口が違っていれば、残り2項目の検証結果も当てになりません。
出口IP:APIはWeb画面以上に固定が重要
Web画面ではCookieやログイン状態で本人確認を行うため、出口アドレスが変わっても、通常は再認証を求められる程度で済みます。API側は事情が異なり、プラットフォームはAPI Keyごとに地域と不正対策のポリシーを紐付けます。出口アドレスが頻繁に変わると異常なアクセス元と判定されやすく、軽ければ再認証、重ければリクエストそのものが拒否されます。
共有出口と固定出口
- 共有出口:複数のユーザーが1つの着信アドレスを共有するため、同じ出口上の挙動が互いに影響し合い、切り分けも難しくなります。
- 固定出口:IEPL専用線は通常、安定した着信出口を提供するため、アドレスをプラットフォームの許可リストに登録する必要がある場面に向いています。
- 中継回線の出口はスケジューリングによって決まるため、固定かどうかは説明書きだけで判断せず、自分の環境で実測して確認してください。
出口が安定しているかを検証する
- ローカルの開発マシンとタスクを実行するサーバーで、それぞれ出口アドレスを出力して記録します。
- 時間を空けて何度か繰り返し、記録が一致するか確認します。バッチ処理は、実際に動かす時間帯にもう一度測っておくとよいでしょう。
- 名前解決がどちら側で行われているかを確認し、解決結果と出口アドレスが食い違わないようにします。
- 呼び出すAPIのドメインをプロキシルールに記載し、リクエストが振り分けルールで直結扱いにならず、実際にプロキシを通るようにします。
# 現在の出口アドレスを出力
curl -sS https://ipinfo.io/ip
# 接続時間と初回バイトまでの時間を出力し、「つながらない」と「つながるが遅い」を切り分ける
curl -sS -o /dev/null \
-w "connect=%{time_connect} start=%{time_starttransfer}\n" \
-H "Authorization: Bearer $API_KEY" \
https://api.openai.com/v1/models
DNS解決はどちら側で行われるか
プロキシがTCPだけを引き受けている場合、名前解決はローカルで行われるため、プラットフォーム側から見た解決位置と出口アドレスが一致せず、一部のエンドポイントが地域エラーを返すことがあります。クライアントでリモートDNS解決を有効にするか、APIドメインがプロキシルールに一致していることを確認してから、次の切り分けに進みます。
同時接続:まず3つの上限を把握する
同時接続数が伸びないときは、多くの場合ローカルの帯域不足ではなく、3つの上限のどれかに先に当たっています。
- クライアント側の上限:プロセスのファイルディスクリプタ数、HTTPコネクションプールのサイズ、スレッドまたはコルーチンの数。
- 回線とNATセッションの上限:同じ出口アドレスで同時に維持できるセッション数には限りがあります。
- プラットフォーム側のレート制限:同じAPI Keyまたは同じ出口アドレスに対する同時リクエスト数の制限で、通常は429が返ります。
HTTP/2の多重化は万能ではない
HTTP/2では1本のTLS接続で複数のリクエストを扱えるため、ハンドシェイクの繰り返しを省けます。ただしサーバー側はSETTINGS_MAX_CONCURRENT_STREAMSで1接続あたりの同時ストリーム数を制限し、超えた分は即座に失敗するのではなくキューに入ります。症状としてはエラーは出ないものの待ち時間がどんどん延び、ログにも目立った異常が出ず、最後はメトリクスを取って初めて気づくことになります。
同時接続の調整方法
- セマフォやコネクションプールを使い、同時に飛んでいるリクエスト数を観測できる範囲に抑え、小さな同時接続数から徐々に増やします。
- リトライには指数バックオフとランダムなジッターを入れ、失敗後にすべてのタスクが同じ秒に一斉リトライするのを避けます。
- より高い同時接続数が必要なときは、1本の回線を限界まで使い切るのではなく、タスクを複数の回線に分散させます。
- 同時接続数、失敗率、平均待ち時間をメトリクスとして送信しておくと、問題が起きたときに判断材料になります。
タイムアウト:ストリーミング出力は総時間ではなくアイドル間隔で設定する
タイムアウトは1つの数値ではなく3種類あり、それぞれ役割が異なります。
- 接続タイムアウト(connect):送信開始からハンドシェイク完了まで。短めに設定してよく、タイムアウトする場合は回線が通っていないか出口に到達できないことを示します。
- 読み取りタイムアウト(read / idle):次のデータチャンクを待つ最大時間。ストリーミング出力では、2つのチャンク間の最大間隔を基準に設定する必要があります。
- 総タイムアウト(total):リクエスト全体の上限。非ストリーミングのリクエストにのみ適しており、ストリーミングで総タイムアウトを設けると正常な長い出力を途中で切ってしまいます。
多くのSDKのデフォルトの読み取りタイムアウトは短いリクエストには十分ですが、推論中に長時間tokenを返さないモデルでは厳しすぎます。接続は静かに見えても実際には切れておらず、クライアント側から一方的に切断され、ログにはread timeoutの1行だけが残ります。
リトライのコスト
ストリーミング出力がすでに内容を返し始めている場合、リトライすると二重課金になる可能性があり、コンテキストも乱れます。リトライは接続確立の段階と、明確な5xx・429に限定することをおすすめします。429はバックオフしてから再試行します。
実用的なタイムアウト設定の考え方
import httpx
from openai import OpenAI
client = OpenAI(
base_url="https://api.openai.com/v1",
# read は「リクエスト全体の長さ」ではなく「2つのデータチャンク間の最大間隔」で設定する
timeout=httpx.Timeout(connect=5.0, read=180.0, write=30.0, pool=5.0),
max_retries=2,
)
ストリーミングのエンドポイントではアイドル判定だけを残し、総タイムアウトは業務上許容できる上限まで緩めます。非ストリーミングのバッチ処理は逆で、明確な総タイムアウトを設けたほうが安全です。
回線の選び方:IEPL専用線・中継・直結
3種類の回線はどれかがどれかを置き換える関係ではなく、それぞれ異なる呼び出し方に対応します。
| 回線タイプ | 経路の特徴 | 適した呼び出し方 | 導入前に確認すること |
|---|---|---|---|
| IEPL専用線 | 端から端まで専用線で収容し、公衆網を経由しない | 長い接続のストリーミング出力、固定出口が必要 | 出口アドレスが固定か、許可リストに追加できるか |
| 中継 | 中継ノードを経由してから海外へ出る | 同時接続数が多めで、コストを抑えたいバッチ処理 | 出口がスケジューリングで変わるか |
| 直結 | 海外ノードに直接接続 | デバッグ、軽いリクエスト | 公衆網の経路は混雑の影響を受けやすい |
VPNFNは120以上の国・地域、180以上の回線をカバーし、3種類の回線すべてが選択肢に入ります。プライバシーポリシーはログを記録しない方針です。おすすめの進め方は、まず直結で機能を通し、安定した出口が必要な呼び出しをIEPL専用線に切り替え、リトライ可能なバッチ処理は中継回線に載せるという順番です。リクエストの形態ごとに経路を分けましょう。
プランと通信量:呼び出し量に合わせた選び方
月額プランは60GB / 250GB / 500GBの3段階です。API呼び出しの通信量は2か所に集中します。長いコンテキストのリクエストではリクエストボディによって上り通信量が大きく増え、ストリーミング出力の下りは実際に生成されたtoken数で計算されます。見積もり方は、1回のリクエストの上り・下りのサイズに1日の呼び出し回数を掛け、さらに30日を掛けて、余裕を足すというものです。
- デバッグ段階や個人スクリプト:まず最小のプランで、出口・同時接続・タイムアウトの3項目を通してから通信量を考えます。
- 長時間のバッチ処理:まず上記の方法で月間通信量を計算し、プランを上げるか通信量パックを追加するかを決めます。
- 利用量の変動が大きい場面:通信量パックは使い切るまで有効で期限がないため、月額プランとは別に保険として持っておくのに向いています。
- デバイス数は無制限で、開発マシン・サーバー・スマートフォンを同時に接続できます。台数ごとに別々に用意する必要はありません。
結論:まず月額プランの最小段階で、出口が固定されているか、同時接続がどこまで出るか、タイムアウトをどう設定するかを検証します。3項目すべてを確認してから、実際の利用量に応じてプランを上げるか、通信量パックで突発的な増加を受け止めます。
公開前チェックリスト
以下をひと通り確認すれば、AI API呼び出しで最も多い接続系のトラブルはほぼカバーできます。
- ✅ 出口アドレスを何度も続けて出力し、開発マシンとサーバーから見える出口が同じか確認する。
- ✅ SDKには接続タイムアウトと読み取りタイムアウトを明示的に設定し、デフォルト値に任せない。
- ✅ ストリーミングのリクエストはアイドル間隔で読み取りタイムアウトを設定し、推論中に長く沈黙するモデルは個別に対応する。
- ✅ リトライは接続確立の段階と5xx・429のみを対象にし、指数バックオフとランダムなジッターを付ける。
- ✅ APIのドメインをプロキシルールに記載し、名前解決がリモート側で行われることも確認する。
- ❌ 特定の回線のアドレスをコードに直書きしない。サブスクリプションリンクは一括で更新できるようにする。
- ❌ API Keyとプロキシ設定を一緒にコードリポジトリへコミットしない。
まとめ:AI APIの安定性の問題は、多くが呼び出しコード自体ではなく、出口アドレスが安定しているか、同時接続が制限されていないか、タイムアウトがストリーミング出力をカバーしているかという3点にあります。まずこの3項目を検証してから、回線とプランを検討しましょう。