Jev Recipe / 棄答と誠実な境界

Jev で棄答を実装:正解が「わからない」のとき

目の前の state から答えられない質問には、どの信頼度しきい値も効きません。型付き Choice に is_answerable Noul と missing_information Choice を組み合わせ、欠けている情報を名指す構造化された「わからない」を返させ、自動化を回答可能性と信頼度の 2 軸でゲートします。

広告

棄答コントラクトを構築する 6 ステップ

01ゲートを書く前に、2 つの失敗モードに名前を付けます。「確信できない」は較正の問題です。モデルは証拠を見たが確信が持てない——ラベル付きデータで較正した信頼度しきい値が解決します。「答えられない」は証拠の問題です。state にそもそも答えがなく、しきい値では救えません——注文 #9012 がいつ出荷されるかを尋ね、注文明細だけを渡して在庫も配送業者データも渡さなければ、モデルはそれでももっともらしい日付を返します。第三者監査は、答えを知り得ない質問に 30% 超の信頼度を報告する判断モデルをすでに捕捉しており、このページの公開前日に発表された 5 モデルの独立評価も同じパターンを確認しました。ローカルの小型モデルは、どれも state だけでは答えられない質問に少なくとも 1 つ回答したのです。棄答は 2 つ目の失敗モードへの型付き回答です。コントラクト自体が「この質問にはここに答えがない」と言います。
02棄答コントラクトを書く:answer はクリーンに保ち、質問を 2 つ足します。既存の Choice に「unknown」選択肢を足したくなりますが——やめてください。選択肢分布が汚染され(すべての実際の回答がキャッチオールの選択肢と競合する)、シグナルは帰属不能になり(後から「証拠不足」と「criteria の書き方の問題」を区別できない)、調整すべきレバーも生まれません。answer Choice は実際の結果だけに絞り、is_answerable を Noul として(「state は、ちょうど 1 つの選択肢を選ぶのに十分な情報を含んでいますか——追加データではなく、推測でもなく?」)、missing_information を Choice として(欠けがちな情報の種類:日付、金額、identity、ポリシールール、別システムのライブ state)加えます。1 回の evaluate 呼び出しが 3 つすべてを返します。出される答え、state がそもそも回答を支えられるか、しない場合に何を集めるべきか。
03まず回答可能性でゲートし、次に信頼度でゲートする。2 つの数字は別の問いに答えるため、1 つのしきい値に合してはいけません。信頼度が較正ラインを下回ればレビューへ——較正の判断です。is_answerable がラインを下回れば棄答——証拠の判断です。危険な象限は高信頼度 × 低回答可能性です。配送業者データのない state が出す 0.88 の出荷日。信頼度だけのゲートは、まさにこの行を自動実行します。is_answerable がゲートを下回ったら、答えがどれほど良く見ても棄答し、型付きオブジェクトを返します。abstained true、missing_information キー、回答可能性の読み取り値。ルールは 5 行のコードですが、それが「わからない」と「信頼度スコア付きのハルシネーション」の違いです。
04棄答を実行可能にする。行き止まりの棄答は丁寧なエラーにすぎません。コントラクトが欠けている情報を名指すため、棄答が仕事を振り分けます。日付の欠落は注文システムへ、ポリシールールの欠落はコンプライアンス wiki へ、identity の欠落は確認メールへ——信頼度ゲート付きフォールバックチェーンが不確実な回答に適用するのと同じ「まさにこれを集める」パターンを、確実性ではなく証拠に向けたものです。実務では missing_information をルーティングキーとして扱います。ハッシュし、カウントし、棄答率の上位カテゴリがそのままデータモデルの TODO リストになります。自動化を妨げる頻度の順に。
05答えられない state で棄答レーンをテストする。自分の state が明確に答えられない質問のセット——未来の結果、収集していないフィールド、別システムのライブ値——を通常のラベル付きセットと並べて用意し、2 つの誤り方向を別々に測定します。答えられる質問への棄答は人の時間を浪費し、答えられない質問への回答はハルシネーションを生み、両者はトレードオフします。このページが引用する独立評価は 18 件のカスタマーメッセージを 5 つの判断モデルに通しました。5 モデルとも 12 件の明確な依頼はほぼ完璧でしたが、棄答こそが差全体でした。ホスト型 Jev は 6 件の回答不能ケースすべてで棄答し、ローカル小型モデルのほとんどは推測で押し切りました(Clef Flash 9B は 6 件中 1 件のみ棄答)。回答可能性しきい値の調整は、信頼度しきい値とまったく同じです。自分の state での実測コストから決めるのであって、感覚からではありません。
06棄答を第一級の判断として記録する。「わからない」が次に起きることを変えた瞬間——人がタスクを受け取り、顧客にメールが届き、チケットがキューに入る——それは判断であり、監査証跡に他と同じフィールドで記録されるべきです。コントラクトバージョン、回答可能性と信頼度の読み取り、レーン、missing_information キー。棄答レコードは時間が経つほど活きます。来四半期に欠落データが state に追加されたら、今四半期の棄答を新しいコントラクトでリプレイすれば、そのデータ追加がどのケースを解決したかが正確に分かります。そしてスキーマ変更後の棄答率の上昇はリグレッションではありません。新しい質問が、答えを支えられない state に届いていることをコントラクトが告げているのです。
schema / 回答・回答可能性・欠落情報の契約
{
  "answer": {
    "type": "choice",
    "instructions": "state に含まれる情報だけを使って答える",
    "criteria": {
      "billing": "支払い・請求・返金",
      "technical": "製品の不具合",
      "sales": "購入・アップグレードの相談"
    }
  },
  "is_answerable": {
    "type": "noul",
    "instructions": "state は、ちょうど 1 つの選択肢を選ぶのに十分な情報を含んでいますか——追加データではなく、推測でもなく?"
  },
  "missing_information": {
    "type": "choice",
    "instructions": "is_answerable が no の場合:欠けているのはどの 1 つですか?",
    "criteria": {
      "none": "state は十分",
      "a_date": "日付または期限",
      "an_amount": "金額または残高",
      "an_identity": "人物またはアカウントの特定",
      "a_policy_rule": "適用されるポリシーまたはバージョン",
      "external_state": "別システムのライブデータ"
    }
  }
}
答えが state に存在しない質問を送ると、is_answerable が下がっても信頼度は高いままという挙動を確認できます。

