Skip to content

Privacy

See the privacy guide for how these fit together.

PrivacyProfile

Bases: BaseModel

Privacy controls attached to a LegalDoc.

Attributes

sensitivity: Broad sensitivity classification. Controls default routing and whether auto-redaction is applied. contains_pii: Set by PIIDetector, not the caller. pii_entities_found: Entity type strings detected by PIIDetector (e.g. ["PERSON", "PASSPORT_NUMBER"]). redaction_applied: True once PIIRedactor.redact() has been applied to the text that will reach an LLM. redaction_strategy: How PII is replaced. Defaults to REPLACE (typed placeholders). llm_routing: Override for LLM provider selection. If None, routing is derived from sensitivity automatically. retention_days: GDPR Art. 5(1)(e) โ€” None means no expiry. data_subject_ids: Natural-person IDs for GDPR Art. 17 right-to-erasure requests. consent_reference: Identifier linking to a consent record for GDPR Art. 7 compliance.

requires_redaction: bool property

True when the document must be redacted before any LLM call.

Redaction is required when sensitivity is at least "confidential" and the document has not yet been redacted.

is_blocked: bool property

True when no LLM may process this document.

is_local_only: bool property

True when only local (Ollama) providers are permitted.

RedactionStrategy

Bases: str, Enum

How PII should be replaced in text before LLM consumption.

REPLACE = 'replace' class-attribute instance-attribute

Substitute with typed placeholder e.g. <PERSON_1>.

HASH = 'hash' class-attribute instance-attribute

Replace with keyed HMAC-SHA256 hash (one-way, suitable for analytics).

MASK = 'mask' class-attribute instance-attribute

Replace with *** (for display / logging โ€” not reversible).

ENCRYPT = 'encrypt' class-attribute instance-attribute

AES-256-GCM encryption with caller-supplied key (reversible).

PIIDetector

Detect PII spans in text with optional Presidio backend.

Parameters

entities: Entity types to detect. Defaults to _DEFAULT_ENTITIES. thresholds: Per-entity minimum confidence score. Falls back to 0.75 for unlisted types. languages: Language codes to attempt detection for. The Presidio backend will detect all listed languages; the regex fallback only supports "en". use_presidio: Force-enable or force-disable Presidio. When None (default), Presidio is used if available; otherwise the regex fallback is used.

using_presidio: bool property

True when Presidio is active as the primary backend.

detect(text: str, language: str = 'en') -> list[PIISpan]

Detect PII spans in text.

Parameters

text: Plain text to analyse. language: BCP-47 language code (used by Presidio backend; regex fallback always treats text as English).

Returns

list[PIISpan] Non-overlapping spans, sorted by start offset, with offsets into text.

add_recognizer(recognizer: RegexPIIRecognizer) -> None

Register a custom regex-based entity recognizer.

set_threshold(entity_type: str, threshold: float) -> None

Override the confidence threshold for a specific entity type.

PIISpan dataclass

A detected PII occurrence in text.

Attributes

entity_type: str Entity type label (e.g. "PERSON", "EMAIL_ADDRESS"). start: int Start character offset (inclusive). end: int End character offset (exclusive). score: float Detection confidence in [0, 1]. text: str The matched text fragment. language: str Language code used for detection.

RegexPIIRecognizer dataclass

A custom regex-based recognizer for project-specific entity types.

Parameters

