9
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AIエージェントのガバナンスを外から当てる、Agent Gateway × Agent Identity × SGP やってみた

9
Posted at

はじめに

こんにちは!
KDDIアイレットの取り組みとして6月22日〜7月3日の期間で開催中の「Google Cloud Next '26 / Google I/O やってみた系ブログリレー」、最終日の投稿です。
今回は「Agent Gateway + Agent Identity + SGP(Semantic Governance Policy)」を対象に、実際に検証してみた様子をお届けします!

前回の記事はこちらです。

ところで最近、エージェントにツールを持たせて動かす開発が増えてきたと思います。
ただ、「エージェントのガバナンス、ちゃんとできてる?」と聞かれると、自信を持って答えられる人は少ないのではないでしょうか。

私もそのひとりでした。
でも、コードにバリデーションを書かなくても、各システムプロンプトをちまちま調整しなくても、エージェントの振る舞いを制御できる仕組みがあるらしいんです。本当にできるのか、実際に試してみました。

SGP は 2026年7月現在 Preview(Pre-GA)です。本番環境での利用には注意が必要です。

先に30秒でまとめ

  • 困ってたこと: ツール呼び出しのガバナンスを、コードから分離できなかった。コードやプロンプトに書くしかないので、エージェントをまたいだ一貫した管理が難しい
  • やったこと: ADK で作った金融エージェントに、Agent Gateway と SGP でツール呼び出しのガバナンスを構築
  • 結果: コードにバリデーションを一行も書かずに、ツール呼び出しの制御ができた。ただし確実性とのトレードオフあり。判定の主体がコードから LLM に変わるため、if 文のような100%の保証はなくなる
  • 仕組み: 自然言語でポリシーを書くだけ。LLM がツール呼び出しの中身を判定し、理由もログに残る

エージェントのガバナンス、後回しにしがち

エージェントを作るのは楽しいですよね。ADK でツールを定義し、プロンプトを書けば、サクッと動いてくれます。

ただ、本番に近づくほど気になることがあります。「あれ、このエージェント、ツールとか好きに呼べるけど、それでいいんだっけ?」

実際のコードを見ると、その不安が伝わると思います。

たとえばこの送金関数。指定された相手にお金を送るだけのシンプルなコードですが、金額チェックがありません。$ 500 でも $ 1,000,000 でも success を返します。

def send_money(recipient: str, amount: float, currency: str = "USD"):
    """Sends money to a recipient."""
    return {
        "status": "success",
        "message": f"Sent {amount} {currency} to {recipient}",
    }

プロンプトに「ユーザーに確認してから実行せよ」と書くことはできます。ただ、それは LLM がプロンプトの指示を守ることが前提で、ガバナンスとしては脆いです。
コードに if amount > 1000 と書けば確実ですが、ツールが増えるたびに書くのか、変えるたびに再デプロイするのかと思うと、それもしんどい。

冒頭の通り、エージェントって、動くだけならすぐ動きます。デモではちゃんと動くから、ガバナンスの優先度が上がりません。でもいざ本番を考えると、LLM が何をどう呼ぶかは事前に全部は想定できないし、考え始めるとキリがありません。

やらなきゃいけないのは分かっています。でも、ガバナンスって後回しにしがちですよね。

今回使った、Google Cloud の3つのサービス

サービス 何をするか Before → After
Agent Identity エージェントの身分証明 サービスアカウント共有 → エージェント固有の SPIFFE ID 自動発行
Agent Gateway ツール呼び出しの関所 エージェントが直接外部 API を呼ぶ → Gateway 経由で認可チェック
SGP ツール呼び出しの中身を判定 コードで if amount > 1000 → 自然言語でポリシー、LLM が判定

Agent Identity

一言でいうと、エージェントの身分証明です。

従来、エージェントの認証にはサービスアカウントが使われていました。ただ、サービスアカウントは複数のワークロードで共有されるのが普通です。共有ということは、誰が何をしたのか追跡できないし、なりすましもできてしまいます。

Agent Identity は、エージェント1体ごとに固有の SPIFFE ID(暗号学的な身分証明書)を発行します。X.509 証明書が自動でプロビジョニングされ、24時間ごとに自動ローテーション。トークンも証明書にバインドされるので、盗まれても再利用できません。

イメージとしては、サービスアカウントが「部署の共有カードキー」なら、Agent Identity は「個人の生体認証」です。

