LLMとは?検算させたら拾い漏れと「拾いました」が並んだ

LLMとは大規模言語モデルの略で、大量の文章から言葉の続き方を学習したモデルです。指摘を全部反映したかの確認まで任せたところ、拾い漏れと「全部拾いました」という報告が同時に成立しました。判定をモデルに渡す限りこれは消えません。判定を一切LLMに渡さず、退屈な文字列一致で機械が検査する設計に変えました。全角空白の除去まで含めた実装と、判定に使ってよい出力・いけない出力の切り分けを書きます。
Total
0
Shares
LLMとは?検算させたら拾い漏れと「拾いました」が並んだ

小学生のころ、漢字の書き取りを自分で採点していました。答え合わせも自分でやりなさい、という宿題です。

私はいつも満点でした。間違えていないからではありません。

書いた字を見て、答えを見て、「これは合っている」と思ったら丸を付けていました。とめもはねも、自分の字は自分には正しく見える。採点者と回答者が同じ人だと、こうなります。

先生が横で見ていて、「これ、違うよ」と言いました。私はそこで初めて、自分が「合っている」と思ったこと自体は何の情報でもないと知りました。

先に一行だけ置きます。LLMとは大規模言語モデルの略で、大量の文章から言葉の続き方を学習したモデルのことです。もっともらしい続きを返す仕組みです。

題材は、レビューの指摘を反映する工程です。前の工程で挙がった指摘を、次の成果物に全部反映したかを確認します。

前置きはさておき、本題に入ります。

今日は、その確認までLLMに任せたら何が起きたかという話をしていこうと思います。

① 教科書どおりに、確認もLLMにやらせる

教科書どおりに、確認もLLMにやらせる

構成はこうでした。

→ 指摘が一覧として溜まる
→ 次の工程で、それを反映した成果物を作る
全部反映したかを確認して、報告する

3つ目も、LLMにやらせました。同じ文脈を持っているので、いちばん詳しいはずだからです。

そして、判定の精度を上げるために、判定役をもう一段重ねることも検討していました。よくある構成です。

報告は、いつも良好でした。 「指摘はすべて反映しました」と返ってきます。

ここまでは、教科書どおりです。
確認させていた項目は3つでした。指摘を全部反映したか、書式が正しいか、識別子を採番したか。3つとも、答えが決まっている問いです。

確認項目答えの決まり方当時の担当
指摘を全部反映したか決まっているLLM
書式が正しいか決まっているLLM
識別子を採番したか決まっているLLM

② そのとおりに回して、拾い漏れが混ざる

そのとおりに回して、拾い漏れが混ざる

ところが、後から成果物を読むと、反映されていない指摘が混ざっていました。

そして、その回の報告には「すべて反映しました」と書いてありました。

嘘をついているわけではありません。両方とも、もっともらしい文章として成立しているだけです。

→ 指摘を拾い漏らす
→ 「拾いました」と報告する

この2つは、同時に成立します。

③ そして、判定の入力が出力だと気づく

そして、判定の入力が出力だと気づく

構造を見直すと、単純な話でした。

私は、成果物を作った当人に、成果物の検査をさせていました。

漢字の書き取りと同じです。採点者と回答者が同じなら、採点はもう情報を持ちません。

判定役を重ねる案も、よく考えると同じでした。重ねた判定役も、もっともらしさを返します。 数が増えるだけで、確からしさは増えません。

④ 原因は「答えが決まっている検査を、確率的な道具に投げていた」こと

原因は「答えが決まっている検査を、確率的な道具に投げていた」こと

原因を一つに絞ると、これでした。

「指摘を全部拾ったか」は、答えが決まっている問いです。

一覧に載っている項目が、成果物に含まれているかどうか。含まれているか、いないか。 解釈の余地がありません。

答えが決まっている問いを、もっともらしさを返す道具に投げていたのが誤りでした。

そして、この誤りは内容の良し悪しの判定とは別です。「この設計は妥当か」はLLMに向いています。「全部あるか」は向いていません。

⑤ 直してみる — 判定を一切LLMに渡さない

直してみる — 判定を一切LLMに渡さない

やったことは3つです。

1. 網羅性の確認を、コードの文字列一致に置き換えた。
未解決の指摘を全件取り出し、それぞれに付いている識別子が、次の成果物の本文に文字列として含まれているかを検査します。含まれていなければ落とす。それだけです。

判定を一切LLMに渡していません。 重ねるのではなく、外しました。

2. 比較の前に、空白を正規化した。
表記ゆれで落ちるので、空白を除去してから比較します。全角空白も対象に含めています。 日本語で運用すると混ざるからです。

地味です。ただ、ここを外すと運用が止まります。

3. 検査に落ちたら、状態を一切書き換えずに終了する。
半分だけ反映された状態が、いちばん厄介だからです。落ちた場合は保存処理に進まず、終了コードで異常を返します。 呼び出している側は、それを見て差し戻します。

シェルの終了コードが、そのまま差し戻しの門になっています。

そして、この方針を文書に明記しました。

