Skip to main content

まず最初に: 生のエラーはレスポンスボディにちょうど1回だけ現れます

APIYI はエラーの詳細を API レスポンスボディ内でのみ 返します。バックエンドログは 課金台帳 であり、課金が発生した呼び出しを記録します。失敗したリクエストは課金されず、そこにも記録されません。つまり、「バックエンドログに見つからない」ことは「発生していない」を意味しません。それは そのエラーの唯一の記録がクライアント側にしかない ことを意味します。もしそれを表示して永続化していなければ、完全に失われますし、こちらでも復元できません。
要点を1行で言うと: 生のレスポンスボディ を返された内容のまま正確に表示してください。プログラムが包んだ1行の文字列だけを残してはいけません。400 Bad Request のような文字列は診断にほとんど役立ちません。本当の答えは、それが捨てた JSON の中にあります。

実例: 400 Bad Request では何も分かりません

あるお客様から、たった 1 行だけの報告がありました:
その文字列は、クライアントフレームワークがエラーをラップした後に生成したものです。モデル名、HTTP メソッド、URL、ステータスコードは保持されていましたが、本当に重要な レスポンス本文 だけが捨てられていました。サポートが出せる最善の回答は次のとおりでした:
400 は通常、コンテンツ安全性かパラメータの問題のいずれかです。おそらくコンテンツ安全性です。
これは 推測 であって、結論ではありません。というのも、まったく同じ呼び出しでも、実際のレスポンス本文は次の 3 つのいずれかだった可能性があり、それぞれで取るべき対応はまったく異なるからです:
同じ 400 でも、対応は 3 通りまったく異なります。 レスポンス本文を捨てると、3 択の判断が当て推量になり、しかも誤った推測はサポートの往復メッセージと不要な再試行を招きます。さらに悪いことに、これら 3 つのケースのうち 2 つは、そもそも再試行すべきではありません。見分けられないなら、盲目的に再試行するしかなく、時間もクォータも浪費します。

バックエンドログにあるもの・ないもの

まず押さえるべき考え方は、バックエンドログはエラーログではなく、課金台帳です。
逆から読むと、接続問題を見分ける最も強力な単独テストになります: ログに課金エントリがあるなら、リクエストは上流側に到達してリソースを消費しています。ないなら、問題はほぼ確実に上流側に届く前に発生しています(ネットワーク、認証、パラメータ検証)。詳しくは ログで課金額を読む を参照してください。

保持すべき 7 つの項目

失敗した 1 回の呼び出しを診断するには、これで十分です。どれか 1 つでも欠けると、診断は推測に逆戻りします。
レスポンスボディを切り詰めないでください。 200 文字に切り詰めるのは通常の業務ログとしては妥当ですが、診断では重要な情報が末尾にあることがよくあります。少なくとも先頭の 2000 文字は残してください。画像エンドポイントで base64 がログを大量に埋めるのが心配なら、status_code >= 400 のときだけ全文を出力してください。エラーボディはたいてい短いです。

エラー捕捉パターンを正しくする

原則は実質 1 つだけです: 2 層で捕捉し、どちらの層でも何も捨てないことです。
  • トランスポート層の失敗: 接続リセット、TLS ハンドシェイク失敗、タイムアウト、DNS 失敗。HTTP 応答はまったくありません — 取得できるのは例外テキストだけです。
  • HTTP 層のエラー: サーバーが 4xx / 5xx を返しました。レスポンスボディがあり、必ずそれを読み取る必要があります。

Python / requests

ボディを読む前に raise_for_status() を呼び出さないでください。 それが送出する HTTPError に含まれるのは 400 Client Error: Bad Request for url: ... だけで、実際のメッセージは resp.text にそのまま残ったまま誰にも読まれません。これは、このページの先頭にあるケースが起きる理由の 1 つです。使う場合は、先に resp.text を取り出してください。

Python / OpenAI SDK

公式 SDK は、3 つの要素をすでに例外オブジェクトに付与しています。多くの人は代わりに、自分の 1 行メッセージを print してしまいます:
1 行でも、print(f"API error: {e}") ではなく print("request failed") にすべきです — SDK の例外の str(e) には すでにサーバーのメッセージが含まれています。実際に情報を失わせるのは、例外オブジェクト自体を完全に捨ててしまうことです。

Node.js

SDK では:
素の fetch では、ここで最もよく失敗します:
このページの先頭にある 400 Bad Request from POST https://api.apiyi.com/v1/images/edits は、文字どおり ${resp.status} ${resp.statusText} from ${resp.method} ${resp.url} です — ボディは一度も読み取られていませんfetch は HTTP 層のエラーでは reject しません。単に resp.okfalse に設定するだけです。その時点で resp.statusText を投げると、レスポンスオブジェクトと一緒にボディも破棄されます。throw する前に必ず await resp.text() してください — その 1 行が、診断可能なレポートと答えの出ないレポートの違いです。

cURL で再現する

