Instructions to use atonlee/Qoamo-PII-Decision with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use atonlee/Qoamo-PII-Decision with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-classification", model="atonlee/Qoamo-PII-Decision", trust_remote_code=True)# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("atonlee/Qoamo-PII-Decision", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
Qoamo-PII-Decision
A routing model that catches personal data in Korean and English requests before they leave the device. Built on skt/A.X-Encoder-base (ModernBERT, 149M parameters), it was fine-tuned as a decision model that answers questions about a text with a probability for every option.
It can be asked four things about a text (whether it contains personal data, whether it contains a strong identifier, its tier, and which of 33 types it holds), and the answers decide whether the text is sent as is, masked and then sent, or blocked. The decision is made from the first three questions; the types are asked only when needed. Locating the values is not its job. For a text it marks as containing personal data, atonlee/koelectra-ko-pii-ner, built on the same tier1 and tier2 criteria, finds and masks the values. This keeps the fast filtering decision separate from precise masking by NER.
On the official KDPII test split, it scored an F1 of 0.951 at telling whether a sentence contains personal data, the highest among the compared models. The weights are a single safetensors file (bf16). If you need ONNX, use one of the four in onnx/ (float32 Β· FP16 Β· INT8 Β· UINT8).
News
π₯ 2026.10 Update
- English: it now judges English text as well as Korean.
- Types 23 β 33: passwords, API keys or tokens, and verification codes or OTPs are asked about separately. Phone numbers are split into regular, business or toll-free, and short public numbers, and personal numbers into employee, member or customer, student or exam, and patient numbers, among others.
- Decision and tier together:
decidereturns the tier (tier1 Β· tier2 Β· none) along with the decision (보λ Β· νμΈ Β· λ§μ). Withwith_types=Trueit returns the types as well. - Asking with different criteria: ask about several types as one (
has), leave some types out (has_pii_except), or regroup the tiers into up to 4 levels (tier). - Long texts: with
document=True, a text of up to 2,000 characters is judged in one go. A longer one is split on line breaks and the strictest decision is returned. - Ready-to-use code: load it with
AutoModel.from_pretrained(..., trust_remote_code=True)and use it in one line withmodel.decide(text). - Accuracy: KDPII F1 0.896 β 0.951 (10 types 0.918 β 0.955). Every sentence holding a tier1 value was caught.
Questions
The model can be asked four things about a text and gives a probability for every option.
pii_anyβ contains personal data? (μ yes Β· μλμ€ no)pii_t1β contains a strong identifier? (μ Β· μλμ€)pii_tierβ tier (tier1 Β· tier2 Β· none): picks the single highest tier in the text.type_*β 33 types (μ Β· μλμ€ for each type)
decide asks only the first three; with with_types=True it also asks the 33 types. The questions never see each other, so the answers are the same however many are asked together, and asking fewer is faster.
Tiers and types
| tier | meaning | recommended handling | types |
|---|---|---|---|
| tier1 | the value alone identifies a person or can cause harm | do not send off-device | resident registration number (μ£Όλ―Όλ±λ‘λ²νΈ), alien registration number (μΈκ΅μΈλ±λ‘λ²νΈ), passport number (μ¬κΆλ²νΈ), driver's licence number (μ΄μ λ©΄νλ²νΈ), i-PIN (μμ΄ν), domestic ID number (κ΅λ΄ μ λΆλ²νΈ), foreign ID number (μΈκ΅ μ λΆλ²νΈ), card number (μΉ΄λλ²νΈ), card expiry date (μΉ΄λ μ ν¨κΈ°κ°), CVC, account number (κ³μ’λ²νΈ), password (λΉλ°λ²νΈ), API key or token (API ν€Β·ν ν°), verification code or OTP (μΈμ¦λ²νΈΒ·OTP) |
| tier2 | identifies a person when combined with other information | find and mask it with NER, then send | name (μ΄λ¦), email (μ΄λ©μΌ), regular phone number (μΌλ° μ νλ²νΈ), business or toll-free number (λνλ²νΈ), short public number (νΉμλ²νΈ), address (μ£Όμ), postal code (μ°νΈλ²νΈ), date of birth (μλ μμΌ), age (λμ΄), gender (μ±λ³), employee number (μ¬λ²), member or customer number (νμΒ·κ³ κ°λ²νΈ), student or exam number (νμΒ·μμλ²νΈ), patient number (νμλ²νΈ), other personal number (κ·Έ λ°μ μ¬λ λ²νΈ), user ID (μ¬μ©μ ID), URL, IP address (IP μ£Όμ), vehicle number (μ°¨λλ²νΈ) |
| none | no personal data | send as is | β |
In code, use the Korean names in parentheses (CVC and URL as written); they are the names in model.types.
Four answers per text
| text | contains personal data? (yes) | strong identifier (yes) | tier | types (yes β₯ 0.5) | decision |
|---|---|---|---|---|---|
| λ°μ§ν λ리νν 010-4821-3307 λ‘ λ¬Έμ λ³΄λ΄ μ€ | 0.999 | 0.000 | tier2 (0.999) | name 0.995 Β· regular phone number 0.994 | νμΈ |
| λ΄ μ£Όλ―Όλ²νΈ 901023-1234567 λ‘ λ±λ‘ | 0.999 | 0.999 | tier1 (0.998) | resident registration number 0.950 | λ§μ |
| μ΄λ² μ£Όλ§μ λΉ μ€λμ§ μλ € μ€ | 0.002 | 0.000 | none (0.997) | none | 보λ |
| Send the signed contract to david.kim@acme.io | 0.996 | 0.000 | tier2 (0.999) | email 0.997 | νμΈ |
Asking with different criteria
The tiers above are the default, and the 보λ Β· νμΈ Β· λ§μ decision was tuned with them. The best results come from using the default tiers as they are. If your policy differs, there are three ways to change the criteria.
- Grouping (
has): ask in one go whether any of several types, such as phone number, email and address, is present. On groups it had not seen in training, it was right 99% of the time. To treat some types more strictly, ask with this and raise the tier in your code. - Excluding (
has_pii_except, or leaving types out intier): leave some types out, so that, for example, a text with only a name does not count as personal data. On texts holding the left-out types it was right 97% of the time or more. - Changing the tiers (
tier): move types between tiers. Use up to 4 levels, named tier1, tier2, tier3, tier4 and none. It is less accurate on texts that hold the moved types. In particular, a type moved up to tier1 tends to stay in its original tier when another tier2 value is present, so use grouping to raise a type.
The fourth and fifth examples under Usage show how. The question wording (the instructions in pii_config.json) can be changed too, but this is not recommended.
Usage
Load it with trust_remote_code=True. Only the files it needs are downloaded, not onnx/. decide gives the decision and the tier from three questions.
pip install "transformers>=5.1" torch
from transformers import AutoModel
model = AutoModel.from_pretrained("atonlee/Qoamo-PII-Decision", trust_remote_code=True)
for text in ["λ΄μΌ μ€ν μΈ μμ ν νμ μ‘μ μ€",
"λ°μ§ν λ리νν
010-4821-3307 λ‘ λ¬Έμ λ³΄λ΄ μ€",
"λ΄ μ£Όλ―Όλ²νΈ 901023-1234567 λ‘ λ±λ‘",
"Send the signed contract to david.kim@acme.io"]:
r = model.decide(text)
print(f"{r.route} | {r.tier} | {text}")
Output:
보λ | none | λ΄μΌ μ€ν μΈ μμ ν νμ μ‘μ μ€
νμΈ | tier2 | λ°μ§ν λ리νν
010-4821-3307 λ‘ λ¬Έμ λ³΄λ΄ μ€
λ§μ | tier1 | λ΄ μ£Όλ―Όλ²νΈ 901023-1234567 λ‘ λ±λ‘
νμΈ | tier2 | Send the signed contract to david.kim@acme.io
To see the types as well, pass with_types=True. It asks 36 questions and is several times slower.
r = model.decide("λ°μ§ν λ리νν
010-4821-3307 λ‘ λ¬Έμ λ³΄λ΄ μ€", with_types=True)
print(r.route, r.tier, r.types)
Output:
νμΈ tier2 ['μ΄λ¦', 'μΌλ° μ νλ²νΈ']
Judge a long text with document=True. A single corporate card number in the middle of the email is enough to block the whole email.
mail = """νμ₯λ, μ§λμ£Ό λΆμ° μΆμ₯ μ μ° κ±΄μΌλ‘ λ©μΌ λ립λλ€.
μΌμ μ 10μ 6μΌλΆν° 8μΌκΉμ§ 2λ° 3μΌμ΄μκ³ , νλ ₯μ¬ λ κ³³κ³Ό λ―Έν
μ μ§ννμ΅λλ€.
κ΅ν΅λΉλ KTX μ볡 119,600μ, μλ°λΉλ 2λ° 236,000μ, μλλ 84,000μμ
λλ€.
μμμ¦ μλ³Έμ λ΄μΌ μ€μ μ€μ κ²½μμ§μν μ μ°ν¨μ λ£μ΄ λκ² μ΅λλ€.
μλ°λΉλ λ²μΈμΉ΄λ 4518-3207-6614-2093 μΌλ‘ κ²°μ νκ³ , λλ¨Έμ§λ κ°μΈ μΉ΄λλ‘ λμ΅λλ€.
κ°μΈ κ²°μ λΆμ μ΄λ² λ¬ κΈμ¬μ ν¨κ» λ£μ΄ μ£Όμλ©΄ λ©λλ€.
λ―Έν
κ²°κ³Όλ λ°λ‘ μ 리ν΄μ κΈμμΌ νμ λ 곡μ λλ¦¬κ² μ΅λλ€.
κ°μ¬ν©λλ€."""
r = model.decide(mail, document=True, with_types=True)
print(r.route, r.tier, r.types)
Output:
λ§μ tier1 ['μΉ΄λλ²νΈ']
To treat contact details more strictly, ask whether there are any and raise the tier if so. Only the second, third and fourth texts, which hold contact details, move up to tier1; the text with only a name and the text with an account number stay as they were.
texts = ["κΉλ―Όμ νμ₯λκ» μ€λ νμλ‘ λ³΄λ΄ λλ €",
"λ°μ§ν λ리νν
010-4821-3307 λ‘ λ¬Έμ λ³΄λ΄ μ€",
"견μ μ jiyoung.park@gmail.com μΌλ‘ λ³΄λ΄ μ€",
"μμΈ κ°λ¨κ΅¬ ν
ν€λλ‘ 152 12μΈ΅μΌλ‘ μλ₯ 보λμ΄",
"μ ν 110-482-339201 λ‘ μμΈ 55λ§ μ λ³΄λ΄ μ€",
"λ΄μΌ μ€ν μΈ μμ ν νμ μ‘μ μ€"]
contact_types = ["μΌλ° μ νλ²νΈ", "μ΄λ©μΌ", "μ£Όμ"]
for text in texts:
before = model.decide(text).tier
after = "tier1" if model.has(text, contact_types, name="μ°λ½μ²") else before
print(f"{before} β {after} | {text}")
Output:
tier2 β tier2 | κΉλ―Όμ νμ₯λκ» μ€λ νμλ‘ λ³΄λ΄ λλ €
tier2 β tier1 | λ°μ§ν λ리νν
010-4821-3307 λ‘ λ¬Έμ λ³΄λ΄ μ€
tier2 β tier1 | 견μ μ jiyoung.park@gmail.com μΌλ‘ λ³΄λ΄ μ€
tier2 β tier1 | μμΈ κ°λ¨κ΅¬ ν
ν€λλ‘ 152 12μΈ΅μΌλ‘ μλ₯ 보λμ΄
tier1 β tier1 | μ ν 110-482-339201 λ‘ μμΈ 55λ§ μ λ³΄λ΄ μ€
none β none | λ΄μΌ μ€ν μΈ μμ ν νμ μ‘μ μ€
To stop counting names as personal data, ask with the default tiers minus names. The first text, which holds only a name, becomes none, and the second, with a name and a phone number, stays tier2 because of the phone number.
custom_tiers = {"tier1": model.tiers["tier1"],
"tier2": [t for t in model.tiers["tier2"] if t != "μ΄λ¦"]}
for text in texts:
print(f"{model.decide(text).tier} β {model.tier(text, custom_tiers)} | {text}")
Output:
tier2 β none | κΉλ―Όμ νμ₯λκ» μ€λ νμλ‘ λ³΄λ΄ λλ €
tier2 β tier2 | λ°μ§ν λ리νν
010-4821-3307 λ‘ λ¬Έμ λ³΄λ΄ μ€
tier2 β tier2 | 견μ μ jiyoung.park@gmail.com μΌλ‘ λ³΄λ΄ μ€
tier2 β tier2 | μμΈ κ°λ¨κ΅¬ ν
ν€λλ‘ 152 12μΈ΅μΌλ‘ μλ₯ 보λμ΄
tier1 β tier1 | μ ν 110-482-339201 λ‘ μμΈ 55λ§ μ λ³΄λ΄ μ€
none β none | λ΄μΌ μ€ν μΈ μμ ν νμ μ‘μ μ€
r.scoresholds the probability of every option for the questions asked.- When the decision is νμΈ, find and mask the personal data with NER, then send.
- With
document=True, a text over 2,000 characters is split on line breaks, each piece is judged, and the strictest decision and tier are returned (the pieces are inr.pieces). - You can ask only the questions you need, like
model.ask(text, ["pii_any", "pii_t1"]). Thedeciderule then does not apply. - To leave ONNX out when downloading the whole repository, use
snapshot_download(..., ignore_patterns=["onnx/*"]).
Evaluation
We measured whether a sentence contains personal data on the official test split of KDPII (CC BY 4.0). Sentences were selected based on our types and the 10 common types (3,998 sentences for our types, 3,737 for the 10 types). KDPII was not used to train this model. Widely used PII detection, decision and guard models on Hugging Face were run on the same sentences and scored the same way.
| model | size | precision | recall | F1 (our types) | F1 (10 types) |
|---|---|---|---|---|---|
| Qoamo-PII-Decision | 149M | 0.954 | 0.947 | 0.951 | 0.955 |
| perplexity-ai/PII-Tracer | about 600M | 0.665 | 0.783 | 0.719 | 0.739 |
| LiquidAI/LFM2.5-Encoder-350M-PII-Detector | 350M | 0.940 | 0.503 | 0.655 | 0.738 |
| convaiinnovations/laya (Korean question) | 322M | 0.205 | 0.998 | 0.340 | 0.259 |
| Qwen/Qwen3Guard-Gen-0.6B | 0.6B | 0.556 | 0.231 | 0.326 | 0.357 |
| SupersonicLabs/Julia-1 (English question) | 144M | 0.178 | 0.523 | 0.266 | 0.205 |
| fastino/GLiNER2.5-multi-Decide (English question) | 287M | 0.279 | 0.217 | 0.244 | 0.214 |
| openai/privacy-filter (reference) | 1.4B | 0.813 | 0.612 | 0.698 | 0.775 |
| OpenMed/privacy-filter-multilingual (reference) | 1.4B | 0.530 | 0.869 | 0.658 | 0.602 |
- F1 (our types): name Β· phone Β· mobile Β· email Β· vehicle number Β· IP Β· user ID Β· date of birth Β· age Β· gender Β· resident registration number Β· alien registration number Β· passport number Β· driver's licence number Β· card number Β· account number
- F1 (10 types): name Β· phone Β· mobile Β· email Β· resident registration number Β· alien registration number Β· passport number Β· driver's licence number Β· card number Β· account number
Data
Training data includes the following.
- BCCard/privacy-filter-openpii-masking (CC BY 4.0)
- nvidia/Nemotron-PII (CC BY 4.0)
- clinc/clinc_oos (CC BY 3.0)
License
Apache-2.0
- Downloads last month
- 90
Model tree for atonlee/Qoamo-PII-Decision
Base model
skt/A.X-Encoder-base