モデルは、書式の検証も、識別子の採番も、網羅性の確認もしない。
置き換えた結果を数字で置いておきます。確認1回あたりの所要は数秒から数ミリ秒へ、費用はゼロに、判定の揺れもゼロになりました。

項目LLMに確認させるコードで検査する
所要数秒数ミリ秒
費用課金ありゼロ
同じ入力での再現性揺れる常に同じ

⑥ あとで知った — 同じ線引きを、別の場所でも引いていた

あとで知った — 同じ線引きを、別の場所でも引いていた

素朴な対処のつもりでしたが、同じ線を、別の担当者が別の場所で引いていました。

プロダクト側の出力検証です。そこには、こう書かれていました。

「これはプログラムによる検証であり、LLMの呼び出しはゼロ」

検査している内容も具体的です。

置き換え忘れの穴埋め語が残っていないか
やり方の説明で終わっていないか(実行するのではなく手順を語り出す癖)
曖昧な委譲が入っていないか(「適宜」「必要に応じて」)
未実装のまま例外を投げるだけの塊が混ざっていないか

どれも、答えが決まっています。 そして、どれもLLMに聞けば「問題ありません」と返ってきうる項目です。

面白いのは、違反の扱いを2つに分けていることでした。実行そのものを失敗にする構造的な違反と、警告だけ足して先へ進めるソフトな違反。止めるべきものと、進めてよいものを分けています。

私が1箇所でやったことを、別の担当者が別の層で同じようにやっていました。 相談したわけではありません。同じ壁に当たると、同じ場所に線を引くのだと思います。

新しい理屈は、ひとつも要りませんでした。

記録に残っている実例

開発中のツールの修正履歴に、この問題がそのまま残っています。

2026年2月27日の修正。 判定結果を文字列から取り出す処理で、取り出せなかったときの既定値を1語だけ変えました。

“`

  • const verdict = verdictMatch ? verdictMatch[1].toUpperCase() : ‘PASS’;

+ const verdict = verdictMatch ? verdictMatch[1].toUpperCase() : ‘FAIL’;
“`

それまでは、判定が読み取れなかったとき、通していました。 判定していないことと、合格したことが、同じ扱いになっていた。1語です。 直した行数は1行でした。

この既定値が `PASS` のままだと、書式が崩れるたびに検査が素通りします。検査が動いていない状態と、全部合格している状態は、ログの上では区別がつきません。

同じ設計思想は、2026年2月2日の決定にも残っています。抽出役の出力はJSONのみに強制し、崩れたら計画全体から1件だけタスクを作る形に落とす。文脈にはこう書かれています。「LLMは時々マークダウンや説明を足す」。 足されることを前提に、外側で剥がしています。

⑦ 何が変わったか

何が変わったか

LLMに聞くことが減りました。

“`
前:作らせる → 確認させる → 報告を読む
後:作らせる → コードで検査する → 落ちたら差し戻す
“`

確認の工程が、速くて安くなりました。 文字列比較なので、待ち時間も課金もありません。

⑧ 現場で使うなら、この2枚

現場で使うなら、この2枚

表1:LLMに判定させてよいか

判定LLMコード
この設計は妥当か向いている不可
この文章は読みやすいか向いている不可
指摘を全部拾ったか向いていない文字列一致
必須項目が埋まっているか向いていない存在確認
識別子の採番向いていない採番処理
禁止語が残っていないか向いていないパターン照合

判定の答えが決まっているなら、コードに渡してください。

表2:検査を入れる場所

場所何を見るか落ちたら
取り込みの直前網羅性・書式状態を書き換えずに終了
出力の直後穴埋め語・未実装の塊構造的なら失敗、軽微なら警告
保存の直前整合性保存しない

「落ちたら」の列を空欄にしないでください。 落ちたあとの挙動を決めていない検査は、警告を出すだけの飾りになります。

表3:正規化で落ちやすい表記ゆれ

ゆれ対処
全角と半角の空白識別子の前後両方とも除去
全角と半角の数字3 と 3片方へ寄せる
括弧の種類[F3] と [F3]片方へ寄せる
前後の改行除去

1行目を外すと、日本語の運用では必ず落ちます。 実際、最初の実装では半角空白しか除去しておらず、検査が通らない事例が続きました。

補:置き換えで動いた数字

項目
1件あたりの所要数秒数ミリ秒
1日の確認回数数十回数十回
課金あり0円
判定の揺れあり0件
検査項目3件3件
落ちたときの挙動続行停止

ところで、6行のうち効いたのは最後の1行です。「落ちたら止める」と決めるまで、警告は出ていたのに誰も見ていませんでした。

運用では、次の5件に名前を付けて配りました。名前が付くと、会議の中で指差せるようになります。

1件目、「拾い漏れと、拾いましたという報告は両立する」。 矛盾ではありません。報告は作業の記録ではなく、その場で作られた文章だからです。

2件目、「網羅性の判定は、LLMに渡さない」。 何件あるべきかを知らない相手に、全部あるかを聞いています。答えは必ず返ってきますが、根拠はありません。

3件目、「退屈で決定的な方法で、コードが検査する」。 文字列一致で足ります。面白くない方法ほど、同じ入力に同じ答えを返します。

