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?

LLMに渡すCSVの個人情報をPythonで伏せる

0
Posted at

ローカルで動くLLMでも、営業CSVをそのまま食わせる判断は危ないです。社外へ送らないとしても、氏名や連絡先が混じったファイルを丸ごと扱う必要は、たいていありません。

8月18日に日本語LLM「LLM-jp-4 33B」が公開されました。こういうモデルが出ると、商談メモの要約や失注理由の分類を手元で試したくなります。僕も今日、営業一覧を渡す前提で小さなCSVを作り、先に伏せ字へ変える処理を通しました。結果を見て気付いたのは、列を消すだけだと商談メモの中に残ったメールアドレスを見落とすことです。

ここ地味に効きます。先に「LLMに要る列」と「要らない個人情報」を分けておくと、試すモデルを替えても、元データを渡す範囲は増えません。

ニュースの元は LLM-jpによる公開案内 です。この記事ではモデルの入れ方には踏み込まず、CSVを渡す直前の一手だけを固めます。

CSVを別ファイルにしてから渡す

元のCSVを上書きせず、LLM用の別ファイルを作ります。下のコードは標準ライブラリだけです。氏名メール電話番号住所などの見出しを丸ごと伏せ、残した列の文章に紛れたメールアドレスと電話番号も置き換えます。

# redact_csv.py
import csv
import re
import sys
from pathlib import Path

MASK_COLUMNS = {"氏名", "担当者", "メール", "電話番号", "住所"}
PATTERNS = (
    (re.compile(r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}"), "[メール伏せ]"),
    (re.compile(r"(?<!\d)(?:0\d{1,4}-?\d{1,4}-?\d{4})(?!\d)"), "[電話伏せ]"),
)

def redact_in_text(value: str) -> str:
    for pattern, replacement in PATTERNS:
        value = pattern.sub(replacement, value)
    return value

def redact_csv(source: Path, destination: Path) -> list[str]:
    with source.open(encoding="utf-8-sig", newline="") as src:
        reader = csv.DictReader(src)
        if not reader.fieldnames:
            raise ValueError("ヘッダー行が見つかりません")

        targets = sorted(MASK_COLUMNS & set(reader.fieldnames))
        if not targets:
            raise ValueError("伏せる列がありません。MASK_COLUMNSをCSVの見出しに合わせてください")

        with destination.open("w", encoding="utf-8-sig", newline="") as dst:
            writer = csv.DictWriter(dst, fieldnames=reader.fieldnames)
            writer.writeheader()
            for row in reader:
                output = {}
                for key in reader.fieldnames:
                    value = row.get(key) or ""
                    output[key] = "[伏せ字]" if key in MASK_COLUMNS else redact_in_text(value)
                writer.writerow(output)
    return targets

if __name__ == "__main__":
    if len(sys.argv) != 3:
        sys.exit("使い方: python3 redact_csv.py 元.csv LLM用.csv")
    masked = redact_csv(Path(sys.argv[1]), Path(sys.argv[2]))
    print("伏せた列:", ", ".join(masked))

たとえば deals.csv を入力にするなら、実行はこれだけです。

python3 redact_csv.py deals.csv deals_for_llm.csv

僕が確認に使った1行では、氏名メール電話番号住所[伏せ字] になり、商談メモの sales@example.jp[メール伏せ] になりました。出力先が元ファイルと別なので、戻したくなったときも慌てません。

Excelで開くなら文字コードも固定する

入出力に utf-8-sig を指定したのは、Excelで開いたときに日本語の見出しが崩れにくいからです。LLMへ渡すだけならUTF-8でも構いません。ただ、営業担当が最終確認するCSVまで文字化けすると、その確認が飛ばされがちです。安全のためのファイルほど、人が開ける形にしておきます。

列名だけの対策では足りない理由

CSVのヘッダーは便利ですが、自由記述欄までは守ってくれません。「担当者は田中さん。折返しは 03-1234-5678」のような文が商談メモに入っていれば、電話列を消しても連絡先は残ります。上のコードが全列へ正規表現をかけているのはこのためです。

一方で、正規表現は氏名や住所を完璧には見つけられません。だから、個人情報が入り得る列を MASK_COLUMNS に明示します。CSVの見出しが 顧客名e-mail なら、その文字列を集合へ足してください。処理後は、まず10行だけでも開いて目で確認します。伏せ字処理はLLMの性能ではなく、入力の確認作業です。

現場での使いどころ

分類や要約の精度を見たいだけなら、個人を特定する値はほぼ不要です。まず deals_for_llm.csv で試し、部署名、商材、失注理由、商談メモだけで答えが足りるかを見る。この順番なら、モデルを変える相談と、誰の情報を渡すかの相談を混ぜずに済みます。

LLM-jp-4 33Bのように日本語で試せる選択肢は増えていきます。便利になった分、入力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?