windlass.providers.preprocessors.privacy¶
privacy
¶
PII detection and redaction.
Once personal data is embedded into a vector index it is very hard to get back out — you cannot un-ring that bell, and "delete the row" does not undo the copies in your backups. Redacting at ingestion is the only reliable point of control.
This preprocessor ships a dependency-free detector covering the categories that
actually turn up in enterprise documents (email, phone, SSN, credit card, IBAN,
IP, passport, API keys), with a Luhn check on card numbers to keep false
positives down. When presidio-analyzer is installed it is used instead, for
NER-based detection of names, locations and organisations.
Example
from windlass.core.types import Document p = PIIPreprocessor() p.process([Document(content="Reach me at ada@example.com or 555-123-4567")])[0].content 'Reach me at [EMAIL] or [PHONE]'
PIIMatch
¶
Bases: NamedTuple
One detected piece of personal data.
Attributes:
| Name | Type | Description |
|---|---|---|
kind |
str
|
Category, e.g. |
value |
str
|
The matched text. |
start |
int
|
Start offset in the source string. |
end |
int
|
Exclusive end offset. |
PIIPreprocessor
¶
PIIPreprocessor(
*,
kinds: Sequence[str] | None = None,
action: str = "redact",
placeholder: str | None = None,
use_presidio: bool = False,
language: str = "en",
**config: Any
)
Bases: Preprocessor
Detects and optionally redacts personal data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kinds
|
Sequence[str] | None
|
Categories to act on. |
None
|
action
|
str
|
|
'redact'
|
placeholder
|
str | None
|
Fixed replacement token, or |
None
|
use_presidio
|
bool
|
Use |
False
|
language
|
str
|
Language passed to Presidio. |
'en'
|
**config
|
Any
|
Forwarded to :class: |
{}
|
Raises:
| Type | Description |
|---|---|
ValueError
|
For an unknown |
Note
Regex detection is precise for structured identifiers and blind to
unstructured ones — it will not find "Ada Lovelace" as a person's name.
Install windlass[pii] and set use_presidio=True when you need
that, and treat any automated detector as a control, not a guarantee.
Example
from windlass.core.types import Document p = PIIPreprocessor(action="tag") p.process([Document(content="a@b.com")])[0].metadata["pii_kinds"]['email']
Source code in src\windlass\providers\preprocessors\privacy.py
aprocess_one
async
¶
Scan a document and apply the configured action.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
document
|
Document
|
The document to scan. |
required |
Returns:
| Type | Description |
|---|---|
list[Document]
|
The processed document, or |
list[Document]
|
found. |
Source code in src\windlass\providers\preprocessors\privacy.py
detect_pii
¶
Find personal data in text.
Overlapping matches are resolved in favour of the earlier, longer one, so an email address is never also reported as a phone number.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
The text to scan. |
required |
kinds
|
Sequence[str] | None
|
Categories to look for. |
None
|
Returns:
| Type | Description |
|---|---|
list[PIIMatch]
|
Matches sorted by position. |
Example
[m.kind for m in detect_pii("write to a@b.com")]['email']
Source code in src\windlass\providers\preprocessors\privacy.py
redact_pii
¶
redact_pii(
text: str, kinds: Sequence[str] | None = None, *, placeholder: str | None = None
) -> tuple[str, list[PIIMatch]]
Replace personal data in text with placeholders.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
The text to redact. |
required |
kinds
|
Sequence[str] | None
|
Categories to redact. |
None
|
placeholder
|
str | None
|
Fixed replacement for every match. |
None
|
Returns:
| Type | Description |
|---|---|
tuple[str, list[PIIMatch]]
|
A |
Example
redact_pii("call 555-123-4567")[0] 'call [PHONE]'