4件目、「判定を重ねても、確からしさは増えない」。 3回聞けば3つの回答が出るだけです。増えたのは回答の数であって、確からしさではありません。

5件目、「精度が上がっても、この性質は消えない」。 モデルが良くなるほど、報告の文章も上手くなります。見分けにくくなる方向に進みます。

この5件は、1枚に印刷して配れる分量に収めています。増やすと、誰も覚えません。

⑨ この考え方が効き続ける理由

この考え方が効き続ける理由

LLMは、これからも賢くなります。それでも、この線引きは動きません。 賢さの問題ではなく、答えが決まっている問いを確率的な道具に投げているという構造の問題だからです。

→ もっともらしさを返す → 「拾いました」ももっともらしい
→ 拾い漏れも同時に成立 → 報告が情報を持たない
→ 情報を持たない → 重ねても増えない

LLMを判定に重ねる設計は、判定の数を増やしているのであって、確からしさを増やしていません。

そして、外すと副産物があります。速くて、安くて、結果が毎回同じです。 文字列一致には揺らぎがありません。同じ入力なら、同じ判定が返ります。

ただし、正直に書いておきます。この方式は、識別子を必ず本文に書かせるという運用に依存しています。 書かれなければ検査は通りません。運用の規律を、検査が要求する形になっています。 そこを窮屈だと感じる人はいるはずで、私もときどき感じます。

置き換えたあとの数字です。確認1件あたりの所要は数秒から数ミリ秒へ、費用は0円、同じ入力に対する判定の揺れも0件になりました。文字列の比較には揺らぎがありません。

運用してからの数字を足しておきます。検査は1日に数十回、落ちた回数は月に2回、落ちた原因は2回とも識別子の書き忘れでした。なお、書き忘れは人ではなくモデル側で起きています。ただし、検査があるので本番には届いていません。

関連して、社内で共有されていた観察を1件引いておきます。深い調査を自動で回す機能について、「出典が多く付いていて情報源を辿りやすい一方で、調査の統合過程で判断軸がブレ、最終的な分析や結論が怪しいケースが多い」。

ところで、これも同じ構造です。工程を重ねるほど、途中で判断軸が入れ替わる。 出典が多いことは、結論が正しいことを保証しません。ただし、辿れること自体には価値があります。辿れるかどうかと、正しいかどうかは別の軸です。

関連して、AI駆動開発で自作基盤とネイティブ機能を分ける話と、プロンプトエンジニアリングを属人化させない話を別に書いています。チャットボットはまず分類器から作る話はすでに公開しています。あわせて読むと、同じ判断が別の場所でも効いていることが分かると思います。

この判断を実際の案件で使う場面については、生成AIの実装のページに整理しています。


検算といえば、学生のころ答案を見直して間違いを見つけたことが、一度もありません。 自分で自分を検算するのは、たぶん人間にも向いていません。

以上です。

You May Also Like

ベクトルデータベースとは?上位10件の壁に当たった話

ベクトルデータベースとは、文章を数値の並びに変えて、意味の近さで探せるようにした保存先です。日本語の全文検索が精度不足だったため、全文検索を避けてベクトル検索に寄せる判断をしました。ところが取得できるのは上位10件までで、そこに入らなかったものは存在しないのと同じになります。全体のナレッジグラフを作るのではなく、各チャンクに関係性のメタデータを持たせて局所的に推論させる方針まで書きます。
View Post

MCPとは?社内ナレッジ検索の画面を作るのをやめて、MCP接続にした話

MCPとはModel Context Protocolの略で、AIと社内の道具やデータを繋ぐための共通の作法です。社内ナレッジを全社に開くために専用のフロントエンドを作ろうとしていましたが、初期フェーズではそれを見送り、MCP接続を主軸にする設計変更をしました。理由は画面が要らないからではありません。画面を作ると、そこに認証も権限も検索体験も全部くっついてくるからです。残したのはAPIキーの発行とフォルダごとの権限管理だけでした。何を作らないと決めたのかを書きます。
View Post

プロンプトインジェクションとは?禁止をプロンプトに書かない

プロンプトインジェクションとは、AIへの指示文に別の指示を紛れ込ませて、本来の制約を外させる攻撃です。対策としてまず思いつくのは、システムプロンプトに禁止事項を書き足すことです。私たちもそうしました。そして守られませんでした。プロンプトによる禁止は確率的で、ツール層のフックは決定的です。44行のフックで守る側へ移した実装と、パスの正規化や失敗時に閉じる設計まで、そのまま書きます。
View Post

プロンプトエンジニアリングとは?AIに組み立てさせるのをやめた

プロンプトエンジニアリングとは、AIから望む出力を得るために指示文を設計する技術です。ところが指示文そのものをAIに組み立てさせると、同じ入力でも毎回わずかに違う文面が生成され、結果が揺れます。原因を追うと、揺れているのはモデルではなくプロンプトのほうでした。CLIが変数を埋め込んだ全文を出力する方式に変えると、プロンプトはバージョン管理できる成果物になります。再現性をどこから取り戻すかを書きます。
View Post