Skip to content

Configuration reference

Module: piighost.config

A configuration file describes a whole pipeline declaratively. piighost reads it as TOML or JSON, chosen by the file suffix, validates it with Pydantic, and builds the pipeline the file describes. This page documents every section and every component type.

from piighost.config import load_config, load_pipeline, load_thread_pipeline

The config extra is required (pip install piighost[config]), which pulls in pydantic-settings. Unknown keys are rejected, so a typo fails validation rather than being ignored. A component type can need an extra of its own, named in the Extra column of the table that documents it.


Entry points

Function Returns Builds Memory
load_config(path) PipelineConfig nothing, validates only any
load_pipeline(path) AnonymizationPipeline a stateless pipeline rejects a [memory] section
load_thread_pipeline(path) ThreadAnonymizationPipeline a thread pipeline requires a [memory] section

load_config parses and validates a file into a PipelineConfig without building any component, so no model loads. load_pipeline builds a stateless AnonymizationPipeline and raises ConfigError if the file declares a [memory] section, since a memory describes a thread pipeline. load_thread_pipeline builds a ThreadAnonymizationPipeline and raises ConfigError if the file declares no [memory] section.

from piighost.config import load_pipeline, load_thread_pipeline

stateless = load_pipeline("pipeline.toml")       # no [memory]
thread = load_thread_pipeline("thread.toml")     # has [memory]

File format

The suffix picks the parser. A .json suffix is read as JSON, compared without regard to case, and anything else as TOML. The two formats carry the same schema. A section is a TOML table or a JSON object.

[detector]
type = "regex"
patterns = { EMAIL = '[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}' }

[linker]
type = "exact"

[anonymizer.placeholder]
type = "redact"
{
  "detector": { "type": "regex", "patterns": { "EMAIL": "[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,}" } },
  "linker": { "type": "exact" },
  "anonymizer": { "placeholder": { "type": "redact" } }
}

Environment overrides

Every top-level key accepts an override from an environment variable prefixed PIIGHOST_, whether it holds a scalar or a whole section. PIIGHOST_NAME overrides the name scalar, and PIIGHOST_DETECTOR overrides the [detector] section with a JSON object, rejected as a validation error when it is not valid JSON. Overrides layer above the file key by key, so an environment value wins over the file value and the keys it leaves out keep theirs.

export PIIGHOST_NAME="local-en"
export PIIGHOST_DETECTOR='{"type": "exact", "values": {"Patrick": "PERSON"}}'

No nested delimiter is configured, so a variable such as PIIGHOST_DETECTOR__TYPE names no field, and it is ignored without an error rather than reaching the type key. A section is overridden by its JSON object only.

Secrets are never read from the file. Each is read from its own environment variable at build time, and a missing one raises ConfigError from build().

Secret Variable Format Used by
Hash pepper PIIGHOST_HASH_PEPPER any non-empty string [memory.hasher]
Cipher key PIIGHOST_CIPHER_KEY base64 of 16, 24, or 32 bytes [memory.cipher]
Moderation key MISTRAL_API_KEY Mistral API key [guard] type moderation
Database URL the url_env value, PIIGHOST_DATABASE_URL by default an async SQLAlchemy URL [memory] type sqlalchemy

Sections

The top-level keys of a PipelineConfig.

Section Required Meaning
name no An optional pipeline name, a top-level scalar overridable by PIIGHOST_NAME
token_memo_ttl no The seconds a thread's memoized token map is kept, a top-level scalar, needs a [memory]
[detector] yes The detect stage
[linker] no The entity linker, defaults to ExactEntityLinker
[anonymizer] no The render stage, defaults to an Anonymizer with a label-counter factory
[overlap_resolver] no Resolves overlapping detections, defaults to ConfidenceOverlapResolver
[expander] no Re-finds missed occurrences of a detected value
[entity_resolver] no Clusters entities that refer to the same thing
[guard] no Re-checks the output for residual PII
[override] no Forces or vetoes detections via a whitelist and a blacklist
[observation_redactor] no A placeholder factory redacting trace payloads
[memory] no The conversation memory, its presence makes a thread pipeline

[detector]

Discriminated on type. Required.

type = "regex"

