メインコンテンツへスキップ
これはインタラクティブなノートブックです。ローカルで実行するか、以下のリンクから利用できます:

PII データを含む Weave の使用方法

このガイドでは、Personally Identifiable Information (PII) データのプライバシーを確保しながら W&B Weave を使用する方法を説明します。PII データを特定・マスキング・匿名化するための次の方法を紹介します。
  1. PII データを特定してマスキングするための 正規表現
  2. Python ベースのデータ保護 SDK である Microsoft の Presidio。このツールはマスキングおよび置換機能を提供します。
  3. PII データを匿名化するために Presidio と組み合わせて使用する、偽データ生成用 Python ライブラリ Faker
さらに、weave.op の入出力ログのカスタマイズautopatch_settings を利用して、PII のマスキングおよび匿名化をワークフローに統合する方法も説明します。詳細は、ログされる入力と出力をカスタマイズするを参照してください。 開始するには、次の手順を実行します。
  1. 概要 セクションを確認します。
  2. 前提条件 を満たします。
  3. PII データを特定、マスキング、匿名化するための 利用可能な方法 を確認します。
  4. Weave 呼び出しにこれらの方法を適用します

概要

このセクションでは、weave.op を使用した入力および出力ログの記録方法の概要と、Weave で PII データを扱う際のベストプラクティスについて説明します。

weave.op を使って入出力のロギングをカスタマイズする

Weave Ops を使用すると、入力と出力の後処理関数を定義できます。これらの関数を使うことで、LLM 呼び出しに渡されるデータや Weave にログとして記録されるデータを変更できます。 次の例では、2 つの後処理関数を定義し、それらを weave.op() の引数として渡しています。
from dataclasses import dataclass
from typing import Any

import weave

# 入力ラッパークラス
@dataclass
class CustomObject:
    x: int
    secret_password: str

# まず、入力と出力の後処理関数を定義します:
def postprocess_inputs(inputs: dict[str, Any]) -> dict[str, Any]:
    return {k:v for k,v in inputs.items() if k != "hide_me"}

def postprocess_output(output: CustomObject) -> CustomObject:
    return CustomObject(x=output.x, secret_password="REDACTED")

# 次に、`@weave.op` デコレーターを使用する際に、これらの処理関数をデコレーターの引数として渡します:
@weave.op(
    postprocess_inputs=postprocess_inputs,
    postprocess_output=postprocess_output,
)
def some_llm_call(a: int, hide_me: str) -> CustomObject:
    return CustomObject(x=a, secret_password=hide_me)

PII データとともに Weave を使用する際のベストプラクティス

PII データを扱う際に Weave を使用する前に、そのベストプラクティスを確認してください。

テスト時

  • 匿名化したデータを記録して、PII 検出を確認する
  • Weave Traces を使って PII 処理プロセスを追跡する
  • 実際の PII をさらすことなく、匿名化処理の性能を測定する

本番環境で

  • 生の PII をログに記録しない
  • ログに記録する前に機微なフィールドを暗号化する

暗号化のヒント

  • 後で復号する必要があるデータには、可逆暗号化を使用する
  • 逆変換する必要のない一意の ID には、一方向ハッシュを適用する
  • 暗号化したまま分析する必要があるデータには、その用途に特化した暗号化方式の利用を検討する

前提条件

  1. まず、必要なパッケージをインストールしてください。
%%capture
# @title 必要なPythonパッケージ:
!pip install cryptography
!pip install presidio_analyzer
!pip install presidio_anonymizer
!python -m spacy download en_core_web_lg    # PresidioはspaCy NLPエンジンを使用
!pip install Faker                          # PIIデータをダミーデータに置き換えるためにFakerを使用
!pip install weave                          # トレースを活用するため
!pip install set-env-colab-kaggle-dotenv -q # 環境変数用
!pip install anthropic                      # Sonnetを使用するため
!pip install cryptography                   # データを暗号化するため
  1. 次の場所でAPIキーを作成します:
%%capture
# @title APIキーを正しく設定する
# 使用方法については https://pypi.org/project/set-env-colab-kaggle-dotenv/ を参照してください。

from set_env import set_env

_ = set_env("ANTHROPIC_API_KEY")
_ = set_env("WANDB_API_KEY")
  1. Weave プロジェクトを初期化します。
import weave

# 新しいWeaveプロジェクトを開始する
WEAVE_PROJECT = "pii_cookbook"
weave.init(WEAVE_PROJECT)
  1. 10個のテキストブロックを含むデモ用PIIデータセットを読み込みます。
