1.1 LLM出力制御の課題
The Challenge of LLM Output Control
LLM出力の非決定性
大規模言語モデル(LLM)は、同じプロンプトに対しても異なる出力を生成する非決定的な性質を持つ。この特性は創造的なタスクでは利点となるが、構造化されたデータを期待するアプリケーションでは深刻な問題となる。
典型的な問題パターン
1. 形式の不一致
JSONを期待してもMarkdownや自然言語で返答される場合がある。
# 期待する出力 {"name": "田中太郎", "age": 30} # 実際の出力例 名前は田中太郎で、年齢は30歳です。
2. フィールドの欠落
必須フィールドが出力されない、または異なるキー名で出力される。
# 期待する出力 {"first_name": "太郎", "last_name": "田中", "email": "tanaka@example.com"} # 実際の出力例(emailが欠落) {"first_name": "太郎", "last_name": "田中"}
3. 型の不一致
数値を期待しても文字列で返される、配列を期待しても単一値で返される等。
# 期待する出力 {"price": 1000, "tags": ["電子機器", "新品"]} # 実際の出力例 {"price": "1000円", "tags": "電子機器"}
なぜ構造化が必要か
LLMをアプリケーションに組み込む場合、その出力は下流の処理(データベース保存、API呼び出し、UI表示等)に渡される。構造化されていない出力は、これらの処理でエラーを引き起こす。
実務での影響
| 場面 | 問題 | 影響 |
|---|---|---|
| データベース保存 | 型不一致、必須フィールド欠落 | INSERT失敗、データ整合性破壊 |
| 外部API呼び出し | パラメータ形式エラー | API呼び出し失敗、リトライコスト |
| UI表示 | 想定外のデータ構造 | 表示崩れ、JavaScript例外 |
| エージェント制御 | ツール選択・引数エラー | 無限ループ、誤動作 |
従来の対処法とその限界
プロンプトエンジニアリング
「必ずJSON形式で返答してください」等の指示をプロンプトに含める方法。効果はあるが、完全な保証はできない。
後処理でのパース
LLM出力を正規表現やパーサーで解析し、構造化を試みる方法。
import json import re def parse_llm_output(text): # JSONブロックを抽出 match = re.search(r'```json\s*(.*?)\s*```', text, re.DOTALL) if match: return json.loads(match.group(1)) # 直接JSONとしてパース return json.loads(text)
この方法は脆弱で、想定外の形式には対応できない。また、パースに成功しても内容の妥当性は検証されない。
Function Calling / Tool Use
OpenAI Function CallingやAnthropicのTool Useは、LLMに構造化出力を促す仕組み。しかし、これも100%の保証はなく、返される引数の値自体の妥当性は検証されない。
求められる解決策
上記の課題を解決するため、以下の機能を持つフレームワークが求められる。
1. スキーマ定義:期待する出力構造を宣言的に定義
2. バリデーション:出力が定義に適合するか検証
3. 自動修正:検証失敗時にLLMに再生成を依頼
4. 統合性:既存のLLMフレームワークと容易に連携
Guardrails AIは、まさにこれらの要件を満たすために設計されたフレームワークである。次節では、Guardrails AIの概要と基本的なアプローチを解説する。
[1] OpenAI - Function Calling - https://platform.openai.com/docs/guides/function-calling
[2] Anthropic - Tool Use - https://docs.anthropic.com/claude/docs/tool-use