本番環境でのOpenRouter運用:何が壊れ、何が機能し、何を改善すべきか

LLMが最初のプロンプトに応答するのは簡単です。

本番環境で信頼性を保つには?

そこからが本当に面白いところです。

ソフトウェア開発で学んだことの一つは、ユーザーは何かが壊れたときに「誰の責任か」に関心がないということです。

AI機能がOpenAIのダウンで失敗しても…

Claudeがレート制限になっても…

ネットワーク接続が5秒間切断されても…

ユーザーはプロバイダーを責めません。

あなたのアプリケーションを責めます。

だからこそ、AIモデルを統合するだけでは不十分なのです。

障害に備えた設計が必要です。どう実現するかについて話しましょう。

エラーハンドリングは退屈であるべき

OpenRouterの最大の利点の一つは、異なるプロバイダーからのレスポンスを標準化することです。

これがないと、プロバイダー固有のエラーハンドリングを書くことになることが多いです。

```typescript
if (provider === “openai”) {
…
}

if (provider === “anthropic”) {
…
}

if (provider === “google”) {
…
}
```

Enter fullscreen mode Exit fullscreen mode

これはすぐに複雑になります。

OpenRouterを使えば、アプリケーションは1つのAPI契約と通信します。

つまり、エラーハンドリングが予測可能になります。

```typescript
try {
const response = await client.chat.completions.create({…});
} catch (error) {
logger.error(error);
}
```

Enter fullscreen mode Exit fullscreen mode

シンプルです。

あなたのコードはどのプロバイダーが失敗したかを気にするべきではありません。

失敗したときにアプリケーションがどう応答するかを気にするべきです。

ログは最良の友

よく見かける間違いは、開発者がエラーメッセージだけをログに記録することです。

それだけではほとんど不十分です。

本番環境で何かが失敗したとき、あなたはコンテキストを求めます。

次のようなものをログに記録しましょう:

  • 使用されたモデル。
  • リクエストの所要時間。
  • トークン使用量。
  • HTTPステータスコード。
  • フォールバックモデルがトリガーされたかどうか。
  • リクエストを行ったユーザー(適切な場合)。
  • タイムスタンプ。

1つのログエントリで、問題を再現しなくても完全なストーリーがわかるようにするべきです。

未来のあなたは感謝するでしょう。

オブザーバビリティは推測に勝る

顧客から次のような報告を受けたと想像してください:

「AIが午後2時頃に動かなくなった。」

オブザーバビリティがなければ、あなたは推測することになります。

バックエンドの問題か?

OpenRouterの問題か?

Claudeの問題か?

Geminiの問題か?

タイムアウトか?

デプロイメントか?

良いオブザーバビリティは推測の必要性を排除します。

OpenRouterはリクエスト追跡を提供し、アプリケーションからのリクエストとダッシュボード内で起きたことを紐づけることができます。

何がどこで間違ったのかを何時間も考える代わりに、バックエンドログからゲートウェイまで、単一のリクエストを追跡できます。

実際のユーザーが関わるようになったら、これは非常に価値があります。

リクエストIDは過小評価されている

すべてのリクエストにアイデンティティを持たせるべきです。

OpenRouterのリクエスト識別子を使うにせよ、自分で相関IDを生成するにせよ、常にログに含めるようにしましょう。

あるユーザーが次のように報告したと想像してください:

私のレスポンスが届かなかった。

リクエストIDがなければ、数千のログエントリを検索することになります。

リクエストIDがあれば、すぐに以下を追跡できます:

  • リクエストがいつ開始されたか、
  • どのモデルが処理したか、
  • 再試行が発生したかどうか、
  • フォールバックがトリガーされたかどうか、
  • そしてすべてにかかった時間。

本番環境のデバッグが劇的に容易になります。

ストリーミングは見た目ほど単純ではない

ストリーミングレスポンスはAIアプリケーションをはるかに高速に感じさせます。

完全な回答を10秒待つのではなく、ユーザーはほぼ即座に単語が表示され始めます。

優れたユーザー体験です。

しかし、注意点があります。

すべてのプロバイダーが全く同じ方法でレスポンスをストリーミングするわけではありません。

OpenRouterはこの動作の多くを正規化していますが、それでも耐障害性のあるフロントエンドパーサーを構築したいと思うでしょう。

すべてのチャンクが完璧に到着すると仮定しないでください。

すべてのイベントにテキストが含まれていると仮定しないでください。

接続が予期せず終了しないと仮定しないでください。

フロントエンドは、レスポンスの途中でクラッシュするのではなく、不完全なストリームを優雅に処理するべきです。

タイムアウトはオプションではない

ユーザー体験を悪化させる最も簡単な方法の一つは、リクエストを永遠にハングさせることです。

プロバイダーが遅くなることもあります。

ネットワークが不安定になることもあります。

単に何かが壊れることもあります。

あなたのアプリケーションは、いつ待つのをやめるべきかを知るべきです。

合理的なタイムアウト制限を設定しましょう。

リクエストが長くなりすぎたらキャンセルしましょう。

ユーザーは、無限のローディングスピナーを眺めるより、15秒後に役立つメッセージを受け取ることを望むでしょう。

いつ諦めるかを知っているアプリケーションは、永遠に待つアプリケーションよりも高速に感じられることが多いです。

優雅な劣化

これは私のお気に入りのエンジニアリング原則の一つです。

1つのプロバイダーが失敗したからといって、機能が完全に消える必要はありません。

主要な推論モデルが利用できなくなったと想像してください。

次のように返す代わりに:

何かがうまくいきませんでした。

より高速なフォールバックモデルに自動的に切り替えてみてはどうでしょうか?

回答がそれほど詳細ではないかもしれません。

創造性に欠けるかもしれません。

しかし、ユーザーはまだレスポンスを受け取れます。

それはエラースクリーンよりも無限に優れています。

優雅な劣化は完璧さについてではありません。

完璧さが不可能なときに機能を維持することです。

再試行を慎重に構築する

再試行は単純に聞こえます。

そうでないときまで。

一時的なネットワークの問題でリクエストが失敗した場合、再試行は理にかなっています。

プロバイダーが過負荷で失敗した場合、短い遅延後に再試行すればうまくいくかもしれません。

しかし、リクエストが無効な場合は?

それを5回再試行しても、悪い入力を魔法のように修正することはありません。

良い再試行戦略は次のようになるべきです:

  • 一時的な失敗のみを再試行する。
  • 即時再試行ではなく指数バックオフを使用する。
  • 最大再試行回数を設定する。
  • すべての再試行試行をログに記録する。
  • 成功の見込みが低い場合は再試行を停止する。

再試行は信頼性を向上させるべきです。

トラフィックを増やすべきではありません。

最後の考察

AI開発で学んだことの一つは、適切なモデルを選択することは、信頼性の高いアプリケーションを構築する上での一部に過ぎないということです。

本当の課題は、モデルが遅くなったり、プロバイダーが利用できなくなったり、ネットワークが不安定になったりしたときに、引き続き動作するシステムを設計することです。

なぜなら、結局…

何かが失敗するからです。

問題はそれが起こるかどうかではありません。

問題はユーザーが気づくかどうかです。

気づかなければ、おそらくシステムはうまく構築されているでしょう。

このシリーズを楽しんでいますか?コメントでお知らせください。

ああ、次のシリーズを見逃さないように、私とつながるのを忘れないでください。