import requests

url = "https://raw.githubusercontent.com/wandb/docs/main/weave/cookbooks/source/10_pii_data.json"
response = requests.get(url)
pii_data = response.json()

print('PII data first sample: "' + pii_data[0]["text"] + '"')

マスキング方法の概要

セットアップ が完了したら、以下の方法で PII データを検出・保護できます。 PII データを検出して保護するために、以下の方法を用いて PII データを特定・マスキングし、必要に応じて匿名化します。
  1. 正規表現 を使って PII データを特定し、マスキングします。
  2. Microsoft Presidio は、マスキングおよび置換機能を提供する Python ベースのデータ保護 SDK です。
  3. Faker は、ダミーデータを生成するための Python ライブラリです。

方法 1: 正規表現を使ったフィルタリング

正規表現(regex) は、PII データを特定してマスキングするための最もシンプルな方法です。regex を使うと、電話番号、メールアドレス、社会保障番号など、さまざまな形式の機微情報にマッチするパターンを定義できます。regex を用いることで、大量のテキストを走査し、より複雑な NLP 手法を使わずに情報を置換またはマスキングできます。
import re

# regexを使用してPIIデータを削除する関数を定義する
def redact_with_regex(text):
    # 電話番号パターン
    # \b         : 単語境界
    # \d{3}      : 正確に3桁
    # [-.]?      : 任意のハイフンまたはドット
    # \d{3}      : さらに3桁
    # [-.]?      : 任意のハイフンまたはドット
    # \d{4}      : 正確に4桁
    # \b         : 単語境界
    text = re.sub(r"\b\d{3}[-.]?\d{3}[-.]?\d{4}\b", "<PHONE>", text)

    # メールアドレスパターン
    # \b         : 単語境界
    # [A-Za-z0-9._%+-]+ : メールユーザー名に使用できる1文字以上の文字
    # @          : リテラルの@記号
    # [A-Za-z0-9.-]+ : ドメイン名に使用できる1文字以上の文字
    # \.         : リテラルのドット
    # [A-Z|a-z]{2,} : 2文字以上の大文字または小文字(TLD)
    # \b         : 単語境界
    text = re.sub(
        r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b", "<EMAIL>", text
    )

    # SSNパターン
    # \b         : 単語境界
    # \d{3}      : 正確に3桁
    # -          : リテラルのハイフン
    # \d{2}      : 正確に2桁
    # -          : リテラルのハイフン
    # \d{4}      : 正確に4桁
    # \b         : 単語境界
    text = re.sub(r"\b\d{3}-\d{2}-\d{4}\b", "<SSN>", text)

    # 簡易名前パターン(網羅的ではない)
    # \b         : 単語境界
    # [A-Z]      : 大文字1文字
    # [a-z]+     : 1文字以上の小文字
    # \s         : 空白文字1文字
    # [A-Z]      : 大文字1文字
    # [a-z]+     : 1文字以上の小文字
    # \b         : 単語境界
    text = re.sub(r"\b[A-Z][a-z]+ [A-Z][a-z]+\b", "<NAME>", text)

    return text
サンプルテキストを使って関数をテストしてみましょう。
# 関数をテストする
test_text = "My name is John Doe, my email is john.doe@example.com, my phone is 123-456-7890, and my SSN is 123-45-6789."
cleaned_text = redact_with_regex(test_text)
print(f"Raw text:\n\t{test_text}")
print(f"Redacted text:\n\t{cleaned_text}")

方法 2: Microsoft Presidio を使用してマスクする

次の方法では、Microsoft Presidio を使用して PII データを完全にマスクします。Presidio は PII を検出して、その PII の種類を表すプレースホルダーに置き換えます。例えば、Presidio は "My name is Alex" の中の Alex<PERSON> に置き換えます。 Presidio には、一般的なエンティティ に対する組み込みサポートがあります。以下の例では、PHONE_NUMBERPERSONLOCATIONEMAIL_ADDRESSUS_SSN であるすべてのエンティティをマスクします。Presidio の処理は 1 つの関数としてカプセル化されています。
from presidio_analyzer import AnalyzerEngine
from presidio_anonymizer import AnonymizerEngine

# Analyzerをセットアップします。NLPモジュール(デフォルトではspaCyモデル)およびその他のPII認識器を読み込みます。
analyzer = AnalyzerEngine()

# Anonymizerをセットアップします。アナライザーの結果を使用してテキストを匿名化します。
anonymizer = AnonymizerEngine()

