Skip to main content

Official Python client for peyeeye.ai — PII redaction & rehydration for LLM prompts.

Project description

peyeeye

PyPI version Python versions Downloads License: MIT Types: py.typed CI Homepage

Official Python client for peyeeye.ai — redact PII on the way into your LLM prompts and rehydrate it on the way out.

pip install peyeeye

Python 3.9+. Single runtime dependency: httpx. Fully type-hinted (py.typed).

Get an API key

  1. Sign up at https://peyeeye.ai/signup (free plan, no card required — 1 M characters / month, all 30+ built-in detectors).
  2. Head to https://peyeeye.ai/dashboard/keysNew key.
  3. Copy the full pk_live_… (or pk_test_…) token shown once — we store only a hash after you close the dialog. Export it where your app reads env vars:
export PEYEEYE_KEY=pk_live_...

Test keys bypass billing and are rate-limited for development; live keys count against your plan. Paid tiers (Build / Pro / Scale) unlock streaming, custom detectors, and higher throughput — see https://peyeeye.ai/pricing.

Quickstart

import os
from peyeeye import Peyeeye
from anthropic import Anthropic

peyeeye = Peyeeye(api_key=os.environ["PEYEEYE_KEY"])
claude = Anthropic()

with peyeeye.shield() as shield:
    safe = shield.redact("Hi, I'm Ada, ada@a-e.com")
    reply = claude.messages.create(
        model="claude-sonnet-*",
        max_tokens=256,
        messages=[{"role": "user", "content": safe}],
    )
    print(shield.rehydrate(reply.content[0].text))

shield() opens a session, redacts, and cleans up on exit. Inside the block, the same real value always maps to the same token — Ada Lovelace is always [PERSON_1] — and tokens never leak across sessions.

Low-level calls

Skip the shield helper when you need more control:

r = peyeeye.redact("Card: 4242 4242 4242 4242")
# r.redacted    → "Card: [CARD_1]"
# r.session     → "ses_…"
# r.entities    → [DetectedEntity(token="[CARD_1]", type="CARD", span=(6, 25), confidence=0.99)]

clean = peyeeye.rehydrate("Confirmation for [CARD_1].", session=r.session)
# clean.text → "Confirmation for 4242 4242 4242 4242."

Stateless sealed mode

Pass stateless=True and peyeeye never stores the mapping — the redact response carries a sealed skey_… blob you hand back to rehydrate. Nothing lives on the server between calls.

with peyeeye.shield(stateless=True) as shield:
    safe = shield.redact("Email ada@a-e.com")
    clean = shield.rehydrate("Reply: [EMAIL_1]")
    # shield.rehydration_key is the skey_... blob, if you need to persist it

Or with raw calls:

r = peyeeye.redact("Email ada@a-e.com", session="stateless")
# r.rehydration_key → "skey_…"
clean = peyeeye.rehydrate("[EMAIL_1] received.", session=r.rehydration_key)

Streaming rehydration

When piping an LLM token stream straight to a user, naive rehydration breaks on mid-token boundaries. rehydrate_chunk() buffers partial tokens across chunks; call flush() once upstream closes.

with peyeeye.shield() as shield:
    safe = shield.redact(prompt)
    for chunk in your_llm_stream(safe):
        sys.stdout.write(shield.rehydrate_chunk(chunk))
    sys.stdout.write(shield.flush())

Never call flush() while the stream is still delivering chunks — you'll emit a half-formed placeholder.

Streaming redact (SSE)

For the /v1/redact/stream endpoint (Build plan and higher):

for event in peyeeye.redact_stream(["Hi, I'm Ada", " — card 4242 4242 4242 4242"]):
    if event.event == "session":
        session_id = event.data["session"]
    elif event.event == "redacted":
        print(event.data["text"])
    elif event.event == "done":
        print("chars:", event.data["chars"])

Custom detectors

peyeeye.create_entity(
    id="ORDER_ID",
    kind="regex",
    pattern=r"#A-\d{6,}",
    examples=["#A-884217", "#A-007431"],
    confidence_floor=0.9,
)

