制作記録

AIに作らせ別のAIに採点させる、改善ループのゲート設計

英会話アプリ「イエタ」の作り直しで、AIが書いたコードを別のAIが操作して採点する改善ループを回しました。偽のPASS、終了コードの見落とし、本番に作られた匿名ユーザーなど、実際の見逃しからゲートを足した記録です。

公開日
AI の利用
AI が作業記録をもとに下書きし、記録との照合を行ったうえで、運営者が内容を確認して公開しています。 編集方針

英会話アプリ「イエタ」(旧名「はなせるAI」)を2026年10月に作り直したとき、コードを書く AI と、それを確かめる AI を分ける改善ループを組みました。10月2日から5日までに135本のコミット(マージ2本を含む)を積み、ループの決まりごとを書いた文書は v1 から v4.3 まで8つの版を重ねました。版を上げたきっかけの多くは、「ゲートを通ったのに、あとから見つかった不具合」です。評価の運用でつまずいた件もあります。ゲートとは、変更を次に進めてよいかを決める関門です。最初から正しく設計できたわけではなく、見逃しが起きるたびに、それを二度と通さない検査を足してきました。その記録です。

ループの形

改善ループは3層です。L1 は1コミットごとに「正しく作れたか」、L2 は「正しいものを作っているか」、L3 は「判定の仕組みが当たっているか」を問います。L3 では5イテレーションごとにゲートを点検し、直したら改訂履歴に1行残します。

根本の決まりは「作った本人に採点させない」です。実装したエージェントの報告だけでは合格にしません。本番への反映や、お金・規約にかかわる決定は「人間キュー」に積み、運営者の私が答えます。

ゲート対象判定するもの
G1 機械すべての変更解析とテストをまとめたスクリプトの終了コード
G2 会話品質プロンプトやモデルの変更固定の問題セットを別のモデルが採点
G3 敵対的レビューサーバー・認証・課金白紙のレビュー用エージェント
G4 実機画面・音声作者がビルドを動かして確認
G4b 全体構成認証・課金・回数上限エミュレータとモックの OpenAI を使った E2E
G5 コスト外部 API の追加・変更月額コストの試算
G6 製品区切りごと・公開前白紙のエージェントがアプリを操作して採点

E2E は画面を端から端まで自動で操作する検査、エミュレータは Firebase の認証やデータベースを手元で模したものです。

G6 の採点表は、初回体験・会話の核・学習の残り方・信頼感・収益の健全性・AI 品質の6軸を各0〜5点で付けます。合格は「ブロッカー0件、かつ加重平均3.5以上」です。ブロッカーは、正誤判定の誤りや、無料枠をただで回避できることなど、1つでもあれば不合格になる欠陥です。AI 品質はモックでは測れないため、G6 では採点しません。

122件のテストが通っても不合格だった

再始動の初日、まず現状を測りました。flutter analyze は指摘0件、flutter test は122件すべて通過です。

同じ日に AI がアプリを実際に操作して採点すると、加重平均2.2、ブロッカー5件で不合格でした。復習の1問目に答えると問題が1つずれ、答えていない問題に「不正解」が出る。Web では広告を見ずに会話の回数が増える。解約方法も特定商取引法の表記もない。122件のテストは、どれもこの種類の不具合を見ていませんでした。

これを受けて v1.1 で G6 を正式なゲートにしました。初回の監査は改善も担当する AI が行ったので、2回目からは作業の文脈を持たない白紙のエージェントに任せています。

直す前でも通るジャーニー

操作して初めて見つかった不具合がもう一つありました。AI の返事を待つあいだに打った文字が消え、入力欄のフォーカスが外れる不具合(P14)です。G4 で3回 FAIL したあと、ブラウザで document.activeElement(いまフォーカスを持つ要素)の移り変わりを記録して、ようやく原因にたどり着きました。そこで v2 に「同じゲートで2回続けて FAIL したら、直すのを止めて観測を増やす」を足しました。原因と直し方は、Flutter Web の PWA でイエタを届けた判断と配信についての制作ノートに書いています。