entity_type: Label to attach to matches (e.g. "MDA_IDNP"). pattern: Python re compatible regular expression. context_words: Surrounding words that increase confidence (used by Presidio's context-aware scoring; ignored by the regex fallback). languages: BCP-47 language codes this recognizer applies to. score: Base confidence assigned to matches (0โ€“1).

PIIRedactor

Replaces PII spans in text with safe placeholders.

Parameters

default_strategy: Default replacement strategy. Defaults to REPLACE. strategy_overrides: Per-entity-type strategy overrides, e.g. {"IBAN_CODE": RedactionStrategy.HASH}. encryption_key: 32-byte key for AES-256-GCM (required when any entity uses ENCRYPT strategy). Generates a random key if not supplied and ENCRYPT is requested. hmac_key: Secret key for HMAC-SHA256 hashing (HASH strategy). Generates a random key if not supplied.

redact(text: str, spans: list[PIISpan]) -> RedactedText

Replace all spans in text according to the configured strategies.

Spans may arrive in any order and may overlap; overlapping spans are merged so every covered character is redacted. Placeholder labels that already occur in text are skipped, so restore() is exact.

Parameters

text: Original text containing PII. spans: Sorted (ascending start) list of PII spans from PIIDetector.

Returns

RedactedText Contains the redacted text and the mapping needed for restoration.

restore(text: str, redaction_map: RedactionMap) -> str

Restore placeholders in text back to their original values.

Only works for REPLACE and ENCRYPT strategies. MASK and HASH replacements are irreversible. ENCRYPT tokens are decrypted with redaction_map.encryption_key (or this redactor's key); without a key they are left in place.

Parameters

text: Text containing placeholders (e.g. LLM output). redaction_map: The RedactionMap produced by the matching redact() call.

Returns

str Text with placeholders replaced by original values (where mapping exists).

RedactedText dataclass

The output of a single PIIRedactor.redact() call.

Attributes

text: Redacted text safe to pass to an LLM. redaction_map: Mapping required to restore original values (REPLACE and ENCRYPT strategies only). entity_types_redacted: Set of entity type strings that were redacted. span_count: Total number of PII spans that were replaced.

RedactionMap dataclass

Forward and reverse mapping produced by a single redact() call.

Attributes

placeholder_to_original: Maps REPLACE placeholders (e.g. "<PERSON_1>") to original values. This is plaintext PII. ENCRYPT tokens are never stored here. original_to_placeholder: Reverse index used to give repeated values the same placeholder. Held in memory only; serialise() does not write it. counters: Per-entity-type incrementing counter used to generate unique labels. encryption_key: Only present when strategy is ENCRYPT. Stored as bytes; callers are responsible for securing this.

register(entity_type: str, original: str, placeholder: str) -> None

Record a mapping in both directions.

next_label(entity_type: str) -> str

Return the next placeholder label for entity_type.

serialise() -> dict[str, Any]

Serialise to a JSON-compatible dict.

The result contains the original REPLACE values in plaintext: store it with the same protection as the source document, never in an audit log.

PrivacyAwareLLMRouter

Routes LLM calls through privacy enforcement.

Parameters

detector: PIIDetector instance. A default instance is created if not supplied. redactor: PIIRedactor instance. A default instance is created if not supplied. auto_redact: When True (default), detect and redact PII in every prompt for documents whose profile requires_redaction. default_profile: Profile applied to documents that have no privacy_profile set. Defaults to PrivacyProfile(sensitivity="public").

route(doc: Any, prompt: str, schema: type[BaseModel], provider: LLMProvider, *, restore_redaction: bool = False) -> BaseModel

Enforce privacy controls then call provider.extract_structured.

Raises

PrivacyBlockedError If the document's routing is "blocked". PrivacyRoutingError If the provider is not permitted for this document.

route_completion(doc: Any, prompt: str, provider: LLMProvider, *, restore_redaction: bool = False, **kwargs: Any) -> str

Enforce privacy controls then call provider.complete.

guard(provider: LLMProvider, *docs: Any) -> LLMProvider

Wrap provider so every call on it is enforced for docs.

Raises PrivacyBlockedError / PrivacyRoutingError immediately if the provider may never be used for these documents. Responses are restored (placeholders replaced by the original values) except when streaming, where a placeholder can be split across tokens.

PrivacyGuardedProvider

Bases: LLMProvider

An LLMProvider whose every call is routed through a PrivacyAwareLLMRouter for a fixed privacy profile. Created by PrivacyAwareLLMRouter.guard().

PrivacyBlockedError

Bases: ContractExError

Raised when a document's privacy profile forbids any LLM processing.

sensitivity="secret" or llm_routing="blocked" triggers this.

PrivacyRoutingError

Bases: ContractExError

Raised when the requested LLM provider is not permitted for this document.

Typically: a provider that is not a LocalProvider is used for a document that requires llm_routing="local_only".