---
title: "AIの意見を聞かない技術 第6回：AIの確認依頼を断る技術"
author: garplab
publisher: TypingTube
license: CC BY 4.0
license_url: https://creativecommons.org/licenses/by/4.0/
license_scope: 「CC BY 4.0」の印から始まる節（仕組み・検証手順・コード）。印の無い本文は著作権を留保
canonical: https://typing-tube.net/articles/kikanai-06-production
series: "AIの意見を聞かない技術"
language: ja
---


> この記事は、連載「AIの意見を聞かない技術」の第 6 回です。各回はファイルやスクリプトを一つ置けば完結します。連載の全体像と各回の一覧は[序論](https://typing-tube.net/articles/kikanai-intro)にあります。

今回は、AI から来る目視確認の依頼の話です。AI の作業は毎回、**「ローカルで画面を開いて、動作をご確認ください」で終わります。**私は、その依頼に応えていません。

前作「読まない技術」の[第 12 回](https://typing-tube.net/articles/1769779c2f1b4b)で、AI が仕上げた作業結果そのものを、読まずに済ませる形を置きました。今回は、その作業結果を「画面を開いて確かめてください」と頼まれたときの話です。**確認のやり方には安いものと高いものがあって、一番高くつくのは撮影ではありませんでした。人に頼むことです。**

まず一つだけ、数えてみてください。直近のセッションで、AI から「**画面でご確認ください**」と言われた回数です。そのうち、実際に画面を開いたのは何回でしたか。

## 目視で確かめられるのは、自分が思いついた場面だけ

目視確認は、確かめ方の中でいちばん高くつきます。人がサーバーを起動し、画面を開き、条件を揃え、目で見て、戻る。しかも**確認できるのは、その人が思いついた場面だけ**です。十数言語で提供しているサービスで、私が開くのは日本語の画面です。対戦は相手が要ります。ライブイベントは時間帯が要ります。**確認したつもりの範囲は、実際に使われている範囲よりずっと狭い。**

そして目視には、合否の基準がありません。「ちゃんと動いているように見える」が答えで、何を見て合格にしたかは残りません。だから次のセッションの AI は、また同じ依頼をしてきます。

私の手元の本番プロジェクト——[typingtube](https://typingtube.net) という、YouTube の音楽動画でタイピング練習ができる Web サービスです——で、これがはっきり出た機能があります。単語帳の対戦です。2 人のプレイヤーが同じ部屋に入り、同じ順番で単語を打ちます。「**2 人に同じ順番で出題されているか**」は、画面を目で見ても分かりません。それぞれが独立して進むので、表示中の単語は簡単にずれます。実際、最初は私が 2 つの画面の表示を突き合わせて判定し、**偽の合格**を出しました。

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

> **目視確認の依頼は、合否の判断を人に渡している。だから応える代わりに、合否を機械が決めるスモークテストを置く。漏れたものは、本番で踏んだ人から届く。**

## 仕組み: スモークテストを 1 本書き、合否は DB で決める

置くのはスモークテスト 1 本です。スモークテストというのは、実際のブラウザで主要な操作を最後まで通して、壊れていないかを見るテストのことです。Playwright を Docker で動かし、データを作り、2 人ぶんのブラウザで対戦を最後まで打ち、終わったらデータを消す。ここまでは普通です。違うのは合否の決め方で、**画面ではなく、残ったデータを見ます。**

```ruby
# lib/tasks/dev_smoke.rake（実物からの簡約版）— スモークテストが終わったあと、画面ではなく DB を見る
orders = VocabularyPlayLog.where(battle_room_id: room.id).map do |log|
  log.entry_play_logs.in_play_order.pluck(:vocabulary_entry_id)
end
puts "entry_order_match=#{orders.uniq.size == 1}"      # 2 人に同じ順で出たか

plan = VocabularyBattleEntryOrder.call(version: record.vocabulary_deck_version,
                                       play_mode: "shuffle", shuffle_seed: record.shuffle_seed)
server_ids = plan.entries.map(&:id)
puts "server_order_match=#{server_ids == orders.first}" # サーバーが決めた順と同じか
```

結果は「Smoke FAILED (playwright=1 verify=1)」のように、2 つの数で返ります。Playwright が最後まで通ったかと、DB の判定が揃ったか。どちらかが落ちれば失敗です。スモークテストは AI が自分で走らせます。走らせるかどうかは、前作の増補回の宣言（走らせる前に、何を確かめたいのかを書かせる回）に任せてあります。私が読むのは、この 2 つの数だけです。

## 漏れたものは、本番で踏んだ人から届く

スモークテストが見るのは、書いた場面だけです。書かなかった場面は本番に出ます。そこは確かめに行かず、**踏んだ人から機械へ届く経路**で受けています。報告のボタンは作っていません。人に押させるのではなく、機械が勝手に送ります。

YouTube 側で動画が削除・非公開になると、こちらは何もしていないのに、ある日から再生できなくなります。プレイヤーは前からそのエラーコードを画面に出していましたが、サーバーには伝えていませんでした。私はそこを繋いだだけです。

```markdown
再生エラー（YT コード 100 / 101 / 150 …）
   ↓ 自動送信（ユーザーの操作は不要）
報告 1 件
   ↓ その場で YouTube API に問い合わせ（二次確認）
NG なら自動で一覧から外す
   ↓ 日次バッチで再確認
復活していれば自動で一覧に戻す
```

報告 1 件で隠すのは乱暴に見えますが、隠す前に機械が YouTube API へ聞き直します。人の報告は「見に行く場所」を教えているだけで、判定は機械がやる。**隠す判断も、戻す判断も、人間を通りません。**

主観の入る問題——歌詞のズレ、音ズレ、要望——は、この経路に載せていません。機械が真偽を決められないからです。そちらは**不具合報告のフォーム**に載せます。領域（タイピング / 対戦 / 歌詞表示…）と種別（不具合 / 要望 / 歌詞の間違い）で分類され、未読・既読・対応済みの状態を持ちます。**機械が判定できるものは自動で処理し、できないものは人のところに一覧で溜まる。** 私が見るのは、溜まった一覧のほうだけです。

## 確認しなくなったもの

画面です。ローカルも、本番も、開いて回ることをやめました。AI から「ご確認ください」と来ても、返すのはいつもの「次の作業を進めてください」です。代わりに見ているのは、スモークテストが返す 2 つの数と、管理画面に出る 2 つの数字——過去 7 日に一覧から外した数と、戻した数——です。数字が動かないうちは、開きません。

## 注意: 最初の 1 人は、必ず踏みます

正直に書きます。**本番の経路は、最初に踏んだ 1 人にエラー画面を見せています。** 押し付けが許されると判断した根拠は 2 つで、戻すことが前提なので誤検出しても被害が小さいこと、対戦やライブイベントで使用中の動画は自動で外さず保留することです。**踏ませてよいのは、踏んだ被害より、放置したまま全員が踏み続ける被害のほうが大きいときだけ**です。条件を書かずにこの形だけ真似すると、たぶん事故ります。

## 検証手順: 判定を 1 つ反転して、失敗になることを確かめる

**前提**

- スモークテストを 1 本書き、**合否をデータで決める判定**（この回の例では、対戦の後に DB に残ったデータを見る部分）が入っていること
- 手元でスモークテストが最後まで通ること

**所要時間**: 20 分（スモークテストの実行時間を含みます）

**手順**

1. **あなたが**、データを見ている判定を 1 つ反転します（本文の例なら、出題順の一致を見る行を「**不一致なら合格**」に書き換えます）。⚠️ 反転するのは**判定の行だけ**です。テストの操作の側は触りません
2. **あなたが**、スモークテストを走らせます
3. **あなたが**、本番の経路も一度だけ確かめます。報告の送信先をわざと壊し、報告の件数を見ます

**合格条件**（すべて満たすこと）

- 手順 2 で、結果が**失敗**になる
- 手順 2 の失敗が、**画面の操作の側ではなく、データの判定の側**で立っている（この回の例では `playwright=0 verify=1` に当たる形）
- 手順 3 で、**報告が 0 件になる**

**合格しなかったとき**

- **画面の操作の側だけが失敗している** —— あなたが反転した判定は、合否に効いていません
- **手順 3 で報告が届き続ける** —— 壊したつもりの送信先が、実際の経路ではありません

⚠️ 手順 3 が本体です。**報告 0 件は「不具合が無い」とも「経路が切れている」とも読めて、数字だけでは区別が付きません。** 一度壊して 0 になるのを見ておくと、次に 0 が続いたときに何を疑えばよいかが分かります。

**後始末**

- 反転した判定の行を戻し、もう一度走らせて合格に戻ることを確かめます
- 手順 3 で壊した送信先を戻し、報告が届くことを確かめます

---

次回は「AIの話を聞かない技術」。**AI が少しズレたことを言っても、私は説明していません。**それでも、AI は正しい向きに戻ります。

---

**連載「AIの意見を聞かない技術」**

- ← 前回: [第 5 回 自動テストを書かない技術](https://typing-tube.net/articles/kikanai-05-machine-check)
- → 次回: [第 7 回 AIの話を聞かない技術](https://typing-tube.net/articles/kikanai-07-no-listening)
- 全回の一覧: [序論 断ったはずの提案が、また来る](https://typing-tube.net/articles/kikanai-intro)