直したあと、同じ不具合が戻らないよう自動のジャーニーにしました。ジャーニーは、操作の手順と合否の条件を書いたファイルです。ここで2つ目の見逃しが起きました。最初のジャーニーは読み上げ OFF の条件で書いていたため、直す前のコードでも PASS したのです。このジャーニーの条件では、読み上げ中の表示が入力欄の直前に差し込まれるときにしか再現しませんでした。新しい利用者の既定は読み上げ ON です。書き直したジャーニーの主な行は次のとおりです。

"steps": [
  "seed:@fixtures/beginner_tts_on.json",
  "type:I like music.",
  "press:Enter",
  "wait:400",
  "type:I play the guitar.",
  "expectActive:INPUT",
  "expectValue:I play the guitar."
]

v3 では「新しいテストは、直す前のコードで FAIL することを一度確かめてから採用する」「利用者の既定の条件で書く」と決めました。

アクセシビリティを有効にすると2通目が送れない現象は、自動操作の道具の癖として扱っていました。3回目の G6 の評価者が、スクリーンリーダーの利用者でも起きるかは人の手で確かめる価値があると書き、フォーカスの推移を150ミリ秒ごとに記録して調べると、アプリの不具合でした。ロールプレイの画面には、P14 と同じ作りの不具合も残っていました。v4.2 で「落とし穴と書く前に観測で確かめる」「直したら同じ作りを全画面で探す」を足しています。

終了コードを見ずに PASS と書いた

10月2日の夜のコミット(accdebd)の本文には「flutter analyze → No issues found」とあります。実際には警告が1件出ていました。検査の出力を tail に流していたため、終了コード(コマンドの成否を表す数値)が最後の tail のものになり、失敗が見えなかったのです。次のコミットで警告を直し、前の本文が誤りだったと明記しました。

v3 では G1 を1本のスクリプトにまとめ、合否はその終了コードだけで判断すると決めました。冒頭には、この件そのものを書いてあります。

# 出力を tail などに流すと終了コードが見えなくなる(2026-10-02 に実際に見落とした)ので、
# このスクリプトの終了コードで合否を判断すること
set -euo pipefail

E2E が本番に匿名ユーザーを作っていた

10月2日から翌3日にかけて、E2E を流すたびに、本番の Firebase Authentication に匿名ユーザーが作られていました。AI をモックに置き換えたビルドが本番の Firebase 設定を持っていて、起動時の匿名ログインが本番に届いていたのです。エミュレータ版の再読み込みでも、接続の順番の都合で本番に届いていました。

エミュレータは demo- で始まる、本番に存在しないプロジェクト ID で動かします。ジャーニーの実行中は本番の Firebase と Functions のドメインへの通信を遮断し、遮断した宛先をログに出します。

この遮断にも穴がありました。2回目の G6 の評価者が、報告の「確認できなかった点」に、全体構成版で匿名ログインが失敗すると書いてきたのです。エミュレータの認証の URL は http://localhost:9199/identitytoolkit.googleapis.com/... のように、パスに本番のドメイン名を含みます。URL 全体の文字列で判定していたため、手元のエミュレータまで遮断していました。判定はホスト名に変えています。

const isProdHost = (host) => /(^|\.)(googleapis\.com|cloudfunctions\.net|firebaseio\.com|firebaseapp\.com|web\.app)$/.test(host);

本番に残った匿名ユーザー29件は、10月5日に私の許可を得て AI が削除しました。データベースに中身が無いことを確かめてからです。v3 で「検証は本番に一切触れない」を独立した節にしました。

評価中にビルドを作り直した

G6 の2回目では、評価者がアプリを操作している最中に、改善を担当するエージェントが build/web を作り直しました。評価者は途中でビルドを複製して固定し、評価を続けました。それでも、35分古い全体構成版のプラン表に、外したはずの「発音評価」が残っているのを見て、新旧を取り違えかけています。

v4 からは、評価者に build/g6_<日時>/ という固定したコピーを渡し、評価中は作者が触れないことにしました。3回目には、評価に使うエミュレータが作者側のバックグラウンド実行の時間制限(既定30分)で止まり、v4.1 で「常駐プロセスは時間制限を最大にして起動する」を足しました。

評価者の報告には「確認できなかった点」を必ず書かせ、採点外の気づきも書いてもらいます。前の節の遮断の穴も、Hosting のキャッシュ設定の問題も、そうした欄から見つかりました。評価者はゲート自体の点検役も兼ねています。

