Skip to content
Open
96 changes: 96 additions & 0 deletions src/models/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2176,6 +2176,102 @@ class SkillsConfiguration(ConfigurationBase):
)


class RedactionRule(ConfigurationBase):
"""A single regex-based redaction rule.

Attributes:
pattern: Raw regex pattern string to match sensitive data.
replacement: Text to substitute for each match.
case_sensitive: Per-rule override for case sensitivity.
When None, the global ``RedactionConfig.case_sensitive``
flag applies.
"""

pattern: str = Field(
...,
title="Pattern",
description="Regex pattern to match sensitive data",
)
replacement: str = Field(
...,
title="Replacement",
description="Replacement string for matched text",
)
case_sensitive: bool | None = Field(
None,
title="Case sensitive",
description=(
"Per-rule case sensitivity override. "
"When None, the global config flag applies."
),
)


class RedactionConfig(ConfigurationBase):
"""Configuration for PII redaction with regex-based rules.

Rules are validated and compiled at construction time. Invalid
regex patterns raise a ``ValueError`` immediately.

Attributes:
rules: Ordered list of redaction rules applied sequentially.
case_sensitive: When False, patterns are compiled with
``re.IGNORECASE``. Defaults to False.
"""

rules: list[RedactionRule] = Field(
default_factory=list,
title="Redaction rules",
description="Ordered list of PII redaction rules",
)
case_sensitive: bool = Field(
False,
title="Case sensitive",
description=("When False, patterns are compiled with re.IGNORECASE"),
)

_compiled_patterns: list[tuple[Pattern[str], str]] = PrivateAttr(
default_factory=list,
)
Comment thread
asimurka marked this conversation as resolved.

@model_validator(mode="after")
def compile_patterns(self) -> Self:
"""Compile regex patterns and reject invalid ones.

Per-rule ``case_sensitive`` overrides the global flag when set.

Raises:
ValueError: If any rule contains an invalid regex pattern.

Returns:
The validated configuration instance.
"""
global_case_sensitive = self.case_sensitive
compiled: list[tuple[Pattern[str], str]] = []
for rule in self.rules:
effective = (
rule.case_sensitive
if rule.case_sensitive is not None
else global_case_sensitive
)
flags = 0 if effective else re.IGNORECASE
try:
pattern = re.compile(rule.pattern, flags)
except re.error as e:
raise ValueError(f"Invalid regex pattern: {rule.pattern}: {e}") from e
compiled.append((pattern, rule.replacement))
self._compiled_patterns = compiled
return self

@property
def compiled_patterns(self) -> list[tuple[Pattern[str], str]]:
"""Pre-compiled (regex, replacement) pairs.

Returns a shallow copy to prevent mutation of internal state.
"""
return list(self._compiled_patterns)


class Configuration(ConfigurationBase):
"""Global service configuration."""

Expand Down
1 change: 1 addition & 0 deletions src/pydantic_ai_lightspeed/capabilities/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Pydantic AI capabilities for Lightspeed Core Stack."""
21 changes: 21 additions & 0 deletions src/pydantic_ai_lightspeed/capabilities/redaction/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""PII redaction capability for Pydantic AI agents."""

from models.config import (
RedactionConfig,
RedactionRule,
)
from pydantic_ai_lightspeed.capabilities.redaction.capability import (
PiiRedactionCapability,
)
from pydantic_ai_lightspeed.capabilities.redaction.core import (
RedactionResult,
redact_text,
)

__all__ = [
"PiiRedactionCapability",
"RedactionConfig",
"RedactionResult",
"RedactionRule",
"redact_text",
]
Loading
Loading