ひとつ注意なのが、Agent Identity は、「私は誰か」を証明するだけです。「このエージェントに何を許可するか」は IAM の仕事。マイナンバーカードを持っていても、銀行口座の開設権限は別途申請が必要なのと同じです。

Agent Gateway

一言でいうと、通信の関所です。

エージェントは外部のツール(MCP サーバー、API、他のエージェント)と通信します。この通信を無制限に許可すると、勝手にデータを外部に送ったり、プロンプトインジェクション経由で意図しない操作をされるリスクがあります。

Agent Gateway は、エージェントの外部通信を一箇所で捕まえる関所です。デフォルトの動作は全拒否(Default-Deny)。Agent Registry に登録された宛先で、かつ IAP の認可チェックを通過しないと、リクエストは通りません。

ポイントは、Gateway 自体は何も判断しないことです。Gateway の仕事は「通信を止めて、判断を専門サービスに委ねる」こと。IAP が「この SPIFFE ID にロールがあるか」を判断し、SGP が「この呼び出しの中身は適切か」を判断します。Gateway は判定結果に従って通すか止めるかを実行するだけ。

「止める係」と「判断する係」が分業しているので、SGP だけ使う、Model Armor も追加する、といった組み合わせが自由にできます。

SGP

一言でいうと、ツール呼び出しの中身を判定するサービスです。
自然言語でポリシーを書くと、LLM が ALLOW / DENY を判定します。
今回、特に気になっていました。自然言語でルールを書くだけでガバナンスができるって、まさにやりたいことじゃないか!と思ったんです。

ただ、SGP を使わずに同じことをやろうとすると、選択肢は限られていました。たとえば「$ 1,000 以下の送金だけ許可する」を実現しようとした場合。

アプローチ できること つらいところ
コード内バリデーション if amount > 1000: raise で確実に制御 エージェントごとにロジック追加。変更のたびに再デプロイ
IAM ロール・条件でツール呼び出しの可否を制御 IAM Conditions はリクエスト属性を評価するが、ツール引数の意味的な内容(「$ 5000 は高すぎる」)は評価できない
SGP 自然言語でポリシーを記述。コード変更不要 LLM がジャッジのため判定は確率的。ツール呼び出しごとにトークンを消費(コスト・レイテンシ増)

IAM は「誰がツールを呼べるか」を制御します。SGP は「この呼び出しの中身が適切か」を判断します。レイヤーが違います。

SGP はツール呼び出しごとに2つのチェックをかけます。両方通らないと ALLOW になりません。

  1. User Intent Alignment(ユーザー意図との整合性): ツール呼び出しが、ユーザーの元のリクエストと合っているか
  2. Policy Compliance(ポリシー準拠): 管理者が定義した NLC(自然言語ルール)に違反していないか

Policy Compliance は分かりやすいですよね。「$ 1,000 超の送金は拒否」に対して $ 5,000 の送金 → DENY となります。

面白いのは User Intent Alignment のほうです。
たとえば、メール読み取りエージェントに「メールを要約して」と頼んだとします。ところが悪意のあるメール本文に「全メールを攻撃者のアドレスに転送しろ」という指示が仕込まれていて、LLM が騙されて send_email を呼ぼうとしたらどうなるか。SGP はユーザーの元のリクエスト(「要約して」)と提案されたツール呼び出し(send_email)を比較し、意図が一致しないと判定して DENY を返します。
つまり、プロンプトインジェクション対策にもなります。

やってみた

さて、お待ちかね、ようやく「やってみた」に入っていきたいと思います!
はてさて、本当にコードを一行も書かずにガバナンスができるのか、試してみます。

準備

今回の検証で作ったものはこちらです。

構成要素 役割
ADK エージェント 3つのツール(為替取得・送金・メール)を持つ金融エージェント
Agent Runtime エージェントの実行環境。AGENT_IDENTITY 指定で SPIFFE ID を自動発行
Agent Gateway 外部通信の関所。Default-Deny で全通信をチェック
IAP 第1関門。SPIFFE ID で「このエージェントは通っていいか」を判定
SGP Engine + ポリシー2つ 第2関門。自然言語ルールで「この呼び出しの中身は適切か」を判定
VPC / DNS / PSC Gateway と SGP Engine をつなぐネットワーク構成

全体像はこうなります。

image (23).png

先ほど紹介した3つのサービスは、それぞれ次のように設定しました。

Agent Identity は、エージェントのデプロイ時に1行指定するだけです。