6 つの質問、1 つの回答可能性ゲート

以下の各質問は、棄答コントラクト(型付き回答・較正済み信頼度・is_answerable Noul)付きで evaluate エンドポイントを通過済みです。回答可能性ゲートを動かし、レーンの再計算を確認してください。危険な行は自信のある行です。注文 #9012 は、state には存在し得ない日付に信頼度 0.88 を示しています。信頼度は「どれだけ確信しているか」であって、「答えが state に存在するか」ではないのです。

回答 · 3棄答 · 31 · 信頼度だけなら自動実行されていた回答
質問と state回答と信頼度is_answerableレーン

注文 #5521 はポリシー v3 で返金対象ですか?

q_101 · 注文明細 + 返品期間の日付 + ポリシーイベント

対象0.960.98回答

どのチームの案件か:billing / technical / sales?

q_102 · 話題が明確なチケット本文一式

billing0.970.95回答

この注文の前に顧客は住所を変更しましたか?

q_103 · タイムスタンプ付き住所が 2 つあるプロファイル

true0.940.91回答

注文 #9012 はいつ出荷されますか?

q_104 · 注文明細のみ——在庫なし、配送業者 SLA なし

2026-10-140.880.31棄答信頼度だけなら自動実行されていた回答

欠落:在庫状態 + 配送業者 SLA テーブル

アカウント #4183 はなぜ解約したのですか?

q_105 · 利用メトリクスのみ——調査も会話もなし

価格感応度0.710.22棄答

欠落:顧客の直接発言——メトリクスは何が起きたかを示すだけで「なぜ」は示さない