# dry-run a pattern before saving
peyeeye.test_pattern(pattern=r"#A-\d{6,}", text="ref #A-884217 and #A-1")
#   → TestPatternResponse(count=1, matches=[PatternMatch(value="#A-884217", ...)])

# inspect / update / retire
peyeeye.list_entities()
peyeeye.update_entity("ORDER_ID", enabled=False)
peyeeye.delete_entity("ORDER_ID")

# starter templates (Twilio SIDs, Stripe keys, AWS access keys, etc.)
for tpl in peyeeye.entity_templates():
    print(tpl.id, tpl.pattern)

Sessions

peyeeye.get_session("ses_…")       # SessionInfo
peyeeye.delete_session("ses_…")    # drop immediately

Framework integrations

LangChain

Drop-in wrapper around any LangChain Runnable (chat model, LLM, or chain). Redacts the prompt before the model sees it, rehydrates tokens in the response, and opens a fresh session per invoke so tokens never leak across requests.

from langchain_openai import ChatOpenAI
from peyeeye import Peyeeye
from peyeeye.langchain import with_peyeeye

peyeeye = Peyeeye(api_key=os.environ["PEYEEYE_KEY"])
model = with_peyeeye(ChatOpenAI(model="gpt-4o-mini"), client=peyeeye)

print(model.invoke("Hi, I'm Ada — email me at ada@a-e.com"))

peyeeye.langchain has no hard dependency on LangChain — if langchain-core is installed the wrapper is a proper Runnable (composable with | in LCEL pipelines); without it you still get a callable with the same .invoke / .ainvoke / .batch surface.

Opt into stateless sealed mode (no server-side mapping) with with_peyeeye(model, client=peyeeye, stateless=True).

Accepted prompt shapes: plain strings, chat-message lists (HumanMessage, SystemMessage, …), tuple shorthand (("human", "Hi Ada")), dict messages ({"role": "user", "content": "…"}), and multimodal content lists — image parts pass through untouched.

LiteLLM

Two ways to bolt peyeeye onto a LiteLLM-based app:

import litellm
from peyeeye import Peyeeye
from peyeeye.litellm import with_peyeeye

peyeeye = Peyeeye(api_key=os.environ["PEYEEYE_KEY"])
completion = with_peyeeye(litellm.completion, client=peyeeye)

resp = completion(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "Hi, I'm Ada"}],
)
print(resp.choices[0].message.content)  # already rehydrated

with_peyeeye wraps sync or async completion functions and opens a fresh session per call. For LiteLLM's proxy/callback path, register a PeyeeyeHandler instead:

import litellm
from peyeeye.litellm import PeyeeyeHandler

litellm.callbacks = [PeyeeyeHandler(client=peyeeye)]

Multimodal content (text + image parts), async acompletion, and stateless sealed sessions (with_peyeeye(..., stateless=True)) are all supported.

Errors

Every non-2xx response raises PeyeeyeError with .code, .status, .message, and .request_id. 429 and 5xx responses are retried with exponential backoff (Retry-After honoured); terminal errors raise immediately.

from peyeeye import PeyeeyeError

try:
    peyeeye.redact("…")
except PeyeeyeError as e:
    if e.code == "rate_limited":
        ...
    elif e.code == "forbidden":
        ...
    else:
        raise

Configuration

Peyeeye(
    api_key="pk_live_…",
    base_url="https://api.peyeeye.ai",
    timeout=30.0,
    max_retries=3,
)

For CI / air-gapped use, Peyeeye(transport=httpx.MockTransport(handler)) lets you mount a mock transport without monkey-patching.

Method reference

