AI API の回線選びで重要なのは「どの回線が最速か」ではなく、「どの回線ならリクエストが常に同じ出口に着地するか」です。Web なら一度切れても再読み込みで済みますが、スクリプトでは 1 回のタイムアウトがバッチ全体の再実行につながり、二重課金を招くこともあります。以下では固定出口・同時接続・タイムアウト再試行の 3 点を分けて説明し、最後にそのまま実行できる切り分け手順を示します。

AI API 呼び出しと Web 画面の違い

同じサブスクリプションでも、ブラウザで開けるサービスがスクリプトでは通らないことがあります。違いは帯域ではなく、リクエストの形と失敗のコストにあります。

  • 接続の持続時間が違います。Web のページ読み込みは数十の短いリクエストで構成され、1 つ 1 つはすぐ終わります。API のストリーミング応答では 1 本の接続が数十秒から数分続き、途中のわずかな揺らぎがそのまま出力に現れます。
  • 失敗のコストが違います。Web なら 1 回リトライすれば済みますが、バッチ処理の失敗は再実行が必要で、消費するのはクォータと費用です。再送信で重複した結果が生まれることもあります。
  • 出口への敏感さが違います。多くの API サービスは「アカウント + 出口 IP」でリスク判定を行います。同じ日に出口が何度も変わると、追加認証や一時的なレート制限が発生し、ひどい場合はリクエスト自体が拒否されます。
  • 同時接続のモデルが違います。ブラウザはせいぜい数タブですが、スクリプトは数十の接続を同時に開けます。負荷はクライアントのコネクションプールと出口の NAT セッションテーブルにかかります。

この 4 を並べて見ると、回線選びの順序はおのずと決まります。まず出口の安定、次に回線の種類、最後にタイムアウトの調整です。

固定出口:なぜ最速より重要なのか

固定出口とは、同じアカウントのリクエストが長期的に同じ 1 つ、または同じグループの出口 IP から出ていくことを指します。これは「サーバー側があなたを認識できるか」という問題を解決するもので、「速いかどうか」ではありません。

出口が変動すると何が起きるか

  • セッションやトークンが IP に紐づいている場合、再接続で IP が変わると既存のセッションが無効になり、再認証が必要になります。
  • IP ホワイトリストを設定した API では、出口が変わった瞬間に 403 が返ります。
  • リスク判定システムは、同じアカウントが短時間に複数の地域からログインしたと見なすと、認証レベルを引き上げます。
  • トラブル調査のとき、ログの出口 IP が毎回違うと、回線の問題なのかコードの問題なのか判断しづらくなります。

共有出口 = 回線が悪い、ではない

「夜のピークに遅くなる」を回線品質のせいにする人は多いですが、実際の原因は出口の共有であることがよくあります。同じ出口 IP を多くの利用者が使い、対象サービスからはその IP の高頻度リクエストに見えるため、レート制限が発動します。見分け方はエラーの種類です。429 や接続リセットなのに帯域テストは正常なら、ほぼ出口側の問題です。

出口が固定されているか確認する方法

リクエスト元 IP を返すエコー用 URL を用意し、プロキシを有効にした状態で連続リクエストして毎回の結果を記録します。切断・再接続してもう 1 ラウンド、さらに数時間後にもう一度測定します。3 ラウンドの結果が一致して初めて「固定」と言えます。

export ECHO_URL="エコー用URL"

# 連続リクエストして、出口 IP が変動しないか観察する
for i in 1 2 3 4 5; do
  curl -s --max-time 10 "$ECHO_URL"
  printf '\n'
  sleep 2
done
結論: 1 つだけ覚えるなら、出口 IP の安定性を覚えてください。認証・リスク判定・切り分けが収束するかどうかを決めるのはこれで、速度は安定したあとに最適化すれば間に合います。

同時接続と回線タイプの相性

同時接続の上限は 4 つの区間で決まります。ローカルのコネクションプール、プロキシクライアントの多重化設定、回線帯域と出口の NAT セッションテーブル、そして対象サービスのアカウント単位のレート制限です。どれか 1 つが飽和すると、症状はタイムアウトか接続拒否になります。1 つだけ見ていると誤判定します。

3 種類の回線の違い

回線タイプ 経路の特徴 出口の挙動 向いている呼び出し方 注意点
IEPL 専用線 通信事業者の専用線で海外に出て、公共インターネットの国際出口を通らない 出口が比較的固定され、揺らぎが小さい 長接続、ストリーミング応答、バッチ処理 コストが高めで、選べる地域は直結より少ない
中継 まず近くの中継ノードに接続し、そこから海外へ出る 出口は着地ノードで決まる 日常的な呼び出し、コストを抑えたい用途 ホップが 1 つ増え、中継ノードが混雑すると全体が遅くなる
直結 ローカルから海外ノードへ直接接続する 出口はノードによって変わる 軽いリクエスト、一時的なデバッグ 国際出口が混雑すると変動が大きい

