Custom Detectors
Write your own detector and add it to the pipeline
Rdakt AI's detection pipeline is extensible. You can write custom detectors that implement the Detector protocol and add them as pipeline stages.
The Detector Protocol
A detector is any object with a detect(text: str) -> list[Entity] method:
from rdakt_ai import Entity
class Detector:
def detect(self, text: str) -> list[Entity]: ...Example: Custom Regex Detector
Detect employee IDs and internal project codes:
import re
from rdakt_ai import Entity
class InternalDataDetector:
"""Detect company-specific PII patterns."""
PATTERNS = {
"EMPLOYEE_ID": r"EMP-\d{6}",
"PROJECT_CODE": r"PRJ-[A-Z]{2}-\d{4}",
"INTERNAL_EMAIL": r"[a-zA-Z.]+@mycompany\.com",
}
def detect(self, text: str) -> list[Entity]:
entities = []
for entity_type, pattern in self.PATTERNS.items():
for match in re.finditer(pattern, text):
entities.append(Entity(
value=match.group(),
type=entity_type,
start=match.start(),
end=match.end(),
))
return entitiesAdding to the Pipeline
Wrap your detector in a DetectorStage and build the pipeline:
from rdakt_ai import (
RdaktMiddleware,
RdaktPipeline,
DetectorStage,
RegexDetector,
)
pipeline = RdaktPipeline(stages=[
DetectorStage(RegexDetector()), # Built-in patterns
DetectorStage(InternalDataDetector()), # Your custom detector
])To use the custom pipeline with the middleware, you'll need to build the middleware state manually or configure it through the config's entity patterns:
from rdakt_ai import RdaktMiddleware, RdaktConfig, EntityConfig
config = RdaktConfig(
entities={
"EMPLOYEE_ID": EntityConfig(pattern=r"EMP-\d{6}", strategy="token"),
"PROJECT_CODE": EntityConfig(pattern=r"PRJ-[A-Z]{2}-\d{4}", strategy="token"),
"INTERNAL_EMAIL": EntityConfig(pattern=r"[a-zA-Z.]+@mycompany\.com", strategy="token"),
},
)
middleware = RdaktMiddleware(config=config)Custom Pipeline Stage
For more control, implement a full RdaktStage:
from rdakt_ai import RdaktStage, RdaktContext
class FilterStage(RdaktStage):
"""Remove entities below a confidence threshold or in an allowlist."""
def __init__(self, allowlist: set[str]):
self.allowlist = allowlist
async def process(self, text, context, call_next):
# Let downstream detectors run first
await call_next(text, context)
# Filter out allowed values
context.entities = [
e for e in context.entities
if e.value.lower() not in self.allowlist
]
def process_sync(self, text, context, call_next_sync):
call_next_sync(text, context)
context.entities = [
e for e in context.entities
if e.value.lower() not in self.allowlist
]Both process (async) and process_sync must be implemented if you want your stage to work with both RdaktMiddleware (async) and RdaktSyncMiddleware (sync).
Testing Your Detector
def test_internal_data_detector():
detector = InternalDataDetector()
entities = detector.detect("Contact EMP-123456 about PRJ-AB-1234")
assert len(entities) == 2
assert entities[0].type == "EMPLOYEE_ID"
assert entities[0].value == "EMP-123456"
assert entities[1].type == "PROJECT_CODE"
assert entities[1].value == "PRJ-AB-1234"