この記事は、連載「言わない技術」の第 9 回です。うまくいかない症状と、その対処を一つずつ並べています。各回はファイルやスクリプトを一つ置けば完結します。その仕組みがなぜ要るのかは、置いたあとに解説を読めば分かります。連載の全体像と各回の一覧は序論にあります。

今回は、推測の話です。AIは、開いていないファイルの中身を、開いたかのように話すことがあります。この記事では、その推測をどうやって止めるかを扱います。

1 作目「読まない技術」の第 4 回で、私は、AIがドキュメントを読みに行ったかは測れても、読んだ中身がそのあとのAIの操作にどれだけ効いたかは測れないと書きました。そこで確かめたのは、手順を踏んだときにだけ作られるファイルが、在るかどうかです。推測にも、同じことが当てはまります。AIがファイルを開いたかは、会話の記録に残ります。けれども、AIが書いたものが開いた中身に沿っているのか、推測なのかは、開いた記録からは分かりません。

まず一つだけ、探してみてください。

指示ファイル(CLAUDE.md や AGENTS.md)を開いて、「推測しない」「確認してから」を検索します。何行ありますか。そのうち、AIが確認しなかったときに落ちる自動テストが付いている行は、何行でしょう。

CC BY 4.0

私の指示ファイルには、1 行だけありました

推測で実装しない。既存コードを確認してから実装する

102 行のうちの 1 行です。Anthropic のプロンプト指針も、幻覚を減らすためのプロンプト例として、同じ趣旨の文を挙げています。開いていないコードについては決して推測せず、特定のファイルを示されたら、答える前に必ずそのファイルを読むように、という内容です。

私の 1 行は、正しい行です。

ただ、この行は、第 1 回で扱った「必ず実行してください」と、まったく同じ書き方をしています。どちらも、AIが読んだときにだけ守られる依頼です。AIが「確認しました」と書いても、それはAIの自己申告です。「読まない技術」の第 9 回に書いたとおり、作業を終えたAIが書く自己申告からは、完了と言うのに都合の悪いものが落ちます。そこで引いた測定では、ファイルのレビューを頼まれたAIが、頼まれたファイルを全部は読まなかった実行のうち、8 割で、最終の応答が、全部読んだと書くか、読み残したことに触れていませんでした。

だから普通は、ここで指示ファイルに、「必ずファイルを開いてから答えてください」「読んだ行番号を書いてください」のような行を足したくなります。けれども、足した行も、守られるのはAIが読んだときだけです。AIが書いた行番号も、AIの自己申告です。

同じリポジトリの別の文書は、指定したファイルに目印を書き込んで初めて書き終えることのできる形でした

手元に、体験設計の原則を並べた文書があります。「こういう体験にする」を 18 個、プログラムが読むことのできる形で書いたものです。そのうちの 1 つは、こうなっています。

<!-- experience-principle
id: A1
name: 名前を持たせない
severity: critical
artifacts:                          # ← この原則が実装されている場所
  - app/models/character.rb
guarded_by:                         # ← それを守っている自動テスト
  - test/reference/a1_name_field_absence_test.rb
forbidden_patterns: []
-->

原則の中身(どういう体験にするか)は、ふつうの文章で書きます。プログラムが読むのは、原則ごとに付けたこの 8 行です。

そのため、原則を 1 つ書くたびに、この 8 行も書く必要があります。私が数えると、18 個の原則に対して、artifacts での指定が 53 件、guarded_by での指定が 27 件でした。平均すると、原則 1 つあたりの指定は、4 箇所を超えます。

そして、原則の文書を確かめる自動テストに合格するには、ファイルを指定したうえで、次の 5 つを満たす必要があります。

自動テスト見ているもの
1全原則が artifact と guarded_by を最低 1 つ持つ
2指定した artifact が実在する
3指定した自動テストファイルが実在する
4自動テストファイルの中に # guards: A1 タグが在る
5artifact の中に @experience-principle A1 マーカーが在る

AIが自動テストに合格するための条件は、指定したファイルの中に、目印(表の 5 の @experience-principle A1 のマーカー)を書き込むことです。AIが Claude Code の編集のツールで目印を書き込むには、そのファイルに一字一句そのまま在る文字列を、書き込む位置としてツールに渡します。渡した文字列がファイルに無ければ、ツールは書き換えません。存在しないファイルを指定すれば、自動テストは 2 で落ちます。実在していても、目印を書き込まずに指定しただけなら、自動テストは 5 で落ちます。

指示ファイルは、102 行のままです。増えたのは、原則の文書に書く欄(artifacts と guarded_by)と、原則の文書を確かめる自動テストです。

あなたがAIに、設計の文書を書かせたとします。文書には、方針ごとに「この方針はこのファイルで実装されている」とファイルが並び、AIの報告には「既存のコードを確認しました」とありました。あとで会話の記録を見ると、並んだファイルのうち 1 つを、AIは一度も開いていませんでした。次に同じ文書を書かせるとき、あなたなら次のどちらにしますか。

  • 報告に、開いたファイルと読んだ行番号を書かせる
  • 並べたファイルの中に目印を書き込むことを文書の欄で求め、目印が在るかを自動テストで確かめる

答えは、目印を書き込ませるほうです。開いていないファイルが並んだまま「確認しました」と報告されたのは、確認したかを伝えているのが、作業を終えたAIの自己申告だからです。自己申告からは、完了と言うのに都合の悪いものが落ちます。「読まない技術」の第 9 回で引いた測定でも、頼まれたファイルを全部は読まなかった実行の 8 割で、最終の応答は、全部読んだと書くか、読み残したことに触れていませんでした。行番号を書かせても、書くのは同じAIで、行番号も自己申告です。開いていないファイルが並ぶことは変わらず、あなたが記録を開くまで見えません。目印を求めれば、文書が通るかどうかは、AIの報告によらず、並べたファイルの中身で決まります。目印を書き込まずに並べたファイルを落とすのは、自動テストです。