どれを選ぶかは呼び出し方で決まり、価格で決まるものではありません。ストリーミング応答とバッチ処理は専用線を優先し、一時的なデバッグなら直結で十分です。中継は両者を分けるのに向いています。デバッグは片方、本番はもう片方という使い分けができます。

プロトコル層で見るべき 2 点

Shadowsocks、VMess、Trojan、VLESS は TCP、Hysteria2、TUIC は QUIC ベースで UDP を使います。API 呼び出しにおいて、プロトコル選択が影響するのは次の 2 点です。

  • 時刻の検証。VMess はクライアントとサーバーの時刻が一致していることに依存し、システム時刻のずれが大きいとハンドシェイクに失敗します。この失敗は回線が通じないように見えますが、実際には時刻を 1 回同期すれば解決します。
  • UDP の可用性。Hysteria2 と TUIC はパケットロスのある経路で安定しますが、ネットワークが UDP を制限していないことが前提です。オフィス網や公共ネットワークの一部は UDP を制限するため、その場合は TCP 系プロトコルに戻すしかありません。
ストリーミング応答が「まとめて返ってくる」場合、多くは回線の問題ではなく、クライアントか中間層がバッファリングしています。調査ではまず最初のバイトが届くまでの時間を見て、その後の出力が区間ごとに届くかを確認します。

タイムアウトと再試行:クライアント側の最小設定

タイムアウトは 1 つの合計値を設定すれば済むものではありません。1 回のリクエストは 4 つの区間に分けて見ます。接続確立、TLS ハンドシェイク、最初のバイトの返却、そしてストリーミング出力中のアイドル間隔です。4 つをまとめて設定すると、切り分けのときにどの区間で問題が起きたのか特定できません。

  • 接続タイムアウトは短めに設定し、つながらなければ回線を切り替えます。ぶら下げたまま待たないこと。
  • 最初のバイトのタイムアウトは TLS とリクエスト送信から最初のバイト返却までをカバーし、接続タイムアウトより緩めで構いません。
  • アイドル読み取りタイムアウトはストリーミング応答向けで、2 つの出力の間隔だけを計算し、合計時間は計算しません。
  • 合計時間はストリーミングリクエストには設定しないでください。長い回答が自分のクライアントに打ち切られます。

再試行には 3 つの条件があります。リクエストが再試行可能であること、エラーが再試行可能であること、回数に上限があることです。接続失敗、読み取りタイムアウト、429、5xx は再試行できます。400、401、403 のようなパラメータや認証のエラーは何度試しても同じで、問題を大きくするだけです。

import random
import time

import httpx

API_URL = "https://api.example.com/v1/chat"

# 4 つのタイムアウトを分けて設定:接続 5s、アイドル読み取り 60s
timeout = httpx.Timeout(connect=5.0, read=60.0, write=10.0, pool=5.0)
# コネクションプールの上限がローカル同時接続の上限を決める
limits = httpx.Limits(max_connections=16, max_keepalive_connections=8)

with httpx.Client(timeout=timeout, limits=limits) as client:
    for attempt in range(4):
        try:
            with client.stream("POST", API_URL, json={"stream": True}) as resp:
                if resp.status_code in (429, 500, 502, 503, 504):
                    raise httpx.HTTPStatusError(
                        "retryable", request=resp.request, response=resp
                    )
                resp.raise_for_status()
                for line in resp.iter_lines():
                    handle(line)  # 1 行ずつ処理し、リストに溜め込まない
            break
        except (httpx.ConnectError, httpx.ReadTimeout, httpx.HTTPStatusError):
            if attempt == 3:
                raise
            # 指数バックオフにジッターを加え、複数タスクの同時再試行を避ける
            time.sleep(min(2 ** attempt, 8) + random.uniform(0, 0.5))
ストリーミング応答で内容を出力した後に再試行すると、重複出力と二重課金が発生します。正しい方法は、出力済みの位置を記録するか、再試行の判断をリクエスト送信前に置くことです。

DNS と振り分けルールの組み合わせ方

DNS リークとは、ドメインの解決をローカル DNS に任せることで、「あなたに最も近い」ノードの IP を得てしまい、「出口に最も近い」ノードの IP を得られないことを指します。多くの API ではサーバー側が共通の入口を使うため影響は限定的ですが、地域ごとに異なる結果を返すサービスでは、解決結果がそのままリクエストの到達地域を決め、認証も失敗することがあります。

グローバルより振り分けのほうが楽

