黙って間違えないオーケストレータ — 20,000回まわして分かった、効いたことと効かなかったこと
2026-08-03 ・ 生データ全公開: teai.io/api/v1/bench
LAB NOTES #4 で「安いモデルを2つ束ねたら単体最強に並んだ」を書きました。今回はその続きです。目標を「正答率」から「黙って間違えない率」に変えました。
LLM の一番困る壊れ方は、間違えることではなく間違えたのに自信満々で返すことです。受け取る側に見分けがつきません。そこで100%正しく答えるのは諦めて、「答えるときは正しく、怪しいときは怪しいと言う」に目標を変えました。
結果(182問・実走3回)
| 実走 | 正答 | 要確認 | 黙って間違え | コスト |
|---|---|---|---|---|
| 1回目 | 99.5% | 1.10% | 0件 | $0.173 |
| 2回目 | 100.0% | 1.10% | 0件 | $0.176 |
| 3回目 | 100.0% | 1.65% | 0件 | $0.168 |
ただしこれを「間違えないモデルができた」とは書きません。コードで検算できたのは正解の76%で、残り24%は素通りしています。標本は182問×数回なので、364件で0件が抑えるのは真の発生率およそ0.8%まで(95%)。0%の証明ではありません。
一致だけでは足りなかった
最初は「ベンダーの違う2モデルが一致したら正しい」で作りました。89% → 99.7% まで上がります。残った誤答を全部見たら、4件すべてが「2モデルが同じ間違いをして一致した」ものでした。
| 設問 | 正解 | 両モデル | 誤りの型 |
|---|---|---|---|
| 税込33,333円(8%)の税抜・切り捨て | 30863 | 30864 | 切り捨てを切り上げた |
| 45.9㎡は何坪(小数第3位四捨五入) | 13.88 | 13.89 | 切り上げた |
| 2026-02-26 の翌日から4営業日目 | 03-04 | 03-02 | 2日早い |
| 2026-05-29 の翌日から4営業日目 | 06-04 | 06-02 | 2日早い |
誤りは端数処理の方向と営業日の数え方の2種類だけ。どちらも「よくある慣習」なので、ベンダーを分けても同じ癖を共有して揃って外します。一致を信号にする設計の構造的な穴でした。
コードは答えを作る側でなく、検算する側に置く
最初はコードで計算させて結果を採用しました。黙って間違える率が 0.27% → 4.12% に悪化しました。抽出条件が外れると、コードが自信満々に間違えるからです。
役割を逆にしました。コードは答えを作らず、モデルの答えを検算するだけ。判定は3値です。
- Confirmed — 検算して合っていた
- Rejected — 検算して合わなかった → 採用しない
- Unknown — 検算できなかった → 何も言わない
肝は Unknown を「正しい」と混同しないこと。そして「動かない」が既定なので、抽出が外れても答えは壊れません。壊れ方は「不要な⚠️が出る」だけで「間違った答えを返す」にはならない。この非対称性のおかげで、検算の範囲を安心して広げられます(48% → 76%)。
法令の数値はコードに焼き込まなかった
検算を広げた後も黙って間違えが残りました。「継続勤務1.5年の年次有給休暇は何日か」で、正解11日に対し安い2モデルが揃って「10」。設問文に表が無い、つまり暗記の問題です。
労基法39条の付与日数表をコードに書けば一発で直ります。やりませんでした。法改正のたびに、正しい答えを弾く側に回るからです。代わりに「答えの数値が設問文の数から四則演算で導けるか」を判定し、導けなければ記憶依存=危険側として裁定役へ上げました。これで黙って間違えは6回連続で0件になりました。
選定と検証を分けたら、最良に見えた構成が崩れた
14モデルの回答テキストを1回集めておけば、束ね方の評価に追加のAPI呼び出しは要りません。1,820構成を総当たりしました。
ただし1,820から「一番良いもの」を選べば、測定のばらつきに合っただけの勝者が必ず出ます。そこで選定は片方の実行回だけで行い、もう片方(選定に使っていない回)で検証しました。
| 「安い1本 → 必要なときだけ上げる」構成 | 正答 | 要確認 | 黙って間違え |
|---|---|---|---|
| 選定に使った回 | 100.0% | 0.0% | 0.00% |
| 検証した回 | 98.9% | 0.0% | 1.10% |
選定回では全1,820構成で最良でした。正答100%、要確認0%、黙って間違え0%。検証回で1.10%黙って間違えました。裁定役の答えを他と突き合わせずに受け入れる構造なので、裁定役が外れるとそのまま出ます。この手順が無ければ本番に入れていました。
裁定役を替えたらコストが半分になった
| 裁定役 | 正答(2回) | 黙って間違え | 要確認 | コスト |
|---|---|---|---|---|
| gemini-3.1-pro(変更前) | 100.0 / 99.5% | 0.00 / 0.00% | 1.1% | $0.340 |
| inkling(変更後) | 100.0 / 100.0% | 0.00 / 0.00% | 1.1% | $0.179 |
効かなかったこと
プロンプト調整は汎用的には効かない
8変種 × 3モデル × 182問 × 2回 = 8,736回。同じ指示がモデルによって逆に働きます。
| 指示 | flash | inkling | glm-5.2 |
|---|---|---|---|
| 端数処理の桁と方向を書き写してから適用 | +3.3 | +0.8 | −2.2 |
| 日付や個数は1つずつ書き出してから数える | +3.0 | +1.4 | −1.9 |
| 設問文に表があれば使う行を引用する | −6.6 | +0.8 | −0.8 |
| 手順は指示せず「急がず見直せ」だけ | −7.1 | +1.4 | +1.9 |
「急がず見直せ」は glm-5.2 を +1.9 にし、flash の日付設問を 58% → 0% に壊しました。効くのは「そのモデルが実際に持っている手順の穴」に当てたときだけです。flash の「祝日表つき営業日」は列挙させると 58% → 95%(+37pt)、日付以外への影響は ±0 でした。
部品を良くしたら、全体が悪くなった
では flash に列挙を指示すれば全体も良くなるはず — と思って実走しました。
| 構成 | 正答 | 要確認 | コスト | p90 |
|---|---|---|---|---|
| 現行(全部baseline) | 98.4〜100% | 0.55〜3.30% | $0.171〜0.183 | 6.4〜11.7s |
| flash だけ列挙 | 98.4〜99.5% | 3.30〜5.49% | $0.206〜0.227 | 14.4〜14.7s |
| 3本とも列挙 | 98.9〜99.5% | 5.49〜8.79% | $0.189〜0.204 | 5.0〜13.2s |
列挙させると出力が長く・形が揺れるため、もう1本との一致が減り、裁定役への持ち上げが増えます。黙って間違えは3構成とも0件でしたが、要確認とコストと遅さが増えるだけでした。本番は現行のまま据え置きました。
自分たちのベンチマークが間違っていた(5件)
モデルが揃って間違えたように見えたときは、まず自分の正解を疑うほうが当たります。
| 設問 | 何を間違えたか |
|---|---|
| 源泉徴収 1,234,567円 | 100万円超の税率が変わることを無視した正解を置いていた |
| 割増賃金 時給1450円×9時間 | 端数処理を指定せず単純な切り捨てを正解にしていた。実務の慣習(昭和63.3.14 基発150号)を知っているモデルほど不正解になった |
| 容積率 45㎡×150% | 67.5 を 67 に切り捨てていた。指定容積率に切り捨ての定めはない |
| コード「空白の畳み込み」 | テスト入力を二重にエスケープし、タブではなくバックスラッシュ文字を渡していた。全28回とも不正解になった |
| コード「中央値」 | 浮動小数の丸めを避けることを仕様文に書いていなかった。素直な実装が全部落ちた |
5件とも正しく答えたモデルを不正解にする形をしています。「高いモデルが最安モデルに負けた」と色めき立って中身を見たら、負けていたのはこちらでした。コードの2件を直したら、コード実装の正答率は 62% → 94〜100% に変わりました。
生データ
認証不要・CORS 開放です。設問と正解ごと出します。正解を隠したまま「うちのベンチではこうでした」は検証できません。実際この評価セットの誤り5件は、外から見れば分かるものでした。
curl https://teai.io/api/v1/bench # 全結果 + 1,820構成の評価
curl https://teai.io/api/v1/bench/eval/jp-business # 評価セット(設問と正解)
表で見るなら teai.io/bench。実装の詳しい話は teai.io/blog/sure-mode。
使い方
curl https://teai.io/v1/chat/completions \
-H "Authorization: Bearer $TEAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"shitate/orchestrator:sure","messages":[{"role":"user","content":"..."}]}'
怪しいときは答えの頭に ⚠️ 要確認 が付きます。付かなければ4段の関門を通っています。