0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Shieldstral公開で見直すAI添付前の個人情報検査Python

0
Posted at

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を一度見る流れを作る。僕はこの順番が、現場では一番続きます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?