API のドメインを特定の回線グループに固定し、それ以外のトラフィックをデフォルトグループに流すと、利点は 3 つあります。出口が安定する、デバッグ時に個別に切り替えられる、1 本の回線に問題が起きても他のタスクに影響しない、です。ルールは IP ではなくドメインのサフィックスで書くほうが安定します。API の入口 IP は変わりますが、ドメインは変わらないからです。

確認の順序

  1. プロキシを有効にした状態で対象ドメインを解決し、結果を記録します。
  2. プロキシを切ってもう一度解決し、2 回の結果が同じ地域に落ちるか比較します。
  3. エコー用 URL で最終的な出口 IP を確認します。解決結果だけを見て判断しないこと。
  4. 解決結果と出口の地域が一致しない場合は、クライアントの DNS 設定と振り分けルールの優先順位を確認します。

回線選びの確認項目

ここまでの内容を 1 枚のチェックリストにまとめました。回線を選ぶときに 1 項目ずつ照らし合わせるほうが、スペック表を見るより役に立ちます。以下の 4 つの数字はサブスクリプション側でそのまま確認できる条件で、速度測定の結果ではありません。

  • 120+ 国と地域で出口を選択可能
  • 150+ 回線、ドメイン単位で振り分け可能
  • 無制限 デバイス台数、ボトルネックは出口側
  • 7 日間 理由不要の返金、1 週間の観察に十分
  • ✅ 出口 IP は固定されているか、切断・再接続しても変わらないか。
  • ✅ 出口の地域を指定できるか、切り替え可能な回線が複数あるか。
  • ✅ 長接続とストリーミング応答に対応しているか、クライアントがバッファリングしないか。
  • ✅ 1 アカウントあたりの同時接続に制限はあるか、制限はローカル側か出口側か。
  • ✅ ドメイン単位の振り分けに対応しているか、ルールを個別に調整できるか。
  • ✅ 課金方式が呼び出し量に合っているか。月払いは開通日にリセット、トラフィックパッケージは使い切るまで有効で期限なし。
  • ❌ 公称帯域だけを見て、出口の安定性を見ない。
  • ❌ デバッグ・本番・バッチ処理を同じ出口に置く。
  • ❌ 1 回のリクエストの結果で回線の良し悪しを判断する。

切り分け手順:出口 IP から同時接続まで

問題が起きたときはパラメータをいじる前に、次の順序で進めてください。各ステップに明確な判断基準があります。

  1. プロキシが効いているか確認。エコー用 URL にリクエストして、出口 IP が目的の地域かどうかを見ます。IP が変わっていなければ、以降を調べる必要はありません。
  2. DNS を確認。プロキシの内外で解決結果を比較し、リークが起きていないか判断します。
  3. システム時刻を確認。VMess などのプロトコルは時刻検証に依存し、ずれが大きいとハンドシェイクに失敗します。
  4. TLS を確認。証明書エラー、SNI の不一致、中間装置による遮断は、いずれも接続リセットとして現れます。
  5. ストリーミング出力を確認。最初のバイトまでの時間と行ごとの到達状況を見て、バッファリングされているか判断します。
  6. 同時接続を確認。低い同時数から徐々に負荷を上げ、平均値ではなくエラー種別の分布を記録します。
  7. 回線切り替えを確認。別の回線で再現するか試します。再現すればクライアントかコードの問題、再現しなければ回線の問題です。
順序が重要です。出口 IP はリクエストが正しく識別されるかを決め、DNS はリクエストがどの地域に届くかを決め、タイムアウトと再試行は失敗後に問題が拡大するかを決めます。この順で切り分けるほうが、回線を何度も替えるより時間を節約できます。

1 週間の観察記録と結論

1 回のリクエストの結果では何も分かりません。国際回線の挙動は時間帯で変動し、平日の日中・夜のピーク・深夜では 3 つの状態があります。同じリクエスト群を 1 週間記録し、毎回 5 つの項目を残すことをおすすめします。

  • 時刻と回線名
  • 出口 IP
  • 最初のバイトまでの時間
  • HTTP ステータスコードまたはエラー種別
  • 再試行が発生したか

1 週間後に見るのは平均値ではなく分布です。エラーがどの時間帯・どの回線・どの種別に集中しているかを見ます。429 が夜のピークに集中していれば出口の共有、接続タイムアウトが特定の回線に集中していればその回線の入口の混雑、TLS エラーが散発するならクライアントと中間装置を優先して調べます。

結論: AI API 呼び出しの回線選びは、固定出口が最優先、回線タイプがその次、タイムアウトと再試行が最後です。出口が安定して初めて、同時接続とタイムアウトを調整する意味が生まれます。出口が不安定なら、どのパラメータをいじっても失敗を先送りするだけです。