# Presidioの秘匿化処理を関数にカプセル化する
def redact_with_presidio(text):
    # テキストを解析してPIIデータを特定する
    results = analyzer.analyze(
        text=text,
        entities=["PHONE_NUMBER", "PERSON", "LOCATION", "EMAIL_ADDRESS", "US_SSN"],
        language="en",
    )
    # 特定されたPIIデータを匿名化する
    anonymized_text = anonymizer.anonymize(text=text, analyzer_results=results)
    return anonymized_text.text
それでは、この関数をサンプルテキストでテストしてみましょう。
text = "My phone number is 212-555-5555 and my name is alex"

# 関数をテストする
anonymized_text = redact_with_presidio(text)

print(f"元のテキスト:\n\t{text}")
print(f"編集済みテキスト:\n\t{anonymized_text}")

Method 3: Faker と Presidio を使った置換による匿名化

テキストをマスクする代わりに、MS Presidio を使って名前や電話番号などの PII を、Faker Python ライブラリで生成した偽データに置き換えることで匿名化できます。たとえば、次のようなデータがあるとします: "My name is Raphael and I like to fish. My phone number is 212-555-5555" このデータを Presidio と Faker を使って処理すると、次のようになります: "My name is Katherine Dixon and I like to fish. My phone number is 667.431.7379" Presidio と Faker を効果的に組み合わせて使うには、カスタムオペレーターへの参照を指定する必要があります。これらのオペレーターによって、PII を偽データに置き換える役割を持つ Faker の関数をどれにするかを Presidio に指示します。
from faker import Faker
from presidio_anonymizer import AnonymizerEngine
from presidio_anonymizer.entities import OperatorConfig

fake = Faker()

# faker関数を作成する(値を受け取る必要があることに注意)
def fake_name(x):
    return fake.name()

def fake_number(x):
    return fake.phone_number()

# PERSONおよびPHONE_NUMBERエンティティのカスタムオペレーターを作成する
operators = {
    "PERSON": OperatorConfig("custom", {"lambda": fake_name}),
    "PHONE_NUMBER": OperatorConfig("custom", {"lambda": fake_number}),
}

text_to_anonymize = (
    "My name is Raphael and I like to fish. My phone number is 212-555-5555"
)

# アナライザーの出力
analyzer_results = analyzer.analyze(
    text=text_to_anonymize, entities=["PHONE_NUMBER", "PERSON"], language="en"
)

anonymizer = AnonymizerEngine()

# 上記のオペレーターを必ずアノニマイザーに渡すこと
anonymized_results = anonymizer.anonymize(
    text=text_to_anonymize, analyzer_results=analyzer_results, operators=operators
)

print(f"Raw text:\n\t{text_to_anonymize}")
print(f"Anonymized text:\n\t{anonymized_results.text}")
コードを 1 つのクラスに整理し、先ほど特定した追加の Entities も含めるように一覧を拡張しましょう。
from typing import ClassVar

from faker import Faker
from presidio_anonymizer import AnonymizerEngine
from presidio_anonymizer.entities import OperatorConfig

# Fakerを拡張してフェイクデータを生成するカスタムクラス
class MyFaker(Faker):
    # faker関数を作成する(値を受け取る必要があることに注意)
    def fake_address(self):
        return fake.address()

    def fake_ssn(self):
        return fake.ssn()

    def fake_name(self):
        return fake.name()

    def fake_number(self):
        return fake.phone_number()

    def fake_email(self):
        return fake.email()

    # Entitiesのカスタムオペレーターを作成する
    operators: ClassVar[dict[str, OperatorConfig]] = {
        "PERSON": OperatorConfig("custom", {"lambda": fake_name}),
        "PHONE_NUMBER": OperatorConfig("custom", {"lambda": fake_number}),
        "EMAIL_ADDRESS": OperatorConfig("custom", {"lambda": fake_email}),
        "LOCATION": OperatorConfig("custom", {"lambda": fake_address}),
        "US_SSN": OperatorConfig("custom", {"lambda": fake_ssn}),
    }

    def redact_and_anonymize_with_faker(self, text):
        anonymizer = AnonymizerEngine()
        analyzer_results = analyzer.analyze(
            text=text,
            entities=["PHONE_NUMBER", "PERSON", "LOCATION", "EMAIL_ADDRESS", "US_SSN"],
            language="en",
        )
        anonymized_results = anonymizer.anonymize(
            text=text, analyzer_results=analyzer_results, operators=self.operators
        )
        return anonymized_results.text
