Skip to main content

PIIGhost

CI codecov PyPI version Python versions License: MIT Security: bandit Discord

piighost is a Python library that protects your personal data (PII) in conversations with LLMs through de-identification. Sensitive values are hidden before they are sent, then restored in the response. LangChain, Pydantic AI, LlamaIndex and Claude Code integrations are provided, together with an OpenAI and Anthropic API connector.

This de-identification spots PII with pluggable detectors (regex, NER, LLM) and replaces each value with a placeholder, the token that takes its place. For example:

  • John Doe becomes <<PERSON:1>>
  • john.doe@example.com becomes <<EMAIL:1>>

This placeholder stays the same from one message to the next with the conversational pipeline, which keeps the mapping between a value and its placeholder across the whole conversation. If john.doe@example.com reappears three messages later, the placeholder is still <<EMAIL:1>>, which lets the LLM follow the thread.

The LLM therefore only receives de-identified text. When it returns placeholders, for example by answering Hello <<PERSON:1>>, piighost replaces them with the real values. The user sees John Doe and never sees the de-identification.

The same mechanism protects agents that call tools. With the LangChain middleware, a tool that needs the real email address receives it in clear, while the LLM that supplies it only writes <<EMAIL:1>>.

A user chats with an agent, PII values are replaced by placeholders before reaching the LLM and restored afterwards for the user and for tool calls.

The LLM only sees placeholders. The tool receives the real address, the user gets a clear-text reply, and your agent code stays the same.

[!NOTE] This retained mapping makes the de-identification a pseudonymization under the GDPR, not a definitive anonymization. With the conversational pipeline, the real values stay stored for the duration of the conversation and must be protected accordingly.

Why PIIGhost

Most PII tooling stops at detection. Presidio, GLiNER, spaCy, and regex catalogs all find entities in text, and they do it well. The hard part for an LLM agent is everything after detection: swapping values without wrecking the model's reasoning, keeping one value mapped to one token across a conversation, handing tools the real value while the model sees only the token, and putting the originals back in the reply. That orchestration is what piighost is.

