Jev Recipe / 信頼性エンジニアリング

LLM フォールバック戦略:信頼度ゲート付き 3 レーンチェーン

本番 LLM システムの信頼性設計はリトライで終わりではありません。較正済み信頼度に応じて、高信頼は自動実行、中間帯はより強力なモデルで再質問、低信頼は人手へと段階的に切り替えます。

信頼度ゲート付きフォールバックチェーン構築 6 ステップ

01まず 3 つの用語を区別する。リトライが扱うのは 5xx やタイムアウトといったインフラ障害、フォールバックが扱うのは「答えが信頼できない」状態、エスカレーションが扱うのは「取り返しがつかない結果」。この 3 つが 1 つのコードパスを共有していることが、フォールバック設計が本番で壊れる最大の原因になる。
02自動実行のゲートは較正済み信頼度だけに置く(しきい値は 0.85 以上)。信頼度 0.9 が実際の正解率約 90% に対応しない限り、しきい値は機能しない。Jev の確率は RLCD 学習で較正されており、この対応関係が成立する。
03中間帯(0.60〜0.85)を先に設計する。より強力なモデルで 1 回だけ再質問し、0.60 未満のロングテールだけを人手キューへ流す。
04判断表はアプリケーションコードに書く。リトライは転送エラーのみ(回数上限あり)、フォールバックは信頼度不足のみ、決済や削除など不可逆操作の行き先は常に人間へ。ポリシーをモデルに任せない。
05ヒステリシスとシャドーモードを用意する。判定が揺れやすい入力は中間帯を広げ、まずはログ記録のみで本番トラフィックを通し、分布を確認してから強制モードへ切り替える。
06answer・confidence・lane の 3 点をすべてのホップで記録し、四半期ごとに実データに基づいてしきい値を再較正する。勘や伝聞ではなくデータで決める。
schema / フォールバック判定コントラクト
{
  "fallback_action": {
    "type": "choice",
    "instructions": "この回答案の次のアクションを判定する",
    "criteria": {
      "execute": "高信頼度かつ可逆 — 自動実行する",
      "escalate_model": "中間帯の信頼度 — より強いモデルで再質問する",
      "human_review": "低信頼度または不可逆 — 人手へ回す"
    }
  },
  "irreversible": {
    "type": "noul",
    "instructions": "この回答を実行すると取り消しが困難か(決済・送信・削除)?"
  }
}
回答案を State として送ると、execute・escalate_model・human_review の 3 レーンへ振り分けられる様子を確認できます。

フォールバック戦略比較:信頼度ゲート付きチェーン vs リトライ・カスケード・人手のみ

METRIC
Jev
盲目リトライ / カスケード / 人手のみ
トリガーシグナル
較正済み信頼度

回答ごとの数値がそのままレーンを決める

代理指標であり信頼度ではない

リトライは 5xx とタイムアウト、カスケードは自己申告スコア、人手のみはユーザー苦情を待つ

追加レイテンシ (P50)
ゲートは約 95ms

0.60〜0.85 の中間帯だけ約 2〜4 秒の再質問を追加

複利的に増加

リトライは 2^n バックオフ、カスケードは毎ホップ再生成、人手キューは数分〜数日

コスト構造
入力のみ課金

ゲートは $0.042/M、高額モデルは中間帯だけに課金

上限なし

リトライは同じ誤答に出力トークンを再課金、カスケードはホップごとに高額単価、人手コストは両者を上回る

障害モード
フェイルセーフ

しきい値未満は人手へ流れ、誤った自動実行は起きない

うるさいか無音か

リトライは同じ誤答を繰り返し、カスケードは品質を静かに落とし、人手のみではキューがパンクする

オブザーバビリティ
判定ごとの監査ログ

全ホップが answer + confidence + lane を出力し、しきい値はコードで追跡可能

散在して見えない

障害はレイテンシのグラフや品質ドリフトの中に隠れ、判定単位の信頼度の記録が残らない

コード対照:盲目リトライ vs 信頼度ゲート付きチェーン

python / 脆弱なパターン:単一モデルの盲目リトライ
import time
import requests

def generate_with_retry(prompt: str, max_retries: int = 5) -> str:
    # 盲目的なリトライ:シグナルは転送層の健全性だけ。
    # 自信たっぷりの誤答は 1 回目で素通りし、リトライ経路には入りません。
    for attempt in range(max_retries):
        try:
            resp = requests.post(
                "https://api.vendor-llm.example/v1/chat/completions",
                json={
                    "model": "cheap-model",
                    "messages": [{"role": "user", "content": prompt}],
                },
                timeout=30,
            )
            resp.raise_for_status()
            return resp.json()["choices"][0]["message"]["content"]
        except (requests.Timeout, requests.HTTPError):
            time.sleep(2 ** attempt)  # 1秒、2秒、4秒、8秒……最大約31秒の無駄待ち

    return ""  # リトライの末、空文字で静かに失敗

# 典型パス:1500〜3000ms の生成レイテンシ、試行ごとに出力トークンを課金。
# フォーマットが正しい誤答に対する防御はゼロ。
python / Jev 信頼度ゲート付き 3 レーンフォールバックチェーン
import requests

JEV_ENDPOINT = "https://api.typesafe.ai/v1/jev/evaluate"
AUTO_EXECUTE_CONFIDENCE = 0.85  # 0.85 以上で自動実行

