> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wandb.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# PII 데이터 처리

> W&B Weave의 데이터 마스킹 및 필터링 도구를 사용하여 LLM 애플리케이션에서 개인 식별 정보(PII)를 처리합니다.

<Note>
  이것은 대화형 노트북입니다. 로컬에서 실행하거나 아래 링크를 사용할 수 있습니다:

  * [Google Colab에서 열기](https://colab.research.google.com/github/wandb/docs/blob/main/weave/cookbooks/source/pii.ipynb)
  * [GitHub에서 소스 보기](https://github.com/wandb/docs/blob/main/weave/cookbooks/source/pii.ipynb)
</Note>

이 가이드에서는 Personally Identifiable Information (PII) 데이터의 프라이버시를 보호하면서 W\&B Weave를 사용하는 방법을 알아봅니다. PII를 보호하면 LLM Tracing 및 Evaluation의 이점을 계속 활용하면서도 개인정보 보호 및 규정 준수 요구 사항을 충족하는 데 도움이 됩니다. 이 가이드는 민감한 사용자 데이터를 처리하는 LLM 애플리케이션에 Weave를 통합하는 개발자를 위한 것입니다.

이 가이드에서는 PII 데이터를 식별하고, 마스킹하고, 익명화하는 다음 방법을 보여줍니다:

1. PII 데이터를 식별하고 마스킹하기 위한 **정규 표현식**
2. Python 기반 데이터 보호 SDK인 **Microsoft의 [Presidio](https://microsoft.github.io/presidio/)**. 이 도구는 마스킹 및 대체 기능을 제공합니다.
3. 가짜 데이터를 생성하는 Python 라이브러리인 \*\*[Faker](https://faker.readthedocs.io/en/master/)\*\*와 Presidio를 함께 사용해 PII 데이터를 익명화하는 방법

또한 *`weave.op` 입력/출력 로깅 사용자 지정* 및 \_`autopatch_settings`\_를 사용해 워크플로에 PII 마스킹과 익명화를 통합하는 방법도 알아봅니다. 자세한 내용은 [Customize logged inputs and outputs](/ko/weave/guides/tracking/ops/#customize-logged-inputs-and-outputs)를 참조하세요.

시작하려면 다음을 수행하세요:

1. [Overview](#overview) 섹션을 검토합니다.
2. [사전 요구 사항](#prerequisites)을 완료합니다.
3. PII 데이터를 식별하고, 마스킹하고, 익명화하는 [사용 가능한 방법](#redaction-methods-overview)을 검토합니다.
4. [이 방법을 Weave Call에 적용합니다](#apply-the-methods-to-weave-calls).

<div id="overview">
  ## Overview
</div>

다음 섹션에서는 `weave.op`을 사용한 입력 및 출력 로깅의 개요와 Weave에서 PII 데이터를 다룰 때의 모범 사례를 설명합니다.

<div id="customize-input-and-output-logging-using-weaveop">
  ### `weave.op`을 사용하여 입력 및 출력 로깅 사용자 지정하기
</div>

Weave Ops를 사용하면 입력 및 출력 후처리 함수를 정의할 수 있습니다. 이 함수들을 통해 LLM Call에 전달되거나 Weave에 로깅되는 데이터를 수정할 수 있습니다.

다음 예시에서는 두 개의 후처리 함수를 정의하고 이를 `weave.op()`에 인수로 전달합니다.

```python lines theme={null}
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)
```

<div id="best-practices-for-weave-with-pii-data">
  ### PII 데이터와 함께 Weave를 사용할 때의 모범 사례
</div>

PII 데이터와 함께 Weave를 사용하기 전에 다음 모범 사례를 검토하세요. 이 모범 사례는 개발 라이프사이클의 각 단계별로 정리되어 있으며, 암호화에 대한 추가 지침도 포함되어 있습니다.

<div id="during-testing">
  #### 테스트 중에는
</div>

* PII 감지를 확인하기 위해 익명화된 데이터를 로깅합니다.
* Weave 트레이스로 PII 처리 과정을 추적합니다.
* 실제 PII를 노출하지 않고도 익명화 성능을 측정합니다.

<div id="in-production">
  #### 프로덕션 환경에서
</div>

* 원본 PII는 절대 로깅하지 마세요.
* 로깅하기 전에 민감한 필드는 암호화하세요.

<div id="encryption-tips">
  #### 암호화 팁
</div>

* 나중에 복호화해야 하는 데이터에는 가역 암호화를 사용하세요.
* 되돌릴 필요가 없는 고유 ID에는 단방향 해시를 적용하세요.
* 암호화된 상태에서 분석해야 하는 데이터에는 특수한 암호화 방식을 고려하세요.

<div id="prerequisites">
  ## 사전 요구 사항
</div>

마스킹 방법을 적용하기 전에 의존성을 설치하고, API 키를 구성하고, Weave 프로젝트를 초기화하고, 샘플 데이터를 로드할 수 있도록 다음 설정 단계를 완료하세요.

1. 먼저 필요한 패키지를 설치하세요.

```python lines theme={null}
%%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                   # 데이터 암호화를 위해
```

2. 다음 위치에서 API 키를 생성하세요:

   * [W\&B User Settings](https://wandb.ai/settings)
   * [Anthropic Console](https://platform.claude.com/login?returnTo=%2Fsettings%2Fkeys)

```python lines theme={null}
%%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")
```

3. Weave 프로젝트를 초기화하세요.

```python lines theme={null}
import weave

# 새 Weave 프로젝트 시작
WEAVE_PROJECT = "pii_cookbook"
weave.init(WEAVE_PROJECT)
```

4. 텍스트 블록 10개가 포함된 데모 PII 데이터셋을 로드하세요.

```python lines theme={null}
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"] + '"')
```

<div id="redaction-methods-overview">
  ## 마스킹 방법 Overview
</div>

[사전 요구 사항](#prerequisites)을 완료한 후에는 다음 방법 중 하나를 선택하여 PII 데이터를 탐지하고 보호할 수 있습니다. 각 방법은 PII 데이터를 파악해 마스킹하고, 필요에 따라 익명화합니다:

1. PII 데이터를 파악하고 마스킹하는 **정규 표현식**.
2. 마스킹 및 대체 기능을 제공하는 Python 기반 데이터 보호 SDK인 **Microsoft [Presidio](https://microsoft.github.io/presidio/)**.
3. 가짜 데이터를 생성하는 Python 라이브러리인 **[Faker](https://faker.readthedocs.io/en/master/)**.

<div id="method-1-filter-using-regular-expressions">
  ### 방법 1: 정규 표현식을 사용한 필터링
</div>

[정규 표현식(regex)](https://docs.python.org/3/library/re.html)은 PII 데이터를 식별하고 마스킹하는 간단한 방법입니다. Regex를 사용하면 전화번호, 이메일 주소, 주민등록번호와 같은 다양한 형식의 민감한 정보와 일치하는 패턴을 정의할 수 있습니다. Regex를 사용하면 더 복잡한 NLP 기법 없이도 대량의 텍스트를 훑어보며 정보를 대체하거나 마스킹할 수 있습니다.

```python lines theme={null}
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
```

함수를 테스트하려면 샘플 텍스트로 다음을 실행하세요:

```python lines theme={null}
# 함수 테스트
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}")
```

<div id="method-2-redact-using-microsoft-presidio">
  ### 방법 2: Microsoft Presidio를 사용해 PII 마스킹하기
</div>

다음 방법은 [Microsoft Presidio](https://microsoft.github.io/presidio/)를 사용해 PII 데이터를 완전히 제거하는 것입니다. Presidio는 PII를 마스킹한 뒤 PII 유형을 나타내는 플레이스홀더로 대체합니다. 예를 들어, Presidio는 `"My name is Alex"`에서 `Alex`를 `<PERSON>`으로 바꿉니다.

Presidio는 [일반적인 entity](https://microsoft.github.io/presidio/supported_entities/)를 기본적으로 지원합니다. 다음 예시에서는 `PHONE_NUMBER`, `PERSON`, `LOCATION`, `EMAIL_ADDRESS`, `US_SSN`인 모든 entity를 마스킹합니다. Presidio 처리 과정은 함수로 캡슐화되어 있습니다.

```python lines theme={null}
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
```

함수를 테스트하려면 샘플 텍스트를 사용해 다음을 실행하세요:

```python lines theme={null}
text = "My phone number is 212-555-5555 and my name is alex"

# 함수 테스트
anonymized_text = redact_with_presidio(text)

print(f"Raw text:\n\t{text}")
print(f"Redacted text:\n\t{anonymized_text}")
```

<div id="method-3-anonymize-with-replacement-using-faker-and-presidio">
  ### 방법 3: Faker와 Presidio를 사용해 대체값으로 익명화하기
</div>

텍스트를 마스킹하는 대신, MS Presidio를 사용해 이름이나 전화번호 같은 PII를 [Faker](https://faker.readthedocs.io/en/master/) 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를 함께 사용하려면 맞춤형 operator에 대한 참조를 제공해야 합니다. 이 operator는 PII를 가짜 데이터로 바꾸는 역할을 하는 Faker 함수로 Presidio를 연결해 줍니다.

```python lines theme={null}
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 엔티티에 대한 맞춤형 operator 생성
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 출력
analyzer_results = analyzer.analyze(
    text=text_to_anonymize, entities=["PHONE_NUMBER", "PERSON"], language="en"
)

anonymizer = AnonymizerEngine()

# 위에서 정의한 operators를 anonymizer에 전달하는 것을 잊지 마세요
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}")
```

코드를 하나의 클래스로 통합하고 entity 목록에 앞서 식별한 추가 항목도 포함되도록 확장하려면 다음을 실행하세요:

```python lines theme={null}
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()

    # entity에 대한 맞춤형 operator 생성
    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
```

함수를 테스트하려면 샘플 텍스트를 사용해 다음을 실행하세요:

```python lines theme={null}
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}")
```

<div id="method-4-use-autopatch_settings">
  ### 방법 4: `autopatch_settings` 사용
</div>

하나 이상의 지원되는 LLM 인테그레이션에서 초기화 시점에 직접 PII 처리를 구성하려면 `autopatch_settings`를 사용할 수 있습니다. W\&B는 특정 인테그레이션의 모든 Call 전반에서 PII 처리를 중앙 집중식으로 관리하려는 경우 이 접근 방식을 권장합니다. 이 방법의 장점은 다음과 같습니다.

1. PII 처리 로직을 초기화 시점에 중앙에서 관리하고 범위를 지정할 수 있어, 여기저기 흩어진 맞춤형 로직의 필요성이 줄어듭니다.
2. 특정 인테그레이션에 대해 PII 처리 워크플로를 사용자 지정하거나 완전히 비활성화할 수 있습니다.

`autopatch_settings`를 사용해 PII 처리를 구성하려면, 지원되는 LLM 인테그레이션 중 하나의 `op_settings`에서 `postprocess_inputs` 또는 `postprocess_output`을 정의하세요.

```python lines theme={null}

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": ...,
            }
        }
    },
)
```

<div id="apply-the-methods-to-weave-calls">
  ## 메서드를 Weave Call에 적용하기
</div>

이제 각 마스킹 방법을 개별적으로 살펴보았으므로, 아래 예시에서는 이를 Weave Models에 통합하고 Weave 트레이스에서 결과를 미리 확인하는 방법을 보여드립니다.

먼저 [Weave Model](https://docs.wandb.ai/weave/guides/core-types/models)을 만드세요. Weave Model은 설정, 모델 가중치, 그리고 모델이 어떻게 동작하는지를 정의하는 코드 같은 정보를 조합한 것입니다.

이 모델에는 Anthropic API를 호출하는 predict 함수가 포함되어 있습니다. Anthropic의 Claude Sonnet은 [트레이스](https://docs.wandb.ai/weave/quickstart)를 사용해 LLM Call을 트레이싱하면서 감성 분석을 수행합니다. Claude Sonnet은 텍스트 블록을 입력으로 받아 다음 감성 분류 중 하나를 출력합니다: *긍정*, *부정*, 또는 *중립*. 또한 이 모델에는 LLM으로 전송되기 전에 PII 데이터가 마스킹되거나 익명화되도록 하는 후처리 함수도 포함되어 있습니다.

이 코드를 실행하면 Weave 프로젝트 페이지 링크와 함께, 실행한 특정 트레이스(LLM Call) 링크도 받게 됩니다. 이 링크를 사용해 입력이 LLM에 도달하기 전에 후처리 함수가 예상대로 마스킹되거나 익명화되었는지 확인하세요.

<div id="regex-method">
  ### Regex 방법
</div>

시작점으로 정규식을 사용해 원본 텍스트에서 PII 데이터를 파악하고 마스킹할 수 있습니다.

```python lines theme={null}
import json
from typing import Any

import anthropic

import weave

# 모델 예측 Weave Op에 regex 마스킹을 적용하는 입력 후처리 함수 정의
def postprocess_inputs_regex(inputs: dict[str, Any]) -> dict:
    inputs["text_block"] = redact_with_regex(inputs["text_block"])
    return inputs

# Weave 모델 / predict 함수
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 lines theme={null}
# system 프롬프트로 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"])
```

<div id="presidio-redaction-method">
  ### Presidio 마스킹 방법
</div>

다음으로, Presidio를 사용해 원본 텍스트에서 PII 데이터를 파악하고 마스킹하세요.

<Frame>
  <img src="https://mintcdn.com/wb-21fd5541/5YxM7MBeu5yJWeCW/media/pii/redact.png?fit=max&auto=format&n=5YxM7MBeu5yJWeCW&q=85&s=bd92878d215f44d72e53ccef153414c7" alt="파악된 PII entity와 마스킹된 텍스트 출력을 보여주는 Presidio PII 마스킹 프로세스" width="3024" height="1890" data-path="media/pii/redact.png" />
</Frame>

```python lines theme={null}
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 모델 / predict 함수
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 lines theme={null}
# system 프롬프트로 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"])
```

<div id="faker-and-presidio-replacement-method">
  ### Faker 및 Presidio 대체 방법
</div>

이 예제에서는 Faker를 사용해 익명화된 대체 PII 데이터를 생성하고, Presidio를 사용해 원본 텍스트에서 PII 데이터를 파악해 대체합니다.

<Frame>
  <img src="https://mintcdn.com/wb-21fd5541/5YxM7MBeu5yJWeCW/media/pii/replace.png?fit=max&auto=format&n=5YxM7MBeu5yJWeCW&q=85&s=e229a219b857a79001cd48b011462a56" alt="원본 텍스트, 파악된 PII, 익명화된 대체 값이 포함된 Faker 및 Presidio PII 대체 과정" width="3024" height="1900" data-path="media/pii/replace.png" />
</Frame>

```python lines theme={null}
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 모델 / predict 함수
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 lines theme={null}
# system 프롬프트로 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"])
```

<div id="autopatch_settings-method">
  ### `autopatch_settings` 방법
</div>

다음 예시에서는 초기화 시 `anthropic`의 `postprocess_inputs`를 `postprocess_inputs_regex()` 함수로 설정합니다. `postprocess_inputs_regex` 함수는 [방법 1: 정규 표현식을 사용한 필터링](#method-1-filter-using-regular-expressions)에 정의된 `redact_with_regex` 방법을 적용합니다. 그 결과, 모든 `anthropic` 모델에 대한 모든 입력에 `redact_with_regex`가 적용됩니다.

```python lines theme={null}
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 모델 / predict 함수
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 lines theme={null}
# system 프롬프트로 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"])
```

<div id="optional-encrypt-your-data">
  ### 선택 사항: 데이터를 암호화하세요
</div>

<Frame>
  <img src="https://mintcdn.com/wb-21fd5541/5YxM7MBeu5yJWeCW/media/pii/encrypt.png?fit=max&auto=format&n=5YxM7MBeu5yJWeCW&q=85&s=1689a2d82373c98daf7164e8db29d4fc" alt="암호화된 텍스트 출력 및 암호화 키 관리가 포함된 PII 데이터 암호화 프로세스" width="3024" height="1890" data-path="media/pii/encrypt.png" />
</Frame>

PII를 익명화하는 것에 더해, cryptography 라이브러리의 [Fernet](https://cryptography.io/en/latest/fernet/) 대칭 암호화를 사용해 데이터를 암호화하면 보안을 한층 더 강화할 수 있습니다. 이 접근 방식을 사용하면 익명화된 데이터가 가로채이더라도 암호화 키 없이는 내용을 읽을 수 없습니다. 다음 예시에서는 입력 텍스트를 로깅하기 전에 암호화하고, 모델의 `predict` 메서드 내부에서 이를 복호화하는 방법을 보여줍니다.

```python lines theme={null}
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)
```
