日本語特化の PII 検出・マスキングミドルウェア(LLM オブザーバビリティ向け)
Project description
fuseji(伏せ字)
日本語特化の PII 検出・マスキングミドルウェア — LLM オブザーバビリティ向け。
English | 日本語
なぜ fuseji か
LLM オブザーバビリティ基盤(Langfuse、LangSmith、Phoenix、OTel)はトレース送信前に PII を伏せるためのマスキングフックを提供しています。しかし、既存の参照実装はすべて英語圏向けで、日本語テキストでは構造的に取りこぼします。
| ツール | 日本語対応 | マイナンバー | 日本の電話番号 |
|---|---|---|---|
| Presidio | ✗(recognizer 未提供) | ✗ | ✗(他ロケールの偶発一致のみ) |
| LLM Guard | ✗(英語 BERT 前提) | ✗ | ✗ |
| GLiNER PII | ✗(欧州 6 言語) | ✗ | ✗ |
| OTel Collector | ✗(regex のみ) | ✗ | ✗ |
| fuseji | ✓ | ✓(番号法対応) | ✓ |
日本語固有の課題(語境界がない、住所が大→小の順序、全角/半角混在、人名と一般名詞の曖昧性、マイナンバーの 12 桁構造)に正面から対応します。
対応エンティティ
v0.1(リリース時)
| Entity | 検出方式 | 備考 |
|---|---|---|
MY_NUMBER |
正規表現 + チェックディジット | 12 桁、総務省公開仕様、全角/半角対応、recall 優先 |
JP_PHONE_NUMBER |
正規表現 + numbering plan 検証 | 携帯 070/080/090、フリーダイヤル 0120、ナビダイヤル 0570、固定電話 |
JP_POSTAL_CODE |
正規表現 + コンテキストブースト | 〒 / XXX-XXXX 形式、文脈語で score 加重 |
EMAIL |
正規表現 | RFC-lite |
CREDIT_CARD |
正規表現 + Luhn 検証 | ロケール非依存 |
PERSON |
NER(GiNZA backend、[ginza] extra) |
CPU で ~100ms / ~100 tokens |
v0.2 以降
JP_ADDRESS(正規化ベースの住所検出)CORPORATE_NUMBER(法人番号、opt-in)BANK_ACCOUNT_JP、DRIVERS_LICENSE_JP- NER バックエンド比較(GiNZA / BERT-NER / GLiNER-ja)
インストール
# 正規表現コアのみ(ゼロ依存)
pip install fuseji
# GiNZA で人名(PERSON)検出も有効化
pip install 'fuseji[ginza]'
# FastAPI サーバー(/mask /detect /healthz)を有効化
pip install 'fuseji[server]'
# 全部入り
pip install 'fuseji[all]'
クイックスタート
from fuseji import Masker
masker = Masker()
result = masker.mask("山田さん(連絡先: 090-1234-5678, taro@example.co.jp、〒123-4567)")
print(result.text)
# 山田さん(連絡先: <JP_PHONE_NUMBER_1>, <EMAIL_1>、<JP_POSTAL_CODE_1>)
for e in result.entities:
print(e.type, e.text, e.score)
# JP_PHONE_NUMBER 090-1234-5678 0.95
# EMAIL taro@example.co.jp 1.0
# JP_POSTAL_CODE 〒123-4567 0.95
仮名化バウルト(復元可能なマスキング)
from fuseji import Masker, InMemoryVault
vault = InMemoryVault() # MY_NUMBER はデフォルトで復元不可(番号法対応)
masker = Masker(vault=vault)
r1 = masker.mask("田中さんと佐藤さん") # <PERSON_1>さんと<PERSON_2>さん(GiNZA 有効時)
r2 = masker.mask("田中さんに連絡して") # <PERSON_1>さんに連絡して(一貫性維持)
# LLM 応答から復元
restored = vault.restore("<PERSON_1>さんへ返信しました")
# 田中さんへ返信しました
マスキング戦略
from fuseji import Masker, Placeholder, Redact, Hash
Masker(strategy=Placeholder()) # <EMAIL_1> 形式(デフォルト)
Masker(strategy=Redact()) # [REDACTED]
Masker(strategy=Hash(length=8)) # SHA256 ハッシュ先頭 8 文字
Langfuse SDK 連携
from langfuse import Langfuse
from fuseji.integrations.langfuse import make_mask_fn
langfuse = Langfuse(mask=make_mask_fn())
# 以降、すべてのトレースが fuseji で自動マスクされる
例外時は fail-closed で [fuseji: masking failed] を返し、原データは絶対に通しません。
サーバーモード(Langfuse 取り込みコールバック / OTel サイドカー)
pip install 'fuseji[server]'
uvicorn fuseji.server.app:app --host 0.0.0.0 --port 8000
# マスキング
curl -X POST http://localhost:8000/mask \
-H 'Content-Type: application/json' \
-d '{"data": "メール: taro@example.com"}'
# {"data": "メール: <EMAIL_1>"}
# 検出のみ
curl -X POST http://localhost:8000/detect \
-H 'Content-Type: application/json' \
-d '{"text": "電話 090-1234-5678"}'
# {"entities": [{"type": "JP_PHONE_NUMBER", ...}]}
OpenAPI スキーマ: http://localhost:8000/openapi.json
セキュリティ・法令上の注意
fuseji は 検出・破棄するが保持しない(detect, never retain) ことを設計原則としています。
- 検出値はメモリ上のみで処理し、永続化しません
InMemoryVaultも session-scoped(プロセスメモリのみ)。永続化はユーザー側責任- マイナンバー(
MY_NUMBER)はVault.DEFAULT_EXCLUDED_TYPESでデフォルト除外 され、復元できません(番号法対応) - Langfuse アダプタは例外時に fail-closed で原データを返しません
詳しくは SECURITY.md を参照してください。
⚠️ fuseji は in-flight masking のみ提供します。検出値の保持・ログ出力は呼び出し側の責任です。
設計と非ゴール
詳細は docs/design.md を参照。
v0.x の非ゴール: ガードレール(プロンプトインジェクション、毒性)、画像/PDF redaction、可逆暗号、ストリーミング token-by-token マスキング、日本語以外の自然文(ASCII パターンは対応)。
ロードマップ
- v0.1(現行): 正規表現/checksum 認識器、GiNZA PERSON、Placeholder/Redact/Hash 戦略、Vault、Langfuse SDK アダプタ、FastAPI サーバー、CI
- v0.2:
JP_ADDRESS、ingestion callback の Docker イメージ、OTel example、Faker 戦略、fuseji-bench - v0.3: NER バックエンド比較(BERT-NER / GLiNER-ja fine-tune)、構造化フィールド対応、batch API
コントリビューション
CONTRIBUTING.md を参照。認識器の追加は first-class でサポートされており、Recognizer プロトコル + テストの 2 点セットで提案できます。
ライセンス
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 fuseji-0.1.0.tar.gz.
File metadata
- Download URL: fuseji-0.1.0.tar.gz
- Upload date:
- Size: 230.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4d3e7c2a6052ce891baba28f3e6848ac77cdbcea3261be2866297f1a6f394666
|
|
| MD5 |
8df917a63a4f2931b59e6a05c0ade077
|
|
| BLAKE2b-256 |
c8dbcbabb022e41596badc1b72de888ccc56a28f130a277e4b02915ea1138bae
|
Provenance
The following attestation bundles were made for fuseji-0.1.0.tar.gz:
Publisher:
publish.yml on sserada/fuseji
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fuseji-0.1.0.tar.gz -
Subject digest:
4d3e7c2a6052ce891baba28f3e6848ac77cdbcea3261be2866297f1a6f394666 - Sigstore transparency entry: 1791089954
- Sigstore integration time:
-
Permalink:
sserada/fuseji@ceb26522c2e45d59aad4e27c9fa755db1cd291d3 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/sserada
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ceb26522c2e45d59aad4e27c9fa755db1cd291d3 -
Trigger Event:
push
-
Statement type:
File details
Details for the file fuseji-0.1.0-py3-none-any.whl.
File metadata
- Download URL: fuseji-0.1.0-py3-none-any.whl
- Upload date:
- Size: 36.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
08fa953c72bcd6fa83fb01b8e3ed19c90a9a1d9aa7d6dab7381b714a3904ea67
|
|
| MD5 |
23b26acde55fa002a1acfe7612aaf0aa
|
|
| BLAKE2b-256 |
3d87bda0a762f9974af7ceaf727b65b4b1e6b9feadae70ba2c96cbe0b2c72cb5
|
Provenance
The following attestation bundles were made for fuseji-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on sserada/fuseji
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fuseji-0.1.0-py3-none-any.whl -
Subject digest:
08fa953c72bcd6fa83fb01b8e3ed19c90a9a1d9aa7d6dab7381b714a3904ea67 - Sigstore transparency entry: 1791090271
- Sigstore integration time:
-
Permalink:
sserada/fuseji@ceb26522c2e45d59aad4e27c9fa755db1cd291d3 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/sserada
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ceb26522c2e45d59aad4e27c9fa755db1cd291d3 -
Trigger Event:
push
-
Statement type: