One JSON line:
{"verdict":"scam","type":"택배사칭","risk":90,"signals":["공식 도메인이 아닌 링크","주소 수정 요구"],"advice":"링크를 누르지 말고 택배사 공식 앱에서 확인하세요."}
verdict is scam, suspicious or safe. type is one of: 택배사칭·공공기관사칭·금융사칭·가족지인사칭·경조사사칭·지원금환급사칭·투자사기·로맨스사기·결제승인사칭·계정보안사칭·기타사기 (scams), 인증번호·결제알림·배송알림·공공안내·광고·개인대화·업무 (normal).
Use
The adapter was trained with this system prompt; use it verbatim:
너는 AZET Nunchi다. 한국어 문자·메신저 메시지나 통화 내용을 받아 사기(스미싱·보이스피싱·사칭) 여부를 판정한다. 반드시 JSON 한 줄로만 답한다: {"verdict":"scam|suspicious|safe","type":"유형","risk":0~100 정수,"signals":["판단 근거 1~3개"],"advice":"받은 사람이 할 일 한 문장"}. type은 택배사칭·공공기관사칭·금융사칭·가족지인사칭·경조사사칭·지원금환급사칭·투자사기·로맨스사기·결제승인사칭·계정보안사칭·기타사기·인증번호·결제알림·배송알림·공공안내·광고·개인대화·업무 중 하나다.
With PEFT:
from transformers import AutoModelForCausalLM, AutoTokenizerfrom peft import PeftModel base = "Qwen/Qwen2.5-Coder-32B-Instruct"tok = AutoTokenizer.from_pretrained(base)model = AutoModelForCausalLM.from_pretrained(base, device_map="auto", torch_dtype="auto")model = PeftModel.from_pretrained(model, "neulketing/azet-nunchi-1") SYS = "너는 AZET Nunchi다. ..." # the full system prompt abovemsg = "[CJ대한통운] 주소 불일치로 보관중입니다. 주소 수정 http://cj-kr.top/a1"ids = tok.apply_chat_template([{"role": "system", "content": SYS}, {"role": "user", "content": msg}], add_generation_prompt=True, return_tensors="pt").to(model.device)out = model.generate(ids, max_new_tokens=220, do_sample=False)print(tok.decode(out[0, ids.shape[1]:], skip_special_tokens=True))
Or call the hosted model (send only the message):
curl https://api.azet.io/v1/chat/completions -H "Authorization: Bearer $AZET_API_KEY" -H "Content-Type: application/json" \ -d '{"model": "azet/nunchi-1", "messages": [{"role": "user", "content": "[CJ대한통운] 주소 불일치로 보관중입니다. 주소 수정 http://cj-kr.top/a1"}]}'
How it was built
- Base: Qwen2.5-Coder-32B-Instruct (Apache-2.0).
- LoRA rank 8, alpha 16, on the attention query and value projections of the last 24 layers; trained with MLX (QLoRA on the 4-bit build), 200 iterations, then converted to PEFT.
- 2,030 training examples: Korean messages across 11 scam types and 8 kinds of normal messages, written for training by the open-weight model Qwen3-30B-A3B (Apache-2.0), plus public voice-phishing call transcripts (HyaDoo/ko-voicephishing-binary-classification, Apache-2.0). Normal messages were kept only when their links point to the sender's official domain.
Evaluation
Scam caught = share of scam messages flagged scam or suspicious. False alarms = share of normal messages flagged. Same instructions for base and adapter.
Through the hosted API (2026-10-08), 60 messages from jmjmjm3/kor-smishing-message (CC BY-NC-SA, evaluation only, never used for training):
Table with columns: System, Scam caught, False alarms| System | Scam caught | False alarms |
|---|
| URL blocklist (200,000 hosts) | 0.0% | 0.0% |
| Qwen2.5-Coder-32B-Instruct | 90.0% | 16.7% |
| AZET Nunchi 1 | 86.7% | 3.3% |
Larger local runs (4-bit build, 2026-10-08):
Table with columns: Test set, n, Base: caught / false alarms, Nunchi 1: caught / false alarms| Test set | n | Base: caught / false alarms | Nunchi 1: caught / false alarms |
|---|
| Public smishing dataset (evaluation only) | 300 | 96.0% / 20.0% | 88.7% / 6.7% |
| Real inbox of a team member's phone (613 normal, 8 spam; not published) | 621 | 87.5% / 64.8% | 75.0% / 7.5% |
| Public voice-phishing transcripts (held out) | 100 | 97.6% / 39.0% | 90.2% / 6.8% |
| Generated messages (held out) | 195 | 99.2% / 41.0% |
Nunchi 1 trades some recall for far fewer false alarms: the base model flags most messages it sees.
Limits
- Korean only. Training messages are synthetic.
- Most remaining false alarms are short bank and card notifications.
- Answers are AI-generated judgments, not a guarantee — when money or identity is involved, check with the organisation through its official number.
AZET LLC · azet.io · hello@azet.io