この記事は、連載「言わない技術」の第 9 回です。各回はファイルやスクリプトを一つ置けば完結します。連載の全体像と各回の一覧は序論にあります。
今回は、推測の話です。AI は、開いていないファイルの中身を、開いたかのように話すことがあります。それをどうやってさせないようにするかを扱います。
前作「読まない技術」の第 4 回で、読んだかどうかは測れないと書きました。測れるのは、ドキュメントを見なければ作られないファイルが在るかどうかだけです。同じ問いが、推測でも立ちます。読んだかどうかが測れないなら、推測したかどうかも測れません。
まず一つだけ、探してみてください。指示ファイル(CLAUDE.md や AGENTS.md)を開いて、「推測しない」「確認してから」を検索します。何行ありますか。そのうち、確認しなかったときに何かが落ちるのは何行でしょう。
CC BY 4.0
私の指示ファイルには、1 行だけありました
推測で実装しない。既存コードを確認してから実装する
102 行のうちの 1 行です。Anthropic のプロンプト指針も、幻覚を減らすためのプロンプト例として同じ文を挙げています——開いていないコードについて決して推測するな。特定のファイルに言及されたなら、答える前に必ず読め。
正しい行です。そして、第 1 回で扱った「必ず実行してください」とまったく同じ形をしています。読まれたときだけ効く。しかも今回はもっと悪くて、確認したかどうかは測れません。「確認しました」と書かれても、それは自己申告です。前作の第 9 回に書いたとおり、自己申告は完了に寄ります。
だから普通は、ここで指示を足したくなります。「必ずファイルを開いてから答えてください」「読んだ行番号を書いてください」。⚠️ この回はそれをやりません。 増やす側は第 6 回の役目で、この連載で「言え」と言うのは 1 回だけと決めています。
同じリポジトリの別の場所は、開かないと書けない形でした
手元に、体験設計の原則を並べた文書があります。「こういう体験にする」を 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 行が付いていないと原則として成立しません。 数えると、18 原則に対して artifacts の参照が 53、guarded_by の参照が 27。1 つの原則を書くのに、平均 4 箇所以上のファイルを名指ししています。
そして、名指しするだけでは通りません。
| 自動テスト | 見ているもの |
|---|---|
| 1 | 全原則が artifact と guarded_by を最低 1 つ持つ |
| 2 | 名指しした artifact が実在する |
| 3 | 名指しした自動テストファイルが実在する |
| 4 | 自動テストファイルの中に # guards: A1 タグが在る |
| 5 | artifact の中に @experience-principle A1 マーカーが在る |
AI は、参照先のファイルを開いて、その中に目印を書き込まないと通りません。存在しないファイルを挙げれば 2 で落ち、実在するけれど開いていないファイルを挙げれば 5 で落ちます。
指示は 1 行も増えていません。 増えたのは、書式のほうの要求です。
理解したことは一文にできます。
読んだかどうかは測れない。だから、読まなければ書けないものを、書式のほうから要求する。
仕組み: 名指しした先に、目印を書かせる
置くのは自動テスト 1 本です(実物はメタテスト 6 本ですが、骨はこれだけです)。
# 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 のガードレールの指針は、こうも書いています——「わからない」と言うことを明示的に許可すると、誤情報は劇的に減る。この構造は、その許可を書式のほうから与えています。目印を書き込めないものは、原則として書けない。書けないものを無理に書く道が、そもそもありません。
CC BY 4.0 はここまで
考えさせなくなったもの
「このファイルはたぶんこうなっている」の部分です。考える量が減ったのではありません——原則を 1 つ書くのに、いまは AI が 4 箇所以上のファイルを開いて目印を入れます。減ったのは、開かずに書ける道のほうです。
CC BY 4.0
注意: 目印のほうが、多い
正直に書きます。この構造が保証しているのは片方向だけです。
数える前は、名指しと目印は 1 対 1 で対応しているつもりでした。そうではありませんでした。原則が名指ししている実ファイルは 43。一方、@experience-principle の目印が書かれているファイルは 75 あります。32 ファイルは、どの原則も名指ししていません。
つまり——「名指ししたものは開いた」は保証されます。「開いたものを全部名指しした」は保証されません。 名指しを 1 つ消せば、目印は宙に浮いたまま、自動テストは合格のままです。
もう一つ。この形は 18 個だから成立しています。 原則が 200 個あれば、目印を入れるコストのほうが先に破綻します。⚠️ すべてのルールをこの形にするのではありません。 これは、前作の第 6 回で「自動テストに翻訳できない」と切り分けたほうの——方針・体験の設計に当てる形です。
検証手順: 実在するファイルを、開かずに名指しする
前提
- 名指しの書式(参照先のファイルを挙げる欄)と、その先に目印を要求する自動テストを置き終えていること
- その自動テストが、今は通っていること
- 作業中の変更が無い状態(
git statusが空)から始めること
所要時間: 10 分
壊し方は 2 つあり、難しいのは 2 つ目です。
手順
- あなたが、名指しの一覧に実在しないファイルを 1 つ足します(
app/models/does_not_exist.rbのような、無いことが明らかなパス)。自動テストを走らせ、結果を控えます - あなたが、今度は実在するが目印の無いファイルを 1 つ足します(
config/routes.rbのような、確実にあるファイル)。自動テストを走らせ、結果を控えます
合格条件(すべて満たすこと)
- 手順 1 で、自動テストが落ちる
- 手順 1 のメッセージに、そのパスが名指しされていることが出る
- 手順 2 でも、自動テストが落ちる
- 手順 2 のメッセージは、「目印が見つからない」という別の理由になっている
合格しなかったとき
- 手順 2 で落ちない —— ⚠️ ここが本体です。 その自動テストは実在しか見ていません。開かずに名指しする道が空いたままになっています
- 手順 1 と手順 2 で同じ理由が出る —— 2 つの壊れ方を区別できていません。直す側(AI でも、あなたでも)が、どちらを直せばよいか分かりません
後始末
- 足した 2 行を消し、自動テストが合格に戻ることと
git diffが空になることを確かめます
⚠️ 手順 1 は「打ち間違いの検出」、手順 2 は「開いていないものを名指しした」の検出です。手順 1 だけを確かめて満足すると、実在チェックしか無い自動テストを、実在+確認の自動テストだと思い込みます。
CC BY 4.0 はここまで
次回は「質問に答えない技術」。AI から「A と B、どちらにしますか」と聞かれても、私は答えていません。それでも、作業は止まりません。
連載「言わない技術」
- ← 前回: 第 8 回 AI にダメ出ししない技術
- → 次回: 第 10 回 質問に答えない技術
- 全回の一覧: 序論