Matches PII by one regex per label, pulled from inline patterns, named catalogs, or both. Catalogs merge first, then inline patterns, so an inline pattern overrides a catalog pattern on the same label. At least one inline pattern or one catalog is required. Each pattern is validated as a compilable regex at load time, then compiled under re.ASCII, so \d matches 0-9 and a shape class stops at the first non-ASCII character. A value such as prénom@corp.com is therefore matched from nom onwards.

Key Type Default Meaning
patterns dict[str, str] {} Inline label-to-regex mapping
catalogs list[str] [] Prebuilt catalogs, only generic, us, eu, fr, any other name failing validation
[detector]
type = "regex"
catalogs = ["generic", "fr"]
patterns = { EMPLOYEE_ID = 'EMP-[0-9]{4}' }

type = "composite"

Runs child detectors together and merges their detections.

Key Type Meaning
detectors list[detector] The child detector configs, at least one, as [[detector.detectors]]
[detector]
type = "composite"

[[detector.detectors]]
type = "regex"
catalogs = ["generic"]

[[detector.detectors]]
type = "exact"
values = { Patrick = "PERSON" }

type = "exact"

Finds occurrences of literal values, each mapped to a label.

Key Type Meaning
values dict[str, str] Literal value to label mapping, at least one
[detector]
type = "exact"
values = { Patrick = "PERSON", Lyon = "LOCATION" }

type = "chunked"

Wraps a detector with a splitter that cuts long text into overlapping chunks.

Key Type Default Meaning
detector detector The detector run on each chunk, as [detector.detector]
chunk_size int 1000 Maximum chunk size, greater than 0
chunk_overlap int 100 Overlap between chunks, below chunk_size
[detector]
type = "chunked"
chunk_size = 2000
chunk_overlap = 200

[detector.detector]
type = "spacy"
model = "en_core_web_sm"

Model-backed detectors

Each needs its own extra, and every one but presidio needs a model. labels accepts a list or an {emitted: internal} map. max_concurrency caps concurrent inferences, or None for unbounded.

type Extra Keys
gliner2 gliner2 model (required), labels (required), threshold (default 0.5), max_concurrency
spacy spacy model (required), labels, max_concurrency
transformers transformers model (required), labels, threshold (default 0.0), aggregation_strategy (default simple), max_concurrency
presidio presidio labels, language (default en), threshold (default 0.0)
llm llm model (required), labels (required), prompt, provider
[detector]
type = "gliner2"
model = "fastino/gliner2-multi-v1"
labels = ["PERSON", "LOCATION"]
threshold = 0.5

The transformers detector passes aggregation_strategy to its token-classification pipeline, which groups sub-word tokens into whole entities.

The presidio detector takes no model key, since the config path builds Presidio's default English AnalyzerEngine with its default recognizers. Another language, a custom recognizer, or a custom NLP engine is the programmatic path, constructing the engine and passing it to PresidioDetector.

The llm detector reads its provider credential from the provider's own environment variable, never from the file.


[linker]

Optional. Defaults to ExactEntityLinker. One linker exists, so type names it rather than discriminating a union.

type Meaning
exact Groups detections by casefolded value
[linker]
type = "exact"

[anonymizer]

Optional. Defaults to an Anonymizer with a label-counter factory. When present it carries one [anonymizer.placeholder] table selecting the placeholder factory, discriminated on type.

type Token Keys
redact <<REDACT>>
label <<PERSON>>
label_counter <<PERSON:1>>
label_hash <<PERSON:a1b2c3d4>> hash_length (default 8, at least 1)
mask P*** visible (default 1, 0 or more), mask_char (default *, exactly one character)
[anonymizer.placeholder]
type = "label_counter"

The middleware needs a delimited factory, so redact, label, label_counter, or label_hash. The mask factory produces P***, which keeps no delimiters and has no recognizer.


[overlap_resolver]

Optional in the file, but the stage runs either way. Omitting the section builds a ConfidenceOverlapResolver, and there is no supported way to disable the stage, since the render stage assumes disjoint spans. One resolver exists, so type names it rather than discriminating a union.

type Meaning
confidence Keeps the highest-confidence detection when two overlap
[overlap_resolver]
type = "confidence"

[expander]

