在一大段文本中找出每一处 PII,并以结构化 span 的形式返回。
基于 OpenAI Privacy Filter 构建:相同的 1.5B 模型、相同的标签、相同的权重。
Privacy Filter 做掩码。Privacy Parser 做提取。
Hi Quindle Testwick ([email protected] / +1-415-555-0102),
account 40702810500001234567, 14 Beautiful Ct, Anytown USA,
password Priv4cy-Filt3r-2026.
private_person Quindle Testwick
private_email [email protected]
private_phone +1-415-555-0102
account_number 40702810500001234567
private_address 14 Beautiful Ct, Anytown USA
secret Priv4cy-Filt3r-2026
不再得到 <REDACTED>,而是得到发现了什么、它在哪里(start、end)以及它是什么类型。
Privacy Filter 回答的是“让这段文本变安全”。有时你需要的是另一个答案:“这里面究竟有什么?”
| 后端 | 需要权重 | 速度 | F1 * | 适用场景 |
|---|---|---|---|---|
PIIParser | 否 | µs | 1.000 | 零依赖,结构化模式 |
ModelPIIParser | 1.5B | ~500 ms CPU | 0.733 | 原始模型输出 |
HybridPIIParser | 1.5B | ~600 ms CPU | 0.929 | 默认选择 |
* 在五个 opf 示例夹具上的精确片段匹配。这是一个小集合:请将其视为冒烟测试,而非基准测试。
git clone https://github.com/openai/privacy-filter
git clone https://github.com/chiefautism/privacy-parser
uv venv
uv pip install -e ./privacy-filter # only for the model backends
uv pip install -e ./privacy-parser
首次运行模型会将 opf 检查点(约 3 GB)下载到 ~/.opf/privacy_filter/。
设置 OPF_CHECKPOINT 或传入 model= 以使用其他位置。
from pii_parser.hybrid import HybridPIIParser
parser = HybridPIIParser(device="cpu") # or "cuda"
result = parser.parse("Hi Quindle Testwick, call me at +1-415-555-0102")
for s in result.spans:
print(s.label, s.start, s.end, s.text)
print(result.to_json())
from pii_parser import parse
result = parse("mail [email protected], token ghp_abcdefghijklmnopqrstuvwxyz0123456789")
result.detected_spans # (DetectedSpan(label='private_email', ...), ...)
result.redacted_text # 'mail <REDACTED>, token <REDACTED>'
result.summary # {'span_counts': {...}, 'total_spans': 2}
正则后端,无需模型:
$ echo "Alice paid 40702810500001234567 on 2026-05-17." | pii-parser --mode spans
account_number 11 31 40702810500001234567
private_date 35 45 2026-05-17
$ pii-parser "mail [email protected]" --mode redacted
mail <REDACTED>
| 参数 | 取值 | 默认值 |
|---|---|---|
TEXT | 文本,或 -f FILE,或标准输入 | |
--mode | json · spans · redacted | json |
--placeholder | 任意字符串 | <REDACTED> |
--indent | 整数,0 表示紧凑 JSON | 2 |
模型后端:
python -m pii_parser.cli_model "Alice paid 40702810500001234567 on 2026-05-17."
python -m pii_parser.cli_model -f dump.txt --device cuda --mode spans
opf v2 分类法,8 个类别:
| 标签 | 示例 |
|---|---|
private_person | Quindle Testwick |
private_email | [email protected] |
private_phone | +1-415-555-0102 |
private_address | 14 Beautiful Ct, Anytown USA |
private_url | https://example.com/u/quindle |
private_date | 2026-05-17 |
account_number | 40702810500001234567 |
secret | sk-…、ghp_…、AKIA…、Bearer … |
当跨度重叠时,较长者优先,其次优先级更高的标签: email > url > secret > account > phone > date > address > person。
text
│
▼
opf 1.5B ──► BIOES logits ──► Viterbi (tuned biases) ──► char spans
│
▼
span merge glues "Quindle" + "Testwick" into one person
│
▼
regex backstop adds url / secret / account where the model missed them
│
▼
spans[]
每个阶段都可以关闭:
HybridPIIParser(
enable_merge=True,
enable_regex_backstop=True,
enable_viterbi_tuning=True,
decode_mode="viterbi", # or "argmax"
)
{
"text": "Hi Quindle Testwick ...",
"spans": [
{ "label": "private_person", "start": 3, "end": 19, "text": "Quindle Testwick" }
]
}
正则 PIIParser 与 opf.RedactionResult 保持一致(schema_version、summary、detected_spans、redacted_text),因此你可以在不修改调用方的情况下切换后端。
测试要求将 privacy-filter 克隆到本仓库旁边(测试会读取其示例测试数据)。
python tests/test_hybrid.py
Fixture F1: 0.929
Scenarios: 8/8 passed
Latency: ~600 ms CPU
这是一个双用途工具。请仅在您拥有或已获授权处理的数据上运行它。在大多数司法管辖区,从泄露或被盗来源中提取个人数据是违法的。