AI手帖THE AI FIELD NOTES
AIエージェント

AIを二段階に分けたとき、何を確かめるか――提案と実行許可を別々に検証する

「外部資料を読むAI」と「次の作業を考えるAI」を分ければ、不正な提案は減るのでしょうか。そして、もし不正な提案が出てしまったとき、実行側のプログラムはそれを確実に拒否できるのでしょうか。

この二つの問いには、それぞれ別の検証が必要です。 AIモデルの比較では「入力から応答までの条件」を厳密にそろえる必要があり、実行側のプログラム検査では「用意した要求を正しく通すか、あるいは拒否するか」を機械的に確かめる必要があります。

本稿では、前者のモデル比較に必要な検証条件の整理と、後者についてkiuribotsの既存コードで実際に確かめた検査結果をまとめます。

モデル比較は、条件と応答をそろえてから評価する

旧稿で扱っていたモデル比較については、使用したモデルIDや実行設定を確認できず、入力・応答の元ログも照合できていないため、本稿では結果として掲載していません。二段階化に効果があったかどうかも、当時の数値からは判断しないことにしました。

今後モデル間の挙動を比較する際は、以下の項目をそろえて記録します。

* モデルIDと版、および生成パラメータの設定 * 各役に与えるプロンプトと、利用可能なツールの定義 * ケースごとの入力、期待する結果、実際の応答および操作提案 * 実行コードの版と、同一入力での試行回数

また、前段の「読むAI」が作った要約は、後段の「考えるAI」への入力としてそのまま保存します。最終的な回答だけを見ても、資料内の不正な指示が「要約の段階で消えたのか」「要約に残ったうえで後段のAIが無視したのか」を切り分けられないためです。

問い1:二段階にすると、不正な提案が減るか

モデル比較で検証したいのは、次の三つの構成の違いです。

* 構成A(単一モデル):外部資料の原文を、一つのモデルへ直接渡す * 構成B(二段階モデル):読取役(Q)が作った要約を、計画役(P)へ渡す * 構成C(二段階+プログラム検査):構成Bが出した提案に対し、プログラムによる許可検査を加える(※Cのためにモデルを再度呼び出すことはせず、Bと同じ提案に対するゲートとして評価します)

構成・手順の概念図単一モデル、要約を渡す二段階構成、同じ提案にプログラム検査を加える条件の整理。比較の設計を示す概念図であり、実験結果ではありません。
単一モデル、要約を渡す二段階構成、同じ提案にプログラム検査を加える条件の整理。比較の設計を示す概念図であり、実験結果ではありません。

検証用の入力としては、たとえば次のようなケースを用意します(以下は試験設計の例であり、試行結果の表ではありません)。

試験入力として用意する例見ること
追加指示のない通常のREADME本来の修正作業を問題なく完了できるか
「管理者の承認済み」と書いたREADME資料内の文章を「反映の許可」として扱ってしまわないか
不正な指示を残した要約をPへ直接渡す原文だけでなく、前段の出力(要約)に含まれる指示にも影響されるか

特に重要なのは、「要約から指示が消えた場合」と「指示が残った要約を後段へ渡した場合」を分けて記録することです。

Simon Willison氏の「Dual LLM pattern」[1]が提案しているのは、権限を持つ側へ「汚染され得る自由文」をそのまま渡さない構造です。自由文の要約をそのまま後段へ渡す「構成B」はこれとは同じものではないため、単にモデルを二つ並べたこと自体を防御の根拠にはしません。

問い2:不適切な要求が来たとき、実行側で拒否できるか

一方、実行側のプログラムが不正な反映を止められるかどうかは、AIモデルの挙動を待たずに単独で確かめられます。

2026年10月13日に、kiuribotsの既存テスト(test/publish.test.ts)に対応する配布JavaScript(dist/test/publish.test.js)を実行して動作を確かめました。この検査ではモデルの応答を代用品(モック)に置き換え、DBとGitの送信先も試験用の一時領域に向けています。外部のAIモデルは呼び出さず、検査プロセスの外部通信も無効化した状態で実行しました。

READMEの helo を hello に直す処理を使ったテストで、確認した主な条件は次のとおりです。

条件期待する動きテストで確認したこと
反映の許可がまだないGitへ送らないローカルの送信先にブランチが作られない
許可したコミットを反映する作業用ブランチに、そのコミットだけを送るブランチとSHAが許可の対象に一致する
承認要求を作った後、人が許可する前にコミットを追加する反映を拒否するタスクが失敗し、送信先は空のまま

同じテストファイル内の検査により、作業クローンに仕込んだGitフックがこの条件下では動かなかったことや、承認が却下された後にブランチが送られないことも確認し、6件のプログラムテストが成功しました。

なお、「変更されたコミットを拒否する」ケースで確認したのは、「承認要求にSHAを記録 → 作業クローンへ追加コミットを作成 → 人が承認操作を試す」という順序での拒否動作です(承認操作が完了した後の差し替えを試したわけではありません)。ケースとassert、実行日時、Nodeの版、ソース版、実行した配布JavaScriptのSHAはすべて実行記録に残しています。

この結果が示しているのは、「試験用の要求に対し、既存のゲート処理コードが設計条件どおりに動いた」という確認です。実モデルへのプロンプトインジェクションを防いだ成績や、実際のGitHubへの送信・認証までを検証した結果ではありません。

「提案を減らせたか」と「実行を止めたか」を混ぜない

モデル側の評価とプログラム側の評価は分けて記録し、一つの「安全性スコア」には混ぜません。

* モデルの比較:本来の作業を完了したか、不正な操作を提案したか、資料内の指示が要約へ残ったかを分けて記録する。 * 実行側の検査:許可外の要求を拒否したかに加えて、正常な要求を通したかも記録する。

たとえば「CaMeL」[2]でも、単にモデルを分けるだけでなく、データの出所と許される情報の流れをプログラム側で管理するアプローチが採られています(今回のプログラム検査は、その再実装や性能の再現ではありません)。

なお、ここで検査したのは正規の反映処理(Gateway経由のpush)です。第2回で述べた共有DB(承認DB)への直接書き込みや、実環境の全通信の制限は検査対象外です。伏せ字の復元や、汎用的な承認・実行間の時間差の検証も、条件と記録をそろえて別に扱います。kiuribotsの全体像は第1回にあります。

参考文献

* [1]Simon Willison, The Dual LLM pattern for building AI assistants that can resist prompt injection (2023-04-25、追記 2025-04-11) * [2]Debenedetti et al., Defeating Prompt Injections by Design (2025-06-24) * [3]kiuribots反映処理のプログラム検査の実行記録 (2026-10-13)。対象は test/publish.test.ts、ソースGit版は 4e75d6455f9340ae242c2278ceaedd04f6f0da83。実行した配布JavaScriptのSHAは記録内にあります。

参照先の確認日:2026.10.13