TDIL

AI の自動チェックを 1 つ足したら、不合格が 0% から 13.5% になった

AI に確認を頼んで「今回も全部できました」と返ってくると、数値まで添えられていればなおさら疑う理由がない。私の環境でも、記事を書く工程や記憶の保存に自動の検査を置いていて、その緑の報告をそのまま受け取ってきた。ところがある研究記事を読んで検査の数を数え直したところ、緑が見ている範囲は思っていたよりずっと狭かった。この記事では、何をどう数えて、何が見えていなかったかを書く。

公式は全部直せたと言い、同じ記事の最後に測っていないものを書いていた

Anthropic が「Automated researchers can reliably mitigate alignment failures」という研究記事を公開している。AI の振る舞いが安全側からずれてしまう不具合(アラインメント不整合)を 10 種類取り上げ、自動の検査を使った研究で直していく内容である。

途中までは成果の話が続くが、最後の「限界」の節に、著者たち自身が次のように書いている。

we only rejected Claude's methods when they degraded a limited set of predetermined capabilities, meaning accepted methods may have degraded other important capabilities that we didn't measure

訳すと、あらかじめ決めた限られた能力が落ちたときにだけ手法を却下したので、採用した手法が、測っていない別の重要な能力を落としていたかもしれない、となる。自分たちが決めた検査項目の外側で何が起きているかは、その検査自身には分からない。公式の研究記事がそれを正直に書いている。

私はこの一文を、自分の環境の検査に当てて確かめることにした。紹介して終えるより、数えたほうが早いと考えたからである。以前、AI エージェント同士の失敗を自分の環境で数えたことがあるが(末尾にリンクがある)、今回はその失敗を見つけるはずの検査のほうを数える。

なお原文は要約を介さず、HTML を自分で取得して通読した。最初に要約経由で読んだとき、その要約には「安全性の差の 26% から 96% を埋めた」という引用符つきの文があったが、原文の本文を検索すると 26% も 96% も存在しなかった。数字を出すときは原文に当たり直す必要がある、という実例が最初の一手で出たので、この記事で原文から使う数字は監視の節の 39 件(2.4%)だけにしてある。

出口検査は、遷移の 1 割にしか掛かっていなかった

私の環境には、記事を段階ごとに区切って進める仕組みがあり、執筆から先へ進むときに「出口検査」という 7 項目の検査を走らせる。この検査がどこから呼ばれているかをコード全体で検索すると、呼び出し口は 1 箇所だった。

$ grep -rn "exit_check" scripts/ .claude/
scripts/review/stage.py:136:    r = subprocess.run([sys.executable, str(HERE / "exit_check.py"), slug])

しかも発火条件が「今の段が執筆中のとき」だけである。段を動かす操作の組み合わせを数えると、次のようになった。

工程の系統段の組み合わせ検査が掛かるもの割合
調べて書く系統49 通り5 通り10.2%
体験を書く系統81 通り7 通り8.6%

さらに、公開する操作にはまったく掛かっていなかった。公開は専用の別の処理が受け持っていて、その処理には出口検査を呼ぶコードがない。実際の道順で書くと、執筆から査読へ進む箇所で 1 回だけ検査が走り、査読から公開準備、公開準備から公開までは一度も走らない。査読で本文を直してから世に出すまでの間、検査は沈黙していた。

次に、この 7 項目を手元の記事 40 本すべてに手で当てた。

対象緑赤
公開済みとして管理している 16 本115
古い置き場の公開済み 16 本016
進行中の下書き 8 本08
合計 40 本139

緑の 1 本は、検査を作った後に公開した記事だった。赤の内訳で多いのは、見出しから図までに長い段落が 4 つ以上あることと、一人称の表記が古いまま残っていることである。これは 39 本が壊れているという意味ではない。規約を作る前に書いた記事に、後から作った規約を当てただけである。それでも、検査を一度も通っていない記事が 39 本あるという事実は、数えるまで知らなかった。

検査を足す前は 1,085 回で不合格ゼロ、足した後は 13.5%

いちばん驚いたのは次の数字である。私の環境では、AI との作業が終わるたびに、その内容を要約して記録に残す自動処理が走る。この処理に、出力が壊れていないかを確かめる検証を足した日がある。その前後でログを数え直した。

期間起動不合格
検証を足す前1,0850
検証を足した後64487(13.5%)

