最小のcurlで「アプリの問題」か「鍵・ネットワークの問題」かを分ける
最初にやることは、SDKもフレームワークもフレームワークの設定ファイルも通さない、最小のリクエストを1回投げることだ。これが通るかどうかで、調べる範囲が半分に減る。Claude(Anthropic API)なら次の形になる。
curl -sS -i https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{"model":"MODEL_ID","max_tokens":16,"messages":[{"role":"user","content":"ping"}]}'OpenAI互換のエンドポイント(OpenAI本体や、同じ形式を名乗る各種サービス)なら認証ヘッダとパスが変わる。
curl -sS -i https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "content-type: application/json" \
-d '{"model":"MODEL_ID","messages":[{"role":"user","content":"ping"}]}'MODEL_IDは必ず公式ドキュメントの一覧からコピーする。モデルIDは追加も終了も速く、記憶やブログの記述は当てにならない。-iを付けているのはレスポンスヘッダを表示するためで、ステータス行とリクエストID(Anthropicはrequest-id、OpenAI互換はx-request-id)は後の問い合わせで使うので控えておく。
ここで分岐する。curlが200を返すのにコードだけ失敗するなら、原因はアプリ側にある。鍵の読み込み経路、base_urlの設定、リトライ処理、レスポンスのパースを疑う。curlも失敗するなら、鍵・モデルID・ネットワークのいずれかだ。以降はステータスコード別に見ていく。
| 症状 | 最初に見る場所 |
|---|---|
| 401 / 403 | 環境変数の値と、鍵の所属プロジェクト |
| 404 | エラー本文(パスの404かモデルの404か) |
| 429 / 529 | retry-afterヘッダと、自分の並列度 |
| 接続できない・タイムアウト | curl -v がどの段階で止まるか |
| 200だが空・途中で切れる | stop_reason / finish_reason |
401と403は別の問題。前者は鍵が読めていない、後者は鍵の持ち主が違う
401は認証そのものの失敗で、鍵が間違っているか、そもそも送られていない。まずecho -n "$ANTHROPIC_API_KEY" | wc -cで長さを見る。0なら環境変数が渡っていない。よくあるのは、.envファイルは自動では読まれないという点だ。python-dotenvやdirenvのような仕組みを入れない限り、ファイルに書いただけでは何も起きない。ターミナルでexportしたケースも、効くのはそのシェルだけで、IDEから起動したプロセス、systemd、cron、Dockerコンテナはそれぞれ別の環境を持つ。変数名の綴りも定番の落とし穴で、SDKが読む変数名は公式ドキュメントで確認する。コピー時に末尾の改行や空白が混ざるとヘッダごと壊れるため、鍵の先頭数文字と末尾数文字だけを表示して確かめるとよい。
403は認証は通ったが権限がない状態で、鍵そのものは正しい。別のプロジェクトやワークスペース、別組織で作った鍵を使っている、鍵に利用可能モデルやIP範囲の制限がかかっている、といった原因が多い。コンソールで鍵の所属を確認し、必要ならそのプロジェクト用に作り直す。
curlで401が出るなら鍵の問題で確定する。curlは通るのにコードだけ401なら、コードが別の鍵を読んでいる。古い環境変数が残っている、テスト用の値がハードコードされている、クライアントを初期化した後に環境変数を書き換えている、のいずれかを探す。
404は「モデルIDの誤り」と「パスの誤り」を先に見分ける
404にはまったく別の原因が2つあるため、エラー本文を読むところから始める。モデルが見つからない旨のメッセージならモデルIDの問題、パスやルートが見つからない旨ならURLの問題だ。後者で多いのはbase_urlの二重指定で、末尾に/v1を含むURLを設定した上でSDKがさらに/v1を付ける形になっている。この場合、鍵もモデルIDも正しいのに404が返り続ける。
モデルIDの問題なら、原因は誤記・提供終了・提供経路の差の3つに分かれる。誤記はハイフンや日付サフィックスの取り違えが中心だ。提供終了は、以前動いていたコードが突然404になる典型で、モデルには非推奨から提供停止までの期限があり、期限を過ぎると呼べなくなる。提供経路の差は見落としやすい。同じモデルでも、API直叩き、Amazon Bedrock、Google Cloud Vertex AIではIDの形式が異なり、さらにリージョンによって使えるモデルが違う。東京リージョンで動かないIDが別リージョンでは動く、という状況は普通に起きる。
確実な確認手段は、モデル一覧を返すエンドポイント(Anthropic・OpenAIともにGET /v1/models)を同じ鍵で叩くことだ。ここに出てこないIDは、綴りの問題ではなく、そのアカウントやリージョンから使えないということになる。
429と529は待ち方が違う
429は自分の利用がレート上限を超えた状態で、1分あたりのリクエスト数、1分あたりの入出力トークン数、同時実行数のいずれかに当たっている。529はサーバー側が一時的に過負荷という意味で、こちらの使い方とは関係がない。前者は自分で減らせるが、後者は待つ以外にない。
どちらも即座に再送してはいけない。レスポンスのretry-afterヘッダがあればその秒数に従い、無ければ指数バックオフに乱数の揺らぎ(ジッター)を加える。揺らぎが要るのは、複数のワーカーが同じ秒数だけ待つと同時に再突入して同じ失敗を繰り返すためだ。Anthropic APIはanthropic-ratelimit-で始まるヘッダで残量とリセット時刻を返すので、どの上限に当たっているかはそこで分かる。
単発のcurlは通るのにアプリでだけ429が出るなら、原因は並列度かリトライの暴走だ。バッチ処理の同時実行数を下げ、リトライ回数に上限を設ける。単発でも429が出るならプラン側の上限に達している。529が数分続く場合は自分のコードを疑うより先にステータスページを見る。なお、400や401などの4xxはリトライしても永久に直らないので、リトライ対象は429と5xx・529に限定する。
接続できない・タイムアウトはプロキシとSSL証明書を疑う
この系統はステータスコードすら返らないので、curl -vでどの段階まで進んだかを見る。名前解決で止まるのか、TCP接続で止まるのか、TLSハンドシェイクで失敗するのかで原因が違う。
企業ネットワークでよくあるのは3つだ。407が返るならプロキシ認証が必要で、HTTPS_PROXYとNO_PROXYを設定する(環境変数は大文字と小文字の両方が参照されうる点に注意する)。証明書検証の失敗を示すエラー、たとえばローカルの発行者証明書が見つからないという内容が出るなら、通信を復号して検査するプロキシが間に入っている。この場合は社内のルート証明書を信頼させるのが正しい対処で、PythonならSSL_CERT_FILEやREQUESTS_CA_BUNDLE、Node.jsならNODE_EXTRA_CA_CERTSで指定する。証明書検証を無効化する回避策は通信を平文同然にするので使わない。3つめは単純なファイアウォールのブロックで、宛先ドメインが許可リストにない場合だ。
切り分けの近道は、テザリングなど社内ネットワーク以外から同じcurlを叩くことだ。そこで通るなら原因はネットワーク側にあると確定し、以降は情報システム部門に渡せる話になる。なお、通常のリクエストは通るのにストリーミングだけ途中で止まる場合は、プロキシがレスポンスをバッファリングしている可能性が高い。長い出力でタイムアウトするだけなら、クライアントの読み取りタイムアウトを延ばすか、ストリーミングに切り替えれば解決することが多い。
200なのに出力が空・途中で切れる
この症状はエラーではないので、まずレスポンスのstop_reason(OpenAI互換ではfinish_reason)を確認する。ここが上限到達を示す値なら、単に出力トークンの上限に達しただけで、異常ではない。Anthropic APIではmax_tokensが必須で、小さい値のまま放置していると途中で打ち切られる。拡張思考を有効にしている場合は思考部分も同じ予算を消費するため、見かけの出力が短くなる。
stop_sequencesを指定しているなら、それが入力や出力の早い位置に一致していないか確認する。ツール利用で終わった場合は本文のテキストブロックが存在しないので、レスポンスの先頭要素をテキストとして取り出す実装だと空文字になる。Anthropic APIのcontentは配列で、先頭がテキストとは限らない。
ストリーミングで空になる場合は、読み落としがほぼすべてだ。イベントを最後まで読み切らずに接続を閉じている、差分イベントの連結を取りこぼしている、途中で流れてくるエラーイベントを無視している、のいずれかである。curlで同じリクエストを非ストリーミングで叩き、生のJSONに中身があるなら、問題はアプリのパース側にあると判断してよい。
それでも直らないときの次の手
ここまでで切り分かない場合は、順に手を広げる。鍵が疑わしければ新しい鍵を発行して差し替え、古い鍵は失効させる。環境が疑わしければ別のマシンや別のネットワークで再現するか確かめ、再現しないなら差分(プロキシ、証明書、ランタイムのバージョン)を比べる。広範囲で失敗しているなら公式ステータスページ(status.anthropic.com、status.openai.com など)を確認する。
問い合わせる段階になったら、リクエストID、発生時刻(UTCで書く)、ステータスコード、エラー本文の全文、使用したモデルIDとリージョン、再現する最小のリクエスト内容を添える。リクエストIDがあるとサーバー側のログを直接引けるため、解決までの時間が大きく変わる。APIキーそのものは絶対に貼らない。
次に何を仕込んでおくか
同じ調査を二度やらないために、コード側に3つ入れておくとよい。起動時に一度だけ最小リクエストを投げて鍵と疎通を確認すること、リトライ対象を429と5xx・529に限って4xxは即座に失敗させること、そして失敗時にステータスコードとリクエストIDをログへ残すことだ。この3つがあれば、次に「動かない」と言われたときの一次切り分けはログを見るだけで済む。
判断の目安として、最小のcurlが通れば原因は自分のコードの中にあり、通らなければ鍵かネットワークか提供側にある。この一線を最初に引くかどうかで、調査時間が数分と数時間に分かれる。