QUESTIONS = {
    "fallback_action": {
        "type": "choice",
        "instructions": "この回答案の次のアクションを判定する",
        "criteria": {
            "execute": "高信頼度かつ可逆 - 自動実行する",
            "escalate_model": "中間帯の信頼度 - より強いモデルで再質問する",
            "human_review": "低信頼度または不可逆 - 人手へ回す",
        },
    },
    "irreversible": {
        "type": "noul",
        "instructions": "この回答を実行すると取り消しが困難か(決済・送信・削除)?",
    },
}

def gate(state: dict) -> dict:
    # リトライする価値があるのは転送エラーだけ。
    # ゲート自体は安価でステートレスな 1 呼び出し(約 95ms、入力トークンのみ)。
    resp = requests.post(
        JEV_ENDPOINT,
        json={"state": state, "questions": QUESTIONS},
        timeout=3,
    )
    resp.raise_for_status()
    return resp.json()

def fallback_chain(task: str, answer_model, strong_model) -> dict:
    # レーン 1:低コストモデルが下書きし、較正済みゲートが判定する。
    draft = answer_model(task)
    check = gate({"task": task, "proposed_answer": draft})
    decision = check["fallback_action"]

    if (
        decision["answer"] == "execute"
        and decision["confidence"] >= AUTO_EXECUTE_CONFIDENCE
        and not check["irreversible"]["answer"]
    ):
        return {"lane": "auto_execute", "answer": draft, "confidence": decision["confidence"]}

    # レーン 2:0.60〜0.85 の中間帯 — より強いモデルで 1 回だけ再質問。
    stronger = strong_model(task)
    recheck = gate({"task": task, "proposed_answer": stronger})
    final = recheck["fallback_action"]

    if (
        final["answer"] == "execute"
        and final["confidence"] >= AUTO_EXECUTE_CONFIDENCE
    ):
        return {"lane": "escalated_model", "answer": stronger, "confidence": final["confidence"]}

    # レーン 3:エスカレーション後もしきい値未満 — 人手キューまたは安全なデフォルト値。
    return {
        "lane": "human_review",
        "answer": stronger,
        "confidence": final["confidence"],
        "first_pass_confidence": decision["confidence"],
    }

# 典型パス:ゲート呼び出し 1 回あたり約 95ms、入力トークンのみ。
# レーン 2 は例外であってデフォルトではなく、大半のトラフィックはレーン 1 を離れません。
typescript / 脆弱なパターン:盲目リトライ
async function generateWithRetry(prompt: string, maxRetries = 5) {
  // 盲目的なリトライ:ループを発火させるのは転送エラーだけ。
  // 自信たっぷりの誤答は 1 回目の試行をそのまま素通りします。
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const resp = await fetch("https://api.vendor-llm.example/v1/chat/completions", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          model: "cheap-model",
          messages: [{ role: "user", content: prompt }],
        }),
      });
      if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
      return (await resp.json()).choices[0].message.content as string;
    } catch {
      await new Promise((r) => setTimeout(r, 2 ** attempt * 1000));
    }
  }
  return ""; // 約31秒のバックオフの末に静かに失敗
}
typescript / Jev 信頼度ゲート付きフォールバックチェーン
const JEV_ENDPOINT = "https://api.typesafe.ai/v1/jev/evaluate";
const AUTO_EXECUTE_CONFIDENCE = 0.85; // 0.85 以上で自動実行

const QUESTIONS = {
  fallback_action: {
    type: "choice",
    instructions: "この回答案の次のアクションを判定する",
    criteria: {
      execute: "高信頼度かつ可逆 - 自動実行",
      escalate_model: "中間帯の信頼度 - 強いモデルで再質問",
      human_review: "低信頼度または不可逆 - 人手へ回す",
    },
  },
  irreversible: {
    type: "noul",
    instructions: "この回答を実行すると取り消しが困難か(決済・送信・削除)?",
  },
} as const;

async function gate(state: Record<string, unknown>) {
  const resp = await fetch(JEV_ENDPOINT, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ state, questions: QUESTIONS }),
  });
  if (!resp.ok) throw new Error("Jev evaluate failed");
  return (await resp.json()) as {
    fallback_action: { answer: string; confidence: number };
    irreversible: { answer: boolean };
  };
}

export async function fallbackChain(
  task: string,
  answerModel: (t: string) => Promise<string>,
  strongModel: (t: string) => Promise<string>,
) {
  // レーン 1:低コストモデルが下書きし、較正済みゲートが判定する(約 95ms、入力のみ)
  const draft = await answerModel(task);
  const first = await gate({ task, proposed_answer: draft });
  if (
    first.fallback_action.answer === "execute" &&
    first.fallback_action.confidence >= AUTO_EXECUTE_CONFIDENCE &&
    !first.irreversible.answer
  ) {
    return { lane: "auto_execute", answer: draft, confidence: first.fallback_action.confidence };
  }

  // レーン 2:0.60〜0.85 の中間帯 — より強いモデルで 1 回だけ再質問
  const stronger = await strongModel(task);
  const recheck = await gate({ task, proposed_answer: stronger });
  if (
    recheck.fallback_action.answer === "execute" &&
    recheck.fallback_action.confidence >= AUTO_EXECUTE_CONFIDENCE
  ) {
    return { lane: "escalated_model", answer: stronger, confidence: recheck.fallback_action.confidence };
  }

  // レーン 3:エスカレーション後もしきい値未満 — 人手キューまたは安全なデフォルト値
  return {
    lane: "human_review",
    answer: stronger,
    confidence: recheck.fallback_action.confidence,
    first_pass_confidence: first.fallback_action.confidence,
  };
}

信頼性設計をさらに深める