この関数をサンプルテキストでテストしてみましょう。
faker = MyFaker()
text_to_anonymize = (
    "My name is Raphael and I like to fish. My phone number is 212-555-5555"
)
anonymized_text = faker.redact_and_anonymize_with_faker(text_to_anonymize)

print(f"Raw text:\n\t{text_to_anonymize}")
print(f"Anonymized text:\n\t{anonymized_text}")

メソッド 4: autopatch_settings を使用する

autopatch_settings を使用して、サポートされている 1 つ以上の LLM インテグレーションに対する PII 処理を、初期化時に直接設定できます。この方法の利点は次のとおりです。
  1. PII 処理ロジックを初期化時点で一元管理しスコープを明確にできるため、各所にカスタムロジックを散在させる必要が減ります。
  2. 特定のインテグレーションごとに、PII 処理ワークフローをカスタマイズしたり、完全に無効化したりできます。
autopatch_settings を使用して PII 処理を設定するには、サポートされている任意の LLM インテグレーションの op_settingspostprocess_inputs および / または postprocess_output を定義します。

def postprocess(inputs: dict) -> dict:
    if "SENSITIVE_KEY" in inputs:
        inputs["SENSITIVE_KEY"] = "REDACTED"
    return inputs

client = weave.init(
    ...,
    autopatch_settings={
        "openai": {
            "op_settings": {
                "postprocess_inputs": postprocess,
                "postprocess_output": ...,
            }
        },
        "anthropic": {
            "op_settings": {
                "postprocess_inputs": ...,
                "postprocess_output": ...,
            }
        }
    },
)

Weave 呼び出しにこれらの手法を適用する

次の例では、PII のマスキングおよび匿名化手法を Weave Models に統合し、その結果を Weave Traces でプレビューします。 まずは Weave Model を作成します。Weave Model は、設定、モデルの重み、モデルの動作を定義するコードなどの情報を組み合わせたものです。 このモデル内に、Anthropic API を呼び出す predict 関数を含めます。Anthropic の Claude Sonnet を使用して感情分析を行い、Traces を使って LLM 呼び出しをトレースします。Claude Sonnet はテキストのブロックを受け取り、次のいずれかの感情分類を出力します: positivenegativeneutral。さらに、PII データが LLM に送信される前にマスキングまたは匿名化されるよう、後処理用の関数も含めます。 このコードを実行すると、Weave のプロジェクトページへのリンクと、実行した特定のトレース(LLM 呼び出し)へのリンクが表示されます。

Regex メソッド

最も単純なケースでは、正規表現 (regex) を使って元のテキストから PII データを特定し、マスクできます。
import json
from typing import Any

import anthropic

import weave

# モデル予測 Weave Op に正規表現によるリダクションを適用する入力後処理関数を定義する
def postprocess_inputs_regex(inputs: dict[str, Any]) -> dict:
    inputs["text_block"] = redact_with_regex(inputs["text_block"])
    return inputs

# Weave モデル / 予測関数
class SentimentAnalysisRegexPiiModel(weave.Model):
    model_name: str
    system_prompt: str
    temperature: int

    @weave.op(
        postprocess_inputs=postprocess_inputs_regex,
    )
    async def predict(self, text_block: str) -> dict:
        client = anthropic.AsyncAnthropic()
        response = await client.messages.create(
            max_tokens=1024,
            model=self.model_name,
            system=self.system_prompt,
            messages=[
                {"role": "user", "content": [{"type": "text", "text": text_block}]}
            ],
        )
        result = response.content[0].text
        if result is None:
            raise ValueError("No response from model")
        parsed = json.loads(result)
        return parsed
python
# システムプロンプトを指定して LLM モデルを作成する
model = SentimentAnalysisRegexPiiModel(
    name="claude-3-sonnet",
    model_name="claude-3-5-sonnet-20240620",
    system_prompt='You are a Sentiment Analysis classifier. You will be classifying text based on their sentiment. Your input will be a block of text. You will answer with one the following rating option["positive", "negative", "neutral"]. Your answer should be one word in json format: {classification}. Ensure that it is valid JSON.',
    temperature=0,
)

print("Model: ", model)
# テキストブロックごとに、まず匿名化してから予測を実行する
for entry in pii_data:
    await model.predict(entry["text"])

Presidio によるマスキング方法