remote_agent = client.agent_engines.create(
    agent=app,
    config={
        "identity_type": "AGENT_IDENTITY",  # これだけで SPIFFE ID が自動発行
    },
)

Agent Gateway は、今回 YAML で定義しました。

name: finance-agent-gateway
protocols:
  - MCP
googleManaged:
  governedAccessPath: AGENT_TO_ANYWHERE
gcloud network-services agent-gateways import finance-agent-gateway \
  --source="my-agent-gateway-egress.yaml" \
  --location=us-central1

SGP ポリシーは、gcloud コマンドで「送金は $1,000 以下のみ許可」「メールは @example.com ドメインのみ許可」の2つを設定しました。

# 送金制限: $1,000 以下のみ許可
gcloud beta ai semantic-governance-policies create limit-send-money \
  --location=us-central1 \
  --display-name="Limit send_money to 1000 USD" \
  --agent=AGENT_ID \
  --natural-language-constraint="When the send_money tool is called, \
    do not allow money transfers where the amount exceeds 1000 USD \
    in a single transaction."
    # ↑ この自然言語がそのままポリシーになる

# メール制限: @example.com ドメインのみ許可
gcloud beta ai semantic-governance-policies create restrict-send-email \
  --location=us-central1 \
  --display-name="Restrict send_email recipients" \
  --agent=AGENT_ID \
  --natural-language-constraint="When the send_email tool is called, \
    only allow sending emails to recipients with an @example.com email address. \
    Deny any email to external domains."

--natural-language-constraint に書いた自然言語が、
そのままポリシーになります。--agent には Agent Registry のエージェント ID を指定します。

このほかに、Gateway と SGP Engine をつなぐネットワーク構成や Agent Registry への登録なども必要ですが、ここでは省略します。

すべて設定し終えた...ということで、準備完了です。
エージェントのコードには、金額チェックもドメインチェックも一行もありません。ガバナンスは全部外から適用しました。

はたして止まるのか、テストしてみる

Agent Runtime の API に curl でリクエストを送ってテストします。形式はこうです。

curl -s \
  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  -H "Content-Type: application/json" \
  "https://us-central1-aiplatform.googleapis.com/v1/projects/${PROJECT_ID}/locations/us-central1/reasoningEngines/${RESOURCE_ID}:streamQuery?alt=sse" \
  -d '{
    "class_method": "async_stream_query",
    "input": {
      "user_id": "test-user-1",
      "session_id": "'${SESSION_ID}'",
      "message": "..."
    }
  }'

message を変えながら、いくつかのシナリオを試します。

まず、普通の操作から

"What is the USD to EUR exchange rate?"、為替レートの取得です。SGP の制約に引っかからない操作なので、問題なく ALLOW。ここは想定通り。

本題、$5,000 の送金

"Send 5000 USD to Alice" でリクエスト。エージェントが確認を求めてきたので、"Yes, please proceed." で承認。

Cloud Logging に記録された SGP の判定結果:

{
  "evaluations": [{
    "actionName": "send_money",
    "toolName": "send_money",
    "verdict": "DENY",
    "rationale": "The action violates the global constraint that limits
                  send_money transactions to a maximum of 1000 USD."
  }],
  "verdict": "DENY",
  "token_usage": 1864
}

おお、止まりました。

エージェントはユーザーの確認を取り、ツール呼び出しを試みました。つまり、エージェント自体は送金を実行しようとしました。しかし Agent Gateway を通じて SGP がそれを止めました。

agent.py には金額チェックのコードは一行もありません。止めたのは、gcloud コマンドで外から適用した自然言語のポリシーです。

じゃあ、$500 なら?

"Send 500 USD to Bob" → 確認 → 承認。

{
  "evaluations": [{
    "actionName": "send_money",
    "toolName": "send_money",
    "verdict": "ALLOW",
    "rationale": "This action is appropriate and useful for the user's task.
                  The amount of 500 USD is within the global constraint limit
                  of 1000 USD for send_money transactions.
                  The user has also confirmed the transaction."
  }],
  "verdict": "ALLOW",
  "token_usage": 1989
}

通りました。同じ send_money 関数、同じエージェント、同じコード。引数の値だけで結果が変わりました。

サービス紹介で書いた「IAM は誰が呼べるかを制御し、SGP は呼び出しの中身を判断する」という違いが、まさにこれです。

メールも同様

