BLOG · 2026-06-27 17:39

LLMはなぜ同じ質問に違う答えを返すのか ― 「ぶれない」を決定論で作る

富士山は何メートルか

ある日、社内の AI(ローカル LLM)に富士山の高さを聞いてみました。temperature=0、seed 固定。理屈の上では「毎回同じ答え」が返るはずの設定です。ところが——

  • 「富士山の高さは」→ 3,776.1 m
  • 「富士山は何メートル」→ 3,776.12 m
  • 「富士山はどのくらい高い」→ 3,776.24 m

同じことを聞いているのに、言い回しを変えただけで答えが 3 通りに割れました。 富士山の標高はひとつのはずです。これが LLM の「ぶれ」です。

雑談なら笑い話で済みます。しかし「昨日の売上は」「3 日前の取引の承認者は」「この送料は税込みか」——事実を扱う業務でこのぶれが出ると、笑えません。

私たちが取り組んだのは、この「ぶれ」を 賢さではなく 構造で消すことでした。LLM をもっと賢くしようとするのではなく、入り口で意味を正規化して、同じ意味の問いを必ず同じ答えに収束させる。今日はその実験記録(社内コードネーム SlimeLab)を紹介します。


発想:意味が同じなら、同じ「鍵」に畳む

キャッシュを思い浮かべてください。同じ質問が来たら、計算せずに前の答えを返す——速いし、何より 毎回同じ答えになります。

問題は「同じ質問」の判定です。普通のキャッシュは文字列が一致しないと効きません。「富士山の高さは」と「富士山は何メートル」は別の文字列なので、別物として扱われ、LLM が毎回ぶれた答えを作ってしまう。

そこで、意味が同じ問いを、決定論的に同じ鍵(キャッシュキー)へ畳む正規化層を入り口に置きました。鍵が一致すれば、ひとつの確定した答えを返せる。ぶれは原理的に消えます。

ポイントは「決定論的に」です。ここに私たちのこだわり——bit-exact(ビット完全)があります。


こだわり①:計算の真ん中を、ごまかさない

「3,776.12 m」と「3.77612 km」と「377,612 cm」は、同じ長さです。これを同じ鍵に畳むには単位を揃える必要があります。素朴にやると——

3.77612 * 1000 == 3776.12   # → False
# 実際の値: 3776.1200000000003

浮動小数点(float)で計算すると、末尾の桁がずれます。しかもこのずれは CPU やライブラリのバージョンで変わりうる。「計算結果がビット単位で再現する」という保証が、計算の真ん中では実は無いのです。通信(誤り訂正)やストレージ(ハッシュ照合)は何十年も「両端」でビット完全を作り込んできたのに、計算・変換の真ん中はずっと「だいたい合ってる」で済まされてきました。

私たちは単位変換を 有理数(分数)で閉じました。

from fractions import Fraction
Fraction("3776.12")          # = 94403/25
Fraction("3.77612") * 1000   # = 94403/25  ← 厳密に一致
Fraction("377612") / 100     # = 94403/25  ← これも一致

94403/25 という既約分数で厳密に一致するので、3 つの表記は同じ鍵に畳めます。float の桁ずれは入り込みません。これが SlimeUnit(単位の正規化)です。


こだわり②:時刻は「基準日」を明示する

「3 日前」「先週の金曜」「2026/06/24」が同じ日を指すこともあります。でも「3 日前」は いつから見て 3 日前なのか? 基準日が無ければ決まりません。

世の中の日付ライブラリは、相対日付をその場の システム時刻(now) で解決します。便利ですが、これは 非決定論です。同じログを後で監査して「あの日のバッチを再現せよ」と言われても、now() が変わっているので再現できない。

そこで SlimeClock は、now() を一切使わず、基準日(as_of)を明示的に受け取って整数演算だけで解く設計にしました。

as_of = 2026-06-27(土)
  3日前         → 2026-06-24
  2026/06/24    → 2026-06-24
  6月24日       → 2026-06-24   ← 全部同じ鍵

金融の「T+3 営業日決済」も同じ枠組みです。週末と祝日(自社の創立記念日まで含めた営業日カレンダー)を整数で飛ばして着地日を確定します。

T+3(as_of=6/27 土)
  → 土27・日28 は週末でスキップ
  → 月29(1)、火30 は創立記念日でスキップ、水7/1(2)、木7/2(3)
  → 2026-07-02

基準日とカレンダーさえ固定すれば、何度計算しても同じ日。サーバの時刻ズレに左右されません。


こだわり③:分からないものは、でっち上げない

ここが LLM と最も違うところです。

LLM は知らないことを聞かれても、それらしい答えを 作ってしまいます(ハルシネーション)。私たちの正規化層は逆で、判断material が足りないものは「分からない(保留)」のまま止めます。これを内部では Null Slot(空きスロット)と呼んでいます。確率的にでっち上げる代わりに、決定論的に「分からない」と言う。