次に、Presidio を使用して元のテキストから PII データを検出し、マスクします。
Presidio PII redaction process with identified PII entities and redacted text output
from typing import Any

import weave

# モデル予測 Weave Op に Presidio リダクションを適用する入力後処理関数を定義する
def postprocess_inputs_presidio(inputs: dict[str, Any]) -> dict:
    inputs["text_block"] = redact_with_presidio(inputs["text_block"])
    return inputs

# Weave モデル / 予測関数
class SentimentAnalysisPresidioPiiModel(weave.Model):
    model_name: str
    system_prompt: str
    temperature: int

    @weave.op(
        postprocess_inputs=postprocess_inputs_presidio,
    )
    async def predict(self, text_block: str) -> dict:
        client = anthropic.AsyncAnthropic()
        response = await client.messages.create(
            max_tokens=1024,
            model=self.model_name,
            system=self.system_prompt,
            messages=[
                {"role": "user", "content": [{"type": "text", "text": text_block}]}
            ],
        )
        result = response.content[0].text
        if result is None:
            raise ValueError("No response from model")
        parsed = json.loads(result)
        return parsed
python
# システムプロンプトを使って LLM モデルを作成する
model = SentimentAnalysisPresidioPiiModel(
    name="claude-3-sonnet",
    model_name="claude-3-5-sonnet-20240620",
    system_prompt='You are a Sentiment Analysis classifier. You will be classifying text based on their sentiment. Your input will be a block of text. You will answer with one the following rating option["positive", "negative", "neutral"]. Your answer should be one word in json format: {classification}. Ensure that it is valid JSON.',
    temperature=0,
)

print("Model: ", model)
# テキストブロックごとに、まず匿名化してから予測する
for entry in pii_data:
    await model.predict(entry["text"])

Faker と Presidio を使った置換方法

この例では、Faker を使って匿名化された PII データを生成し、Presidio を使って元のテキスト中の PII データを検出して置換します。
Faker と Presidio による PII 置換プロセス:元のテキスト、検出された PII、匿名化された置換値
from typing import Any

import weave

# モデル予測 Weave Op に Faker の匿名化と Presidio の編集を適用する入力後処理関数を定義する
faker = MyFaker()

def postprocess_inputs_faker(inputs: dict[str, Any]) -> dict:
    inputs["text_block"] = faker.redact_and_anonymize_with_faker(inputs["text_block"])
    return inputs

# Weave モデル / 予測関数
class SentimentAnalysisFakerPiiModel(weave.Model):
    model_name: str
    system_prompt: str
    temperature: int

    @weave.op(
        postprocess_inputs=postprocess_inputs_faker,
    )
    async def predict(self, text_block: str) -> dict:
        client = anthropic.AsyncAnthropic()
        response = await client.messages.create(
            max_tokens=1024,
            model=self.model_name,
            system=self.system_prompt,
            messages=[
                {"role": "user", "content": [{"type": "text", "text": text_block}]}
            ],
        )
        result = response.content[0].text
        if result is None:
            raise ValueError("No response from model")
        parsed = json.loads(result)
        return parsed
python
# システムプロンプトを指定して LLM モデルを作成する
model = SentimentAnalysisFakerPiiModel(
    name="claude-3-sonnet",
    model_name="claude-3-5-sonnet-20240620",
    system_prompt='You are a Sentiment Analysis classifier. You will be classifying text based on their sentiment. Your input will be a block of text. You will answer with one the following rating option["positive", "negative", "neutral"]. Your answer should be one word in json format: {classification}. Ensure that it is valid JSON.',
    temperature=0,
)

print("Model: ", model)
# テキストブロックごとに、まず匿名化してから予測を実行する
for entry in pii_data:
    await model.predict(entry["text"])

autopatch_settings メソッド

次の例では、初期化時に anthropic 向けの postprocess_inputspostprocess_inputs_regex() 関数を設定します。postprocess_inputs_regex 関数は、Method 1: Regular Expression Filtering で定義した redact_with_regex メソッドを適用します。これにより、すべての anthropic モデルへの入力に redact_with_regex が適用されるようになります。
from typing import Any

import weave

client = weave.init(
    ...,
    autopatch_settings={
        "anthropic": {
            "op_settings": {
                "postprocess_inputs": postprocess_inputs_regex,
            }
        }
    },
)

# モデル予測 Weave Op に対して正規表現による秘匿化を適用する入力後処理関数を定義する
def postprocess_inputs_regex(inputs: dict[str, Any]) -> dict:
    inputs["text_block"] = redact_with_regex(inputs["text_block"])
    return inputs