契約 #77 は次四半期に更新されますか?

q_106 · 利用トレンド——更新結果は未来の情報

true0.660.41棄答

欠落:結果はまだ存在しない——あるのは意図のシグナルだけ

デモデータのみ——6 件の模擬質問。レーンはレシピ本文と同じ回答可能性ルールで再計算されます。

棄答の挙動:型付きコントラクト vs プロンプトで「わからない」vs 信頼度だけのゲート

METRIC
Jev
プロンプトで「わからない」 / 信頼度のみのゲート
「わからない」の置き場所
コントラクト内の Noul——is_answerable はゲート対象かどうかにかかわらず、毎回の呼び出しで確率を返す
モデルが言うかどうかも分からない一文。散文を解析し、拒否が現れることを祈る
棄答が持ち帰るもの
欠落を型付きで:missing_information が集めるべき日付・金額・identity・ポリシールールを名指す
構造のない拒否——フォローアップの質問は、人が文言から推測するしかない
調整できるか
回答可能性しきい値は他のゲートと同じ:誤棄答率とハルシネーション率をラベル付きセットで実測できる
プロンプト調整(「…のときだけわからないと言え」)は拒否挙動を予測不能に動かし、次のバッチでまた失敗する
自信のあるハルシネーション
信頼度が高くても回答可能性の軸が捕捉する——2 つの読み取りは独立してゲートする
何も捕捉できない:流暢な誤答と流暢な正答は、第 2 のシグナルなしでは見分けがつかない
その後何が起きるか
missing_information はルーティングキー——ワークフローがまさにその情報を収集し、棄答は判断として記録される
チャットログの行き止まりの拒否:ルーティングするフィールドも、しきい値も、リプレイするものもない
独立エビデンス
5 モデルの独立評価(2026 年 10 月、n=18・探索的)で全体 18/18、回答不能 6 ケースすべてで棄答。RLCD 較正済み信頼度と公開 ECE 方法論
LLM の棄答は双方向に較正が狂う——AbstentionBench が存在すること自体、プロンプトされた拒否が回答可能性に追従しない証拠

実装コード:棄答コントラクトの TypeScript / Python

python / ベースライン:「わからない」と頼み込むプロンプト
import os

import requests

resp = requests.post(
    "https://api.openai.com/v1/chat/completions",
    headers={"Authorization": f"Bearer {os.environ['OPENAI_API_KEY']}"},
    json={
        "model": "gpt-4o-mini",
        "messages": [{
            "role": "user",
            "content": (
                "注文 #9012 はいつ出荷されますか?以下のデータのみ使用してください。"
                "データに答えがない場合は、一字一句違わず I DON'T KNOW と返してください\n"
                "データ:{'lines': [...], 'channel': 'web'}"
            ),
        }],
    },
    timeout=30,
)
text = resp.json()["choices"][0]["message"]["content"]

if "I DON'T KNOW" in text:
    ...

# 見えない失敗:言い方を「遅くともいつまでに」に変えたり、口調を柔らかくしたり、
# データを長く貼ったりすると、同じ state が「10 月 14 日に出荷」と返してきます——
# 流暢で、反証不可能で、根拠のある答えと区別がつきません。ゲートしていたのは散文で、
# 散文にはしきい値がありません。
python / jev:棄答コントラクトとゲート
import requests

JEV_ENDPOINT = "https://api.typesafe.ai/v1/jev/evaluate"
ANSWERABILITY_GATE = 0.60  # is_answerable がこれ未満 -> 棄答
AUTO_CONFIDENCE = 0.85     # 回答の信頼度がこれ未満 -> レビュー

