👏

agnoのGuardrail機能を試してみた

に公開

今回は昨日に引き続きagnoを利用してみました。agnoではGuardrailの機能について提供しており、そのサンプルを通して挙動を確認してみようと思います。昨日のagnoの導入記事もぜひ合わせてご覧ください。

https://zenn.dev/akasan/articles/80953b8e206dd0

早速使ってみる

今回は以下のページを参考にサンプルを試してみます。

https://docs.agno.com/concepts/agents/guardrails/overview
https://docs.agno.com/examples/concepts/agent/guardrails/pii_detection

PIIの検知

PIIとはPersonally Identifiable Informationの略であり、個人を識別できる情報のことを言います。日本で言えばマイナンバーなどはこの情報に該当するでしょう。生成AIを利用するときにこのような情報が利用されてしまうと個人情報流出のリスクがあるため、Guardrailを利用して事前に検知する必要があります。agnoではこれらを実施するためのデフォルトの機能が用意されていて、それを試してみます。今回は公式サンプルから抜粋して使ってみます。

pii_sample.py
import asyncio

from agno.agent import Agent
from agno.exceptions import InputCheckError
from agno.guardrails import PIIDetectionGuardrail
from agno.models.openai import OpenAIChat


async def main():
    """Demonstrate PII detection guardrails functionality."""
    print("🛡️ PII Detection Guardrails Demo")
    print("=" * 50)

    # Create an agent with PII detection protection
    agent = Agent(
        name="Privacy-Protected Agent",
        model=OpenAIChat(id="gpt-5-mini"),
        pre_hooks=[PIIDetectionGuardrail()],
        description="An agent that helps with customer service while protecting privacy.",
        instructions="You are a helpful customer service assistant. Always protect user privacy and handle sensitive information appropriately.",
    )

    print("\n✅ Test 1: Normal request without PII")
    print("-" * 30)
    try:
        agent.print_response(
            input="Can you help me understand your return policy?",
        )
        print("✅ Normal request processed successfully")
    except InputCheckError as e:
        print(f"❌ Unexpected error: {e}")

    print("\n🔴 Test 2: Request with credit card number")
    print("-" * 30)
    try:
        agent.print_response(
            input="Please help me update my payment method. My credit card number is 4532 1234 5678 9012."
        )
    except InputCheckError as e:
        print(f"✅ PII blocked: {e.message}")
        print(f"   Trigger: {e.check_trigger}")


if __name__ == "__main__":
    asyncio.run(main())

まずはエージェントを定義します。エージェントの定義を実装する際にpre_hooksを利用することで、推論が実行される前に実行されるフックを定義できます。今回はPIIの検知のために用意されているagno.guardrails.PIIDetectionGuardrailを指定しています。

agent = Agent(
    name="Privacy-Protected Agent",
    model=OpenAIChat(id="gpt-5-mini"),
    pre_hooks=[PIIDetectionGuardrail()],  # <- PII検知のフックを定義
    description="An agent that helps with customer service while protecting privacy.",
    instructions="You are a helpful customer service assistant. Always protect user privacy and handle sensitive information appropriately.",
)

あとはエージェントを単純に呼び出すだけです。Guardrailのチェックに引っかかった場合はagno.exceptions.InputCheckErrorが発せられるため、それをtry/exceptでキャッチすることでPIIが検知されたことを認識し、、エラーを表示させています。

try:
    agent.print_response(
        input="Please help me update my payment method. My credit card number is 4532 1234 5678 9012."
    )
except InputCheckError as e:
    print(f"✅ PII blocked: {e.message}")
    print(f"   Trigger: {e.check_trigger}")

それでは実際にこちらのコードを実行してみます。

uv run pii_sample.py

# 結果
🛡️ PII Detection Guardrails Demo
==================================================