Optional, and disabled when omitted. One expander exists, so type names it rather than discriminating a union.

type Keys Meaning
word_boundary case_sensitive (default false) Re-finds a detected value's other whole-word occurrences
[expander]
type = "word_boundary"
case_sensitive = false

[entity_resolver]

Optional. Discriminated on type.

type Extra Keys Meaning
merge Unions entities that share detections
separate Keeps every entity distinct
fuzzy fuzzy threshold (default 0.85) Clusters entities at or above a Jaro-Winkler similarity
[entity_resolver]
type = "fuzzy"
threshold = 0.85

[guard]

Optional. Discriminated on type. Re-checks the de-identified output for residual PII and refuses it when PII remains.

type Extra Re-checks with
detector A detector re-run on the output
llm llm A chat model prompted to find residual PII
moderation mistral A Mistral moderation model scoring the output

type = "detector"

Re-runs a detector on the output. Carries a nested [guard.detector] config.

[guard]
type = "detector"

[guard.detector]
type = "regex"
patterns = { EMAIL = '[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}' }

type = "llm"

Prompts a chat model to find residual PII.

Key Type Meaning
model str The chat model identifier (required)
labels list or dict The labels to look for (required)
prompt str A prompt overriding the default, or omitted
provider str The provider, or omitted to infer from the model

type = "moderation"

Scores the output with a Mistral moderation model. The credential is read from MISTRAL_API_KEY at build time, and build() raises ConfigError when it is unset.

Key Type Default Meaning
model str mistral-moderation-latest The moderation model
threshold float 0.5 The category score at or above which the text is flagged

[override]

Optional. Forces detections through a whitelist and vetoes them through a blacklist. Each list is a detector config, [override.whitelist] and [override.blacklist], and both are optional.

Key Values Default Meaning
[override.whitelist] detector A detector whose hits are forced into the set
[override.blacklist] detector A detector whose hits invalidate detections
blacklist_strategy exact, value, overlap value How a blacklist hit invalidates, same casefolded value, same span and label, or any overlapping span
whitelist_strategy respect_provenance, force respect_provenance Whether a whitelist hit leaves an assistant-introduced value in clear, or tokenizes it regardless
conflict_strategy whitelist_wins, blacklist_wins, raise whitelist_wins Who wins when the two lists contradict. raise refuses the collision with ConflictingOverrideError
[override]
blacklist_strategy = "value"

[override.whitelist]
type = "regex"
patterns = { CODENAME = 'ACME-[A-Z]+' }

[override.blacklist]
type = "exact"
values = { "public@corp.com" = "EMAIL" }

[observation_redactor]

Optional. A placeholder factory config, same type values as [anonymizer.placeholder], redacting the payloads sent to a tracing backend so a trace holds tokens, not raw values.

[observation_redactor]
type = "label"

Omitting the section traces the clear text and the detection values, and a live tracer then emits a PIIGhostSecurityWarning. The pipeline's trace_clear_text flag, which silences that warning, has no key in a configuration file, so a file-built pipeline cannot acknowledge clear-text tracing. Passing trace_clear_text=True to the pipeline is the programmatic path.


[memory]

Optional. Its presence makes the pipeline a ThreadAnonymizationPipeline keeping per-thread state. Discriminated on type.

The token_memo_ttl scalar goes with it, at the top level rather than in this section, since it bounds the pipeline's own memoized token map and not the store. Setting it without a [memory] raises, because a stateless pipeline memoizes nothing. Why it matters on a multi-worker deployment is in Multi-instance deployment.

type Extra Store
in_memory Process-local, lost on restart
redis redis Persistent, shared across workers
sqlalchemy sqlalchemy Durable, in a SQL database

type = "in_memory"

A process-local store, lost on restart and not shared across workers.

Key Type Default Meaning
max_threads int None Cap on kept threads, LRU eviction beyond it (at least 1)
ttl float None Expire an idle thread lazily on next access, in seconds (greater than 0)
[memory]
type = "in_memory"

type = "redis"

A persistent, multi-worker store, optionally keying each stored message with a hasher and encrypting each stored value with a cipher.

