エラーについて
LangChainを使って1時間以上開発したことがあるなら、おそらくこの苛立たしい壁に突き当たったことがあるでしょう。きれいなPythonオブジェクトを期待しているのに、コンソールには次のような赤いエラーメッセージが延々と表示されます。
OutputParserException: Could not parse LLM output
これは、大規模言語モデル(LLM)が「おしゃべり」になったときに発生します。生のJSONオブジェクトを返す代わりに、パーサーが処理方法を知らない会話的なつなぎ言葉や、マークダウンのコードブロックでデータを包んでしまうことがあるのです。
発生原因
LLMは、厳格なデータ出力機ではなく、役立つアシスタントとして設計されています。「JSONのみ」と明示的に要求しても、モデルは予測可能な形で失敗することがよくあります。Llama 3を用いたテストでは、厳格なプロンプトがない場合、モデルが有効なJSONを返せない確率が約15〜20%にのぼることがわかりました。主な原因は以下の通りです:
- 会話的な接頭辞: 「もちろんです!ご要望のデータはこちらです: { ... }」
- マークダウン形式: レスポンスを
json ...ブロックで囲んでしまう。 - 構文エラー: 末尾のカンマの欠落や、文字列内でのダブルクォートのエスケープ漏れ。
ステップバイステップの解決策
1. 生の出力を確認する
目に見えないものは修正できません。プロンプトを微調整し始める前に、LLMが返した正確な文字列をキャプチャしましょう。チェーンをtry-exceptブロックで囲み、原因を特定します。
from langchain_core.exceptions import OutputParserException
try:
response = chain.invoke({"input": "Get user data for John Doe"})
except OutputParserException as e:
# これにより、LLMが実際に出力した内容を確認できます
print(f"The LLM sent this back: {e.llm_output}")
raise e
e.llm_output を確認することは、モデルがスキーマをハルシネーション(捏造)しているのか、それとも単に不要なマークダウンタグを追加しているだけなのかを判断する最短の方法です。
2. フォーマット指示を注入する
LLMに構造を推測させてはいけません。LangChainのパーサーには、モデル向けの正確な技術的要件を生成するメソッドが組み込まれています。プロンプトにこれらが含まれていない場合、モデルは実質的に目隠し状態で動いているようなものです。
from langchain.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field
class UserInfo(BaseModel):
name: str = Field(description="User's name")
age: int = Field(description="User's age")
parser = PydanticOutputParser(pydantic_object=UserInfo)
# parser.get_format_instructions() は特定のJSONスキーマを生成します
prompt = PromptTemplate(
template="Answer the query.\n{format_instructions}\n{query}\n",
input_variables=["query"],
partial_variables={"format_instructions": parser.get_format_instructions()},
)
3. JsonOutputParserに切り替える
PydanticOutputParser は厳格です。LLMがバッククォート(```)を含めると、失敗することがよくあります。この壁にぶつかっている場合は、JsonOutputParser に切り替えてください。これは大幅に堅牢で、通常、クラッシュすることなくマークダウンの囲いを自動的に取り除くことができます。
from langchain_core.output_parsers import JsonOutputParser
# このパーサーは、マークダウンブロックに対してより「寛容」です
parser = JsonOutputParser(pydantic_object=UserInfo)
chain = prompt | model | parser
4. OutputFixingParserを実装する
時として、LLMは閉じ括弧 } を忘れるといった、修正可能な小さなミスを犯します。アプリケーションをクラッシュさせる代わりに、OutputFixingParser を使用しましょう。これはエラーをキャッチし、不正な出力を修正指示と共にLLMに送り返します。APIコールが1回増えますが、成功率を100%に近づけることができます。
from langchain.output_parsers import OutputFixingParser
from langchain_openai import ChatOpenAI
# 最初のパスが失敗した場合、GPT-4に送り返して構文を修正させます
new_parser = OutputFixingParser.from_llm(parser=parser, llm=ChatOpenAI(model="gpt-4o"))
結果の検証
曖昧なクエリでチェーンをテストしてください。修正が成功していれば、モデルが余計なテキストを追加して親切心を見せようとしても、きれいなPydanticオブジェクトが返ってくるはずです。
result = chain.invoke({"query": "I'm John, and I just turned 30!"})
print(type(result))
# 期待値: <class '__main__.UserInfo'>
print(result.age)
# 期待値: 30
本番環境向けのプロのアドバイス
- Temperatureを0に固定する: 構造化データが必要なタスクでは、常に
temperature=0に設定してください。0.7程度まで少し上げるだけでも、小規模なモデルではフォーマットのハルシネーションが30%増加することがあります。 - ネイティブのTool Callingを使用する: OpenAI、Anthropic、またはGeminiを使用している場合は、手動のパーサーの使用をやめ、
model.with_structured_output(UserInfo)を使用してください。これはモデル固有のTool Calling用APIを利用するため、テキストベースのパースよりもはるかに信頼性が高いです。 - JSONを検証する: 手動でプロンプトを作成する場合は、JSON Formatter & Validator のようなツールを使用してください。Few-shotの例に、LLMを混乱させるような隠れた構文エラーがないか確認するのに役立ちます。
- Few-Shotプロンプティング: Llama 3 (8B) や Mistral のような小規模モデルの場合、プロンプトに期待されるJSONの例を2つ含めてください。これは、どのパーサー設定よりも効果的な場合が多いです。

