ローカルで動く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を最初に整える担当を決めた会社のほうが、現場で使い続けやすいはずです。派手な仕組みではありませんが、僕ならこの別ファイルを作るところから始めます。