Shieldstral公開で見直す、AI添付前の個人情報検査Python
Mistral's Shieldstral: 3B open-weights model for multimodal moderation
8月4日にMistralが公開したShieldstralの告知を見た。画像を含む内容のモデレーションを、オープンウェイトで扱えるようにするモデルだという。
これを見て、僕が先に片付けたくなったのはモデル選びではありません。営業資料、議事録、スクリーンショットを生成AIに添付する前の確認です。内容が適切かを判定する仕組みが進んでも、添付したファイルに担当者の連絡先やカード番号が残っていたら、送る判断は別に要ります。
社内で「送る前に見ておいて」と言っても、忙しいと抜けます。そこで、添付候補のファイル名とOCR済みテキストをJSONに出し、個人情報らしい文字列があれば送信保留にする小さなPythonを置きました。ここ地味に効きます。人に最終判断を残しつつ、見落としやすい箇所だけを先に赤くできます。
参照したニュース: Mistral: Shieldstral
先に決めるのは「検知したら送らない」範囲
このスクリプトは、メールアドレス、電話番号、マイナンバーらしい12桁、カード番号らしい13〜19桁を拾います。正規表現だけなので、個人情報を完全に判定するものではありません。たとえば12桁の受注番号も止まる可能性があります。
それでも送信直前のゲートとしては十分使えます。誤検知なら内容を確認して通す。検知を見逃したときだけ、そもそも添付操作に進まない。この優先順位です。
入力はOCRツールやPDF抽出ツールの出力を、次の形にそろえます。本文をAPIへ渡す必要はありません。
[
{"name": "提案メモ.txt", "text": "窓口は tanaka@example.jp、03-1234-5678"},
{"name": "製品概要.pdf", "text": "公開済みの製品説明です"}
]
添付候補をCSVにして止める
attachment_preflight.py として保存します。
import csv
import json
import re
import sys
from pathlib import Path
PATTERNS = {
"メールアドレス": re.compile(r"(?<![\w.+-])[\w.+-]+@[\w-]+(?:\.[\w-]+)+(?![\w.+-])"),
"電話番号": re.compile(r"(?<!\d)(?:0\d{1,4}[- ]?\d{1,4}[- ]?\d{4})(?!\d)"),
"マイナンバーらしい12桁": re.compile(r"(?<!\d)\d{12}(?!\d)"),
"カード番号らしい連番": re.compile(r"(?<!\d)(?:\d[ -]?){13,19}(?!\d)"),
}
def mask(value: str) -> str:
compact = re.sub(r"\s+", " ", value)
return compact[:3] + "…" if len(compact) > 3 else compact
def find_hits(text: str) -> list[tuple[str, str]]:
hits = []
for label, pattern in PATTERNS.items():
for match in pattern.finditer(text):
hits.append((label, mask(match.group())))
return hits
def main() -> None:
if len(sys.argv) != 2:
raise SystemExit("使い方: python3 attachment_preflight.py attachments.json")
source = Path(sys.argv[1])
attachments = json.loads(source.read_text(encoding="utf-8"))
if not attachments:
raise SystemExit("添付候補がありません")
rows = []
for item in attachments:
name = item["name"]
hits = find_hits(item.get("text", ""))
rows.append({
"ファイル名": name,
"判定": "送信保留" if hits else "確認済み",
"検知項目": " / ".join(label for label, _ in hits) or "-",
"検知例(伏せ字)": " / ".join(value for _, value in hits) or "-",
})
output = source.with_name("attachment_review.csv")
with output.open("w", newline="", encoding="utf-8-sig") as f:
writer = csv.DictWriter(f, fieldnames=rows[0].keys())
writer.writeheader()
writer.writerows(rows)
print(f"{output} を作成しました")
for row in rows:
print(f"{row['判定']:4} {row['ファイル名']} {row['検知項目']}")
if __name__ == "__main__":
main()
手元で上のJSONを attachments.json として実行した出力です。
$ python3 attachment_preflight.py attachments.json
attachment_review.csv を作成しました
送信保留 提案メモ.txt メールアドレス / 電話番号
確認済み 製品概要.pdf -
utf-8-sig でCSVを書いているのは、現場ではExcelで開く人がいるからです。BOM付きUTF-8にしておくと、日本語のヘッダーが文字化けしにくい。ここでCSVを開くなら、数式として解釈される値を扱う運用とは分けたほうが安全です。
なぜ本文だけでなくファイル名も台帳に残すのか
最初は本文だけを検査していました。ただ、共有フォルダから選んだスクリーンショットの名前に、取引先名と担当者名が入っていることがありました。本文が空でも、ファイル名を一覧に出すだけで気づけます。
このコードはファイル名の文字列そのものを検知対象にはしていません。取引先名まで機械的に止めると、営業資料がほぼ全部止まるためです。代わりにCSVへ必ず出し、送信保留の行だけでなく、添付物の取り違えも目で確認する形にしました。検査の精度より、作業を止めずに確認が続くことを優先しています。
OCR結果がない画像やPDFは、text を空にして「確認済み」と見なさない運用が必要です。その場合は入力JSONを作る段階で OCR未実施 の行を追加するか、画像添付をいったん保留にします。ここを自動で通してしまうと、画像対応のモデレーションモデルが出た話と、業務上の送信確認がごちゃ混ぜになります。
次に効くのは、添付操作の前にこのCSVを置くこと
Shieldstralのようなモデルが増えるほど、画像も含めてAIへ渡せる範囲は広がります。だからこそ、業務側では「何を判定させるか」より先に「何を渡してよいか」を短い手順にしておくと回ります。
まずはよく使う提案メモ2件で動かし、誤検知した番号を記録するといいです。正規表現を賢くする前に、送信保留のCSVを一度見る流れを作る。僕はこの順番が、現場では一番続きます。