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: decide returns the tier (tier1 Β· tier2 Β· none) along with the decision (보냄 Β· 확인 Β· λ§‰μŒ). With with_types=True it 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 with model.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 in tier): 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.scores holds 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 in r.pieces).
  • You can ask only the questions you need, like model.ask(text, ["pii_any", "pii_t1"]). The decide rule 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.

License

Apache-2.0

Downloads last month
90
Safetensors
Model size
0.1B params
Tensor type
BF16
Β·
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support

Model tree for atonlee/Qoamo-PII-Decision

Finetuned
(16)
this model