QUESTIONS = {
    "answer": {
        "type": "choice",
        "instructions": "state に含まれる情報だけを使って答える",
        "criteria": {
            "billing": "支払い・請求・返金",
            "technical": "製品の不具合",
            "sales": "購入・アップグレードの相談",
        },
    },
    "is_answerable": {
        "type": "noul",
        "instructions": "state は、ちょうど 1 つの選択肢を選ぶのに十分な情報を含んでいますか——追加データではなく、推測でもなく?",
    },
    "missing_information": {
        "type": "choice",
        "instructions": "is_answerable が no の場合:欠けているのはどの 1 つですか?",
        "criteria": {
            "none": "state は十分",
            "a_date": "日付または期限",
            "an_amount": "金額または残高",
            "an_identity": "人物またはアカウントの特定",
            "a_policy_rule": "適用されるポリシーまたはバージョン",
            "external_state": "別システムのライブデータ",
        },
    },
}

def decide_or_abstain(state: dict) -> dict:
    resp = requests.post(
        JEV_ENDPOINT,
        json={"state": state, "questions": QUESTIONS},
        timeout=5,
    )
    resp.raise_for_status()
    data = resp.json()

    knows = data["is_answerable"]
    if not knows["answer"] or knows["confidence"] < ANSWERABILITY_GATE:
        # 型付き棄答:ワークフローは欠落キーでルーティングし、
        # このオブジェクトは記録・集計・リプレイ可能です。
        return {
            "abstained": True,
            "missing": data["missing_information"]["answer"],
            "is_answerable": knows["confidence"],
        }

    answer = data["answer"]
    lane = "auto" if answer["confidence"] >= AUTO_CONFIDENCE else "review"
    return {
        "abstained": False,
        "answer": answer["answer"],
        "confidence": answer["confidence"],
        "lane": lane,
    }
typescript / ベースライン:散文の拒否を解析
import OpenAI from "openai";

const openai = new OpenAI();

export async function whenDoesItShip(orderData: object) {
  const resp = await openai.chat.completions.create({
    model: "gpt-4o-mini",
    messages: [
      {
        role: "user",
        content: `この注文はいつ出荷されますか?以下のデータのみ使用。データに答えがない場合は、一字一句違わず I DON'T KNOW と返してください\nデータ:${JSON.stringify(orderData)}`,
      },
    ],
  });
  const text = resp.choices[0].message.content ?? "";

  // ゲートしているのは散文です。質問の言い換え、柔らかい口調、長いデータ貼り付けで
  // 拒否挙動は変わります——そしてこの戻り値の型には、根拠のある日付と
  // 自信のある推測を区別するものが何もありません。
  return { text, refused: text.includes("I DON'T KNOW") };
}
typescript / jev:型付き棄答オブジェクト
const JEV_ENDPOINT = "https://api.typesafe.ai/v1/jev/evaluate";
const ANSWERABILITY_GATE = 0.6; // is_answerable が未満 -> 棄答
const AUTO_CONFIDENCE = 0.85; // 回答の信頼度が未満 -> レビュー

const QUESTIONS = {
  answer: {
    type: "choice",
    instructions: "state に含まれる情報だけを使って答える",
    criteria: {
      billing: "支払い・請求・返金",
      technical: "製品の不具合",
      sales: "購入・アップグレードの相談",
    },
  },
  is_answerable: {
    type: "noul",
    instructions: "state は、ちょうど 1 つの選択肢を選ぶのに十分な情報を含んでいますか——追加データではなく、推測でもなく?",
  },
  missing_information: {
    type: "choice",
    instructions: "is_answerable が no の場合:欠けているのはどの 1 つですか?",
    criteria: {
      none: "state は十分",
      a_date: "日付または期限",
      an_amount: "金額または残高",
      an_identity: "人物またはアカウントの特定",
      a_policy_rule: "適用されるポリシーまたはバージョン",
      external_state: "別システムのライブデータ",
    },
  },
} as const;

export type Outcome =
  | { abstained: true; missing: string; is_answerable: number }
  | {
      abstained: false;
      answer: string;
      confidence: number;
      lane: "auto" | "review";
    };

