pip install nerguardThe NER model (~300 MB) downloads automatically from HuggingFace on first use.
from nerguard import Redactor
ng = Redactor(
model_path=None, # str β local path or HuggingFace Hub ID for the NER model
llm_routing=False, # bool β enable entropy-gated LLM routing
llm_source="openai", # str β "openai" or "ollama"
llm_model="gpt-4o", # str β LLM model name
api_key=None, # str β API key for OpenAI (or None to use OPENAI_API_KEY env var)
typed=True, # bool β typed placeholders ([NAME]) vs generic ([PII])
)
result = ng.redact("Hi, I'm John Smith. Email: john@acme.com")
print(result.text)
# "Hi, I'm [NAME] [NAME]. Email: [EMAIL]"
print(result.mapping)
# {"NAME_0": "John", "NAME_1": "Smith", "EMAIL_0": "john@acme.com"}
print(result.entities)
# [{"label": "GIVENNAME", "text": "John", "confidence": 0.998, "source": "base"}, ...]Batch:
texts = [
"Hi, I'm John Smith. Email: john@acme.com",
"Call me at +1-800-555-0199 or find me on LinkedIn.",
]
results = [ng.redact(t) for t in texts] # model stays cached across calls
for r in results:
print(r.text)
# "Hi, I'm [NAME] [NAME]. Email: [EMAIL]"
# "Call me at [PHONE] or find me on LinkedIn."
# Collect all mappings
all_mappings = {k: v for r in results for k, v in r.mapping.items()}
# {"NAME_0": "John", "NAME_1": "Smith", "EMAIL_0": "john@acme.com", "PHONE_0": "+1-800-555-0199"}Improves recall on ambiguous spans (phone numbers, IDs, dates) by routing uncertain predictions to an LLM.
# Cloud β pass key explicitly or set OPENAI_API_KEY env var
ng = Redactor(llm_routing=True, llm_source="openai", llm_model="gpt-4o", api_key="sk-...")
# Local β no data leaves the machine (requires Ollama)
ng = Redactor(llm_routing=True, llm_source="ollama", llm_model="qwen2.5:7b")nerguard # interactive REPL
nerguard --file report.txt # redact a file
nerguard --llm --backend ollama --model qwen2.5:7b # with local LLM
nerguard --format rag # RAG-optimised output| REPL command | Description |
|---|---|
/mode [human|rag|json|generic] |
Switch output format |
/llm |
Toggle LLM routing |
/backend [openai|ollama] |
Switch LLM backend |
/model NAME |
Set LLM model |
/file PATH |
Redact a file |
/help |
Show all commands |
Redactor(
model_path=None, # str β local path or HuggingFace Hub ID for the NER model
llm_routing=False, # bool β enable entropy-gated LLM routing
llm_source="openai", # str β "openai" or "ollama"
llm_model="gpt-4o", # str β LLM model name
api_key=None, # str β API key for OpenAI (or None to use OPENAI_API_KEY env var)
typed=True, # bool β typed placeholders ([NAME]) vs generic ([PII])
)| Parameter | Type | Default | Description |
|---|---|---|---|
model_path |
str |
HuggingFace auto-download | Local filesystem path or HuggingFace Hub ID for the NER model. Omit to download exdsgift/NerGuard-0.3B automatically on first use. |
llm_routing |
bool |
False |
Enable entropy-gated LLM routing. When True, spans where the base model is uncertain are re-evaluated by the LLM. Improves recall on ambiguous tokens (phone numbers, dates, IDs) at the cost of extra latency. |
llm_source |
str |
"openai" |
LLM backend to use when llm_routing=True. "openai" calls the OpenAI API; "ollama" runs inference locally via Ollama (no data leaves the machine). |
llm_model |
str |
"gpt-4o" |
Model name passed to the selected LLM backend. Examples: "gpt-4o", "gpt-4o-mini" for OpenAI; "qwen2.5:7b", "llama3.1:8b" for Ollama. Only used when llm_routing=True. |
api_key |
str |
None |
API key for the OpenAI backend. If None, falls back to the OPENAI_API_KEY environment variable. Ignored when llm_source="ollama". |
typed |
bool |
True |
Controls placeholder style. True β typed placeholders such as [NAME], [EMAIL], [PHONE] (preserves semantic context for downstream LLMs). False β every entity becomes [PII] regardless of type (maximum compression, no semantic signal). |
ng.redact(text) returns a RedactResult dataclass with three fields:
| Field | Type | Description |
|---|---|---|
text |
str |
Redacted text with placeholders replacing PII spans. |
entities |
list[dict] |
One dict per detected entity, with keys: label (entity type), text (original value), start/end (char offsets), confidence (0β1), source ("base" or "llm"). |
mapping |
dict[str, str] |
Maps each placeholder instance to its original value, keyed as "<LABEL>_<index>" (e.g. "NAME_0", "EMAIL_0"). Useful for auditing or selective de-redaction. |
result = ng.redact("Hi, I'm John Smith. Email: john@acme.com")
result.text
# "Hi, I'm [NAME] [NAME]. Email: [EMAIL]"
result.mapping
# {"NAME_0": "John", "NAME_1": "Smith", "EMAIL_0": "john@acme.com"}
result.entities
# [
# {"label": "GIVENNAME", "text": "John", "start": 8, "end": 12, "confidence": 0.998, "source": "base"},
# {"label": "SURNAME", "text": "Smith", "start": 13, "end": 18, "confidence": 0.995, "source": "base"},
# {"label": "EMAIL", "text": "john@acme.com", "start": 27, "end": 40, "confidence": 0.991, "source": "base"},
# ]GIVENNAME Β· SURNAME Β· EMAIL Β· TELEPHONENUM Β· SOCIALNUM Β· CREDITCARDNUMBER Β· IBAN Β· PASSPORTNUM Β· IDCARDNUM Β· DRIVERLICENSENUM Β· TAXNUM Β· STREET Β· BUILDINGNUM Β· CITY Β· ZIPCODE Β· DATE Β· TIME Β· AGE Β· SEX Β· TITLE
NerGuard works as a LangChain DocumentTransformer and Tool out of the box.
pip install nerguard[langchain]Anonymize documents in a RAG pipeline:
from langchain_core.documents import Document
from nerguard.langchain import NerGuardAnonymizer
anonymizer = NerGuardAnonymizer()
docs = [Document(page_content="John Smith's email is john@acme.com")]
anon_docs = anonymizer.transform_documents(docs)
print(anon_docs[0].page_content)
# "John Smith's email is [EMAIL]"
print(anon_docs[0].metadata["nerguard_mapping"])
# {"EMAIL_0": "john@acme.com"}As a Tool for LangChain agents:
from nerguard.langchain import NerGuardTool
tool = NerGuardTool()
result = tool.invoke({"text": "Call Alice at +33 6 12 34 56 78"})
# "Call [NAME] at [PHONE]"MIT
