はじめに
LangChain v1でエージェントを構築する際、create_agent 関数の引数で response_format がサポートされました。Pydanticモデルを渡すだけで出力を構造化してくれる便利な機能ですが、「出力の形を整える」以上の影響をエージェントの思考プロセス(ツール呼び出しの挙動)に与えているようです。
知らずに、response_format を使ってAIエージェントが思い通りに動作せず解析に苦労したのでココにメモを残しておく。
検証の結果、response_format でエビデンスを回答に含めるように指定すると、エージェントがエビデンスの検証ツールを 「明らかに積極的に」 呼び出すようになることが分かりました。
結論:何が変わるのか?
特定の「エビデンス(根拠)」フィールドを含むスキーマを response_format に設定した場合と、設定せずにプロンプトだけで指示した場合を比較したサマリーがこちらです。
| 設定 |
validate ツール呼び出し率 |
|---|---|
response_format あり(Pydantic等) |
100% |
response_format なし(プロンプト指示) |
40% |
※ OpenAIの ProviderStrategy と ToolStrategy いずれにおいても同様の傾向が見られました。
つまり、response_format を使うとエージェントは「出力を埋めるために必要な情報を集めなければならない」という強い拘束力を受け、結果として 情報の正確性が向上する という副作用(副次的メリット)があるのです。
1. response_format の3つの戦略
まずは内部的な仕組みを整理します。create_agent で使用される response_format には、主に以下の戦略が存在します。
| 戦略 | 説明 | 主なユースケース |
|---|---|---|
None |
構造化なし。ReActパターンでループ | 自由な探索が必要な場合 |
ToolStrategy |
スキーマを「ツール」として登録し、それを呼んだら終了 | 構造化出力が必要な場合 |
ProviderStrategy |
OpenAIのNative Structured Outputs等を使用 | 精度の高い構造化出力 |
AutoStrategy |
モデルに応じて自動選択(gpt-4o系はNative優先) | 手軽に実装したい場合 |
2. なぜ「真面目」にツールを呼ぶようになるのか?
コードを読み解くと、大きな要因が3つあることが分かりました。
2.1 構造化スキーマが「ツール」として定義される
response_format を指定すると、背後ではPydanticモデルがJSON Schemaに変換され、一つの「ツール」としてLLMに渡されます。
# Pydanticモデルがツールとして定義されるプロセス
return cls(
schema=schema_spec.schema,
tool=StructuredTool(
args_schema=schema_spec.json_schema,
name=schema_spec.name,
description=schema_spec.description,
),
)
2.2 tool_choice="required" の強制
ここが重要なポイントです。response_format がある場合、LangChainは内部的に tool_choice="any"(OpenAI APIでは "required")を強制します。
これにより、LLMは 「最終的に必ず構造化出力ツールを呼ばなければならない」 という制約下で推論を開始します。
2.3 終了条件の違い
-
なしの場合: LLMが「もうツールを呼ぶ必要がない」と判断(
tool_callsが空)したら終了 - ありの場合: 「構造化出力ツールを呼び出した」 時点で終了
この「出口戦略」の違いが、ループの執着心に直結するようです
3. LLMの認識はどう変わるのか(比較図)
LLMが受け取る情報の差を可視化してみます
LLMの内部動作は不明であるため挙動からの推測になります
response_format ありの場合
エージェントは「ゴール(必須ツール)」から逆算して行動します。
response_format なしの場合
「できればやってね」というプロンプト指示は、しばしば無視されたり、ツールを使わずに適当な回答(ハルシネーション)で済まされたりします。
4. 検証実験:四則演算エージェントでの実演
この挙動を証明するために、複雑な四則演算をステップ実行し、最後に「検証ツール」を呼ぶかどうかをテストしました
検証内容
- タスク: 暗算が難しい複雑な計算(10ケース)
- 指示: 「検証してください」という明示的な言葉は含めない
-
スキーマ:
validation_resultフィールドを定義
検証結果
| 設定 |
validate 呼び出し回数 |
|---|---|
| ProviderStrategy (with_validation) | 10回 / 10回 |
| ToolStrategy (with_validation) | 10回 / 10回 |
| なし (プロンプトのみ) | 4回 / 10回 |
結果は一目瞭然です。response_format を指定した場合は、100%の確率で検証ツールを実行してから回答を生成しました。
一方、指定しない場合は「計算だけして満足してしまい、検証をスキップする」ケースが目立ちました。
5. 実装のアドバイス
この特性を踏まえると、用途に合わせて以下のように使い分けるのがベストです。
徹底的に情報を収集・検証させたい場合
構造化出力スキーマに、具体的な型と説明(description)を持ったエビデンスフィールドを定義しましょう。
class AnalysisResult(BaseModel):
is_anomaly: bool
# 具体的な説明を書くことで、LLMが「ツールを使わなきゃ」と思いやすくなる
evidence: List[str] = Field(description="ログから引用した具体的な証拠リスト")
validation_status: str = Field(description="検証ツールの実行結果")
自由な探索(ReAct)を優先したい場合
あえて response_format を指定せず、エージェントに終了タイミングを委ねる方が、余計なツール呼び出し(コストと時間の増大)を抑えられる場合があります。
まとめ
LangChain v1 の response_format は、単なる出力のパース機能ではなく、LLMの推論に「責任」を持たせるための強力な拘束具として機能します。
エージェントがなかなかツールを使ってくれない、あるいは詰めが甘い と感じている場合には response_format で厳格なスキーマを定義するのが良いですが、 毎回検証なんかされたら動作が遅くなってこまる 場合には response_format を使わないほうが良い場合もあるようです。