たとえば人物の特定。SlimeWho は社内の人事データと突き合わせて人物参照を解決します。

  • 「田中部長」「田中一郎」「t.tanaka@…(社内メール)」「営業の田中さん」「社員番号 E1001」→ すべて同一人物として同じ鍵へ
  • ただの「田中」→ 社内に田中さんが 2 人いれば、どちらか分からないので保留。勝手に片方に決めない

「勝手に決めない」が決定的に重要です。稟議の承認者を取り違えたら監査で命取りになります。間違えるくらいなら、止まる。

※ 人物データは社外に出しません。照合は社内データ内で完結し、共有ログには社員番号も名前も出さず、復元不可能なハッシュ(仮名)だけを記録します。


割れたときだけ、裏を取る

冒頭の富士山に戻ります。「3,776.1 / .12 / .24」と答えが割れたとき、どれを正解として鍵に固定すべきか?

ここで LazyVerify(怠惰な裏取り)という仕組みを入れました。考え方はシンプルです——答えが一致しているときは何もしない。割れたときだけ裏を取る。

1. まず多数決。今回は 3,776.12 が最多(無料・オフライン)。

2. それでも決まらない/慎重を期す場合だけ、信頼できる出典を 1 回だけ参照。実際に Wikipedia の富士山ページを引くと「山体の最高地点は 3,776.12 m」と書かれていました。

3. 確定した答えを凍結。以後は参照せず、同じ鍵には同じ確定値を返す。

「割れたときだけ」なので、外部参照は実トラフィックのごく一部でしか発火しません。そして一度確定したら凍結されるので、その後はずっとビット完全に再現します。1 回だけの不確定を境界で確定し、中身は決定論に保つ——通信やストレージが「両端」でやってきたことを、計算の真ん中で再現した形です。


仕上げ:「誰が・いつ・いくら・何を」を 1 つの鍵に

ここまでの 3 つ(人物・時刻・金額)を束ねると、面白いことが起きます。次の 3 文は、表記がまったく違います。

  • 「田中部長が 3 日前50 万円承認した」
  • 「田中一郎が 2026/06/24500,000 円承認した」
  • 「t.tanaka@… が 6 月 24 日¥500000 を承認した」

これを正規化すると、3 文とも

( 承認者=E1001, 日付=2026-06-24, 金額=¥500000, 行為=承認 )

になり、同一の監査キー(sha256) に畳まれます。表記の揺れに関係なく「誰がいついくら何を」が一意に確定する。しかも先ほどの原則どおり、承認者が曖昧な「田中が承認した」は 保留扱いになり、別キーへ落ちます——誰だか分からない決裁を、確定ログに素通りさせない。

J-SOX のような内部統制監査では、まさにこの「承認者の一意特定」と「再現性」が肝です。表記の海外取引(円とドル)も、為替レートをその日付で固定すれば同じ鍵に乗ります。


結果

小さな実験ですが、手応えのある数字が出ました。

  • 同義クエリの正規化:11 通りの言い回し → 4 つの鍵に収束(残り 7 件は確定済みの答えを再利用)
  • ぶれの実測:生の LLM は temperature=0 でも同義 4 変種に 4 通りの別回答。正規化+確定で 完全に一致
  • すべて LLM 不使用・浮動小数点 不使用・外部ライブラリ依存ゼロ。整数・有理数・ハッシュだけ
  • ログには本文を一切残さない(ハッシュとフラグのみ)設計で、機密を保ったまま効果を計測可能

「賢い AI」ではなく「ぶれない仕組み」。確率的な層(LLM)はどうしても best-effort ですが、決定論的な層(正規化+確定)が安定を担保する。この役割分担が芯です。


なぜこれをやるのか

私たちは「計算結果はビット単位で再現すべきだ」という、地味だけれど大事な原則(bit-exact)を、業務の現場に持ち込もうとしています。計算が一致しても、代表に選んだ答えが嘘だったり、承認者が曖昧だったりすれば意味がない。 だから「一致(再現性)」に「正しさ(裏取り)」と「特定(誰だか確定)」を足す。そこまでやって初めて「決定論的であること=正しいこと」が成立します。

AI が賢くなるほど、AI が でっち上げないための足場が重要になります。分からないものを分からないと言い、揺れを構造で消し、確定したものは何度でも再現する——その地味な土台づくりの記録でした。


この記事の内容は社内の実験(PoC)に基づきます。製品仕様ではありません。SlimeClock / SlimeUnit / SlimeWho / LazyVerify は実験上のコードネームです。

投稿日時: 2026-06-27 17:39

← ブログ一覧へ戻る