# Weave モデル / 予測関数
class SentimentAnalysisRegexPiiModel(weave.Model):
    model_name: str
    system_prompt: str
    temperature: int

    async def predict(self, text_block: str) -> dict:
        client = anthropic.AsyncAnthropic()
        response = await client.messages.create(
            max_tokens=1024,
            model=self.model_name,
            system=self.system_prompt,
            messages=[
                {"role": "user", "content": [{"type": "text", "text": text_block}]}
            ],
        )
        result = response.content[0].text
        if result is None:
            raise ValueError("No response from model")
        parsed = json.loads(result)
        return parsed
python
# システムプロンプトを使用して LLM モデルを作成する
model = SentimentAnalysisRegexPiiModel(
    name="claude-3-sonnet",
    model_name="claude-3-5-sonnet-20240620",
    system_prompt='You are a Sentiment Analysis classifier. You will be classifying text based on their sentiment. Your input will be a block of text. You will answer with one the following rating option["positive", "negative", "neutral"]. Your answer should be one word in json format: {classification}. Ensure that it is valid JSON.',
    temperature=0,
)

print("Model: ", model)
# テキストブロックごとに、まず匿名化してから予測を実行する
for entry in pii_data:
    await model.predict(entry["text"])

(オプション)データを暗号化する

暗号化されたテキスト出力と暗号鍵管理を含む PII データ暗号化プロセス
PII を匿名化することに加えて、cryptography ライブラリの Fernet 対称鍵暗号を使ってデータを暗号化し、セキュリティをさらに強化できます。この方法により、匿名化されたデータが第三者に傍受されたとしても、暗号鍵がなければ内容を読み取ることはできません。
import os
from cryptography.fernet import Fernet
from pydantic import BaseModel, ValidationInfo, model_validator

def get_fernet_key():
    # 環境変数にキーが存在するか確認する
    key = os.environ.get('FERNET_KEY')

    if key is None:
        # キーが存在しない場合、新しいキーを生成する
        key = Fernet.generate_key()
        # キーを環境変数に保存する
        os.environ['FERNET_KEY'] = key.decode()
    else:
        # キーが存在する場合、バイト型であることを確認する
        key = key.encode()

    return key

cipher_suite = Fernet(get_fernet_key())

class EncryptedSentimentAnalysisInput(BaseModel):
    encrypted_text: str = None

    @model_validator(mode="before")
    def encrypt_fields(cls, values):
        if "text" in values and values["text"] is not None:
            values["encrypted_text"] = cipher_suite.encrypt(values["text"].encode()).decode()
            del values["text"]
        return values

    @property
    def text(self):
        if self.encrypted_text:
            return cipher_suite.decrypt(self.encrypted_text.encode()).decode()
        return None

    @text.setter
    def text(self, value):
        self.encrypted_text = cipher_suite.encrypt(str(value).encode()).decode()

    @classmethod
    def encrypt(cls, text: str):
        return cls(text=text)

    def decrypt(self):
        return self.text

# 新しい EncryptedSentimentAnalysisInput を使用するよう sentiment_analysis_model を変更
class sentiment_analysis_model(weave.Model):
    model_name: str
    system_prompt: str
    temperature: int

    @weave.op()
    async def predict(self, encrypted_input: EncryptedSentimentAnalysisInput) -> dict:
        client = AsyncAnthropic()

        decrypted_text = encrypted_input.decrypt() # カスタムクラスを使用してテキストを復号する

        response = await client.messages.create(
            max_tokens=1024,
            model=self.model_name,
            system=self.system_prompt,
            messages=[
                {   "role": "user",
                    "content":[
                        {
                            "type": "text",
                            "text": decrypted_text
                        }
                    ]
                }
            ]
        )
        result = response.content[0].text
        if result is None:
            raise ValueError("No response from model")
        parsed = json.loads(result)
        return parsed

model = sentiment_analysis_model(
    name="claude-3-sonnet",
    model_name="claude-3-5-sonnet-20240620",
    system_prompt="You are a Sentiment Analysis classifier. You will be classifying text based on their sentiment. Your input will be a block of text. You will answer with one the following rating option[\"positive\", \"negative\", \"neutral\"]. Your answer should one word in json format dict where the key is classification.",
    temperature=0
)

for entry in pii_data:
    encrypted_input = EncryptedSentimentAnalysisInput.encrypt(entry["text"])
    await model.predict(encrypted_input)