0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

gpt-4o-mini から gpt-5-mini に差し替えたら本番が静かに壊れかけた話 — Chat Completions の 3 つの非互換

0
Posted at

何が起きたか

本番で LLM-as-a-judge の採点パイプラインを回しています。判定モデルを gpt-4o-mini から gpt-5-mini に差し替えることにしました。価格は同じ($0.15 / $0.60 per 1M トークン)で判定品質だけ上がる、コスト中立のアップグレードのはずでした。

モデル名を書き換えて、ユニットテストは全部緑。デプロイしました。

その直後に手が止まりました。テストは全部モックで書いてあります。実 API がパラメータを拒否するケースは、一件も検証していない。慌てて実 API に投げて確認したところ、全リクエストが 400 で落ちる状態でした。次の定期実行が来る前にロールバックして事なきを得ましたが、あと数十分遅ければ本番の採点が全部止まっていました。

gpt-5 系(と o 系)は Chat Completions の互換モデルではありません。ハマった非互換は 3 つです。

非互換 1: max_tokens が使えない

gpt-5 系に max_tokens を渡すと 400 が返ります。

Unsupported parameter: 'max_tokens' is not supported with this model.
Use 'max_completion_tokens' instead.

max_completion_tokens に置き換えれば通ります。機械的な置換で済むので、これは一番軽傷です。

非互換 2: temperature が既定値以外を受け付けない

判定の再現性のために temperature: 0 を渡していました。これも 400 です。

Unsupported value: 'temperature' does not support 0 with this model.
Only the default (1) value is supported.

gpt-5 系は temperature の指定自体を捨てる必要があります。「judge は temperature 0 で固定」という定石が使えなくなるので、判定のブレは実測で確認するしかありません(後述しますが、実測では実用上問題ありませんでした)。

非互換 3(最重要): reasoning トークンが出力予算を食い尽くす

これが一番怖い挙動でした。gpt-5 系は応答の前に内部で reasoning トークンを消費します。このトークンは max_completion_tokens の予算から引かれ、課金対象にもなります

つまり max_completion_tokens: 100 のような小さい上限で JSON を返させようとすると、reasoning が予算を使い切り、HTTP 200 なのに content が空文字で返ってきます。finish_reasonlength です。

このパターンの何が怖いかというと、エラーにならないことです。うちでは別の監視系で response_format: { type: "json_object" } を使っていて、空文字が JSON.parse に渡って例外になり、「判定不能」という中間的な状態として現れました。400 なら気付きますが、200 + 空応答は「モデルの調子が悪い」ように見えて原因究明が遅れます。

対処は 2 つの合わせ技です。

  • max_completion_tokens を余裕を持って確保する(体感で従来の 3〜5 倍。うちは空応答になっていた箇所を 2000 にして解消しました)
  • 深い推論が不要な用途では reasoning_effort: "minimal" を渡して reasoning の消費自体を抑える

対処: パラメータの出し分けをヘルパー 1 本に集約する

呼び出し箇所ごとに if を書くと必ず漏れます。うちは移行時にヘルパー 1 本に集約しました。

/** モデル世代に応じた Chat Completions パラメータを返す */
export function chatCompletionParams(
  model: string,
  opts: { maxOutputTokens: number; temperature?: number },
): Record<string, unknown> {
  const isReasoningFamily = /^(gpt-5|o\d)/.test(model);
  const params: Record<string, unknown> = {
    // max_tokens は gpt-5 系で 400 になるため、常に新パラメータ側に寄せる
    max_completion_tokens: opts.maxOutputTokens,
  };
  if (!isReasoningFamily && opts.temperature !== undefined) {
    params.temperature = opts.temperature;
  }
  if (/^gpt-5/.test(model)) {
    params.reasoning_effort = "minimal";
  }
  return params;
}

呼び出し側は ...chatCompletionParams(model, { maxOutputTokens: 2000, temperature: 0 }) を spread するだけです。モデル名を将来また差し替えても、呼び出し箇所を触らずに済みます。

実際このヘルパーは移行の翌日、別の監視系(gpt-5-mini + response_format: json_object で空応答になっていた箇所)にもそのまま適用して直せました。集約した意味が 1 日で回収できた形です。

移行後に起きる「良い変化」にも注意

差し替え後の初回実行で、採点結果の分布が変わりました。gpt-4o-mini はほぼ全項目に満点を付けていたのに対し、gpt-5-mini は項目ごとに差を付けてきます(例: 正確性 3.5、簡潔さ 5)。

judge が厳しくなるのは望ましい変化ですが、スコアの基線が入れ替わるので、移行直後は品質スコアが下がって見えます。ダッシュボードやアラートの閾値を旧基線のままにしていると偽アラートが出ます。移行日をメモしておいて、数日〜1 週間は基線の入れ替わり期間として読む必要があります。

チェックリスト

gpt-4o 系から gpt-5 系への差し替えでやることをまとめます。

  1. max_tokensmax_completion_tokens に全置換
  2. temperature 指定を gpt-5 系では出さない(出すと 400)
  3. max_completion_tokens を従来の 3〜5 倍に増やす(reasoning が予算を食う)
  4. 深い推論が不要なら reasoning_effort: "minimal"
  5. response_format: json_object を使っている箇所は「200 + 空 content」のハンドリングを確認
  6. パラメータの出し分けはヘルパー 1 本に集約(呼び出し箇所ごとの if は漏れる)
  7. モックのテストだけで判断せず、実 API に 1 本投げてから deploy する
  8. 移行日を記録し、スコア基線の入れ替わり期間(数日〜1 週間)を想定する

7 番が本質です。今回の障害は、ユニットテストが全部緑のままデプロイして起きました。パラメータの互換性は実 API しか教えてくれません。

まとめ

gpt-5-mini は gpt-4o-mini と同価格で判定品質が上がる、移行する価値のあるモデルです。ただし Chat Completions のパラメータ互換性はなく、「モデル名の書き換えだけ」で移行すると 400 か、より怖い「200 + 空応答」で静かに壊れます。ヘルパー集約と実 API での事前検証をセットでやれば、移行自体は半日で終わります。


筆者は LLM API の呼び出しを記録・監視する SaaS を個人開発しており、本記事の障害と対処はその本番環境で実際に起きたものです。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?