gheim

PII round-trip · Apache 2.0 · runs on your device

Anonymise
PII before it leaves
your device.

gheim detects names, addresses, phones, IBANs and more across the four official Swiss languages plus English, swaps them for stable sentinels before you call any LLM, and restores the originals when the response comes back — even mid-stream.

Test F1
0.910
P 0.890 · R 0.932 · char 0.946
Languages
5
de_CH · fr_CH · it_CH · rm · en
Categories
8
person · address · email · …
Parameters
560M
xlm-roberta-large

§ 01 — Live demo

Type something. Watch the model
redact it in real time.

Inference runs entirely on your device via @huggingface/transformers on WebGPU (or WASM). No text leaves this page. The model is fetched once from Hugging Face and cached.

Model not loaded

First run downloads ~280 MB from Hugging Face

Cached in IndexedDB after that. Roughly 30–60 s on broadband.

§ 02 — How it works

Four moves between you
and any remote model.

gheim is framework-agnostic. It plugs into the OpenAI Python and Node SDKs as a drop-in client, and it works against any HTTP LLM you call yourself. The detection model is yours to swap — the Swiss-tuned gheim-ch-560m is the default, and any openai/privacy-filter -compatible token-classification model drops in.

  1. 01

    Detect

    A token-classification model scans the prompt for names, addresses, phones, IBANs, dates, URLs, emails, and secrets across five languages.

  2. 02

    Substitute

    Each span is replaced with a stable sentinel — <PERSON_1>, <EMAIL_1>, <ACCOUNT_1> — that the LLM treats as opaque tokens.

  3. 03

    Send

    The redacted prompt goes to whichever LLM you use. Streaming, function calls, multi-turn chat — all transparent.

  4. 04

    Restore

    On the way back, every sentinel in the response is rewritten to the original value. Works mid-stream so the user sees Joel, never <PERSON_1>.

Round-trip in one paragraph

01 — User input

Please draft a polite reminder to Joel Barmettler (jbarmettler@proton.me) that invoice CH9300762011623852957 is overdue since 15 May 2026.

02 — Sent to OpenAI

Please draft a polite reminder to <PERSON_1> (<EMAIL_1>) that invoice <ACCOUNT_1> is overdue since <DATE_1>.

03 — LLM response

Subject: Friendly reminder. Dear <PERSON_1>, this is a friendly reminder that invoice <ACCOUNT_1> has been outstanding since <DATE_1>. We will follow up by email at <EMAIL_1>.

04 — Restored for user

Subject: Friendly reminder. Dear Joel Barmettler, this is a friendly reminder that invoice CH9300762011623852957 has been outstanding since 15 May 2026. We will follow up by email at jbarmettler@proton.me.

§ 03 — Install

Three minutes from
pip install to redacted prompts.

The Python and Node packages ship a drop-in replacement for the official OpenAI client. Anonymisation and round-trip restoration happen automatically — including in streamed responses. Running a LiteLLM proxy? Use the guardrail plugin instead.

Python

Install
# Drop-in OpenAI wrapper + on-device detection
uv add "gheim[local,openai]"

# Or, if you'll point gheim at a remote detection server:
uv add "gheim[openai]"
Drop-in OpenAI client
from gheim.openai import OpenAI

client = OpenAI()  # same args as openai.OpenAI

r = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user",
                "content": "Hi, my name is Joel"}],
)

# r.choices[0].message.content contains "Joel".
# OpenAI only ever saw <PERSON_1>.

Node / Bun

Install
# Server-side / Bun / Node 18+
npm install gheim openai @huggingface/transformers

# Or with bun:
bun add gheim openai @huggingface/transformers
Drop-in OpenAI client
import { OpenAI } from "gheim/openai";

const client = new OpenAI();

const r = await client.chat.completions.create({
  model: "gpt-4o",
  messages: [{ role: "user", content: "Hi, my name is Joel" }],
});

// r.choices[0].message.content contains "Joel".
// OpenAI only ever saw <PERSON_1>.

LiteLLM

Install
# Your own LiteLLM (venv, VM, pod)
pip install "gheim[litellm,local]"

# Official LiteLLM Docker image: extend it.
# Its virtualenv has no pip, so bootstrap it.
FROM ghcr.io/berriai/litellm:main-stable
RUN /app/.venv/bin/python -m ensurepip \
 && /app/.venv/bin/python -m pip install \
    --extra-index-url https://download.pytorch.org/whl/cpu \
    "gheim[litellm,local]"


# Build, then keep the ~2.2 GB model on a volume or the
# container re-downloads it on every start:
docker build -t litellm-gheim .
docker run -p 4000:4000 -v $PWD/config.yaml:/app/config.yaml \
  -v gheim-hf-cache:/root/.cache/huggingface \
  litellm-gheim --config /app/config.yaml

# No torch in the proxy? Install "gheim[litellm]" and
# point it at a gheim-server container (see README).
config.yaml · guardrail
guardrails:
  - guardrail_name: gheim-pii
    litellm_params:
      guardrail: gheim.litellm.GheimGuardrail
      mode: pre_call   # post_call is added for you
      default_on: true
      # Every knob is plain YAML:
      # detector: composite | remote | local
      # model_detector: remote   # model in a gheim-server container
      # api_base: http://gheim:8080
      # o_bias: 0.5
      # propagate_names: true
      # custom_patterns:
      #   - label: case_id
      #     regex: 'Dossier\s+(ZH-\d{4}-\d{5})'
      #     group: 1

# Every provider behind the proxy only sees
# <PERSON_1>; your clients get "Joel" back.

Browser

Install
# Bundle for the browser (Vite, Webpack, etc.)
npm install gheim @huggingface/transformers
In-browser detection · WebGPU
import { Session, LocalDetector,
         anonymizeText, deanonymizeText } from "gheim";

const session = new Session();
session.detector = new LocalDetector({
  // Defaults: model = joelbarmettler/gheim-ch-560m,
  // device = "auto" (probes WebGPU, falls back to WASM).
  dtype: "q8",
});

const clean = await anonymizeText("Hi, my name is Joel", session);
// → "Hi, my name is <PERSON_1>"
// ...send `clean` to any LLM endpoint, then:
const final = deanonymizeText(responseText, session);

Full reference: README on GitHub.