1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【実践】10行でLLMプロンプトからPIIを自動除去する方法(Python / Node.js)

1
Posted at

はじめに

前回の記事では、LLM APIを使う前に知っておくべきセキュリティの基本を紹介しました。

今回は実践編です。実際にコードを書いて、プロンプトから個人情報(PII)を自動的に検出・除去し、LLMのレスポンスで元に戻す方法をハンズオンで学びます。

使うのは CloakLLM — オープンソースのPII保護ミドルウェアです。

なぜこれが必要なのか?

OpenAIやAnthropicにプロンプトを送ると、その内容はプレーンテキストでプロバイダーのサーバーに到達します。つまり:

"田中太郎様(メール: tanaka@example.com)の契約を要約してください"

このプロンプトをそのまま送ると、顧客の名前とメールアドレスがプロバイダーのログに残ります。

CloakLLMのアプローチ:

"[PERSON_0]様(メール: [EMAIL_0])の契約を要約してください"

LLMは [PERSON_0] を人名として理解できるので、レスポンスの品質は変わりません。でも実際のデータは外に出ません。

セットアップ

Python

pip install cloakllm
python -m spacy download ja_core_news_sm  # 日本語NERモデル

Node.js

npm install cloakllm

ハンズオン①:基本のサニタイズ → LLM → デサニタイズ

Python版(10行)

from cloakllm import Shield, ShieldConfig
from openai import OpenAI

# 1. Shieldを初期化(日本語ロケール)
shield = Shield(ShieldConfig(locale="ja"))

# 2. 個人情報を含むプロンプト
prompt = "田中太郎様(メール: tanaka@example.com)の申請内容を要約してください"

# 3. サニタイズ — PIIをトークンに置換
sanitized, token_map = shield.sanitize(prompt)
# → "[PERSON_0]様(メール: [EMAIL_0])の申請内容を要約してください"

# 4. サニタイズ済みプロンプトをLLMに送信
client = OpenAI()
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": sanitized}]
)

# 5. レスポンスの中のトークンを元の値に復元
restored = shield.desanitize(response.choices[0].message.content, token_map)
print(restored)

Node.js版

const { Shield } = require('cloakllm');
const OpenAI = require('openai');

const shield = new Shield({ locale: 'en' });
const client = new OpenAI();

const prompt = "Email john@acme.com about the meeting with Sarah Johnson";

// サニタイズ
const { sanitized, tokenMap } = shield.sanitize(prompt);
// → "Email [EMAIL_0] about the meeting with [PERSON_0]"

// LLMに送信
const response = await client.chat.completions.create({
  model: "gpt-4o",
  messages: [{ role: "user", content: sanitized }]
});

// 復元
const restored = shield.desanitize(response.choices[0].message.content, tokenMap);
console.log(restored);

ハンズオン②:もっと簡単に — ワンライン統合

毎回 sanitize() / desanitize() を書くのは面倒ですよね。CloakLLMにはワンライン統合があります:

Python — OpenAI SDK

from cloakllm import enable_openai
from openai import OpenAI

client = OpenAI()
enable_openai(client)  # これだけ!

# 以降、すべてのAPI呼び出しが自動的に保護される
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "田中太郎のメールはtanaka@example.comです"}]
)
# プロバイダーは実際のPIIを見ない
# レスポンスには元の値が自動復元される

Python — LiteLLM(100以上のプロバイダー対応)

import cloakllm
cloakllm.enable()  # すべてのLiteLLM呼び出しが保護される

Node.js — OpenAI SDK

const cloakllm = require('cloakllm');
const OpenAI = require('openai');

const client = new OpenAI();
cloakllm.enable(client);  // これだけ!

ハンズオン③:マイナンバーの検出

日本の開発者にとって重要なのが、マイナンバー(個人番号)の検出です。locale="ja" を設定すると、以下のPIIを自動検出します:

  • 日本人の氏名(spaCy NERモデル使用)
  • マイナンバー(12桁)
  • 日本の電話番号(090/080/070、固定電話)
  • 日本語のメールアドレス
from cloakllm import Shield, ShieldConfig

shield = Shield(ShieldConfig(locale="ja"))

text = "佐藤花子さん(マイナンバー: 123456789012、電話: 090-1234-5678)"
sanitized, token_map = shield.sanitize(text)

print(sanitized)
# → "[PERSON_0]さん(マイナンバー: [MY_NUMBER_JP_0]、電話: [PHONE_JP_0])"

print(token_map.entities)
# → {'PERSON': ['佐藤花子'], 'MY_NUMBER_JP': ['123456789012'], 'PHONE_JP': ['090-1234-5678']}

ハンズオン④:監査ログの確認

CloakLLMはすべてのサニタイズ操作をハッシュチェーンで記録します。改ざんがあれば検知できます。

# 監査チェーンの整合性を検証
python -m cloakllm verify ./cloakllm_audit/
# ✅ Audit chain integrity verified — no tampering detected.

# 統計情報を確認
python -m cloakllm stats ./cloakllm_audit/
# 何件のPIIが検出・保護されたかを確認

EU AI Act(2026年8月施行)のArticle 12は、高リスクAIシステムに改ざん防止ログを要求しています。このハッシュチェーンはその要件に対応しています。

ハンズオン⑤:暗号的証明(Ed25519署名)

「PIIを除去しました」と言うだけでなく、数学的に証明できます:

from cloakllm import Shield, ShieldConfig, DeploymentKeyPair

# Ed25519鍵ペアを生成
keypair = DeploymentKeyPair.generate()

# 署名付きShieldを初期化
shield = Shield(ShieldConfig(locale="ja", attestation_key=keypair))

# サニタイズ → 証明書が自動生成
sanitized, token_map = shield.sanitize("田中太郎のマイナンバーは123456789012です。")

# 暗号的に検証
cert = token_map.certificate
assert cert.verify(keypair.public_key)  # True — 改ざんなし

この証明書には、入出力のSHA-256ハッシュ、タイムスタンプ、エンティティ数がEd25519で署名されています。

よくある質問

Q: トークンに置換するとLLMの回答品質は落ちますか?

A: ほとんどの場合、落ちません。LLMは [PERSON_0] を「ある人物」として理解し、文脈に沿った回答を生成します。固有名詞そのものが回答に必須なケース(例:「この人物の経歴を教えて」)では影響がありますが、要約・分析・翻訳などのタスクでは問題ありません。

Q: 対応しているPIIの種類は?

A: v0.4.0時点で36パターン、13ロケール対応。メール、電話番号、クレジットカード、SSN、IBAN、APIキー、IPアドレス、マイナンバー、パスポート番号など。

Q: ストリーミングレスポンスに対応していますか?

A: はい。v0.3.0からストリーミングデサニタイズに対応しています。

まとめ

やりたいこと コード
基本のサニタイズ shield.sanitize(text)
ワンライン統合(OpenAI) enable_openai(client)
ワンライン統合(LiteLLM) cloakllm.enable()
日本語PIIの検出 ShieldConfig(locale="ja")
監査ログの検証 python -m cloakllm verify ./
暗号的証明 DeploymentKeyPair.generate()

10行のコードで、顧客データがLLMプロバイダーに漏れるリスクをゼロにできます。

リンク

MIT ライセンス | 527テスト | 13ロケール対応

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?