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] ¶
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".