理解したことは一文にできます。

開いたかは記録に残っても、中身に沿って書いたかは測れない。だから、指定したファイルの中に書き込まなければ通らない目印を、文書の欄で要求する。

仕組み: 指定したファイルに、目印を書かせる

置くのは、表の 2 と 5 を見る自動テスト 1 本です。手元のコードでは、原則の文書を確かめる自動テスト 6 本のうちの 1 本で、それを短くしたものが次のコードです。

# test/reference/experience_principles_test.rb(手元のコードを短くしたもの)
test "each artifact carries an `@experience-principle` marker for a known id" do
  known_ids = @principles.map { |p| p["id"] }.to_set

  @principles.each do |p|
    p["artifacts"].each do |path|
      content = File.read(REPO_ROOT.join(path))          # 実在しなければここで落ちる
      markers = content.scan(MARKER_REGEX).flatten

      assert_includes markers, p["id"],
                      "principle #{p['id']}: marker not found in #{path}"
      markers.each do |id|
        assert_includes known_ids, id, "unknown principle id `#{id}` in #{path}"
      end
    end
  end
end

目印に、原則の文書に無いIDを書いても、自動テストは落ちます。自動テストに合格するのは、文書とコードの両方をそろえて書き換えたときだけです。

Anthropic のガードレールの指針には、次の一節もあります。AIに「わからない」と言うことを明示的に許可すると、誤った情報が劇的に減りうる、という内容です。私は、その許可を、指示ファイルの文の代わりに、原則の文書の欄で与えています。目印を書き込んでいないファイルは、欄に並べても、自動テストで落ちます。

CC BY 4.0 はここまで

考えさせなくなったもの

AIに考えさせなくなったのは、「このファイルはたぶんこうなっている」と推測する部分です。AIが考えること自体は、減っていません。原則を 1 つ書くのに、いまはAIが 4 箇所以上のファイルに、目印を入れます。なくなったのは、指定したファイルに手を入れずに原則を書き終えるやり方のほうです。

CC BY 4.0

注意: 目印のあるファイルは、指定したファイルより多い

正直に書きます。この仕組みが確かめるのは、指定したファイルに目印があるかどうかだけです。目印のあるファイルがどれも指定されているかどうかは、確かめていません。

数える前は、指定したファイルと目印は 1 対 1 で対応しているつもりでした。数えてみると、原則が指定しているファイルは、重複を除いて 43 個でした。

一方、@experience-principle の目印のあるファイルは 75 個あります。残りの 32 個は、どの原則からも指定されないまま、目印だけを持っています。

つまり、この仕組みが保証するのは「指定したファイルには目印がある」までで、「目印のあるファイルを全部指定した」は保証の外です。原則の文書から指定を 1 つ消しても、そのファイルの目印は残り、自動テストは合格のままです。

注意はもう一つあります。この仕組みを続けることができているのは、原則が 18 個と少ないからです。原則が 200 個になれば、同じ割合で、800 を超える箇所に目印を入れる手間がかかります。「読まない技術」の第 6 回では、自動テストに翻訳することができるのは、プログラムが真偽を決めることのできるルールだけで、判断や方針はAIに読ませる側に残すと書きました。この仕組みを当てるのは、その読ませる側に残る方針や、体験の設計です。

検証手順: 実在するファイルを、目印を書き込まずに指定する

前提

ファイルを指定する欄(原則が実装された場所と、それを守る自動テストを指定する欄)と、指定したファイルに目印を要求する自動テストを置き終え、その自動テストがいまは合格している状態から始めます。作業中の変更が無いこと(git status が空)も確かめておきます。

所要時間: 10 分

あなたが、ファイルを指定する欄に、わざと誤った指定を足して、自動テストを確かめます。足し方は 2 つあり、自動テストが見落としやすいのは 2 つ目です。

手順

  1. あなたが、ファイルを指定する欄に、実在しないファイルを 1 つ足します(app/models/does_not_exist.rb のような、無いことが明らかなパス)。自動テストを走らせ、結果を控えます
  2. あなたが、今度は実在するが目印の無いファイルを 1 つ足します(config/routes.rb のような、確実にあるファイル)。自動テストを走らせ、結果を控えます

合格条件(すべて満たすこと)

合格の条件は 4 つです。手順 1 では、自動テストが落ち、そのメッセージに足したパスが出ることを確かめます。手順 2 でも自動テストが落ち、そのメッセージが「目印が見つからない」という別の理由になっていることを確かめます。

合格しなかったとき

  • 手順 2 で落ちない —— この検証手順で確かめたいのは、ここです。その自動テストが見ているのは、ファイルが実在するかどうかだけです。AIが目印を書き込まずに指定しても、自動テストは合格します
  • 手順 1 と手順 2 で同じ理由が出る —— 自動テストが、2 つの誤った指定を区別していません。直す側(AIでも、あなたでも)は、メッセージを見ても、どちらを直せばよいかが分かりません

後始末

足した 2 つの指定を消し、自動テストが合格に戻ることと git diff が空になることを確かめます。

手順 1 は「打ち間違いの検出」、手順 2 は「目印を書き込んでいないファイルを指定した」の検出です。手順 1 で落ちたことだけを見て終えると、あなたは、実在だけを確かめる自動テストを、目印まで確かめる自動テストだと思い込みます。


CC BY 4.0 はここまで

次回は「質問に答えない技術」。AI から「A と B、どちらにしますか」と聞かれても、私は答えていません。 それでも、作業は止まりません。

連載「言わない技術」