
ほとんどの開発者がAIコーディングエージェントを使うときの流れはこうだ。チャットの窓を開いて、「設定ページにダークモードの切り替えを追加して」と打ち込む。エージェントは何かを返してくる。一応動く。でもところどころ粗い。次の二十分は、開発者が行ったり来たりして直し続ける。
それから、このモデルはたいしたことないと決めつける。
モデルは悪くない。問題はプロンプトだ。
プロンプトの曖昧さは、そのままモデルがあなたの代わりに下す判断になる。ダークモードの設定はセッションをまたいで保持すべきか。OSの設定に従う必要があるか。保存先はどこか。localStorageか、データベースのユーザープロフィールか、クッキーか。どのコンポーネントライブラリを使っていて、テーマはどう動くのか。
あなたは何も言わなかった。だからモデルは推測する。当たることもある。外れることも多い。その結果、ある環境では動いて別の環境では壊れるコード、あるいはユーザーがすでに設定した項目を無視するコンポーネントができあがる。
これは情報の問題だ。
本気でAI支援の開発フローを作っているチームがたどり着いたのは、こういうことだ。モデルは、ほとんどの開発者がどう頼めばいいかを知っている以上に、仕事をうまくやるために何が必要かを知っている。
モデルは膨大な量の、仕様の明確なエンジニアリングタスクを処理してきた。だから完全な機能リクエストがどんな形か、曖昧なバグ報告に何が欠けているのが普通かを知っている。コツは、何かを頼む前に、足りないものを教えてもらうことだ。
流れはこうなる。
最後のステップは任意だが、やる価値がある。エージェントに「私の回答に基づいて、元の依頼を完全なタスク仕様に書き直して」と頼める。返ってくるのは、モデル自身が実行に十分だと判断したプロンプトだ。それを実際のタスクとして提出する。
これをタスク実行の逆プロンプトエンジニアリングと呼ぶ人もいる。最初から完璧なプロンプトを書くのではなく、モデルに完璧なプロンプトに何が入るかを出させて、そこから進める。
設定ページにダークモードの切り替えを追加したいとしよう。二つのやり方でどう違うかを見ていく。
素朴なやり方:
「設定ページにダークモードの切り替えを追加して。」
エージェントは、ハードコードされた切り替え付きのコンポーネントを作り、設定をコンポーネントの状態に保存してリロードで消え、既存のテーマシステムを無視し、すでにあるCSS変数やテーマトークンではなくインラインスタイルを使う。
次の一時間は直しに費やす。
逆プロンプトのやり方:
同じ一文から始めて、こう尋ねる。「始める前に、これを正しく実装するには何を知っておく必要がある? 何を明確にすべき?」
エージェントは次のようなことを返す。
- 設定はどこに保存すべきか(localStorage、データベース、クッキー)
- 既存のテーマシステムはあるか(next-themes、CSS変数、Tailwindのdark: バリアント)
- 切り替えはOSレベルのダークモード設定をデフォルトとして尊重すべきか
- 設定ページのどこに置くべきか。既存の「外観」セクションはあるか
- ユーザーがログインしている場合、設定はセッション間で同期すべきか
これらの質問に答える。アプリは next-themes を使っていて、設定は既存の PATCH /users/me エンドポイント経由でユーザープロフィールに保存され、設定ページには特定のコンポーネントパターンに従う「外観」セクションがすでにあると伝える。
これでエージェントは必要なものを手に入れた。作られる実装は、正しいテーマプロバイダーにつながり、既存のAPIを使い、既存のUIパターンに合い、OSの設定へのフォールバックも処理する。直すところは何もない。
バグ報告は、曖昧なプロンプトの被害が最も大きい場所だ。文脈が足りないモデルは、不出来なコードを出すだけでなく、自信満々で間違ったコードを出す。
誰かが「ログインボタンがときどき動かない」とバグを報告したとする。
素朴なやり方: それをそのままエージェントに貼り付ける。エージェントは認証コードを探し始め、怪しいasync/awaitのパターンを見つけ、おそらくそれだろうと決めて関数を書き直し、PRを出す。バグはまだある。変更した関数は無関係だった。
逆プロンプトのやり方:
エージェントにバグ報告を渡して、こう尋ねる。「これを効果的に診断するには何を知っておく必要がある? コードに触る前に、シニアエンジニアはどんな質問をする?」
エージェントはこう返す。
- 「動かない」とはどういう意味か。クリックに反応しないのか、クリックはするが何も起きないのか、エラーが表示されるのか
- 毎回再現するのか、それとも断続的か。何かパターンはあるか(特定のブラウザ、ネットワーク環境、ユーザー状態)
- 発生時にコンソールエラーや失敗するネットワークリクエストはあるか
- ボタンのクリックを処理しているのはどのコンポーネントで、認証を処理しているのはどのサービスか
- 以前から壊れていたのか、最近のリグレッションか。リグレッションなら、何が変わったか
報告者に戻って答えを得る。断続的で、ユーザーがページに十分以上滞在したときだけ起きる。コンソールエラーはないが、ネットワークリクエストはまったく送信されない。これはまったく別のバグだ。考えられる原因はトークンの期限切れか、イベントハンドラー内の古いクロージャだ。
その情報をエージェントに渡すと、今度は正しい場所へ行く。初期レンダリングから認証トークンを捕捉する、依存関係が古い useCallback を見つける。修正は一つの的を絞った変更だけだ。
AI支援のコーディングが交渉のように感じられるのは、最初のプロンプトがモデルにあまりに多くの判断を委ねるからだ。何ラウンドも出力を磨き直して行ったり来たりする。ラウンドごとの修正は、あなたが指定しなかったせいでモデルがした推測を正す作業だ。
逆プロンプトは、そうしたラウンドの大半を最初の一回の会話にまとめる。モデルの質問は、自分ならどんな判断を下したかを正確に教えてくれる。それをあなたが代わりに下す。完全な情報があったので、出力は最初から正しい。
もう一つ副次的な利点がある。モデルが投げる質問はチェックリストになる。新しい設定の追加や認証バグの修正のような似たタスクに同じテクニックを使い続けると、質問にだんだん見覚えが出てくる。やがて聞かれなくてもプロンプトにそれらの詳細を含めるようになり、プロンプトの基本の質が上がる。
このテクニックは、ツールとファイルへのアクセスを持つエージェント、つまりCursorやエージェントモードのGitHub Copilotなどで最も効果を発揮する。コードベースを読めるエージェントは、何も見ずに作業するエージェントよりずっと具体的な質問をする。
二往復の流れ(何が必要か -> ここにある -> ではタスクを)は確かに手間を増やす。小さく明確なタスクには過剰だ。だが複数のファイルに触れる仕事や既存システムとの統合は、実装に直行するより一貫して良い結果になる。
ステップ3で作られた仕様は保存する価値がある。再利用できるテンプレートだ。次に誰かがユーザープロフィールに保存される設定を追加する必要が出たとき、すでに仕様の明確なプロンプトを持っている。
ほとんどの開発者がエージェントにプロンプトを書く方法は、質を犠牲にして速度を最適化している。一文を素早く入れて、平凡な出力を受け取り、十分間の掃除をする。それが一日に何十回も繰り返される。
逆プロンプトのアプローチはこのトレードオフを逆転させる。最初に少し時間をかけて、モデルが必要なものを持っていることを確認する。その見返りに、出力は求めたものに近づき、直す時間は減る。
あるタスクにとって最良のプロンプトは、モデルが自分に必要だと教えてくれるものだ。
© Melvin Laplanche - All rights reserved.