こんにちは、おうどんです🍜
AI Agentに「調べて、整理しておいて」とお願いする。
ここまでは平和です。
でも、もし検索だけでなく、ファイル更新、メール送信、データ削除まで同じ権限で実行できたら?
……急に元気すぎる新人が爆誕します。
AI Agentは、LLMが回答文を作るだけではなく、目的に合わせてツールを選び、結果を見て次の操作へ進める仕組みです。OpenAIの公式ドキュメントでも、エージェントの実行は「モデルを呼ぶ→ツール呼び出しがあれば実行→結果を受けて続行→最終回答」というループで説明されています。OpenAI公式:Running agents
便利になるほど大切なのが、「何を考えさせるか」より「どこまで実行させるか」です。
この記事では、AI Agentの権限を4段階に分け、承認ゲート、構造化された操作要求、監査ログ、再実行対策まで整理します。特定製品の導入手順ではなく、実務で設計を始めるためのひな型です。
普通のチャット・ワークフロー・AI Agentは何が違う?
最初に境界だけそろえます。
| 方式 | 次の処理を決めるもの | 外部操作 | 向いている場面 |
|---|---|---|---|
| チャット | 主に人間 | 原則なし、または人間が実行 | 相談、要約、文章作成 |
| 固定ワークフロー/RPA | 事前に決めたルール | 決めた順序で実行 | 手順と分岐が安定した反復作業 |
| AI Agent | LLMと実行基盤 | 許可されたツールを選択して実行 | 状況に応じて調査や手順が変わる作業 |
AI Agentも魔法の自動化ではありません。判断の一部をモデルへ渡し、ツールを使えるようにしたシステムです。ツールにはWeb検索、ファイル参照、コード実行、外部API、MCP経由のサービスなどがあります。OpenAI公式:Tools
つまり、文章生成の品質だけを評価しても足りません。
「正しいツールを選んだか」「引数は正しいか」「実行してよい操作だったか」までが評価対象です。回答が少しズレるのと、宛先を間違えて送信するのでは、同じ1ミスでも破壊力が違いますからね。。。
権限を4段階に分ける
まずは操作を次の4段階へ分類すると考えやすくなります。
| レベル | 操作例 | 基本方針 |
|---|---|---|
| L1:Read | 公開情報検索、許可済み資料の参照、状態確認 | 対象と取得量を制限して自動実行候補 |
| L2:Draft | メール下書き、SQL案、変更差分、実行計画の作成 | 自動作成可。実行対象とは分離 |
| L3:Write | 許可フォルダの更新、チケット更新、DBの限定的な変更 | 条件付き承認。対象・件数・差分を表示 |
| L4:External / Destructive | 送信、公開、削除、決済、本番変更、権限付与 | 原則として操作直前に人間が承認 |
ここでポイントは、「読み取りだから無条件で安全」ではないことです。
機密資料を大量に読み、その内容を外部サービスへ渡せば情報漏えいになり得ます。OpenAIの安全ガイドも、接続先へユーザーが意図した以上の情報を共有する可能性や、外部の不審な文章がツール呼び出しを誘導するプロンプトインジェクションを挙げています。OpenAI公式:Safety in building agents
そのためL1にも、参照可能な場所、列、期間、最大件数、外部送信の可否が必要です。
処理の流れ:モデルの提案と実行を分離する
おすすめは、モデルへ直接APIを叩かせるのではなく、間にポリシー判定を置く構成です。
利用者の依頼
↓
Agent(計画・ツール選択)
↓ 構造化した操作要求
Policy Gate(許可範囲・承認要否・件数上限)
├─ deny ─────────→ 拒否理由を返す
├─ approval ─────→ 人間が対象・差分を確認
└─ allow ────────→ Tool Executor
↓
結果検証・監査ログ
↓
Agentが次の処理を判断
モデルが出す操作要求は自由文ではなく、例えば次のように固定します。
{
"tool": "ticket.update",
"action": "write",
"target": "PROJECT-A/123",
"fields": {
"status": "investigating"
},
"reason": "調査開始を反映する",
"idempotency_key": "run-20260915-ticket-123-status"
}
列挙値、必須項目、対象ID、最大文字数をスキーマで制限します。外部から取得した自由文をそのままツール引数へ流さないことも重要です。公式ガイドでは、ノード間を固定スキーマや必須フィールドでつなぎ、意図しない命令やデータが自由文に紛れ込む経路を減らす方法が案内されています。OpenAI公式:Safety in building agents
動かせる最小例:ポリシー判定をPythonで書く
以下は外部APIを呼ばず、操作要求をallow / approval / denyへ分類する確認用コードです。Python 3.11で実行確認しています。
from dataclasses import dataclass
from typing import Literal
Decision = Literal["allow", "approval", "deny"]
@dataclass(frozen=True)
class ToolRequest:
tool: str
action: Literal["read", "draft", "write", "external", "delete"]
target: str
estimated_items: int = 1
READ_ALLOWLIST = {"docs.search", "ticket.read"}
WRITE_ALLOWLIST = {"ticket.update"}
ALLOWED_TARGET_PREFIXES = ("PROJECT-A/",)
def decide(req: ToolRequest) -> Decision:
if req.estimated_items < 0 or req.estimated_items > 100:
return "deny"
if req.action == "read":
return "allow" if req.tool in READ_ALLOWLIST else "deny"
if req.action == "draft":
return "allow"
if req.action == "write":
trusted = (
req.tool in WRITE_ALLOWLIST
and req.target.startswith(ALLOWED_TARGET_PREFIXES)
)
return "approval" if trusted else "deny"
# 外部送信・削除は、この例では必ず人間の確認へ送る
if req.action in {"external", "delete"}:
return "approval"
return "deny"
samples = [
ToolRequest("docs.search", "read", "public/manual", 10),
ToolRequest("ticket.update", "write", "PROJECT-A/123"),
ToolRequest("mail.send", "external", "customer@example.invalid"),
ToolRequest("db.execute", "write", "PRODUCTION/users", 5000),
]
for request in samples:
print(decide(request), request)
期待結果は上からallow、approval、approval、denyです。
この例はポリシー判定だけで、実際の更新・送信・DB操作はしません。メールアドレスも予約済みの.invalidドメインです。実装時は、モデルが申告したactionを信用せず、ツール定義側で操作種別を固定してください。「削除ですがreadです」と名札を付け替えられたら、警備員が泣きます。
承認画面で見せるもの
承認ボタンだけ出しても、何を承認するのか分からなければ儀式になります。
最低限、次を表示します。
- ツール名と操作種別
- 対象環境、対象ID、宛先
- 更新前後の差分
- 予定件数と上限
- 外部送信されるデータ
- 実行理由と取り消し方法
- 同じ処理を再実行した場合の挙動
OpenAI Agents SDKのHuman-in-the-loopでは、承認が必要なツール呼び出しで実行を一時停止し、人間が承認または拒否した後に元の状態から再開できます。ツール名や引数を含む保留項目を確認でき、needs_approvalで常時または条件付きの承認を定義できます。OpenAI公式:Human-in-the-loop
ただし、承認前にガードレールを並列実行すると、判定完了前にモデルがトークンを使ったり、別のツールを動かしたりする場合があります。副作用を避けたい入口チェックは、先に完了させるブロッキング実行が必要です。OpenAI公式:Guardrails
再実行対策:承認された1回を、2回実行しない
長時間処理では、タイムアウト後に「失敗したように見えるが、実は更新済み」が起こります。
対策は、操作ごとのidempotency_key、実行前後の状態、外部サービスの受付IDを保存すること。リトライ前に同じキーの完了記録を確認します。
監査ログには、少なくとも次を残します。
{
"run_id": "run-20260915-001",
"actor": "agent-support-01",
"tool": "ticket.update",
"target": "PROJECT-A/123",
"decision": "approved",
"approved_by": "reviewer-id",
"arguments_hash": "sha256:...",
"started_at": "2026-09-15T08:10:00+09:00",
"result": "succeeded"
}
本文や機密データを何でもログへ残すのではなく、追跡に必要なID・ハッシュ・差分の保管場所を設計します。承認待ち状態自体にも、保持期限とエージェント定義のバージョンを付けます。数日後に承認したら、ツール仕様が別物になっていた。。。は笑えないので。
テストは「正解率」だけでは足りない
AI Agentでは、次の観点を分けて評価します。
| 観点 | 確認例 |
|---|---|
| タスク成功 | 正しい調査結果・成果物を返したか |
| ツール選択 | 不要なツールを呼んでいないか |
| 引数 | 対象、件数、宛先、環境が正しいか |
| 権限 | 許可外の操作がdenyになったか |
| 承認 | 高リスク操作で必ず止まったか |
| 再実行 | 二重更新・二重送信を防げたか |
| 情報管理 | 機密情報を不要な接続先へ渡していないか |
正常系だけでなく、「Webページ内に別の命令が書かれている」「対象が曖昧」「承認後に引数が変わる」「結果がタイムアウトした」も試します。OpenAIは、ツール呼び出しや判断を含むトレースを評価し、複数の対策を組み合わせることを案内しています。OpenAI公式:Safety in building agents
最初の導入はDraftまででも十分
いきなり「全部自動でやって」は気持ちいいです。
言うだけなら。
最初は、情報収集→要約→変更案や送信文の下書きまで。実行は人間。そこで誤り方、必要なログ、曖昧になりやすい対象を集めます。
次に、範囲を限定した読み取りを自動化。最後に、差分が見え、再実行可能で、取り消し方法がある更新だけを条件付きで解放する。
AI Agentの価値は、確認をゼロにすることではありません。
人間が確認すべき場所を、危険度の高い数か所へ絞ることです。
「任せる」と「丸投げ」は似ているようで別物。 権限表を見ると、その違いが急に現実になります🍜

コメント