誰かに問題の再現を依頼するとき、このコマンドが最小の手間です — ステータスコード、ヘッダー、ボディ、時間を一度に表示します:
  • -i はレスポンスヘッダーを表示します。x-request-id があるのはそこです;
  • -sS は進行状況バーを隠しつつ、エラー出力は保持します;
  • -w はステータスコードと合計時間を付け足します。タイムアウト設定との比較に便利です。

ラッパーと社内ゲートウェイ

よい例

このエラーは、お客様の ComfyUI ノードから発生しました:
400 Bad Request よりはるかに見た目は悪いですが、それでも 完全 なので、原因の方向性は数秒で絞れます: 結論はすぐに分かります。これは トランスポート層 の問題であり、コンテンツの安全性やパラメータとは無関係で、課金も発生しません(リクエストが最後まで完了していないためです)。トラブルシューティングの手順: 画像 API の接続切断
2 つを比べてください: 一方はきれいにまとまっているのに何も説明しておらず(400 Bad Request)、もう一方は長くて見苦しいものの、根本原因をまっすぐ示しています(errno 10054)。診断では、手を加えて整えた親切なエラーより、むき出しで見苦しくても完全なエラーのほうが常に優れています。

一般的なツールで生の出力を見つける場所

社内ゲートウェイの 3 つのルール

1

そのまま通し、決して書き換えない

中間層はコンテキスト(どのサービスか、どのテナントか、何回目のリトライか)を 追記 しても構いませんが、上流の error.message置き換えてはなりません。一度書き換えると、元の情報を取り戻せる場所はもうありません。
2

ユーザー向けメッセージを生のものと分けて保存する

Gemini の画像エラー処理 で使われている 3 要素構成に従ってください: userMessage(エンドユーザー向けの親しみやすい文面)、devMessage(開発者向けの分類)、rawResponse(変更せずそのままのレスポンス本文)です。最初の 2 つは自由に整え、3 つ目はそのまま保存してください。
3

不明なエラーを返さない

フォールバック分岐では、statusx-request-id、そして本文の最初の 2000 文字を記録してください。元のテキストを含む「未分類エラー」なら診断できますが、きれいな「不明なエラー」では診断できません。

診断を不可能にするアンチパターン

  • except Exception as e: print("request failed") — 例外オブジェクトが失われ、どのレイヤーが失敗したのかもわかりません。
  • status code だけを記録して body を記録しないこと — まさにこのページの冒頭のケースです。
  • 先に resp.text を読まずに raise_for_status() を呼び出すこと — メッセージはまだメモリ上にあるのに、取得されないままです。
  • if (!resp.ok) throw new Error(resp.statusText) in fetch — body は response オブジェクトと一緒に破棄されます。
  • 成功した再試行のあとにきれいな 200 だけを残すこと — 各試行を個別にログしてください。そうしないと、transport が何回失敗したのか見えず、自分の再試行を gateway の挙動と誤認するおそれがあります。
  • stdout のみにログを出す、または毎日上書きでローテーションすること — 顧客が問題を報告するころには、元の記録はたいてい流れ去っています。
  • 画面の写真をスマホで撮って問題を報告すること — 代わりに テキスト を貼り付けてください。スクリーンショットではエラーの 1 行の半分がよく切れてしまいます。

サポートに連絡すべき場合

まずは上記のキャプチャと解釈の手順を進めてください。以下のいずれかに当てはまる場合は、資料をサポートにお送りください。
  • 完全なレスポンス本文と error.message が上流に示されている(upstream_error、生の上流 5xx、または明示的なチャネルエラー);
  • 同じリクエストパラメータが別のモデルや別の時刻では動作し、特定の 1 つのモデルだけが一貫して失敗する;
  • エラーが 500 + write_response_body_failed、または同様の下流配信失敗であり、しかも一貫して再現する(これらは課金されません。接続の切断 を参照してください);
  • 実際の呼び出し内容と課金が一致していないと疑っており、正確に突き合わせられる唯一の手がかりが request_id である。

サポートチケットのテンプレート(コピー&ペースト)

WeCom サポート

WeCom サポートの QR コードコードを読み取るか、このカードをクリックして WeCom サポートにアクセスしてください。Telegram は @apiyi001、またはメールアドレス [email protected] でもご連絡いただけます。
上のテンプレートを テキストとして 送信してください — 説明を書くよりはるかに効率的です。request_id があれば、その 1 回の呼び出しの完全なトレースにすぐ進めます。『だいたい何時で、どのモデルですか?』と聞き返す必要はありません。request_id の検索方法は ログクエリ API をご覧ください。

関連ドキュメント

API マニュアル

よくあるエラーコード、認証、レート制限

接続切断

ECONNRESETerrno 10054、SSL EOF に関する完全なトラブルシューティング手順

ログ照会 API

API で呼び出しログを取得する — request_idの確認方法と課金の照合方法

課金額の読み方

失敗した呼び出しがログに残らない理由と、それを診断テストとして使う方法

タイムアウト設定

モデル種別ごとのタイムアウト階層と、延長しても改善しない場合に確認する点

画像 API の基本

同期呼び出し、base64 プレフィックスの違い、400 invalid_image_file の前処理