What piighost adds on top:

  • Pluggable detectors: regex catalogs (generic, US, EU, FR), NER (GLiNER2, spaCy, Transformers), an LLM detector, plus exact-match, composite, and chunked detectors (chunking splits text that overruns a model's context window), and you keep the one you trust (Presidio plugs in through an extra).
  • Reversible, transparent tokens: each value becomes a stable id like <<PERSON:1>> and is put back automatically, so the end user reads john.doe@example.com and never sees a token. Label-only, masked, and keyed-hash factories are available too.
  • Consistent across a conversation: the same value keeps the same token for the whole thread, backed by in-process, Redis, or SQLAlchemy memory (Redis and SQL can encrypt values at rest and hash keys).
  • Agent integrations with a tool boundary: LangChain middleware, Pydantic AI hooks, and LlamaIndex. The tool receives the real value while the model sees only the token, with token-by-token streaming restoration.
  • A customizable staged pipeline: detect, link, resolve overlaps, expand, de-identify, and an optional guard rail that refuses a reply with residual PII (a detector, an LLM, or Mistral moderation). Swap in fuzzy matching to tolerate typos or add your own stage.
  • Config-driven and self-hostable: build a whole pipeline from a TOML/JSON file with a CLI to validate it, run it in your process, or as a service through the companion piighost-api (OpenAI- and Anthropic-compatible proxies).
  • Typed and observable: ships py.typed and a minimal core with everything heavy behind extras, plus OpenTelemetry per-stage spans (viewable in Langfuse or Jaeger) with optional payload redaction.
  • Scope, live text and conversations: piighost protects a running conversation message by message, not a static dataset.

For how it stacks up against Presidio, LangChain, the cloud APIs, and others, see How PIIGhost compares.

Limitations and trade-offs

  • The token does not embed the encrypted value, on purpose. Unlike a format-preserving encryption token (where the ciphertext is the token, e.g. Google DLP), piighost uses an id (<<PERSON:1>>) backed by a cache. The reason: a token that carries the ciphertext can be captured today and cracked in 20 years ("harvest now, decrypt later", the quantum threat to classical crypto), whereas an id reveals nothing on its own. In return, you need a cache to hold the token-to-value mapping, so a memory backend to deploy, share across workers, and persist in production.
  • That cache stores the real values, so reversibility is pseudonymization, not anonymization (GDPR). The real values stay stored for the duration of the conversation. The library gives you the means to protect them (AES-GCM encryption of the values, Argon2id hashing of the keys), but the database architecture itself must be secured in production once you use Redis or PostgreSQL.
  • No dataset anonymization. No k-anonymity, l-diversity, differential privacy, or tabular data. piighost protects live text and conversations, not a whole dataset. For that, see ARX, Amnesia, or Google DLP.
  • No checksum validation (Luhn / IBAN / NIR), by choice. The RegexDetector matches on shape alone so it never lets a real value mangled by OCR leak (a checksum would reject it and it would pass in clear). In exchange, it sometimes flags a string that only looks like PII, which costs nothing beyond one extra token.

Quickstart

pip install piighost   # or: uv add piighost

De-identify a text

ExactMatchDetector de-identifies a dictionary of known values without downloading a model.

import asyncio

from piighost.components.detector import ExactMatchDetector
from piighost.pipeline import AnonymizationPipeline

detector = ExactMatchDetector({"John Doe": "PERSON", "john.doe@example.com": "EMAIL"})
pipeline = AnonymizationPipeline(detector)

result = asyncio.run(pipeline.anonymize("Write to John Doe at john.doe@example.com."))
print(result.text)  # Write to <<PERSON:1>> at <<EMAIL:1>>.

Conversations and agents (LangChain)

The middleware wraps a conversational pipeline and handles every agent turn for you, so the same de-identification applies without any change to your agent logic.

pip install 'piighost[langchain]'   # or: uv add 'piighost[langchain]'
import asyncio

from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain_core.messages import HumanMessage
from langchain_core.tools import tool

from piighost.components.detector import ExactMatchDetector
from piighost.integrations.langchain import PIIAnonymizationMiddleware
from piighost.pipeline import ThreadAnonymizationPipeline

SYSTEM_PROMPT = (
    "Some inputs contain placeholders like <<PERSON:1>> that stand in for real "
    "values withheld for privacy. Treat each placeholder as the real value, never "
    "comment on its format, and pass it to tools unchanged."
)


@tool
def send_mail(to: str, body: str) -> str:
    """Send an email to `to` with the given body."""
    print(f"[tool] send_mail received to={to!r}")
    return "Email successfully sent."


async def main() -> None:
    # This example calls OpenAI, so set OPENAI_API_KEY in your environment first.
    labels = {"Patrick Dupont": "PERSON", "patrick@acme.com": "EMAIL"}
    detector = ExactMatchDetector(labels)
    pipeline = ThreadAnonymizationPipeline(detector)
    middleware = PIIAnonymizationMiddleware(pipeline)
    # gpt-5.6-terra is a reasoning model; reasoning_effort="none" lets it call
    # function tools over chat/completions.
    model = init_chat_model("openai:gpt-5.6-terra", reasoning_effort="none")
    # The system prompt tells the model to treat placeholders as real values and
    # pass them to tools unchanged, so it does not balk at the tokens.
    agent = create_agent(
        model=model,
        system_prompt=SYSTEM_PROMPT,
        tools=[send_mail],
        middleware=[middleware],
    )
    config = {"configurable": {"thread_id": "demo-thread"}}

    message = HumanMessage(
        "Use the send_mail tool to send a welcome note to Patrick Dupont at patrick@acme.com."
    )
    result = await agent.ainvoke({"messages": [message]}, config=config)
    print(f"user sees: {result['messages'][-1].content!r}")


if __name__ == "__main__":
    asyncio.run(main())

This is the LangChain integration, but it is only one option. piighost also has connectors for Pydantic AI and LlamaIndex, and the companion piighost-api exposes OpenAI- and Anthropic-compatible proxies, so you can move de-identification to the HTTP boundary with only a base URL change.

For a real detector and the conversational pipeline, see the Quickstart and the LangChain integration.

Documentation

Full documentation

Browse the docs by section

Project

Release files for piighost 1.7.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for piighost 1.7.0
File Size Uploaded
piighost-1.7.0.tar.gz 112.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for piighost 1.7.0
File Interpreter ABI Platform
piighost-1.7.0-py3-none-any.whl Python 3 none any Details

Total release size: 275.6 kB

Release files / piighost-1.7.0.tar.gz

Download URL piighost-1.7.0.tar.gz
Size 112.9 kB
Tags Source
SHA-256 checksum
How to use checksums
df46a489237c18124f993b7f5c57756573cb5af4075e0ea0f9107c9bdaee9c34
BLAKE2b-256 checksum
How to use checksums
50dac85eecf76d02ae2219d784ea0e7d42e6b8deee6ea3358bd0a2cc8a5f05bd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / piighost-1.7.0-py3-none-any.whl

Download URL piighost-1.7.0-py3-none-any.whl
Size 162.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f8cc3cef496384b9ea09930d9c8fff22c951a5bbad5268a9273c8d4c702a6d01
BLAKE2b-256 checksum
How to use checksums
6b3f55a479f5b2a57889f1453d39dae5b13926a784c6611eaaebdf31390e1078
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

1.8.0

2 release files

1.7.1

2 release files

This release

1.7.0 This release

2 release files

1.6.1

2 release files

1.6.0

2 release files

1.5.0

2 release files

1.4.0

2 release files

1.3.0

2 release files

1.2.0

2 release files

1.1.1

2 release files

1.1.0

2 release files

1.0.1

2 release files

1.0.0

2 release files

0.14.0

2 release files

0.12.0

2 release files

0.10.0

2 release files

0.9.1

2 release files

0.9.0

2 release files

0.8.0

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page