メインコンテンツへスキップ

Langfuseにおける個人情報(PII)マスキング:Gemini 2.5 Flash Liteで実現するLLMベースPIIフィルター

著者
Hiromi Kuwa

LangfuseにおけるPIIマスキング手法の検討
#

前回の記事 では、Guardrails for Amazon Bedrockを利用したPIIマスキングについて紹介しました。個人情報を列挙した形のテストデータにおいては精度が高く、大半の情報は除去できましたが、一部、フィルターが準備されていないなど、追加の対応が必要な項目が残っていました。

また、この機能はAmazon Bedrockに依存するため、AWS以外のクラウドを利用している方にとっては導入のハードルがやや高くなると考えます。

そこで本記事では、PIIマスキングをLLM(大規模言語モデル)に任せるアプローチを検証します。

LLMを活用する利点は、プロンプトの調整によって非エンジニアでも容易に設定変更できること、そして文脈を考慮した判断が可能になる点です。(例えば、芸能人の名前や公開されている会社の住所は公開情報としてマスキングしないなど)

さらに、PIIフィルター用のプロンプトもあわせてLangfuseで管理することで、コードのデプロイを都度行う必要や、クラウドのコンソール画面を触ることなく、非エンジニアでもプロンプトの調整を容易に行えるようになります。

今回のLLMモデルには、Gemini 2.5 Flash-Lite を採用します。

Gemini 2.5 Flash Liteとは?
#

Googleが提供する最新の基盤モデルファミリーであり、低レイテンシのユースケース向けに最適化された、最もバランスの取れたGeminiモデルです。

ただし、現在パブリックプレビュー中のため、利用できる場面は限られています。

なぜGemini 2.5 Flash Liteなのか
#

処理速度が高速で、料金も入力トークン100万あたり、$0.1、出力は$0.4と低コストで提供されています。料金は、Gemini 2.5 Flash と比較すると、おおよそ3分の1以下の費用に抑えられています。

PIIフィルターは呼び出し頻度が高くなるため、高速かつ低コストで利用できる点を高く評価し、今回採用しました。

PIIマスキングのテスト方法
#

前回、前々回と同様のアプリケーションコードと、テストデータを用いて検証を行います。

アプリケーションコード
#

前回同様、masking_function の内部を調整します。

client = genai.Client(api_key="GOOGLE_API_KEY")

def masking_function(data: any, **kwargs) -> any:
    if isinstance(data, str):
        # マスキング処理
        response = client.models.generate_content(
            model='models/gemini-2.5-flash-lite-preview-06-17',
            contents=data
        )
        sanitized_data = response.text
        return sanitized_data
    else:
        return data

テストデータ
#

前回、前々回でも利用した以下のダミーデータを、今回も同様に利用します。

ダミーデータ

私の個人情報は以下の通りです:

氏名:山田 太郎 Name: Taro Yamada

生年月日:1985年3月15日 Birthday: March 15, 1985

住所:東京都新宿区西新宿2-8-1 Address: 2-8-1 Nishi-Shinjuku, Shinjuku-ku, Tokyo, Japan

電話番号:03-1234-5678 / 090-9999-8888 Phone: +81-3-1234-5678 / +81-90-9999-8888

メールアドレス:taro.yamada@example.com Email:taro.yamada@example.com

運転免許証番号:123456789012 Driver’s License: DL-123456789012

パスポート番号:TK1234567 Passport Number: TK1234567

社会保障番号:987-65-4321 Social Security Number: 987-65-4321

クレジットカード情報: - カード番号:4111-2222-3333-4444 - 有効期限:12/25 - セキュリティコード:123

Credit Card Information: - Card Number: 4111-2222-3333-4444 - Expiry Date: 12/25 - Security Code: 123

銀行口座情報: - 銀行名:みずほ銀行 - 支店名:新宿支店 - 口座番号:1234567

Bank Account Information: - Bank Name: Mizuho Bank - Branch: Shinjuku Branch - Accou****nt Number: 1234567

検証結果1
#

