Zink lets you safeguard privacy by detecting sensitive information and replacing it with secure, customizable placeholders.
Project description
ZINK (Zero-shot Ink)
Abstract
The proliferation of Large Language Models (LLMs) heightens challenges in protecting Personal Identifiable Information (PII), particularly Quasi-Identifiers (QIs), in unstructured text. QIs enable re-identification when combined and pose significant privacy risks, highlighted by their use in security verification. Current approaches face limitations: large LLMs offer flexibility for detecting diverse QIs but are often hindered by high computational costs, while traditional supervised NER models require domain-specific labeled data and fail to generalize to heterogeneous, unseen QI types. Furthermore, evaluating QI identification methods is hampered by the lack of diverse benchmarks. To address this need for evaluation resources, we present the Quasi-Identifier Benchmark (QIB), a new corpus with 1750 examples across 35 diverse QI categories (e.g., personal preferences, security answers) designed to assess model robustness against QI heterogeneity. To facilitate the application of flexible identification methods on such diverse data, we also introduce ZINK (Zero-shot INK), a Python package providing a unified framework for applying existing zero-shot NER models to QI identification and anonymization, simplifying model integration and offering configurable redaction and replacement.
Evaluation using ZINK on QIB shows strong performance, achieving an F4-score of 0.9206. This result outperforms both supervised models like BERT (0.6109) and paid large language models like GPT-4-Nano (0.9007), while remaining competitive with top-tier models like GPT-4 (0.9726). QIB and ZINK provide valuable resources enabling standardized evaluation and development of flexible, practical solutions for quasi-identifier anonymization in text.
Installation
Install the package using uv or pip.
CPU Support (Recommended for most users):
uv add "zink[cpu]"
# or
pip install "zink[cpu]"
GPU Support:
pip install "zink[gpu]"
Quick Start
Get started with ZINK in just a few lines of code. The redact function replaces identified entities with [LABEL]_REDACTED.
import zink as zn
text = "John works as a doctor and plays football after work and drives a toyota."
labels = ("person", "profession", "sport", "car")
result = zn.redact(text, labels)
print(result.anonymized_text)
Output:
person_REDACTED works as a profession_REDACTED and plays sport_REDACTED after work and drives a car_REDACTED.
Key Features
1. Replacing Entities
Instead of simple redaction, you can replace entities with realistic synthetic data using the replace function. ZINK uses the Faker library to generate context-aware replacements.
import zink as zn
text = "John Doe dialled his mother at 992-234-3456 and then went out for a walk."
labels = ("person", "phone number", "relationship")
result = zn.replace(text, labels)
print(result.anonymized_text)
Possible Output:
Warren Buffet dialled his Uncle at 2347789287 and then went out for a walk.
2. Custom Replacements (Bring Your Own Data)
You can provide your own dictionary of replacements for specific labels. This is useful for consistent mapping or using domain-specific datasets.
import zink as zn
text = "Melissa works at Google and drives a Tesla."
labels = ("person", "company", "car")
custom_replacements = {
"person": "Alice",
"company": "OpenAI",
"car": ("Honda", "Toyota")
}
result = zn.replace_with_my_data(text, labels, user_replacements=custom_replacements)
print(result.anonymized_text)
Possible Output:
Alice works at OpenAI and drives a Honda.
3. Shielding LLM and API Calls
Protect sensitive data in your RAG pipelines or API calls using the @zink.shield decorator. It automatically anonymizes inputs before they reach the function and re-identifies the output, creating a secure "shield" around your logic.
import zink as zn
# This mock function simulates calling an external API (like OpenAI or Gemini)
@zn.shield(target_arg='prompt', labels=('person', 'company'))
def call_sensitive_api(prompt: str):
# The prompt received here is already anonymized.
# e.g., "Report for person_REDACTED from company_REDACTED."
return f"Analysis for {prompt} is complete."
# The original, sensitive text
sensitive_data = "Report for John Doe from Acme Inc."
# Call the function normally. The decorator handles anonymization and re-identification.
final_result = call_sensitive_api(prompt=sensitive_data)
print(final_result)
Output:
Analysis for John Doe from Acme Inc. is complete.
4. Persistent Entity Mapping
To ensure consistent redaction across multiple sessions or calls, you can use a persistent mapping file. This is particularly useful for maintaining context in long-running conversations.
import zink as zn
# First call: Generates IDs and saves them to 'mapping.json'
text1 = "My name is Alice."
result1 = zn.redact(text1, labels=["person"], numbered_entities=True, mapping_file="mapping.json")
print(result1.anonymized_text)
# Output: person_1234_REDACTED ...
# Second call: Reuses the SAME ID for 'Alice' from 'mapping.json'
text2 = "Alice is here again."
result2 = zn.redact(text2, labels=["person"], numbered_entities=True, mapping_file="mapping.json")
print(result2.anonymized_text)
# Output: person_1234_REDACTED ...
Docker Support
You can run zink easily using Docker. The image comes with the model pre-installed and exposes a REST API.
1. Build the Image
Run this command in the root of the repository:
docker build -t zink .
2. Run the Container
Start the API server on port 8000:
docker run --rm -p 8000:8000 zink
Tip: If port 8000 is already in use (e.g., you see a "Bind for 0.0.0.0:8000 failed" error), map it to a different port like 8001:
docker run --rm -p 8001:8000 zink
3. Use the API
Once the container is running, you can send requests to the /redact endpoint.
Example Request:
curl -X POST "http://localhost:8000/redact" \
-H "Content-Type: application/json" \
-d '{"text": "John Doe lives in New York.", "labels": ["person", "location"]}'
(Note: If you used port 8001, replace localhost:8000 with localhost:8001)
Example Response:
{
"original_text": "John Doe lives in New York.",
"anonymized_text": "<PERSON> lives in <LOCATION>.",
"replacements": [
{
"label": "person",
"original": "John Doe",
"pseudonym": "<PERSON>",
"start": 0,
"end": 8,
"score": 0.99
},
{
"label": "location",
"original": "New York",
"pseudonym": "<LOCATION>",
"start": 18,
"end": 26,
"score": 0.99
}
]
}
Run Tests
To verify the installation, you can run the test suite inside the container:
docker run --rm zink pytest zink/tests
How It Works
GLiNER
GLiNER is a Named Entity Recognition (NER) model capable of identifying any entity type using a bidirectional transformer encoder (BERT-like). It provides a practical alternative to traditional NER models, which are limited to predefined entities, and Large Language Models (LLMs) that, despite their flexibility, are costly and large for resource-constrained scenarios.
NuNer
NuNerZero is a compact, zero-shot Named Entity Recognition model that leverages the robust GLiNER architecture for efficient token classification. It requires lower-cased labels and processes inputs as a concatenation of entity types and text, enabling it to detect arbitrarily long entities.
Faker & Training Data
Zink leverages both the Faker library and the extensive training data from the GLiNER model to generate realistic, synthetic replacements for sensitive information.
- GLiNER Training Data: Access to thousands of label categories from the model's training set for diverse and accurate replacements.
- Dynamic Data Generation: Faker generates context-aware values (e.g., names, addresses).
- Location Handling: Swaps countries/cities with valid alternatives.
- Date Replacement: Handles various date formats intelligently.
- Roles: Differentiates between roles (e.g., doctor vs. patient) for appropriate naming.
Benchmarks
Here is a comparison of ZINK against other models on Quasi Identifier Benchmark ([QIB])(https://huggingface.co/datasets/deepanwa/QIB)
| Model | Overall Recall | Overall Precision | Overall F4_SCORE | True Positives (TP) | False Negatives (FN) | Total Redaction Markers |
|---|---|---|---|---|---|---|
| gpt_41_nano | 0.8971 | 0.962 | 0.9007 | 1570 | 180 | 1632 |
| gpt_41 | 0.9726 | 0.9737 | 0.9726 | 1702 | 48 | 1748 |
| zink_single | 0.9 | 0.8858 | 0.8992 | 1575 | 175 | 1778 |
| zink_topic | 0.9126 | 0.6502 | 0.8914 | 1597 | 153 | 2456 |
| zink_human (run 1) | 0.9371 | 0.6597 | 0.9145 | 1640 | 110 | 2486 |
| zink_human (run 2) | 0.9446 | 0.6544 | 0.9206 | 1653 | 97 | 2526 |
| tars_topic | 0.5983 | 0.762 | 0.6059 | 1047 | 703 | 1374 |
| bert | 0.628 | 0.4255 | 0.6109 | 1099 | 651 | 2583 |
Development
Testing
To run the tests, navigate to the project directory and execute:
pytest
Contributing
Contributions are welcome! Please feel free to submit pull requests or open issues to suggest improvements or report bugs.
- Fork the repository.
- Create a new branch:
git checkout -b feature/your-feature - Make your changes.
- Commit your changes:
git commit -m 'Add your feature' - Push to the branch:
git push origin feature/your-feature - Submit a pull request.
Citation
If you are using this package for your work/research, use the below citation:
Wadhwa, D. (2025). ZINK: Zero-shot anonymization in unstructured text. (v0.2.1). Zenodo. https://doi.org/10.5281/zenodo.15035072
License
This project is licensed under the Apache 2.0 License.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file zink-0.6.4.tar.gz.
File metadata
- Download URL: zink-0.6.4.tar.gz
- Upload date:
- Size: 32.1 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.5.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a6231f9b21e0326618b53a9718f4cc0c23ad2b4a8338bd6327105c12b9242c4e
|
|
| MD5 |
2ffabf340bdf82ac263f9b6a55a41591
|
|
| BLAKE2b-256 |
96b402e9d9a1096c577fd959a157934eef23f23d340c203735593dc2e92af0d1
|
File details
Details for the file zink-0.6.4-py3-none-any.whl.
File metadata
- Download URL: zink-0.6.4-py3-none-any.whl
- Upload date:
- Size: 24.4 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.5.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
024f51afd3a2bc3da5c40f445a95ffae4473f56d33a175946f4a708b901b9511
|
|
| MD5 |
5db6ed3c11a2b5d552bce8cf80371328
|
|
| BLAKE2b-256 |
00e9b555c2b0bbb52c9310de5d969f1c38d08d5fb7f20fe684e233c735995cf9
|