LLMの「無限空白ループ」を叩き潰す:Structured Output運用におけるJSONパースエラー回避の決定打

Structured Outputを使用しているにもかかわらず、稀に数万文字の空白が返されバリデーションエラーが発生する事態は、本番環境において致命的な問題だ。

この現象はモデルのトークン生成挙動とパース処理の乖離に起因しており、適切なトークン制御とリトライロジックの組み込みによる解決が最適解となる。

技術の本質:なぜ「構造化」していても壊れるのか

OpenAIのStructured Output機能はJSONスキーマへの準拠を保証するが、内部的にはロジットバイアスや文法制御を用いた制限に過ぎない。gpt-4o-miniのような軽量モデルでは、稀に終了トークン(EOS)を生成できず、代わりに空白や改行トークンを無限に生成し続ける「ハルシネーション的なループ」に陥る。

これはスキーマ定義の問題ではなく、推論エンジンのエッジケースだ。筆者の経験上、この現象はリクエストが集中する時間帯や、入力プロンプトがスキーマの境界条件に近い場合に発生しやすい傾向にある。

定量的メリット:コストと信頼性の両立

この問題に対処することで、以下のメリットを確実に享受すべきだ。

  • 無駄なトークン消費の根絶: 異常発生時に数万トークンの空白を生成する前に処理を遮断し、APIコストを99%以上削減する。
  • レスポンス速度の安定化: タイムアウト設定とmax_tokensの最適化により、異常レスポンスを即座に検知し、平均レイテンシのスパイクを防止する。

現場での注意点とハマりどころ

temperature=0に設定していても、この現象を完全には防げない点が極めて厄介だ。特にLangChainのwith_structured_outputを使用している場合、背後で発生しているfinish_reason: length(トークン不足による中断)やパースエラーが抽象化され、原因特定が遅れるリスクがある。

実際の運用現場では、単純なプロンプト調整に頼るのではなく、「インフラ側でのガードレール」の実装を最優先すべきだ。

堅牢な実装パターンの提示

以下のコードでは、max_tokensによる物理的制限と、異常なレスポンスを検知してリトライするガードレールを実装している。


from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field

# 1. 出力トークンを厳密に制限し、異常な空白生成を物理的に防ぐ
llm = ChatOpenAI(
    model="gpt-4o-mini",
    temperature=0,
    max_tokens=500,  # 必要なJSONサイズに対して余裕を持たせた最小値を設定
    timeout=10
)

# 2. 構造化出力の設定
routing_llm = llm.with_structured_output(RouteDecision)

def robust_invoke(messages):
    for attempt in range(3):
        try:
            return routing_llm.invoke(messages)
        except Exception as e:
            # 空白ループによるパースエラーやEOFエラーをキャッチしてリトライ
            if "EOF" in str(e) or "Invalid JSON" in str(e):
                continue
            raise e

結論

Structured Outputは強力だが、過信は禁物だ。

軽量モデル特有の挙動に対する防御策として、max_tokensによる物理的な制限と、パースエラーを前提とした例外処理を組み合わせること。これが、本番環境で安定したLLMアプリケーションを運用するための唯一の道である。

タイトルとURLをコピーしました