0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

reasoning_effortの接続先別の失敗と3つの対処

0
Posted at

📝 この記事は forge.workstyle.tech に掲載した記事の転載です。

OpenAI互換の/v1/chat/completionsを使い、OpenAIのクラウドと自前のvLLMなどを切り替えています。ブラウザ操作が1回も実行されず、画面上ではモデルがツールを呼ばないように見えました。原因はreasoning_effortの扱いが接続先ごとに異なり、400の本文を読まずにツール非対応と判定していたことでした。

① クラウドは、ツールと設定の組み合わせを断っていた

最初の断りは、ツールとreasoning_effortを同時に送ったときでした。接続先はgpt-5.6-lunaです。返ってきたのは400で、本文にはこうありました。

Function tools with reasoning_effort are not supported for gpt-5.6-luna in /v1/chat/completions. To use function tools, use /v1/responses or set reasoning_effort to 'none'.

アプリは本文のtoolとnot supportedを見て、「このモデルはツールに非対応」と覚えていました。以後、ツールそのものをリクエストから外します。

これが冒頭の誤判定です。ブラウザ操作の依頼をしても、ツールを渡していないので操作は1回も実行されません。モデルがツールを呼ばないように見えても、そもそも呼ぶ機会がありませんでした。

本文には直し方も書いてありました。ツールを使うなら/v1/responsesを使うか、reasoning_effortを'none'にする。アプリはその部分を読まず、ツールを捨てる判定を先にしていたのです。

直し方は、断りをツールの対応可否として覚える前に読むことでした。ツールを持ったままreasoning_effort: 'none'を付けて、1回やり直します。代わりに、このモデルがツールを使う回は考えるのを止めます。

速さだけを見れば、この変更には魅力がありました。reasoning_effort: 'none'にすると、会話は入力から発話の始まりまで4〜5秒だったものが2秒になりました。

② 自前のvLLMは、値そのものを断った

同じ設定をツール呼び出しの経路にも入れると、自前のvLLMから400が返りました。

Input should be 'low', 'medium' or 'high'

エラーの種類はliteral_errorです。欄が存在しないのではなく、noneという値を受け付けないという断りでした。

会話だけを試していると、ここで止まります。会話は速くなるので、うまくいったように見えました。ツール呼び出しの経路を試して初めて、接続先によって値の受け取り方が違うと分かりました。

この接続先では、値の拒否を見分けて、以後reasoning_effortを送りません。思考を切るために、代わりにchat_template_kwargs: { enable_thinking: false }を送ります。

実測値も並べてみました。

思考の切り方 所要時間
何もしない 87秒
/no_think 154秒
enable_thinking=false 65秒
reasoning_effort none 2秒

数字は端末内のモデルで測ったものです。方法ごとの差は大きく、設定名が似ているだけで動作まで揃うわけではありませんでした。

そのうえ、思考の出力欄も揃っていません。名前はreasoning、reasoning_content、thinkingの3つです。落とす側は3つを知っていたのに、読む側はreasoningしか見ていませんでした。

vLLMはreasoning_contentに入れます。自前のvLLMへ切り替えたとたん、分類は5回連続で「応答が空」になりました。モデルは思考を返していたのに、読む側がその欄を見ていなかったので、思考の量すら数えられなかったのです。

③ 中継サーバーは、逐次のときだけ空を返した

3つ目は、逐次応答の扱いでした。

stream: trueを無視して普通のJSONを返す接続先がありました。アプリはdata:で始まらない行を全部捨てます。結果として切れ端は0個でした。response.okなので例外も出ず、画面には何も出ません。

ここはcontent-typeを見て、逐次応答か普通のJSONかを判定するように直しました。

それで終わりではありません。ある中継サーバーの接続先は、逐次のときだけ200を94msで返し、中身は空でした。逐次でない呼び出しは400で断ります。空の応答には、エラー本文のような手掛かりがありません。

この接続先では、空を例外にします。思考の指定を外して1回やり直し、それ以降は送らないようにしました。指定を外して送り直すと、327msで正しいJSONが返りました。

接続先ごとの実測を表にした

接続先の挙動 送るもの
普通(OpenAIなど) reasoning_effort: 'none'
値の一覧にnoneがない(vLLM、400で断る) chat_template_kwargs: { enable_thinking: false }
逐次で空を返す(中継サーバー) 何も送らない

どれも実測で決めました。利用者には訊きません。接続先側の応答を見て、アプリが覚えます。

自分で入れた回帰

接続先ごとの違いを覚えさせる修正を入れたあと、今度は私が別の失敗を作りました。

回帰1:400の本文を広く読みすぎた

値の拒否を見分ける判定が、invalidという文字も見ていました。本文にinvalid_request_errorが入る400なら、どのエラーでも同じ扱いになります。

その結果、gpt-5.6-lunaを「reasoning_effortの欄が無い接続先」と覚えてしまいました。①で必要だったnone指定まで止まり、ブラウザ操作が丸ごと落ちました。

原因は、試験用のエラー文面を私が作っていたことです。実際の断りと少し違えば、判定のずれを見つけられません。記録に残した本文を、そのまま試験に使うようにしました。

回帰2:自前向けの設定をクラウドにも送った

声の会話では、思考を自動で切る設定を入れていました。その設定を公式のクラウドにも効かせたため、OpenAIにnoneを送るようになりました。

返事を作る依頼だけが毎回60秒止まり、500になりました。4回とも同じでした。OpenAIの稼働状況は「正常」でした。

コードには元から、「クラウドのプロバイダは同じ鍵を別の意味で使う」と書いてありました。接続先ごとの意味を見ずに、共通の設定として広げてしまったのは私です。自前の提供者だけに効かせるように直しました。

別件として、断られた欄を外してやり直す仕組みにも穴がありました。1回の発話につき、やり直しは1回だけです。temperatureとfrequency_penaltyの両方を断られると、1回目の発話が黙りました。

断られるたびに外してやり直すようにしました。8回で止めます。1文字でも返した後に落ちた場合は、やり直しません。

まとめ

  • 400の本文を読み、実際の断りを試験に使う。
  • 接続先ごとにreasoning_effortの値の受け取り方を確認する。
  • vLLMでnoneが拒否されたら、chat_template_kwargs: { enable_thinking: false }を使う。
  • 逐次応答が空なら思考の指定を外して再試行し、その接続先では以後送らない。
  • 接続先ごとの実測に合わせて設定を適用する。

関連記事


元記事: https://forge.workstyle.tech/blog/openai-compatible-reasoning-effort/?utm_source=qiita&utm_medium=crosspost&utm_campaign=openai-compatible-reasoning-effort

0
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?