Key Type Default Meaning
url str The Redis connection URL (required)
namespace str piighost The key prefix isolating this library's keys
ttl int None Seconds a stored message lives, or omitted to keep until eviction
[memory.hasher] hasher Optional (both or neither). The hasher keying each message
[memory.cipher] cipher Optional (both or neither). The cipher encrypting each value

Configure both [memory.hasher] and [memory.cipher], or neither. With neither, the backend stores the mapping in clear and warns. With exactly one, build() raises ConfigError.

The hasher, [memory.hasher], is discriminated on type.

type Extra Keys Meaning
sha256 HMAC-SHA256, a fast keyed digest
argon2 argon2 time_cost (default 2), memory_cost (default 19456), parallelism (default 1), hash_length (default 32) Argon2id, a slow memory-hard digest

The cipher, [memory.cipher], has one type.

type Extra Meaning
aesgcm crypto AES-GCM authenticated encryption of stored values

The hasher reads its pepper from PIIGHOST_HASH_PEPPER and the cipher reads its base64 key from PIIGHOST_CIPHER_KEY, both at build time. A missing or malformed value raises ConfigError.

[memory]
type = "redis"
url = "redis://localhost:6379/0"
namespace = "piighost"
ttl = 3600

[memory.hasher]
type = "argon2"

[memory.cipher]
type = "aesgcm"

type = "sqlalchemy"

A durable, multi-worker store backed by any SQLAlchemy-supported database (SQLite, PostgreSQL, ...). It reads the database URL from an environment variable rather than the config file, so the URL and its password stay out of version control. An optional hasher and cipher protect the stored values exactly as they do for Redis.

Key Type Default Meaning
url_env str PIIGHOST_DATABASE_URL The environment variable holding the async database URL
table_name str piighost_conversation_messages The table storing per-thread messages
[memory.hasher] hasher Optional (both or neither). The hasher keying each message
[memory.cipher] cipher Optional (both or neither). The cipher encrypting each value

Configure both [memory.hasher] and [memory.cipher], or neither, exactly as for Redis. With neither, the backend stores the mapping in clear and warns. With exactly one, build() raises ConfigError.

The URL must use an async driver, for example postgresql+asyncpg://... or sqlite+aiosqlite://.... A missing environment variable raises ConfigError at build time. Call await memory.create_schema() once at startup to create the table.

[memory]
type = "sqlalchemy"
url_env = "PIIGHOST_DATABASE_URL"
table_name = "piighost_conversation_messages"

[memory.hasher]
type = "argon2"

[memory.cipher]
type = "aesgcm"

Full example

The keys of examples/config/pipeline.toml, a stateless pipeline pulling a catalog, adding one inline pattern, and enabling several optional stages. The file itself carries the same keys with a comment on each stage.

[detector]
type = "regex"
catalogs = ["generic"]
patterns = { EMPLOYEE_ID = 'EMP-[0-9]{4}' }

[overlap_resolver]
type = "confidence"

[expander]
type = "word_boundary"

[entity_resolver]
type = "fuzzy"
threshold = 0.85

[linker]
type = "exact"

[anonymizer.placeholder]
type = "label_counter"

[override.whitelist]
type = "regex"
patterns = { CODENAME = 'ACME-[A-Z]+' }

[guard]
type = "detector"

[guard.detector]
type = "regex"
patterns = { EMAIL = '[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}' }

[observation_redactor]
type = "label"

The same content in JSON, chosen by a .json suffix, is equivalent. A table becomes an object, an inline table becomes a nested object, and an array of tables becomes an array of objects.


Errors

Error Raised when
ConfigFileError The file is missing, unreadable, or invalid TOML or JSON
ConfigValidationError The parsed data fails schema validation
ConfigError A secret is missing at build time, or the wrong entry point is used for the memory declared

ConfigFileError and ConfigValidationError are subclasses of ConfigError, so catching ConfigError covers all three. The classes live in piighost.exceptions, so a caller can catch them without the config extra.


See also

  • examples/config/ in the repository for six runnable files, detector_only.toml, minimal.toml, minimal.json, pipeline.toml, thread_redis.toml and thread_sqlalchemy.toml, all six loaded by examples/config/run.py.
  • Command-line interface for validating a file from the shell.
  • Detectors reference for the detector each type builds.
  • LangChain middleware reference for driving a thread pipeline in an agent.