3.3 カスタムバリデーター

Custom Validators

カスタムバリデーターの作成

ビジネス固有のルールや複雑な検証ロジックには、カスタムバリデーターを作成する。

基本構造

from guardrails.validators import (
    Validator,
    register_validator,
    PassResult,
    FailResult
)

@register_validator(name="my-custom-validator", data_type="string")
class MyCustomValidator(Validator):
    def __init__(self, param1, param2=None, **kwargs):
        super().__init__(**kwargs)
        self.param1 = param1
        self.param2 = param2

    def validate(self, value, metadata) -> PassResult | FailResult:
        if self._check_valid(value):
            return PassResult()
        return FailResult(
            error_message=f"'{value}' は条件を満たしません",
            fix_value=self._suggest_fix(value)
        )
    
    def _check_valid(self, value):
        # 検証ロジック
        return True
    
    def _suggest_fix(self, value):
        # 修正候補を返す(オプション)
        return value

実用的なカスタムバリデーター例

例1: 日本語のみを許可

import re

@register_validator(name="japanese-only", data_type="string")
class JapaneseOnly(Validator):
    def validate(self, value, metadata):
        # ひらがな、カタカナ、漢字、句読点のみ許可
        pattern = r'^[\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF\u3000-\u303F\s]+$'
        if re.match(pattern, value):
            return PassResult()
        return FailResult(
            error_message="日本語以外の文字が含まれています"
        )

例2: 禁止語句チェック

@register_validator(name="no-banned-words", data_type="string")
class NoBannedWords(Validator):
    def __init__(self, banned_words: list[str], **kwargs):
        super().__init__(**kwargs)
        self.banned_words = [w.lower() for w in banned_words]

    def validate(self, value, metadata):
        value_lower = value.lower()
        found = [w for w in self.banned_words if w in value_lower]
        if not found:
            return PassResult()
        return FailResult(
            error_message=f"禁止語句が含まれています: {found}"
        )

# 使用例
class Response(BaseModel):
    text: Annotated[str, NoBannedWords(banned_words=["競合A", "競合B"])]

例3: 価格フォーマット検証

@register_validator(name="valid-price-format", data_type="string")
class ValidPriceFormat(Validator):
    def validate(self, value, metadata):
        # "¥1,000" または "1000円" 形式を許可
        patterns = [
            r'^¥[\d,]+$',
            r'^[\d,]+円$',
            r'^\d+$'
        ]
        for p in patterns:
            if re.match(p, value):
                return PassResult()
        return FailResult(
            error_message="価格フォーマットが不正です",
            fix_value=self._normalize(value)
        )
    
    def _normalize(self, value):
        # 数値部分を抽出して正規化
        nums = re.findall(r'\d+', value)
        if nums:
            return f"¥{','.join(nums)}"
        return value

カスタムバリデーターの登録と使用

# モデルで使用
class ProductInfo(BaseModel):
    name: Annotated[str, JapaneseOnly()]
    description: Annotated[str, NoBannedWords(banned_words=["NG語"])]
    price: Annotated[str, ValidPriceFormat()]

# Guardで使用
guard = Guard.from_pydantic(ProductInfo)
result = guard(
    llm_api=openai.chat.completions.create,
    model="gpt-4",
    messages=[...]
)
ベストプラクティス

・fix_valueを実装してFIXアクションを有効活用

・明確なerror_messageでデバッグを容易に

・再利用可能な粒度で設計

参考文献
[1] Guardrails AI - Custom Validators - https://docs.guardrailsai.com/how_to_guides/custom_validators/