ときどき起きる不具合

表現ノートのボタンの文字が切れる不具合は、フォントの読み込みの順番で出たり出なかったりしました。修正前のコードでも、ジャーニーは3回中2回 PASS します。v4 では、先に失敗率 p を測り、修正前でも全部通ってしまう確率 (1−p)^N が5%未満になる回数 N だけ流すと決めました。この件は開いて2.5秒後の撮影なら5回中5回切れたので、その条件で修正後に5回とも切れないことを確かめています。

点数の推移

G6 は5回行いました。AI 品質を除く5軸の点数は次のとおりです。

回日付初回体験会話の核学習の残り方信頼感収益の健全性加重平均ブロッカー
110-02332212.25
210-034322.522.672
310-034.54.03.53.02.03.461
410-03〜044.54.54.02.52.03.632
510-044.54.54.03.02.03.711

2回目のブロッカーの一つは、穴埋めの復習で great と答えると「不正解」になるものでした。AI が返すおすすめ表現は文なので、末尾にピリオドが付いたまま保存され、答え合わせが句読点まで比べていたのです。

復習の穴埋めで great と入力し、不正解・正解 great. と表示された画面
G6 2回目の評価者が撮影(2026-10-03、AI をモックにしたビルド)。句読点の有無で不正解になっていた
同じ種類の問題で great と入力し、正解と表示された画面
G6 3回目の評価者が撮影(2026-10-03、同上)。採点は直ったが、説明文に答えが見えていることを4回目に指摘された

点数はまっすぐ上がったわけではありません。4回目は信頼感が下がり、ブロッカーも増えました。評価者がアカウントまわりを初めて深く調べ、Web の Google ログインが必ず失敗することなどを見つけたからです。

その修正の途中では、データ同期のレビュー(G3)が2回続けて FAIL しました。2回目の指摘は1回目の修正から生まれていて、時刻で区切る削除の記録が、匿名で試した端末から既存アカウントの会話を消し得る穴になっていました。3回目のパッチは当てず、削除の記録を ID で持つ設計に変えています(v4.3)。

5回目に残ったブロッカーは1件で、無料枠の回数を守る仕組みに抜け道がある、というものでした。一方で、登録なしで始められることは初回体験の5点の条件です。どちらを取るかは製品の判断なので、ゲートは不合格と書いたうえで人間キューに回しました。

足したゲートの一覧

起きたこと変えたこと版
122件のテストが通っても、操作すると不合格(復習のずれなど)G6 製品ゲートと採点表を新設v1.1
テストが P14 を見逃し、G4 でも3回 FAIL2回続けて FAIL したら観測を増やす。G4 の確認をジャーニーに残すv2
P14 のジャーニーが修正前でも PASS修正前のコードで FAIL を確かめてから採用v3
終了コードを見落として PASS と記録G1 を1本のスクリプトにし、終了コードだけで判断v3
E2E が本番の認証に匿名ユーザーを作ったdemo のプロジェクト ID、本番ドメインの遮断v3
起動のたびに登録ユーザーが匿名に置き換わる不具合を単体テストが見逃した全体構成の E2E(G4b)を新設v3
ときどき起きる不具合のジャーニーが修正前でも3回中2回 PASS失敗率を測ってから回数で合否を出すv4
G6 の評価中にビルドを作り直した固定したコピーを渡すv4
評価に使うエミュレータが30分で止まった常駐プロセスは時間制限を最大で起動v4.1
道具の癖と思った現象がアプリの不具合だった落とし穴と書く前に観測で確かめるv4.2
同期の修正が新しい穴を作った3回目のパッチの前に設計を見直すv4.3

これから

v4 の点検では、G1・退行ジャーニー・G3・G4b・G6 のどれもが直近で問題を見つけており、外せる部品はないと判断しました。ただ、ここまでの合否はすべて公開前の代わりの指標で、合格線の3.5も仮置きです。公開後は、継続率や有料への転換率といった利用者の行動と照らして、合格線と採点の軸を直す予定です。

イエタの今の姿はイエタの製品ページで紹介しています。会話1回あたりの AI の原価は、AI の1回あたりの原価を実測した制作ノートにまとめています。

制作ノートの一覧へ