LLMは「選ぶ・抜く」だけにする
LLM 構造化出力で型をそろえても、答えが正しいとは限らない。文章は書かせず、選ばせて抜かせ、原文との一致を機械で確かめる。
LLM をアプリに組み込むと、最初は「要約を書かせる」「説明文を書かせる」から始めがちです。けれども、自由に書かせた文章は、正しいかどうかを機械で確かめられません。もっともらしい誤りが、そのまま画面に出ます。編集部の検証環境では、LLM の役目を「決まった選択肢から選ぶ」「原文から抜き出す」の2つに絞り、文章は書かせない設計に寄せています。この記事は、その考え方と、確かめ方をまとめたものです。
書かせると、確かめられない
自由文の誤りは、見た目では分かりません。文法も整い、口調も自然です。誤りを見つけるには、人間が原文と突き合わせるしかありません。件数が増えると、それは続きません。
一方、「選ぶ」と「抜く」は、結果を機械で確かめられます。
- 選ぶ:答えが、渡した選択肢のどれかであるかを確かめられる
- 抜く:答えが、原文にそのまま含まれているかを確かめられる
文章が必要な画面では、抜いた語句や選んだラベルをテンプレートに差し込みます。言い回しは人間が書いたものを使うので、そこに誤りは入りません。
公式の推奨も「引用してから」
Anthropic の公式ドキュメントも、ハルシネーションを減らす手として、長い文書ではまず一字一句そのままの引用を抜き出させ、その引用をもとに作業させることを挙げています。主張ごとに裏付けの引用を探させ、見つからなければその主張を取り下げさせる方法や、「分からない」と答えることを明示的に許す方法も挙げています(出典:https://platform.claude.com/docs/en/test-and-evaluate/strengthen-guardrails/reduce-hallucinations 、2026-10-16確認)。
ここで大事なのは、引用が本当に原文にあるかを、モデルに任せず機械で確かめることです。
型が合っていても、中身は別
出力の形をそろえるには、構造化出力が使えます。公式ドキュメントは、構造化出力が制約付きのデコードで、スキーマに合った応答を保証すると書いています。JSON の解析エラーが無くなり、型と必須のフィールドがそろう、という説明です(出典:https://platform.claude.com/docs/en/build-with-claude/structured-outputs 、2026-10-16確認)。
ただし、保証されるのは形だけです。スキーマで選択肢を絞れば「選択肢に無いラベル」は出なくなりますが、抜粋の欄に何が入るかは、スキーマでは縛れません。言い換えた文でも、文字列の型としては正しいからです。
編集部で、型付きの判断を返すモデルに、データの突き合わせを任せたことがあります。結果は、かなりの割合が誤検出でした。出力の型はすべて正常でした。答えが現実に対して誤っていたのは、根拠になる情報が入力の文字列の中に無かったからです。モデルに判断させてよいのは、根拠が入力の中にあるときだけです。文字列の照合で決まるものは、照合のほうが強い、というのがこの件の教訓です。
抜粋を機械で確かめる
手元で、ダミーの原文と、LLM が返したことにした出力を用意し、検査を回しました。API は呼んでいません。
def norm(s): return unicodedata.normalize("NFKC", s)
if norm(o["quote"]) not in norm(SOURCE): errs.append("原文に無い抜粋")
if o["label"] not in LABELS: errs.append(f"選択肢に無いラベル: {o['label']}")
OK 開封済みの商品は対象外です。 / -
NG 開封した商品は返品できません。 / 原文に無い抜粋
NG 送料は お客様の負担となります。 / 原文に無い抜粋
NG 返品は購入から14日以内に受け付けます。 / 選択肢に無いラベル: 期限切れ
2行目は、意味は同じでも言い換えです。3行目は、空白が1つ混ざっただけです。どちらも人間が読めば気づきにくく、部分文字列の検査なら確実に落ちます。全角と半角の揺れだけは NFKC で正規化してから比べていますが、それ以上は緩めません。緩めるほど、言い換えが通ります。
この検査は、画面に差し込む直前に置きます。落ちたものは表示せず、再試行するか、空欄のままにします。
落ちた件数も記録しておきます。普段はほとんど落ちない検査で、急に落ちる割合が増えたら、モデルの更新や、原文の書式の変化を疑う合図になります。検査を「通すための関門」としてだけでなく、「入力と出力の変化を知らせる計器」としても使うわけです。再試行は回数の上限を決め、上限に達したら空欄で出し、人間が後から見られるように記録に残します。上限を決めないと、外れ続ける入力でいつまでも費用がかかります。
「該当なし」は別の質問にする
公式は「分からない」と答えることを許すよう勧めています。ところが、以前の実測では、選択肢の中に「該当なし」を混ぜても、200件で1回も選ばれませんでした(この記事のために API は呼んでいません)。モデルは、並んだ選択肢の中から何かを選ぼうとします。
そこで、「該当するものがあるか」を先に別の質問で聞き、あると答えたものだけ選択肢から選ばせる形に分けています。「分からない」を許す、という公式の考え方を、質問の構造で実現した形です。
質問に前提を入れない
質問文に前提を含む言葉を入れると、その前提が崩れる入力で答えが壊れます。「この増加の理由は」と聞けば、増えていない入力にも理由を作ってきます。質問は前提を含まない形にし、前提が成り立つかどうかは別に確かめます。
同じ質問と入力を繰り返したときの出力のばらつきは、以前の実測ではごく小さく、ほとんど揺れませんでした。ぶれて見えたら、モデルより先に、質問か入力の側を疑います。
決まるものは関数で決める
文字列の照合や数の比較で決まる判定は、LLM に推論させず、普通の関数と単体テストで書きます。同じ入力に同じ答えが返り、テストで固定できるからです。LLM に任せるのは、「どの抜粋が関係するか」「どの選択肢に当たるか」のように、関数では書けない部分だけにします。
「動いているように見えて、実は中身が違う」問題は、監視でも同じ形で起きます。その例は「常駐Bot監視の落とし穴」に書きました。選んで抜くだけの処理は軽いので、手元のローカル LLM で回したくなるかもしれません。その場合の速度の考え方は「ローカルLLMの遅さの正体」にまとめています。
まとめ
- LLM には文章を書かせず、選択肢から選ばせ、原文から抜かせる。文章はテンプレートで組む。
- 構造化出力が保証するのは形だけ。抜粋が原文にあるかは、部分文字列の検査で確かめる。
- 根拠が入力の中に無い判断は、型が正しくても誤る。
- 「該当なし」は選択肢に混ぜず、先に別の質問で聞く。
- 質問に前提を入れない。決まるものは関数とテストで決める。