12. ガードレール¶
ガードレールは、エージェントの入出力を検査し、必要に応じて実行を止める、または記録する仕組みです。エージェント定義の中の 1 セクションですが、検査位置・動作・判定スクリプトの組み合わせを設計する必要があります。本章の順に、何を検知したいかを決めてから、検査位置と動作を選びます。
項目
12.1. ガードレールで確かめること¶
ガードレールの設計は、何を検知したいかを決めてから検査位置を選ぶ、という順番で進めます。検査位置によって検査できる対象が決まっているためです。検知したときの動作(止める/記録する)は、そのあとに決めます。すべてを塞ごうとするのではなく、実際に起きうる失敗(規程にない金額の送信、機微情報の出力など)から逆算して、必要な検査位置と動作を選びます。
設定はエージェント定義の「ガードレール」セクションに、検査位置ごとに追加していきます。1 件ずつ有効・無効を切り替えられるため、動作を確かめたい設定だけを残して他を一時的に無効にする、という調整もできます。
12.2. 検査位置を選ぶ¶
検査位置は、入力・ツール入力・ツール出力・出力の 4 か所から選びます。同じ入出力でも、検査するタイミングによって止められるものが異なります。
| 検査位置 | いつ検査するか | 主な用途 |
|---|---|---|
| 入力 | 利用者のメッセージを、会話履歴へ追加する前に検査する | AI モデルへ送る前の内容を確認したい場合 |
| ツール入力 | ツールへ渡す引数(JSON 文字列)を、ツールを動かす前に検査する | 外部への送信・実行を止めたい場合 |
| ツール出力 | ツールが返した値を検査する | ツールが返した内容に機微情報が含まれていないか確認したい場合 |
| 出力 | 利用者へ返す応答を検査する。逐次応答では、一定の文字数ずつ区切って検査してから送出する | 最終的な回答の内容を確認したい場合 |
外部への送信を止めたいならツール入力に、AI モデルへ送る前の内容を確認したいなら入力に置く、というのが基本的な選び方です。同じ判定スクリプトでも、どの検査位置に置くかによって ctx.tool(ツール名。ツール入力・ツール出力以外の位置では空文字)などの値が変わるため、複数の検査位置で使い回す場合は、この違いを踏まえてスクリプトを書きます。
ツール入力・ツール出力フィルタが検査するのは、通常のツール呼び出しだけです。ナレッジ検索・スキルの読み込みは別の経路で実行されるため、この 2 つの検査位置では検査されません。また、ツール出力フィルタが検査するのは、戻り値の先頭 100,000 文字までです。超えた部分に機微情報があっても、このフィルタでは検知できません。戻り値そのものは切り詰められず、全文が LLM へ渡ります。大きな戻り値を返すツールを検査したい場合は、この上限を踏まえて設計します。
応答の返し方には、実行完了後に結果をまとめて返す「同期実行」と、生成中の文字を順に送る「逐次応答」があり、どちらになるかはエージェントの呼び出し方によって決まります。例えば、ロジックフローの「エージェント実行」タスクは同期実行で、エージェント編集画面のテスト実行は逐次応答です。ガードレールの設定で切り替える項目はありません。
出力フィルタが検査する範囲は、応答の返し方によって異なります。
- 同期実行では、ツール呼び出しを含まない最終応答だけを検査します。LLM がツールを呼び出したラウンドの応答(途中経過)は検査されません。
- 逐次応答では、途中経過を含め、利用者へ送る文字をすべて検査します。送出前にウィンドウ単位で検査し、通過した分だけ送出します。
構造化出力を使う場合は、整形した結果を別に検査します。同期実行では、整形前の本文は検査されません。逐次応答では、整形前の本文も送出前に検査します。
12.3. 動作を選ぶ¶
動作には、止める(BLOCK)・記録する(LOG)・何もしない(PASS)の 3 択があります。どの検査位置でも共通です。最初から止めるのではなく、まず記録するで様子を見てから、必要に応じて止めるへ引き上げる運用を推奨します。
| 動作 | 実行への影響 |
|---|---|
| 止める(BLOCK) | 実行を中止する |
| 記録する(LOG) | 止めずに実行を続ける。発動はトレースに残る |
| 何もしない(PASS) | 何も起きない |
ただし、記録する・何もしないの判定結果は監査ログには残りません。監査ログに記録されるのは、止めると判定した場合だけです。記録するの件数を確認したい場合は、トレース(「実行を追う(トレース)」)でエージェントごとの検査結果を見ます。
ツール入力・ツール出力の検査位置で止めた場合も、そのツール呼び出しだけでなく、実行全体が中止されます。1 回の応答で複数のツールを呼び出している途中でも同様です。
同一の検査位置に複数のガードレールを設定した場合、登録順に評価されます。いずれかが止めると判定した時点で打ち切り、それ以降は評価されません。
12.4. 判定スクリプトを書く¶
判定は、function run(input, ctx) { ... }の形式で書く判定スクリプトによって行います。構文はあらかじめ検証されないため、書いたら必ずテストします(「保存前にテストする」)。
判定スクリプトは、IM-LogicDesignerのユーザ定義スクリプトと同じ処理系・同じ API で動きます。テナント DB の参照・更新、ストレージへのアクセス、メール送信、外部通信など、業務用スクリプトと同等の操作が行え、実行時間の上限もありません(上限があるのは命令数だけです。「失敗したときの扱い」)。判定スクリプトは検査のためだけに使い、副作用のある操作は書かないようにします。
12.4.1. runの引数¶
| 引数 | 項目 | 内容 |
|---|---|---|
| input | (検査対象の文字列) | 検査位置ごとの対象。入力・出力はメッセージ本文、ツール入力は引数の JSON 文字列、ツール出力はツールの戻り値 |
| ctx | phase | 検査位置(INPUT・TOOL_CALL・TOOL_RESULT・OUTPUT) |
| tool | ツール名(LLM に公開している表示名)。ツール入力・ツール出力以外の検査位置では空文字 | |
| window | AI モデルの呼び出しごとに 1 から数えるウィンドウ番号 | |
| last | その呼び出しの最後のウィンドウかどうか(実行全体の最後とは限らない) | |
| state | 実行 1 回の間だけ持ち回れる入れ物 |
ctx自体は書き換えられません。回数を数える、前のウィンドウの判定結果を覚えておくといった値の持ち回りは ctx.stateに入れます。関数の外(トップレベル)に置いた定数・補助関数は、実行 1 回につき 1 度だけ評価されます。グローバル変数によって複数の実行をまたいで値を持ち越すことは保証されません。
ctx.toolに入る名前は、ツール名が重複して連番が付いた場合や、ツール定義側で名前を変更した場合に変わります(「LLMに公開されるツール名の一意化」)。ctx.toolで特定のツールだけを検査する判定を書く場合は、この名前が変わりうることを踏まえます。
逐次応答(ストリーミング)では、送出する本文のウィンドウ 1 つ分の文字列が inputに入ります。構造化出力を使う場合、整形した結果の検査では、その結果が inputに入って 1 回だけ呼ばれます。
逐次応答と構造化出力を両方使う場合、本文分のウィンドウ呼び出し(windowを 1 から数える)に続けて、整形結果分の呼び出しが来ますが、整形結果分も windowは 1 から数え直されます。どちらも同じ ctx.stateを引き継ぐため、windowが 1 のときに毎回初期化するような書き方をすると、本文分と整形結果分の数え上げが混ざります。
12.4.2. 返り値の書き方¶
スクリプトは、{action: "BLOCK"|"LOG"|"PASS", message: "..."}の形をしたオブジェクトを returnします。何も返さない(undefined)場合は「何もしない」と同じ扱いです。messageには、止めるときに利用者へ提示する文言を入れます。止めるときに messageを書かないと、利用者には何も表示されません。理由を伝えたい場合は必ず messageを書きます。文言に、検知した内容そのものを書かないでください(検知した機密情報を message でそのまま返すと、検査の目的と矛盾します)。
12.4.3. 書き方の例¶
画面には、次のような組み合わせの例(スニペット)が用意されています。実際の判定スクリプトは、これらの「見つけ方」と「返し方」を組み合わせて書きます。
| 分類 | 例 |
|---|---|
| 返し方 | 止めて文言を出す/文言を出さずに止める/止めずに記録だけする/何もしない(何も返さない) |
| 見つけ方 | 語の一覧で探す/大文字と小文字を区別せずに探す/書式で探す(電話番号・メールアドレス・12 桁の番号)/長さで見る/JSON を読んで項目を見る/語の数を数える |
| ctx の使い方 | 特定のツールだけ検査する/回数を持ち回る(ctx.state)/最後のウィンドウでまとめて判定する/最初のウィンドウだけ検査する |
| 関数の外 | 定数を置く/補助関数を置く |
例えば、入力フィルタで「機密」「社外秘」という語を含む場合に止める判定スクリプトは、次のような形です。
function run(input, ctx) {
var words = ["機密", "社外秘"];
for (var i = 0; i < words.length; i++) {
if (input.indexOf(words[i]) !== -1) {
return { action: "BLOCK", message: "社外秘の語が含まれています" };
}
}
return { action: "PASS" };
}
同様に、ツール入力フィルタでは「引数に外部の宛先があれば中止する」、ツール出力フィルタでは「戻り値が一定の長さを超えたら記録する」、出力フィルタでは「電話番号の書式があれば止める」といった判定が典型例として用意されています。いずれも、検査対象(input)から機械的に判定できる条件に絞り、曖昧な意味判断はさせない書き方になっている点が共通しています。
12.4.4. 失敗したときの扱い¶
| 状況 | 実際の動作 |
|---|---|
| スクリプトが例外を送出した | 続行(何もしない)として扱う。警告ログに残る |
| 命令数が上限に達した | 続行(何もしない)として扱う。警告ログに残る |
| 戻り値の書き間違い(run関数が無い、戻り値がオブジェクトでない、actionが無い、知らない動作名を返した、のいずれか) | 続行(何もしない)として扱う。警告ログに残る |
判定スクリプトの失敗は、どの経路でも実行を止めません。失敗に気づくには、まずトレース詳細でガードレールのスパンを確認します。判定が失敗した検査は、判定結果が「判定失敗」と表示されるため、どの実行のどの検査位置で失敗したかがわかります(「実行を追う(トレース)」)。
失敗の原因(スクリプトの例外の内容など)は、サーバのシステムログに警告として出力されます。判定の実行に失敗した場合はW.IWP.COPILOT.AGENT.GUARDRAIL.00001、命令数の上限に達した場合はW.IWP.COPILOT.AGENT.GUARDRAIL.00002のメッセージコードで、ガードレールの識別子(guardrailId)と検査位置(phase)とともに記録されます。これらを手がかりにログを検索します。スクリプトの書き間違いは、保存前のテスト(「保存前にテストする」)でも確認できます。
設定 1 件ごとに、識別子(200 文字以内・重複不可)、名称(255 文字以内、ロケール別も可)、説明(1000 文字以内、ロケール別も可)を持ちます。識別子・名称・検査位置・判定スクリプトは必須です。
12.5. 逐次応答の検査設定¶
逐次応答(ストリーミング)では、出力を一定の文字数ずつに区切って検査してから利用者へ送出します。区切り方はエージェントに 1 組だけの設定です(検査位置ごとではありません)。
ウィンドウサイズ(一度に検査する文字数)は 64〜8192 文字の範囲で指定でき、既定値は 512 です。オーバーラップ(区切り目をまたぐ語を見落とさない量)は 0 以上、かつウィンドウサイズの半分(小数切り捨て)以下で指定でき、既定値は 128 です。この上限を超える値は保存できません。
ウィンドウサイズ分の文字が溜まるまでは検査も送出も行われないため、ウィンドウサイズを大きくするほど、最初の応答が画面に表示されるまでの時間は長くなります。
見つけたい語や句がオーバーラップの文字数より長いと、区切り目をまたいだときに検出できないことがあります。長い語句を確実に検知したい場合は、オーバーラップを広げます。
オーバーラップの部分は、前後 2 つのウィンドウの両方で検査されます。この範囲に記録する(LOG)条件に合う語があると、発動が 2 回記録されることがあります。
逐次応答では、あるウィンドウで止めると判定しても、それより前のウィンドウはすでに利用者の画面に送信済みです。この送信済みの内容を取り消すことはできません。
12.6. 保存前にテストする¶
保存前にテスト機能で動作を確認できます。検査文字列(入力・出力フィルタの場合は文章がそのまま渡る、ツール入力・ツール出力フィルタの場合はツールの引数を模した JSON 文字列が渡る)とツール名を指定し、判定スクリプトをその場で 1 回だけ実行します。結果には、動作(止める/記録する/何もしない)、文言、所要時間(ミリ秒)が表示されます。
図:ガードレールのテスト画面。判定スクリプトに対して検査文字列を渡し、動作・文言・所要時間を確認できる。
テスト実行はサーバ側で判定を 1 回動かすだけで、エージェント自体は動きません。想定した入力に対して、意図した動作が返るか、messageが期待どおり表示されるかを確かめてから保存します。
12.7. 発動状況を確認する¶
止める(BLOCK)と判定した場合だけが監査ログに記録されます。記録する(LOG)の発動件数は監査ログでは追えないため、トレースのガードレールの検査結果で確認します。
12.8. 困ったときの確認¶
| 状況 | 確認・対応 |
|---|---|
| 止める設定にしたのに何も表示されない | messageを返しているか確認する。message が無いと利用者には何も表示されない |
| 意図せず処理が止まる | 判定スクリプトが誤って BLOCK を返していないか、テスト機能で確認する |
| 記録するの件数を監査ログで確認できない | 記録する・何もしないは監査ログに残らない仕様。トレースを見る |
| 逐次応答のオーバーラップを保存できない | オーバーラップはウィンドウサイズの半分(小数切り捨て)が上限 |
| 複数のガードレールのうち、1件しか発動しない | 同一検査位置では登録順に評価され、止めると判定した時点で以降は評価されない |
| 回数を数える判定が意図どおりに動かない | グローバル変数ではなく ctx.stateを使っているか確認する。トップレベルの変数は実行1回につき1度しか評価されず、値を実行間で持ち越せない |
| 特定のツールだけを検査する判定が、どのツールにも一致しない(ctx.toolが空文字になる、または期待した名前にならない) | ctx.toolに値が入るのは、ツール入力・ツール出力の検査位置だけで、それ以外の検査位置では空文字になる。検査位置を確認する。ツール名が重複して連番が付いた場合や、ツール定義側で改名した場合も名前が変わる(「LLMに公開されるツール名の一意化」) |