上記の準備を行った上で、アプリケーションを実行します。

トレースを確認したところ、以下の結果が登録されていました。

ご提示いただいた個人情報につきまして、私はAIアシスタントであり、あなたの個人情報を直接保存・管理することはできません。

いただいた情報は、ご自身の確認のために役立つかもしれませんが、これらの情報は非常に機密性の高いものです。これらの情報を安易に他者に共有することは、悪用されるリスクを高めます。

特に、クレジットカード情報や銀行口座情報は、絶対に第三者に教えないでください。

もし、これらの情報を安全に管理したいのであれば、パスワードで保護された安全な場所(例えば、パスワードマネージャーなど)に保存することをお勧めします。

もし、私にこれらの情報について何か特別な操作(例えば、どこかに登録するなど)を求めていらっしゃるのであれば、その意図を具体的に教えていただけますでしょうか?ただし、どのような場合でも、私のシステムに直接個人情報を入力したり保存したりすることはできませんので、その点ご了承ください。

ダミーデータをそのままLLMに投入したため、データに対する具体的な指示や解凍形式が適切に含まれていませんでした。

比較を行う前に、この点を改善します。

LLMフィルタリング用のプロンプト設計
#

LLMから意図する回答を得るためには、プロンプトの改善が必要です。アプリケーションコードの修正を都度行うのは煩わしいため、PIIフィルター用のプロンプトも Langfuse で管理することにしました。

アプリケーションコード(プロンプト管理版)
#

Langfuse で管理しているプロンプトを利用する形になるよう調整します。

# Langfuse で管理しているプロンプトの取得
pii_prompt_template = langfuse.get_prompt("pii_filter_prompt", label=langfuse_config.prompt_label)
client = genai.Client(api_key="GOOGLE_API_KEY")

def masking_function(data: any, **kwargs) -> any:
    if isinstance(data, str):
        prompt_content = pii_prompt_template.compile(userMessage=data)
        # マスキング処理
        response = client.models.generate_content(
            # モデル情報もプロンプトの config から取得
            model=pii_prompt_template.config.get("model", "models/gemini-2.5-flash-lite-preview-06-17"),
            contents=prompt_content
        )
        sanitized_data = response.text
        return sanitized_data
    else:
        return data

プロンプト設定
#

先のコード修正により、 pii_filter_prompt という名称のプロンプトが利用可能になりました。Langfuse 側で該当の名前のプロンプトを作成し、調整を行います。

今回の検証では、以下の点を満たす PII フィルター用のプロンプトを設定します。

  • 過去2回で対象とした項目を網羅できること
  • どのようなデータがマスクされたかわかること
  • 入力値以外の応答文等が入らないこと

3点目については、簡易的なプロンプトで個人情報がマスクされるかを試したところ、下記のように入力値にない応答文が含まれる結果となりました。

これでは、入力値からPII情報のみを抽出・マスキングするという本来の目的を達成できないため、この点をプロンプトの条件に追加しています。

承知いたしました。以下のように個人情報をマスクしました。

私の個人情報は以下の通りです:

氏名:**** ****

Name: **** **** (後略)

Langfuse 上でプロンプトの調整を行いつつ、最終的に、今回の検証では以下のプロンプトをPIIフィルター用として設定しました。

以下のテキストから、個人情報をマスクしてください。

# 条件
- マスク処理以外は行わない
- テキストの前後に余分なテキストを追加しない
- どのようなデータがマスクされたかわかるようにする(例:山田太郎 -> [氏名1]、000-0000-0000 -> [電話番号1])
- 同じ個人情報種別でも、異なる情報の場合は別物であるとわかるようにする(例:山田さんと佐藤さん -> [氏名1]さんと[氏名2]さん)

# 置換対象の個人情報
- 氏名
- 生年月日
- 住所
- 電話番号
- メールアドレス
- 運転免許証番号
- パスポート番号
- 社会保障番号
- クレジットカード情報(カード番号, 有効期限, セキュリティコード)
- 銀行口座情報(銀行名, 支店名, 口座番号)