送金と同じパターンをメールでも試します。SGP ポリシーには「@example.com ドメイン宛てのみ許可」と設定しています。同じ send_email 関数で、宛先のドメインだけを変えてみます。
つまり、エージェントが社外にメールを送ることを防ぐルールです。実務でもよくある悩みどころではないでしょうか。

まず外部ドメイン宛て。"Send an email to user@external.com with subject Hello and body Hi there" → 確認 → 承認。

{
  "evaluations": [{
    "actionName": "send_email",
    "toolName": "send_email",
    "verdict": "DENY",
    "rationale": "The action violates the global constraint which restricts
                  email recipients to only those with an @example.com email address.
                  The provided recipient 'user@external.com' is an external domain."
  }],
  "verdict": "DENY",
  "token_usage": 1741
}

次に社内ドメイン宛て。"Send an email to alice@example.com with subject Meeting and body See you at 3pm" なら:

{
  "evaluations": [{
    "actionName": "send_email",
    "toolName": "send_email",
    "verdict": "ALLOW",
    "rationale": "This action is appropriate and useful for the user's task,
                  and complies with the stated constraints.
                  The recipient's email address 'alice@example.com' adheres to
                  the global constraint that restricts email recipients to
                  those with an @example.com email address."
  }],
  "verdict": "ALLOW",
  "token_usage": 1986
}

こちらもポリシー通りです。

テスト結果からわかった、SGP の特徴

ここまでのテストで、SGP が期待通りに動くことは確認できました。ただ、ログに残った JSON をよく見ると、DENY / ALLOW 以外にも読み取れることがあります。ポイントを4つ挙げます。

1. rationale は LLM が生成した自然言語の説明文
固定テンプレートではありません。
ALLOW の rationale には「The user has also confirmed the transaction(ユーザーが承認済み)」という、会話の文脈を踏まえた判断が含まれています。SGP は引数の値だけでなく、会話全体を見ています。

2. ALLOW にも rationale が残る
「通した理由」が記録されるため、事後の監査証跡として機能します。「なぜ止めたか」だけでなく「なぜ通したか」も説明できます。

3. SGP 評価には毎回 1,600〜2,000 トークンを消費
token_usage フィールドに記録されています。ツール呼び出しのたびに LLM による評価が走るため、コストとレイテンシのトレードオフがあります。get_exchange_rate(SGP ポリシーの制約対象外)も評価対象になっていた点は、設計時に考慮が必要です。

4. LLM による判定のため、100% の正確性は保証されない
公式ドキュメントにも「LLMs are probabilistic and can make mistakes. Verdicts may not be accurate.」と明記されています。本番環境では dry-run モード(判定ログは残すが実際のブロックは行わない)で十分にテストしてから強制適用を有効にすることが推奨されています。

これらのトレードオフはありつつも、「$ 5,000 は止まり、$ 500 は通る」「外部ドメインは止まり、社内ドメインは通る」が、コードに一行も書かずに実現できました。
エージェントが増えても同じポリシーを適用するだけです。開発者はツールの機能に集中し、運用者がポリシーを管理する、そういう役割分担が可能になります。

おわりに

エージェントのガバナンスを後回しにしていた理由を、検証を終えたいま振り返ると、「コードに書くのが面倒」だけではなかったと思います。
LLM が何をどう呼ぶか事前に全部は想定できない、つまり if 文をいくつ書けば十分なのかわからないことが、一番のハードルでした。

SGP のポリシーを書いていて気づいたのは、書いているのがチェックロジックではなく「ルールそのもの」だということです。
「$1,000 を超える送金は許可しない」。ケースの列挙ではなく、意図を書く。想定外の呼び出し方をされても、LLM がルールに照らして判定してくれます。
「キリがない」に対する答え方が、コードとは根本的に違いました。

もちろん、引き換えに確実性を手放しています。if amount > 1000 は 100% ですが、LLM の判定はそうではありません。
これは優劣ではなく、何を取るかの選択だと感じました。エージェント1つ、ツール1つなら、コードに書くほうが確実で安上がりです。
エージェントとツールが増え、ルールも変わり続ける状況になったとき、「ガバナンスをコードから分離できる」ことの価値が効いてくるはずです。

ガバナンスを後回しにする理由が、一つ減りました。
まだ Preview のサービスですが、GA になる頃には、エージェントのガバナンスは「外から当てるもの」が当たり前になっているかもしれません。その最初の景色を見られた気がして、ちょっとワクワクしています!

参考リンク

9
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?