同じ機械が同じ仕事を 1,085 回して不合格はゼロだったのに、検証を 1 つ足した途端に 13.5% が不合格になった。検査を足した日に環境が壊れ始めたように見える。

しかし、過去の失敗記録を読むと、同じ壊れ方は検証を足す前からあった。要約のかわりにエラー文がそのまま記録に残る不具合が、数日連続で起きていた回があり、さらに 2 か月前にも同じ症状が 3 件あったと書き残されている。つまり不合格ゼロの 1,085 回のうちにも、同じ壊れ方が紛れ込んでいた。増えたのは失敗そのものではなく、見える範囲のほうである。

しかも、それを見つけたのは検査ではなく、人が手作業で記録を採点していたときだった。検査がなかった間、壊れた要約はそのまま次の処理に取り込まれ、翌朝の読み込みにも流れていた。原文の限界の節には「あまりに稀か、出てきたばかりで、測る基準がそもそも存在しない失敗もありうる」という趣旨の文があり、私の環境ではこれが過去の話ではなく、いま不合格ゼロで通っている他の検査にも当てはまりうる。当てはまるかどうかは、その検査自身には答えられない。

捕まえた失敗が、次の成功で消えていた

同じ記録を累計で数えると、起動は 1,729 回、初回の検証で不合格になって再試行に入ったものが 87 回(5.0%)、再試行しても直らず、要約を保存しなかったものが 17 回(1.0%)だった。ただし、ログは動き続けているので、数えた時点でしか正しくない。同じ日の数時間後に別の作業が独立に数え直したときには 1,732、89、17 で、最終不合格の 17 は一致した。

研究記事の側にも似た数字がある。AI の不正な振る舞いを監視するために約 1,600 本の記録を読ませ、39 件(2.4%)で不正の試みを見つけた。著者たちはこの監視が大半を捕まえたと慎重に楽観視しつつ、将来のモデルでも同じとは限らないと書いている。捕まえ損ねた数は、監視自身には出せない。

私の環境には、それよりもう 1 段悪い点があった。この処理の合否は、健全性を示す 1 つのファイルに毎回上書きして書かれる。失敗した回には異常の状態が書かれるが、次に 1 回でも成功すると、ファイルは正常に戻る。数えた日の 1 つを例にとると、47 回起動して、最初の判定で 11 回不合格になり、最終的にも直らなかったのは 1 回だった。この 1 回については、失敗を知らせるメモが自動で作られ、対応待ちのまま残っている。ところが同じ日の健全性ファイルは、正常・連続不合格 0 回としか書いていなかった。

調べていくと、さらにもう 1 段あった。仮に異常が書かれている瞬間があっても、記録をまとめてセッションの冒頭に知らせる処理が、その状態を見ていなかった。この処理の説明書きには「正常でないものを報告する」と書いてあるのに、実際に分岐していたのは 2 つの異常の状態だけで、停止フックが書く 3 つ目の状態はどの分岐にも入らなかった。二重に表示しないための除外条件も、同じ状態を飛ばしていた。二重表示を避けるつもりで、表示がゼロになっていた。

整理すると、同じ失敗が 2 つの理由で見えなくなっていた。

なお、読む側の処理は、数えた後に直してある。ここに書いたのは直す前の状態である。

赤ゼロは、問題が無いことを意味しなかった

構造を調べる検査もすべて走らせた。

$ python3 tools/launch_graph.py
  [赤 0件]
  [警告 15件]
     status: unassessed が 72 件(93スキル中)
     空の Step 合計 57/434(13%)

$ org_build.py --check
  ドリフトなし

どちらも緑である。同じ日に、記事 39 本と、対応待ちの失敗メモ 8 件が赤だった。赤がゼロなのは見た範囲に赤が無いだけで、93 本あるスキルのうち 72 本(77%)は、そもそも判定を受けていなかった。

テストが 108 本あっても、走る場所がなかった

数えている途中で、同じ形がもう 1 つ出てきた。今度は検査ではなく、検査を守るはずのテストである。リポジトリにはテストファイルが 108 本ある。まとめて収集しようとすると、途中で止まる。

$ pytest -q --co
ERROR collecting scripts/note_api/test_html_quality.py
import file mismatch: ...
!!!!!! Interrupted: 1 error during collection !!!!!!