# 対象のテキスト
{{userMessage}}

こちらの状態で、PIIフィルターの検証を再度行います。

検証結果2
#

前回の Guardrails for Amazon Bedrock、前々回のllm-guard を利用した際の結果と比較していきます。

LLM利用
(Gemini 2.5 Flash-Lite)
llm-guard(デフォルト設定)Guardrail for Amazon Bedrock
氏名(日本語)[氏名1][REDACTED_PERSON_1][REDACTED_PERSON_2][REDACTED_PERSON_3]{NAME}
氏名(英語)[氏名2][REDACTED_PERSON_4]{NAME}
生年月日[生年月日1]--
住所[住所1]-{ADDRESS}
電話番号(日本)[電話番号1] / [電話番号2]03-1234-[REDACTED_PHONE_NUMBER_1]{PHONE}
電話番号(海外)[電話番号3] / [電話番号4][REDACTED_PHONE_NUMBER_2]{PHONE}
メールアドレス[メールアドレス1][REDACTED_EMAIL_ADDRESS_1]{EMAIL}
運転免許証番号[運転免許証番号1]-{DRIVER_ID}
パスポート番号[パスポート番号1]-{US_PASSPORT_NUMBER}
社会保障番号[社会保障番号1][REDACTED_US_SSN_RE_1] 987-65-4321{US_SOCIAL_SECURITY_NUMBER}
クレジットカード(カード番号)[クレジットカード番号1][REDACTED_CREDIT_CARD_RE_1]{CREDIT_DEBIT_CARD_NUMBER}
クレジットカード(カード番号以外)有効期限:[クレジットカード有効期限1]
セキュリティコード:[クレジットカードセキュリティコード1]
-有効期限:{CREDIT_DEBIT_CARD_EXPIRY}
セキュリティコード:{CREDIT_DEBIT_CARD_CVV}
銀行口座情報銀行名:[銀行名1]
支店名:[銀行支店名1]
口座番号:[銀行口座番号1]
Bank Name: [銀行名2]
Branch: [銀行支店名2]
Account Number: [銀行口座番号2]
-銀行名:みずほ銀行
支店名:新宿支店
口座番号:{US_BANK_ACCOUNT_NUMBER}
Bank Name: Mizuho Bank
Branch: {ADDRESS} Branch
Account Number: {US_BANK_ACCOUNT_NUMBER}

llm-guard、Guardrails for Amazon Bedrock との比較において、いずれかと差異のある項目のみ背景色を変更しています。

前回確認した、Guardrails においては、フィルターが対応している項目においてはおおむね問題なく検知できているという結果でしたが、今回の LLM を利用したフィルターについては、過去2回で検証した結果と比較して、検出漏れ・誤検出が少ない結果となりました。

今回は LLM を利用するケースが優位に見える結果になりましたが、検証に利用しているダミーデータは自然な文脈が存在せず、個人情報が明示的に記載されているため、自然な文脈で利用した場合にはまた違う結果となる可能性があります。

また、今回提示しているアプリケーションコードでは、PIIフィルター処理そのものに対するトレース保存は行っていませんが、この処理に対してもトレースを保存することで、より詳細なコスト感を把握できるようになります。

PIIフィルターのプロンプトに関しては、Langfuseの評価機能を活用することで、さらなる制度の工場が期待できます。

まとめ
#

本記事では、LLM(Gemini 2.5 Flash-Lite)を利用したPIIマスキングについて検証しました。Langfuseのトレース情報から個人情報を確実に除去するという目的のもと、PIIの除去については、概ね問題なく行える見込みです。

ただし、今回の検証は限定的な条件下での確認であるため、実際にアプリケーションで利用する際には、さらなる詳細な検証とプロンプトの改善が必要不可欠です。また、前回の記事でも触れましたが、LLMを利用するため、本実装のままではコスト面などから実際のアプリケーションで利用するには最適ではない可能性もあります。プロンプトや実装のアプローチについて、多角的に比較・検討することをお勧めします。