Skip to content

Commit 8efa018

Browse files
authored
Merge pull request #1915 from arin-deloatch/feat/LCORE-1831
LCORE-1831: Implement Redaction Safety Capability in Pydantic AI
2 parents 21d6bc5 + 62a26e0 commit 8efa018

9 files changed

Lines changed: 1124 additions & 0 deletions

File tree

src/models/config.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from log import get_logger
3232
from utils import checks
3333
from utils.mcp_auth_headers import resolve_authorization_headers
34+
from utils.types import CompiledPatterns
3435

3536
logger = get_logger(__name__)
3637

@@ -2347,6 +2348,102 @@ class QuestionValidityConfig(ConfigurationBase):
23472348
)
23482349

23492350

2351+
class RedactionRule(ConfigurationBase):
2352+
"""A single regex-based redaction rule.
2353+
2354+
Attributes:
2355+
pattern: Raw regex pattern string to match sensitive data.
2356+
replacement: Text to substitute for each match.
2357+
case_sensitive: Per-rule override for case sensitivity.
2358+
When None, the global ``RedactionConfig.case_sensitive``
2359+
flag applies.
2360+
"""
2361+
2362+
pattern: str = Field(
2363+
...,
2364+
title="Pattern",
2365+
description="Regex pattern to match sensitive data",
2366+
)
2367+
replacement: str = Field(
2368+
...,
2369+
title="Replacement",
2370+
description="Replacement string for matched text",
2371+
)
2372+
case_sensitive: bool | None = Field(
2373+
None,
2374+
title="Case sensitive",
2375+
description=(
2376+
"Per-rule case sensitivity override. "
2377+
"When None, the global config flag applies."
2378+
),
2379+
)
2380+
2381+
2382+
class RedactionConfig(ConfigurationBase):
2383+
"""Configuration for PII redaction with regex-based rules.
2384+
2385+
Rules are validated and compiled at construction time. Invalid
2386+
regex patterns raise a ``ValueError`` immediately.
2387+
2388+
Attributes:
2389+
rules: Ordered list of redaction rules applied sequentially.
2390+
case_sensitive: When False, patterns are compiled with
2391+
``re.IGNORECASE``. Defaults to False.
2392+
"""
2393+
2394+
rules: list[RedactionRule] = Field(
2395+
default_factory=list,
2396+
title="Redaction rules",
2397+
description="Ordered list of PII redaction rules",
2398+
)
2399+
case_sensitive: bool = Field(
2400+
False,
2401+
title="Case sensitive",
2402+
description=("When False, patterns are compiled with re.IGNORECASE"),
2403+
)
2404+
2405+
_compiled_patterns: CompiledPatterns = PrivateAttr(
2406+
default_factory=list,
2407+
)
2408+
2409+
@model_validator(mode="after")
2410+
def compile_patterns(self) -> Self:
2411+
"""Compile regex patterns and reject invalid ones.
2412+
2413+
Per-rule ``case_sensitive`` overrides the global flag when set.
2414+
2415+
Raises:
2416+
ValueError: If any rule contains an invalid regex pattern.
2417+
2418+
Returns:
2419+
The validated configuration instance.
2420+
"""
2421+
global_case_sensitive = self.case_sensitive
2422+
compiled: CompiledPatterns = []
2423+
for rule in self.rules:
2424+
effective = (
2425+
rule.case_sensitive
2426+
if rule.case_sensitive is not None
2427+
else global_case_sensitive
2428+
)
2429+
flags = 0 if effective else re.IGNORECASE
2430+
try:
2431+
pattern = re.compile(rule.pattern, flags)
2432+
except re.error as e:
2433+
raise ValueError(f"Invalid regex pattern: {rule.pattern}: {e}") from e
2434+
compiled.append((pattern, rule.replacement))
2435+
self._compiled_patterns = compiled
2436+
return self
2437+
2438+
@property
2439+
def compiled_patterns(self) -> CompiledPatterns:
2440+
"""Pre-compiled (regex, replacement) pairs.
2441+
2442+
Returns a shallow copy to prevent mutation of internal state.
2443+
"""
2444+
return list(self._compiled_patterns)
2445+
2446+
23502447
class Configuration(ConfigurationBase):
23512448
"""Global service configuration."""
23522449

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""PII redaction capability for Pydantic AI agents."""
2+
3+
from models.config import (
4+
RedactionConfig,
5+
RedactionRule,
6+
)
7+
from pydantic_ai_lightspeed.capabilities.redaction.capability import (
8+
PiiRedactionCapability,
9+
)
10+
from pydantic_ai_lightspeed.capabilities.redaction.core import (
11+
RedactionResult,
12+
redact_text,
13+
)
14+
15+
__all__ = [
16+
"PiiRedactionCapability",
17+
"RedactionConfig",
18+
"RedactionResult",
19+
"RedactionRule",
20+
"redact_text",
21+
]

0 commit comments

Comments
 (0)