領域展開 — AIの嘘を構造で封じる
前回、AIが嘘をつくという話を書いた。ツールハルシネーション。AIがツールを呼ばずに「呼びました」と言う、あの問題。
じゃあ具体的にどう対処したのか。今回はその設計の話をする。
前の記事で書いた通り、僕のシステムはオーケストレーター(Claude)が司令塔として中心にいて、Typewriter(代筆屋)やHandler(タスク管理)といった実行アプリがMCP経由でぶら下がっている。
問題は、オーケストレーターが実行アプリに指示を出さずに「やりました」と嘘をつくこと。これをどう防ぐか。
最初に試したのは、一番素朴なアプローチだった。システムプロンプト(AIへの事前指示)に「捏造するな」と書く。
書き方には工夫した。「嘘をつくな」みたいな抽象的なルールじゃなくて、「postdraftby_idを呼ばずに結果を返すのは禁止」というレベルまで具体的にした。「自律的に判断して」と書くと捏造するけど、「〜は禁止」と書くと従う確率が上がる。自由を与えると悪い方向に使い、制約を与えると守る。人間の直感とは逆かもしれないけど、今のLLMはそういう性質を持っている。
で、効果はあったか? ゼロではない。捏造しない時がちょっと増えた。でも「ちょっと増えた」程度だった。相変わらず嘘をつき続けた。前回紹介した論文でも、プロンプトによる改善はわずか2.7%。体感的にもそんなもんだった。
プロンプトはあくまで「行動指針」であって「強制力」じゃない。確率を上げることはできるけど、100%にはできない。ベースラインの底上げにはなるけど、これだけで解決するのは無理だとわかった。
二つ目の対策が tool_choice というAPIパラメータだ。
Claude APIには、AIに特定のツールを必ず呼ばせるオプションがある。これを使うと、Claudeは指定されたツールを呼ぶしかない。プロンプトによる「お願い」じゃなくて、APIレベルの「強制」。これは確実に効く。
ただ、問題はいつ強制するかだった。常時強制したら、普通の会話もできなくなる。Claudeは雑談も相談もする相手だから、ツール呼び出しを四六時中強制されたら使い物にならない。
最初はキーワードトリガーを試した。ユーザーの発言に「投稿」「下書き」みたいな言葉が含まれていたら自動で発動する仕組み。でもこれは全然ダメだった。関係ない会話でトリガーが発動する。「最近の投稿って何が伸びてる?」みたいな、ただの雑談でツール強制が走ってしまう。
次に試したのが、ボタン式。チャットUI上に「Typewriter」ボタンを作って、押している間だけtool_choiceを強制する。GPTやClaudeのDeep ResearchやDeep Thinkingボタンみたいな発想だ。ユーザーがモードを選んで、そのモード中だけ特定の挙動になる。
効果はあった。ボタンを押している間は確実にツールが呼ばれる。でも使い勝手が致命的に悪かった。しょっちゅう解除し忘れる。逆に、押し忘れて素の状態でTypewriterへの指示を出してしまい、また捏造される。人間の運用に依存する仕組みは、人間のミスで崩壊する。
ここで発想を変えた。
僕のチャットUIには右パネルがある。画面上部にタブが並んでいて、「Typewriter」や「Handler」といった実行アプリの名前が書いてある。タブをクリックすると右パネルがにゅっと出てきて、そのアプリに関する最低限の情報が表示される。下書き一覧とか、タスクの状態とか。そこで確認したり、操作したりできる。
で、気づいた。このパネル、僕はしょっちゅう使っている。開いたら用が済めば自然に閉じる。ボタンみたいに解除し忘れることがほとんどない。UIとして自然だからだ。
じゃあ、この右パネルが開いている間だけtool_choiceを強制すればいいんじゃないか。
これが「領域展開」の始まりだった。呪術廻戦から借りた名前だけど、構造的にすごくしっくりくる。パネルがにゅっと出てくる感じも含めて。
右パネルが開いている間 = 領域展開中。この間はtool_choiceが強制され、Claudeは必ず実行アプリにツールを呼ぶ。嘘をつく余地がない。
右パネルを閉じれば領域解除。普通の会話に戻る。Claudeは自由に喋れる。
キーワードトリガーみたいな誤発動がない。ボタンみたいな解除忘れもない。UIの自然な動作に乗っかっているから、人間の運用ミスが入り込む隙間がほとんどない。
さらに領域展開中には、Watchmanという監視システムも同時に発動する。
名前の通り、見張り番。Claudeの発言を入口と出口の両方でチェックする。
入口チェックは「この指示はツール実行が必要か?」の判定。ユーザーが「投稿して」と言ったら実行が必要、「投稿について相談したい」と言ったら会話だけでいい。
出口チェックは「実行結果と発言が一致しているか?」の検証。Claudeが「投稿しました」と言ったのに、ツールの実行ログがなかったら弾く。
最初はこの入口・出口の両方にHaiku(Anthropicの軽量・高速モデル)を使っていた。でもこれだとAPI呼び出しが毎回2回増える。遅いしコストも高い。
そこで出てきたのが、chat enum方式というアイデアだ。
発想はシンプル。Claudeに「次にやること」を毎回選ばせる。選択肢はこんな感じ。
["chat", "save_draft", "post_draft", "get_drafts", ...]
普通の会話をしたい場合は「chat」を選ぶ。ツールを使いたい場合は該当するアクションを選ぶ。そして、この選択自体を tool_choice で強制する。つまりClaudeは「必ず何かを選ぶ」ことになる。会話もツール実行も、すべて選択肢の中から選ぶ。選ばずにスキップする余地がない。
これで出口チェックの設計がガラッと変わった。
chatを選んだ場合 → Haikuで「本当にただの会話か?タスク操作したと嘘ついてないか?」をチェック。chat以外を選んだ場合 → 実行アプリからのレスポンスがあればOK、なければREJECT。機械的に判定できる。AIを使う必要すらない。
結果、通常会話ではHaikuチェック1回だけ。ツール操作ではAI不要の機械的判定。速度もコストも大幅に改善した。
振り返ると、対策は3つの層で成り立っている。
一つ目はプロンプト層。具体的な禁止事項を書いて、正しい行動を取る確率をちょっとだけ上げる。これだけだと全然足りないけど、ベースラインの底上げにはなる。
二つ目と三つ目が「領域展開」。右パネルが開いている間だけ発動する、toolchoice強制とWatchmanのセットだ。toolchoiceがClaudeにツール使用を強制し、Watchmanが結果を検証する。この二つが同時に動くことで、嘘をつける隙間がなくなる。
どれか一つで十分かというと、たぶん足りない。プロンプトだけだと破られる。tool_choiceだけだとパラメータの正しさまでは保証できない。Watchmanだけだと事後対応になる。重ねて初めて、実用に耐えるレベルになる。
セキュリティの世界で「多層防御」と呼ばれる考え方と同じだ。一枚の壁を厚くするより、薄い壁を何枚も重ねた方が強い。
正直なところ、ここまでやらないといけないのかと思わなくもない。AIに嘘をつかせないために、監視システムを作って、API強制して、プロンプトで禁止事項を列挙して。なかなかの物量だ。
でも考えてみれば、人間の組織だって同じことをやっている。ルール(プロンプト)を作り、権限管理(tool_choice)を設定し、監査(Watchman)を入れる。信頼を前提にしつつ、仕組みで担保する。
AIエージェントの設計は、結局のところ組織設計なんだと思う。どこまで自由を与え、どこに制約を設け、どうやって検証するか。その答えが「領域展開」だった。
少なくとも今のところは。
No Comments