✅ Test 1: Normal request without PII
------------------------------
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃                                                                                                                                                                                                               ┃
┃ Can you help me understand your return policy?                                                                                                                                                                ┃
┃                                                                                                                                                                                                               ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (24.5s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃                                                                                                                                                                                                               ┃
┃ I can — do you mean a specific store or service, or would you like a general overview of a typical return policy? Below is a concise summary of the common elements so you know what to look for and how to   ┃
┃ proceed.                                                                                                                                                                                                      ┃
┃                                                                                                                                                                                                               ┃
┃ Common return-policy elements                                                                                                                                                                                 ┃
┃ - Return window: Many retailers allow returns within 14–90 days (30 days is typical). Holiday purchases sometimes have extended windows.                                                                      ┃
┃ - Condition required: Items usually must be unused, in original packaging, with tags/labels attached. Some items (food, perishable goods, opened personal care items) are often final sale.                   ┃
┃ - If an item is defective, save photos and any serial numbers — the retailer may request them.                                                                                                                ┃
┃ - If you were charged a restocking or shipping fee you didn’t expect, contact customer service and ask for clarification or escalation.                                                                       ┃
┃ 以下省略...                                                                                                                                                                                                               ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
✅ Normal request processed successfully

🔴 Test 2: Request with credit card number
------------------------------
ERROR    Validation failed: Potential PII detected in input | Check: CheckTrigger.PII_DETECTED
▰▱▱▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃                                                                                                                                                                                                               ┃
┃ Please help me update my payment method. My credit card number is 4532 1234 5678 9012.                                                                                                                        ┃
┃                                                                                                                                                                                                               ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
✅ PII blocked: Potential PII detected in input
   Trigger: CheckTrigger.PII_DETECTED

一つ目のサンプルはリターンポリシーを聞いているだけなのでPII検知はされませんが、後者はクレジットカード番号をプロンプトに含めているのでPII検知されています。ちなみにですが、例えばクレジットカード番号の一部をマスキングして特定されないようにした場合、検知は進むようです。

try:
    agent.print_response(
        input="Please help me update my payment method. My credit card number is 4532 XXXX 5678 9012."
    )
except InputCheckError as e:
    print(f"✅ PII blocked: {e.message}")
    print(f"   Trigger: {e.check_trigger}")
# 実行結果
------------------------------
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃                                                                                                                                                                                                               ┃
┃ Please help me update my payment method. My credit card number is 4532 XXXX 5678 9012.                                                                                                                        ┃
┃                                                                                                                                                                                                               ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━ Response (19.1s) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃                                                                                                                                                                                                               ┃
┃ Thanks — I can help, but for your safety please don’t post full card numbers, CVV codes, or other sensitive payment details in this chat. I won’t ask for or store those. I can walk you through secure ways  ┃
┃ to update your payment method or give step‑by‑step instructions for a specific account or service.                                                                                                            ┃
┃                                                                                                                                                                                                               ┃
┃ Quick general steps to update a credit card securely                                                                                                                                                          ┃
┃ 1. Sign in to the service’s website or mobile app.                                                                                                                                                            ┃
┃ 2. Go to Account (or Profile) > Billing, Payment Methods, or Subscriptions.                                                                                                                                   ┃
┃ 3. Choose Add Payment Method (or Edit). Enter:                                                                                                                                                                ┃
┃    - Card number                                                                                                                                                                                              ┃
┃    - Expiration date (MM/YY)                                                                                                                                                                                  ┃
┃    - CVV (3–4 digit code)                                                                                                                                                                                     ┃
┃    - Name on card and billing address                                                                                                                                                                         ┃
┃ 4. Save and, if needed, set the new card as the default payment method.                                                                                                                                       ┃
┃ 5. Optionally remove the old card or keep it for future use.                                                                                                                                                  ┃
┃ 6. Confirm by checking for a confirmation email or a small verification charge (often $0 or $1 which is refunded).                                                                                            ┃
┃                                                                                                                                                                                                               ┃
┃ 以下省略... ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

カスタムGuardrailの実装

agnoではカスタムのGuardrail実装機能も提供されています。チュートリアルではURLが含まれていることを検知してくれるGuardrailの実装れいが提供されています。それを利用したコードは例えば以下になります。

custom_url_guardrail.py
import asyncio
import re

from agno.exceptions import CheckTrigger, InputCheckError
from agno.guardrails import BaseGuardrail
from agno.run.agent import RunInput
from agno.agent import Agent
from agno.guardrails import PIIDetectionGuardrail
from agno.models.openai import OpenAIChat


class URLGuardrail(BaseGuardrail):
    """Guardrail to identify and stop inputs containing URLs."""

    def check(self, run_input: RunInput) -> None:
        """Raise InputCheckError if the input contains any URLs."""
        if isinstance(run_input.input_content, str):
            # Basic URL pattern
            url_pattern = r'https?://[^\s]+|www\.[^\s]+|[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}[^\s]*'
            if re.search(url_pattern, run_input.input_content):
                raise InputCheckError(
                    "The input seems to contain URLs, which are not allowed.",
                    check_trigger=CheckTrigger.INPUT_NOT_ALLOWED,
                )

    async def async_check(self, run_input: RunInput) -> None:
        """Raise InputCheckError if the input contains any URLs."""
        if isinstance(run_input.input_content, str):
            # Basic URL pattern
            url_pattern = r'https?://[^\s]+|www\.[^\s]+|[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}[^\s]*'
            if re.search(url_pattern, run_input.input_content):
                raise InputCheckError(
                    "The input seems to contain URLs, which are not allowed.",
                    check_trigger=CheckTrigger.INPUT_NOT_ALLOWED,
                )


async def main():
    agent = Agent(
        name="Privacy-Protected Agent",
        model=OpenAIChat(id="gpt-5-mini"),
        pre_hooks=[URLGuardrail()],
        description="An agent that helps with customer service while protecting privacy.",
        instructions="You are a helpful customer service assistant. Always protect user privacy and handle sensitive information appropriately.",
    )

    try:
        agent.print_response(
            input="I want to purchase some books from https://fake.com",
        )
        print("✅ Normal request processed successfully")
    except InputCheckError as e:
        print(f"❌ Unexpected error: {e}")


if __name__ == "__main__":
    asyncio.run(main())

カスタムのGuardrailを実装するにはagno.guardrails.BaseGuardrailを継承したクラスを定義します。今回は正規表現を利用してURLが含まれているか検知し、検知した場合agno.exceptions.InputCheckErrorをraiseするようにしています。

class URLGuardrail(BaseGuardrail):
    """Guardrail to identify and stop inputs containing URLs."""

    def check(self, run_input: RunInput) -> None:
        """Raise InputCheckError if the input contains any URLs."""
        if isinstance(run_input.input_content, str):
            # Basic URL pattern
            url_pattern = r'https?://[^\s]+|www\.[^\s]+|[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}[^\s]*'
            if re.search(url_pattern, run_input.input_content):
                raise InputCheckError(
                    "The input seems to contain URLs, which are not allowed.",
                    check_trigger=CheckTrigger.INPUT_NOT_ALLOWED,
                )

    async def async_check(self, run_input: RunInput) -> None:
        """Raise InputCheckError if the input contains any URLs."""
        if isinstance(run_input.input_content, str):
            # Basic URL pattern
            url_pattern = r'https?://[^\s]+|www\.[^\s]+|[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}[^\s]*'
            if re.search(url_pattern, run_input.input_content):
                raise InputCheckError(
                    "The input seems to contain URLs, which are not allowed.",
                    check_trigger=CheckTrigger.INPUT_NOT_ALLOWED,
                )

このサンプルを試しに実行してみると、以下のようにURLが検知されたと表示されます。

uv run custom_url_guardrail.py

# 結果
ERROR    Validation failed: The input seems to contain URLs, which are not allowed. | Check: CheckTrigger.INPUT_NOT_ALLOWED
▰▱▱▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃                                                                                                                                                                                                               ┃
┃ I want to purchase some books from https://fake.com                                                                                                                                                           ┃
┃                                                                                                                                                                                                               ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
❌ Unexpected error: The input seems to contain URLs, which are not allowed.

例えばこの仕組みを利用して16桁のクレジットカード番号を検知する仕組みを自前で導入すると以下のようになるでしょうか。

custom_credit_card_guardrail.py
import asyncio
import re

from agno.exceptions import CheckTrigger, InputCheckError
from agno.guardrails import BaseGuardrail
from agno.run.agent import RunInput
from agno.agent import Agent
from agno.guardrails import PIIDetectionGuardrail
from agno.models.openai import OpenAIChat


class CreditCardGuardrail(BaseGuardrail):
    """Guardrail to identify and stop inputs containing URLs."""

    def check(self, run_input: RunInput) -> None:
        """Raise InputCheckError if the input contains any URLs."""
        if isinstance(run_input.input_content, str):
            pattern = r"[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{4}"
            if re.search(pattern, run_input.input_content):
                raise InputCheckError(
                    "The input seems to contain credit card number, which are not allowed.",
                    check_trigger=CheckTrigger.INPUT_NOT_ALLOWED,
                )

    async def async_check(self, run_input: RunInput) -> None:
        """Raise InputCheckError if the input contains any URLs."""
        if isinstance(run_input.input_content, str):
            pattern = r"[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{4}"
            if re.search(pattern, run_input.input_content):
                raise InputCheckError(
                    "The input seems to contain credit card number, which are not allowed.",
                    check_trigger=CheckTrigger.INPUT_NOT_ALLOWED,
                )


async def main():
    agent = Agent(
        name="Privacy-Protected Agent",
        model=OpenAIChat(id="gpt-5-mini"),
        pre_hooks=[CreditCardGuardrail()],
        description="An agent that helps with customer service while protecting privacy.",
        instructions="You are a helpful customer service assistant. Always protect user privacy and handle sensitive information appropriately.",
    )

    try:
        agent.print_response(
            input="I want to purchase some books using my credit card which number is 1234-5678-9012-3456",
        )
        print("✅ Normal request processed successfully")
    except InputCheckError as e:
        print(f"❌ Unexpected error: {e}")


if __name__ == "__main__":
    asyncio.run(main())

正規表現のパターンを変更しただけですが、ハイフンありの16桁の番号であれば検知できます(クレジットカードの種類によっては桁数が変わったりするので全ては網羅できません)。

pattern = r"[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{4}"

このコードを実行すると以下のようになり、想定通り検知されることを確認できました。

uv run custom_credit_card_guardrail.py

# 結果
ERROR    Validation failed: The input seems to contain credit card number, which are not allowed. | Check: CheckTrigger.INPUT_NOT_ALLOWED
▰▱▱▱▱▱▱ Thinking...
┏━ Message ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃                                                                                                                                                                                                               ┃
┃ I want to purchase some books using my credit card which number is 1234-5678-9012-3456                                                                                                                        ┃
┃                                                                                                                                                                                                               ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
❌ Unexpected error: The input seems to contain credit card number, which are not allowed.

まとめ

今回はagnoのGuardrail機能を試してみました。エージェントを利用するサービスを展開するときは特にGuardrail昨日は重要であり、サービス価値を損なわないためのベース技術となります。100%全てのリスクを排除するのは難しいものの、Guardrailの導入はとても重要な要素ですので、ぜひ利用を検討してみてください。

Discussion