Content Filtering › Implementation: Rule-Based PII Filtering

Michael BrenndoerferFebruary 22, 20262 min read
Language AI HandbookAll chapters
Part 60 of 67Safety and Security
Chapter 461Content Filtering › Implementation: Rule-Based PII Filtering
Reading section 7 of 14 · Implementation: Rule-Based PII Filtering · 2 minSection 7 of 14 · 2 min

Book contents

Section 7 of 14 · 2 min read

Content Filtering › Implementation: Rule-Based PII FilteringOne section · Sections in this chapter7 / 14 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

Let's also implement a rule-based filter for detecting and redacting personally identifiable information (PII) in model outputs. This is a common practical need where the regularity of the patterns makes regex a better choice than ML.

In[10]:
Code
import re
from dataclasses import dataclass
from typing import List, Tuple


@dataclass
class PIIMatch:
    category: str
    matched_text: str
    start: int
    end: int
    replacement: str


# PII pattern registry: maps category name to (regex_pattern, replacement_text)
PII_PATTERNS = {
    "email": (
        r"\b[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}\b",
        "[EMAIL REDACTED]",
    ),
    "phone_us": (
        r"\b(?:\+?1[\s.\-]?)?\(?\d{3}\)?[\s.\-]?\d{3}[\s.\-]\d{4}\b",
        "[PHONE REDACTED]",
    ),
    "credit_card": (
        r"\b\d{4}[\s\-]?\d{4}[\s\-]?\d{4}[\s\-]?\d{4}\b",
        "[CARD REDACTED]",
    ),
    "ssn": (r"\b\d{3}-\d{2}-\d{4}\b", "[SSN REDACTED]"),
}


def detect_and_redact_pii(text: str) -> Tuple[str, List[PIIMatch]]:
    """
    Detect PII using regex patterns and return the redacted text plus a list of matches.
    Handles overlapping matches by sorting and applying replacements in order.
    """
    # Collect all matches with their span and category
    raw_matches = []
    for category, (pattern, replacement) in PII_PATTERNS.items():
        for m in re.finditer(pattern, text):
            raw_matches.append(
                (m.start(), m.end(), category, m.group(), replacement)
            )

    # Sort by start position to apply replacements left to right
    raw_matches.sort(key=lambda x: x[0])

    matches = []
    redacted = text
    offset = 0

    for start, end, category, matched_text, replacement in raw_matches:
        adj_start = start + offset
        adj_end = end + offset
        matches.append(
            PIIMatch(
                category=category,
                matched_text=matched_text,
                start=start,
                end=end,
                replacement=replacement,
            )
        )
        redacted = redacted[:adj_start] + replacement + redacted[adj_end:]
        offset += len(replacement) - (end - start)

    return redacted, matches
In[11]:
Code
# Test PII redaction on model outputs
test_outputs = [
    "You can reach me at john.doe@example.com or call 555-867-5309.",
    "Please charge card number 4111-1111-1111-1111 for the order.",
    "My SSN is 123-45-6789 and my email is alice@corp.net.",
    "No sensitive information here, just a normal response.",
]

pii_results = []
for text in test_outputs:
    redacted, matches = detect_and_redact_pii(text)
    pii_results.append((text, redacted, matches))
Out[12]:
Console
Original:  You can reach me at john.doe@example.com or call 555-867-5309.
Redacted:  You can reach me at [EMAIL REDACTED] or call [PHONE REDACTED].
Detected:  email: 'john.doe@example.com', phone_us: '555-867-5309'

Original:  Please charge card number 4111-1111-1111-1111 for the order.
Redacted:  Please charge card number [CARD REDACTED] for the order.
Detected:  credit_card: '4111-1111-1111-1111'

Original:  My SSN is 123-45-6789 and my email is alice@corp.net.
Redacted:  My SSN is [SSN REDACTED] and my email is [EMAIL REDACTED].
Detected:  ssn: '123-45-6789', email: 'alice@corp.net'

Original:  No sensitive information here, just a normal response.
Redacted:  No sensitive information here, just a normal response.
Detected:  none

The rule-based PII filter reliably catches structured patterns. Notice how combining both ML classification and regex-based rule filters covers complementary failure modes: the ML classifier handles context-dependent semantic content (hate, threats, harmful instructions) while regex handles structured format violations (PII, card numbers, credentials) with higher precision.