InstructorRetryExceptionの解決方法:LLMがスキーマに従わない場合の対処法

intermediate🧠 AI Tools2026-06-28| Python 3.9+, Instructor library, Pydantic v2, OpenAI/Anthropic/Gemini API

Error Message

instructor.exceptions.InstructorRetryException: Failed to extract data after 3 retries
#instructor#openai#pydantic#llm#python#debugging

エラーの理解このエラーは、Instructorライブラリからの「降参」のサインだと考えてください。これは、LLMがPydanticモデルに適合するレスポンスを生成しようと何度も試みたものの、最終的にすべて失敗したことを意味します。デフォルトでは、Instructorは3回の試行の後に諦めます。この例外が発生した場合、コード自体のバグであることは稀です。むしろ、スキーマとLLMの出力が根本的に一致していないというシグナルです。

instructor.exceptions.InstructorRetryException: Failed to extract data after 3 retries

なぜ抽出に失敗するのかモデルが怠けていると決めつけないでください。ほとんどの失敗は、指示が厳格すぎるか、データが乱雑であるために起こります。主な原因は以下の通りです:

  • 深くネストされたスキーマ: GPT-3.5-TurboやClaude Haikuのような小型モデルは、オブジェクトが3階層以上にネストされると構造を見失うことがよくあります。- 正規表現のボトルネック: マイナンバーや電話番号などで pattern=r"^\d{3}-\d{2}-\d{4}$" のような厳格なパターンを使用すると、LLMが余分なスペースやプレフィックスを1つ追加しただけで失敗の原因になります。- 曖昧さ: フィールド名が説明なしに type とされている場合、Enumが 'ADMIN_USER' を期待しているのに、モデルは 'User' と推測してしまうかもしれません。- トークン制限による中断: 非常に長いレスポンスの場合、モデルが4,096トークンの制限に達し、JSONの途中で停止してパース不能な文字列が残ることがあります。## 解決策1:フィールドの説明でモデルに地図を与えるLLMは読心術師ではありません。彼らはPydanticモデル内のメタデータに大きく依存しています。フィールドに description を追加することで、生のテキストを構造にマッピングするために必要なコンテキストをモデルに提供できます。

問題点:曖昧なスキーマ```

from pydantic import BaseModel

class UserInfo(BaseModel): name: str age: int status: str # モデルはここで「status」が何を意味するかわかりません


### 解決策:コンテキストが豊富なスキーマ```
from pydantic import BaseModel, Field

class UserInfo(BaseModel):
    name: str = Field(description="フルネーム。各単語の最初の文字を大文字にしてください。")
    age: int = Field(description="年齢。正の整数である必要があります。")
    status: str = Field(description="現在の雇用形態:'Employed'、'Unemployed'、または 'Student' から選択してください。")

解決策2:バリデーション制約を緩和する厳格なバリデーションはデータベースには最適ですが、LLMには困難です。特定の電話番号形式を要求し、LLMが 5551234567 ではなく (555) 123-4567 を出力した場合、バリデーションは失敗します。まずは生の文字列としてデータを抽出し、その後、Pydanticの @field_validator や別のPython関数でクリーンアップすることを検討してください。

# 生の抽出ではこれを避ける
# phone: str = Field(pattern=r"^\+\d{10,15}$") 

# 代わりにこれを使用する
phone: str = Field(description="テキストに表示されている通りの電話番号。")

解決策3:モデルのアップグレードとリトライ回数の増加複雑なタスクに小型モデルを使用している場合、スキーマに従うための「推論」能力が不足している可能性があります。GPT-3.5からGPT-4oにアップグレードするだけで、リトライの問題が即座に解決することがよくあります。また、max_retries を増やすことで、モデルが自らの間違いを修正する機会を増やすこともできます。

import instructor
from openai import OpenAI

# from_openai を使用したモダンな Instructor 構文
client = instructor.from_openai(OpenAI())

try:
    user = client.chat.completions.create(
        model="gpt-4o", # GPT-4o は GPT-3.5 よりも複雑な JSON の扱いに優れています
        response_model=UserInfo,
        max_retries=5,  # JSONを正しく生成するために5回のチャンスを与える
        messages=[{"role": "user", "content": "次のデータから抽出してください:Johnは30歳で、現在は学生です。"}]
    )
except instructor.exceptions.InstructorRetryException as e:
    # 失敗した理由を確認するために生の出力を調査する
    print(f"最後の試行の出力: {e.last_completion}")

解決策4:Chain of Thought (CoT) の実装複雑さは精度の敵です。「思考(explanation)」フィールドを追加することで、最終的なJSONを書き出す前に、モデルに抽出プロセスの論理的な思考を強制させることができます。この単純なステップにより、複雑なタスクにおけるバリデーションエラーを30%以上削減できる場合があります。

class ComplexExtraction(BaseModel):
    explanation: str = Field(description="データを見つけた方法のステップバイステップの論理。")
    data_points: list[str]
    confidence: float

デバッグのヒントリトライループから抜け出せないときは、推測をやめましょう。InstructorRetryException をキャッチし、e.last_completion 属性をログに記録してください。これにより、LLMが実際に何を生成したかを正確に確認できます。多くの場合、プロンプトを少し調整するだけで修正できる小さな、繰り返されるフォーマットエラーが見つかります。また、ソーステキストに含まれていることが100%保証されていないフィールドには、Optional 型を使用することも検討してください。

Related Error Notes