export async function decideOrAbstain(state: object): Promise<Outcome> {
  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");
  const data = await resp.json();

  const knows = data.is_answerable as { answer: boolean; confidence: number };
  if (!knows.answer || knows.confidence < ANSWERABILITY_GATE) {
    return {
      abstained: true,
      missing: (data.missing_information as { answer: string }).answer,
      is_answerable: knows.confidence,
    };
  }

  const answer = data.answer as { answer: string; confidence: number };
  return {
    abstained: false,
    answer: answer.answer,
    confidence: answer.confidence,
    lane: answer.confidence >= AUTO_CONFIDENCE ? "auto" : "review",
  };
}
// 1 回の呼び出し、3 つの型付き読み取り。棄答ブランチは欠落キーを持ち運ぶので、
// ワークフローはまさにその情報を収集しに行けます。

判断モデルの棄答 FAQ

判断モデルの棄答(abstention)とは何ですか?

入力 state に答えが含まれていないとき、モデルが推測するのではなく、明示的で型付きの「わからない」——欠けている情報の名指し付き——を返すことです。このページで構築するコントラクトでは、棄答は is_answerable Noul としきい値で決まるレーンであり、棄答オブジェクトは missing_information キーを持つため、ワークフローは欠けているものを正確に収集しに行けます。棄答は判断品質における証拠の軸です。信頼度しきい値は確実性の軸であり、本番システムは両方でゲートします。

Choice に「unknown」選択肢を足せばいいのでは?

3 つの理由で推奨しません。選択肢分布が汚染されます——すべての実際の回答がキャッチオールの選択肢と競合し、選択肢ごとの確率が同じ意味を持たなくなります。シグナルの帰属が不能になります——後から「証拠の欠落」と「criteria の書き方の問題」を区別できません。そしてレバーがありません——統合された選択肢は過剰棄答と過剰確信を別々に扱えませんが、独立した Noul なら各誤り方向に実測率と専用のゲートを与えられます。

信頼度しきい値だけで足りるのでは?

足りません——信頼度と回答可能性は違う仕方で失敗します。信頼度は較正されています。0.62 とは、モデルが証拠を見た上で 62% の確信があるという意味です。棄答が担うのは、どんなに確信が高くても根拠にならないケースです。出荷日は state にない、解約理由は記録されていない、更新はまだ起きていない。このページが引用する監査と評価はどちらも、モデルがまさにそうした state で高信頼度を維持することを示しています。だからこそゲートは最初に is_answerable を読み、次に信頼度を読みます。

棄答は Jev にネイティブに組み込まれていますか?

Jev はプリミティブを提供します——型付き回答、較正済み信頼度、Noul 質問タイプ、1 パスの並列質問——そして棄答レーンはその上の 5 行のアプリケーションコードです。判断の隣に is_answerable を問い、ゲートし、missing_information でルーティングする。切り替える「IDK モード」は存在せず、それは双方向に誠実です。何もあなたの背後で勝手に棄答せず、システムが行うすべての棄答は、あなたがルールを書きテストできるものです。

回答可能性のしきい値はどう決めますか?

信頼度しきい値と同じ方法です。実測した誤りコストから決めます。回答可能な state と明確に回答不能な state の両方を含むラベル付きセットを作り、is_answerable ゲートを掃引し、2 つの曲線を読みます——誤棄答(人の作業の浪費)とハルシネーション(高価な方向)。2 つのコスト曲線の交点がゲートです。スキーマ変更後には掃引をやり直してください。測定方法論は較正ガイドが示しています。

判断モデルは実際のところ、上手に棄答できるのですか?

モデルによって大きく異なりますし、測定可能です。このページが引用する 5 モデル評価(2026 年 10 月、18 メッセージ、探索的)では、ホスト型 Jev は回答不能 6 ケースすべてで棄答した一方、ローカル小型モデルはどれも少なくとも 1 つを推測で答えました——Clef Flash 9B の棄答は 6 件中 1 件。これは限界ページにある、より広範な過信エビデンスと整合します。ベンダーの数字を信頼する前に(私たちのものも含め)、自分のドメインから取った回答不能 state でテストしてください。

信頼できる判断のツールボックスを広げる