原因は同じ名前のファイルが 2 か所にあることで、直すこと自体は難しくない。問題は、止まっていたことに誰も気づいていなかった点である。変更のたびに自動でテストを走らせる仕組み(CI)は用意していなかった。動いているものを見張る 22 個の記録(うち健全性記録として成立しているのは 21 個)にも、テストの項目はない。設定ファイルには pytest のコマンドが 10 行並んでいるが、これはすべて手で叩くときに確認を省くための許可で、走らせる仕組みではなかった。

実際にまとめて叩ける一角を叩くと、97 件が通った。壊れているわけではない。走っていないのは、誰も叩かない残りである。さらに、ルートの設定ファイルが登録しているテスト用の印(マーカー)は、走る一角の設定が先に読まれるせいで届かず、実行のたびに「そんな印は知らない」という警告が出ていた。後から足したテストが、走る一角に 1 本も入っていなかった。

同じ構造は、その日の修正にも出た。公開層の秘密情報の検査に付けたオプションが、指定したパスを黙って無視していた欠陥は、既存のテストでは出なかった。関数を直接試すテストはすべて緑で、落ちていたのは引数を受け取る配線だけであり、そこを通るテストが 1 本もなかった。関数を試すテストは、その関数が呼ばれているかを試していない。出口検査は 7 項目を持っていても呼び出し口が 1 箇所だったのと、同じ形である。

面を変えると、同じ検査がノイズになった

秘密情報の検査は、既定では配布するプラグインだけを見る。ここは緑だった。同じ検査を、実際に公開しているサイトの側に当てると 117 件が赤になった。内訳を全部分類すると次のとおりである。

内訳件数中身
メールアドレスの検出64すべて画像のバイト列。画像をテキストとして読んだ誤検出
禁止語の検出53すべて同じ 1 語。内部向けページを対象外とする規約の範囲だった
実際の違反0

違反は 0 件だったが、毎回 117 件の赤を見せる検査は、3 回目には誰も読まなくなる。この検査は、公開側に当てていなかったのではなく、素のままでは当てられなかった。研究記事の限界の節にも、評価は現実の問題の代理にすぎない(only proxies)という文がある。同じコードでも、当てる先が変われば別の道具になる。なお、この 0 件は既存の禁止語のリストと構造パターンに対してのもので、リストに無いものは見つからない。

論文の対策は、構造上こちらでは使えなかった

研究記事は、実行前に AI の考えている手法をすべて読む監視役を置いている。私の環境では、各プロファイルは他のプロファイルを起動できない設計にしてあるので、実行前に別のエージェントが読むという配置が作れない。代わりに置いているのは先の段の門番だが、これは意図ではなく成果物の形だけを見る。やろうとしていることが間違っている場合は捕まえられず、実例として、ある直しがワークフローを 47 日間起動不能にしていたことがあった。できあがった構文は検査され、その直しが道具を壊すことは検査されなかった。

そのときの失敗記録には、もう 1 つ学びがある。停止を知らせるアラートは「回していない」と「回せない」を同じ文言で出し、12 日間、毎回のセッション冒頭に表示されていたのに、見て通り過ぎていた。研究記事が心配しているのは監視の取りこぼしだが、私の環境で実際に起きたのは、監視が鳴り続けて無視されることだった。取りこぼしと鳴りっぱなしは、どちらも検査が効かないという同じ結果に着地する。

言えること、言えないこと

数えて言えるのは、検査は当てた経路しか緑にせず、当てていない経路がどうなっているかは検査自身からは分からない、という一点である。

言えないことも多い。出口検査の 7 項目が正しい規約かどうかは判定していない。公開済みの記事が赤になったとき、間違っているのが記事なのか検査なのかを、検査は教えてくれない。数も小さく、研究記事が扱う 10 種類・約 1,600 本の記録に対して、こちらは検査 22 件、記事 40 本、起動 1,729 回である。ログには、残っていない期間も 1 か月ほどある。モデルを訓練していないので、研究記事が測っている整合性の失敗の緩和率は測れず、当てたのは検査という装置の性質だけである。

それでも、同じ機序が個人の環境でも出たことは確かで、検査を 1 つ足す前と後で不合格が 0% から 13.5% になった理由は、環境が悪化したからではなかった。

関連して、ハーネスという考え方の経緯は AI に指示ではなく環境を渡すようになるまで に書いた。

この記事は、note に書いた次の 2 本を前提にしている。

記事の一覧へ