LLM の非決定性を前提に設計する#
LLM の出力は本質的に非決定的。同じ入力でも呼び出しごとに違う結果が返る。これを「バグ」として扱うと無限に消耗する。前提として受け入れ、その上で設計するのが実用的な態度。
非決定性の源#
flowchart LR
I[同じ入力] --> M[LLM]
M --> O1[出力 A]
M --> O2[出力 B]
M --> O3[出力 C]
S[Temperature > 0] --> M
V[モデル内部の揺らぎ] --> M
D[プロバイダーのアップデート] --> M
- サンプリング: Temperature・top-p・top-k による確率的選択
- モデル内部の微小な揺らぎ: ハードウェアやタイミング依存
- プロバイダー側の変更: ファインチューニング・モデル差し替え
Temperature=0 にしても完全な決定性は得られない。
戦ってはいけない戦い#
以下を「完全に解決」しようとすると失敗する。
- 「毎回必ず同じ回答が欲しい」
- 「絶対にハルシネーションさせない」
- 「同じプロンプトで 100% 同じフォーマット」
受け入れて設計する方向に発想転換する。
設計方針#
1. 決定論的な層でサンドイッチする
flowchart LR
I[入力] --> V1[前処理<br/>決定論的]
V1 --> L[LLM<br/>非決定的]
L --> V2[検証<br/>決定論的]
V2 -->|OK| O[出力]
V2 -->|NG| R[リトライ or フォールバック]
LLM を「非決定的な中間処理」として扱い、前後を決定論的ロジックで挟む。
- 前処理: 入力の正規化、バリデーション、コンテキスト組み立て
- LLM: 推論・生成・判断
- 検証: 出力のスキーマチェック、禁止語チェック、事実確認
2. 複数回実行してサンプリング
重要な判断は、同じプロンプトで複数回実行し、多数決や統計的手法で集約する。
results = [llm.generate(prompt) for _ in range(5)]
# 多数決、平均、最頻値 等で集約
- 信頼性が上がる
- コストは N 倍
3. 評価セットで期待値を測る
決定論を求めず、期待値(平均スコア)を指標にする。個別の失敗を気にせず、分布の改善を目指す。
4. フォールバックを用意する
LLM が期待した出力を返さなかったときの代替パスを必ず持つ。
- デフォルト値を返す
- ルールベースのフォールバック
- ユーザーに再試行を促す
得られる考え方の転換#
| Before | After |
|---|---|
| 「バグを直す」 | 「統計的に改善する」 |
| 「100% を目指す」 | 「95% を狙い、5% はフォールバック」 |
| 「単発の成功」 | 「評価セット平均の向上」 |
| 「絶対に失敗させない」 | 「失敗しても影響を最小化」 |
アンチパターン#
- 決定論を要求する仕様書: 仕様側で「100% 正確」「同じ出力」を要求すると、実装が破綻する
- 単発の成功で満足する: 1 回動いたから OK と判断。評価セットで再現性を測らない
- 失敗時のフォールバックなし: 失敗すれば即エラー。リカバリー経路を作らない
- エラーログを残さない: 非決定的な失敗は再現が難しい。全量ログが必要
関連する概念#
- EDD(Eval-Driven Development): 評価セットで期待値を測る
- プロンプトインジェクション: 非決定性を悪用する攻撃
- コンテキストは有限で劣化する資源: 長いセッションほど非決定性が増す
まとめ#
LLM の非決定性は治すべきバグではなく、前提条件。前提として受け入れ、決定論的ロジックで挟む・統計的に改善する・フォールバックを用意する、の 3 点で設計する。