Method HTTP Purpose
peyeeye.redact(text, ...) POST /v1/redact Redact PII; returns token stream + session.
peyeeye.rehydrate(text, session=...) POST /v1/rehydrate Substitute tokens back. Accepts ses_… or skey_….
peyeeye.redact_stream(chunks, ...) POST /v1/redact/stream (SSE) Stream-safe redact.
peyeeye.get_session(id) GET /v1/sessions/{id} Inspect mapping metadata.
peyeeye.delete_session(id) DELETE /v1/sessions/{id} Evict a session.
peyeeye.list_entities() GET /v1/entities Built-ins + your custom detectors.
peyeeye.create_entity(...) POST /v1/entities Custom detector.
peyeeye.update_entity(id, ...) PATCH /v1/entities/{id} Toggle / tweak.
peyeeye.delete_entity(id) DELETE /v1/entities/{id} Retire.
peyeeye.test_pattern(pattern, text) POST /v1/entities/test Dry-run a regex.
peyeeye.entity_templates() GET /v1/entities/templates Starter patterns.

Full request / response schemas: https://peyeeye.ai/docs.

Using this SDK from an AI coding assistant

Drop these into your agent's context. Each snippet is self-contained and compiles as-is.

# Install
# pip install peyeeye

from peyeeye import Peyeeye, PeyeeyeError
import os

client = Peyeeye(api_key=os.environ["PEYEEYE_KEY"])  # or explicit base_url

# Round-trip: redact → call LLM → rehydrate (session-scoped)
with client.shield() as shield:
    safe = shield.redact("Hi, I'm Ada, ada@a-e.com")
    # ... send `safe` to the LLM, get `reply` back ...
    out = shield.rehydrate(reply)

# Stateless (zero server-side state; key is yours to persist)
with client.shield(stateless=True) as shield:
    safe = shield.redact("...")
    key = shield.rehydration_key  # skey_...
    clean = shield.rehydrate("[EMAIL_1] confirmed.")

# Low-level one-shot
r = client.redact("Card 4242 4242 4242 4242")
clean = client.rehydrate("Receipt: [CARD_1].", session=r.session)

# Error handling
try:
    client.redact(text)
except PeyeeyeError as e:
    # e.code ∈ {"rate_limited","forbidden","invalid_request","server_error", ...}
    # e.status, e.message, e.request_id
    raise

Endpoint envelope: all requests use Authorization: Bearer <api_key> against https://api.peyeeye.ai/v1/*. Errors follow {code, message, request_id} and surface as PeyeeyeError. Responses are plain JSON (dataclasses via from_dict).

Do: reuse one Peyeeye(...) per process; call .close() or use it as a context manager at shutdown. Don't: open a new client per request, call flush() mid-stream, or parse skey_ blobs yourself — the API opens them.

License

MIT.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

peyeeye-1.1.3.tar.gz (28.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

peyeeye-1.1.3-py3-none-any.whl (21.4 kB view details)

Uploaded Python 3

File details

Details for the file peyeeye-1.1.3.tar.gz.

File metadata

  • Download URL: peyeeye-1.1.3.tar.gz
  • Upload date:
  • Size: 28.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for peyeeye-1.1.3.tar.gz
Algorithm Hash digest
SHA256 dd200da98943a165ca6be3e351df56779c708e7caa1bb30e72fa49cec5386dcd
MD5 0168e7755f081d8849c0109fb0de1e36
BLAKE2b-256 2c46a75ed22ec58c5a4af078b406867585abfe77f8de5f808331067ccd64fcf6

See more details on using hashes here.

Provenance

The following attestation bundles were made for peyeeye-1.1.3.tar.gz:

Publisher: publish.yml on peyeeye/peyeeye-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file peyeeye-1.1.3-py3-none-any.whl.

File metadata

  • Download URL: peyeeye-1.1.3-py3-none-any.whl
  • Upload date:
  • Size: 21.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for peyeeye-1.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 1f8eda3b6ea3a13db4ee463743802b4a2be31d57753c987b1140b7b0228ddf48
MD5 bb25d8289dd45ba21309478e4238bb15
BLAKE2b-256 cb1809304b8bf38645974e9fbd02f7b31059f4446123f4c2e55beb0f44e22e88

See more details on using hashes here.

Provenance

The following attestation bundles were made for peyeeye-1.1.3-py3-none-any.whl:

Publisher: publish.yml on peyeeye/peyeeye-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page