Presidio PII Masking with LiteLLM - 완전한 튜토리얼
Presidio PII Masking with LiteLLM - 완전한 튜토리얼
이 튜토리얼은 Microsoft Presidio와 LiteLLM Gateway로 PII(개인 식별 정보) 마스킹을 설정하는 방법을 안내합니다. 끝나면 LLM 요청에서 민감한 정보를 자동으로 감지하고 마스킹하는 프로덕션 준비 완료된 설정을 갖게 됩니다.
배울 내용
- PII 감지를 위한 Presidio 컨테이너 배포
- 민감한 데이터를 자동으로 마스킹하도록 LiteLLM 구성
- 실제 예시로 PII 마스킹 테스트
- guardrail 실행 모니터링 및 추적
- 출력 파싱과 언어 지원 같은 고급 기능 구성
PII 마스킹을 쓰는 이유
LLM을 사용할 때 사용자는 다음 같은 민감한 정보를 부지불식간에 공유할 수 있어요:
- 신용카드 번호
- 이메일 주소
- 전화번호
- 사회보장번호(SSN)
- 의료 정보(PHI)
- 개인 이름과 주소
PII 마스킹은 이 정보가 LLM에 도달하기 전에 자동으로 감지하고 편집(redact)하여 사용자 프라이버시를 보호하고 GDPR, HIPAA, CCPA 같은 규정 준수를 돕습니다.
전제 조건
이 튜토리얼을 시작하기 전에 다음을 확인하세요:
- Docker가 머신에 설치됨
- 테스트용 LiteLLM API 키 또는 OpenAI API 키
- YAML 구성에 대한 기본 지식
- 테스트용
curl또는 유사한 HTTP 클라이언트
1부: Presidio 컨테이너 배포
Presidio는 두 가지 주요 서비스로 구성됩니다:
- Presidio Analyzer: 텍스트에서 PII 감지
- Presidio Anonymizer: 감지된 PII 마스킹 또는 편집
1.1단계: Docker로 배포
Presidio용 docker-compose.yml 파일을 만드세요:
version: '3.8'services: presidio-analyzer: image: mcr.microsoft.com/presidio-analyzer:latest ports: - "5002:3000" environment: - GRPC_PORT=5001 networks: - presidio-network presidio-anonymizer: image: mcr.microsoft.com/presidio-anonymizer:latest ports: - "5001:3000" networks: - presidio-networknetworks: presidio-network: driver: bridge
1.2단계: 컨테이너 시작
docker-compose up -d
1.3단계: Presidio 실행 확인
analyzer 엔드포인트를 테스트하세요:
curl -X POST http://localhost:5002/analyze \
-H "Content-Type: application/json" \
-d '{
"text": "My email is [email protected]",
"language": "en"
}'
다음 같은 응답을 볼 수 있어요:
[
{
"entity_type": "EMAIL_ADDRESS",
"start": 12,
"end": 33,
"score": 1.0
}
]
✅ Checkpoint: Presidio 컨테이너가 이제 실행 중이고 준비됐어요!
2부: LiteLLM Gateway 구성
이제 LiteLLM이 자동 PII 마스킹에 Presidio를 사용하도록 구성해 봅시다.
2.1단계: LiteLLM 구성 생성
config.yaml 파일을 만드세요:
model_list:
- model_name: gpt-5.6-luna
litellm_params:
model: openai/gpt-5.6-luna
api_key: os.environ/OPENAI_API_KEYguardrails:
- guardrail_name: "presidio-pii-guard"
litellm_params:
guardrail: presidio
mode: "pre_call" # Run before LLM call
presidio_score_thresholds: # optional confidence score thresholds for detections
CREDIT_CARD: 0.8
EMAIL_ADDRESS: 0.6
pii_entities_config:
CREDIT_CARD: "MASK"
EMAIL_ADDRESS: "MASK"
PHONE_NUMBER: "MASK"
PERSON: "MASK"
US_SSN: "MASK"
2.2단계: 환경 변수 설정
export OPENAI_API_KEY="your-openai-key"export PRESIDIO_ANALYZER_API_BASE="http://localhost:5002"export PRESIDIO_ANONYMIZER_API_BASE="http://localhost:5001"
2.3단계: LiteLLM Gateway 시작
litellm --config config.yaml --port 4000 --detailed_debug
guardrails가 로드됐다는 출력을 볼 수 있어요:
Loaded guardrails: ['presidio-pii-guard']
✅ Checkpoint: LiteLLM Gateway가 PII 마스킹을 활성화한 채 실행 중입니다!
3부: PII 마스킹 테스트
다양한 유형의 민감한 데이터로 PII 마스킹을 테스트해 보세요.
테스트 1: 기본 PII 감지
- Request with PII
- What LLM Receives
- Response
curl -X POST http://localhost:4000/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ***" \
-d '{
"model": "gpt-5.6-luna",
"messages": [
{
"role": "user",
"content": "My name is John Smith, my email is [email protected], and my credit card is 4111-1111-1111-1111"
}
],
"guardrails": ["presidio-pii-guard"]
}'
LLM은 마스킹된 버전을 받습니다:
My name is , my email is , and my credit card is
{
"id": "chatcmpl-123abc",
"choices": [
{
"message": {
"content": "I can see you've provided some information. However, I noticed some sensitive data placeholders. For security reasons, I recommend not sharing actual personal information like credit card numbers.",
"role": "assistant"
},
"finish_reason": "stop"
}
],
"model": "gpt-5.6-luna"
}
테스트 2: 의료 정보(PHI)
curl -X POST http://localhost:4000/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ***" \
-d '{
"model": "gpt-5.6-luna",
"messages": [
{
"role": "user",
"content": "Patient Jane Doe, DOB 01/15/1980, MRN 123456, presents with symptoms of fever."
}
],
"guardrails": ["presidio-pii-guard"]
}'
환자 이름과 의료 기록 번호가 자동으로 마스킹됩니다.
테스트 3: PII 없음(일반 요청)
curl -X POST http://localhost:4000/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ***" \
-d '{
"model": "gpt-5.6-luna",
"messages": [
{
"role": "user",
"content": "What is the capital of France?"
}
],
"guardrails": ["presidio-pii-guard"]
}'
PII가 감지되지 않으므로 이 요청은 변경 없이 통과합니다.
✅ Checkpoint: PII 마스킹 테스트를 성공적으로 마쳤어요!
4부: 고급 구성
민감한 엔티티 차단
마스킹 대신 특정 PII 유형이 포함된 요청을 완전히 차단할 수 있어요:
guardrails:
- guardrail_name: "presidio-block-guard"
litellm_params:
guardrail: presidio
mode: "pre_call"
pii_entities_config:
US_SSN: "BLOCK" # Block any request with SSN
CREDIT_CARD: "BLOCK" # Block credit card numbers
MEDICAL_LICENSE: "BLOCK"
차단 동작을 테스트하세요:
curl -X POST http://localhost:4000/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ***" \
-d '{
"model": "gpt-5.6-luna",
"messages": [
{"role": "user", "content": "My SSN is 123-45-6789"}
],
"guardrails": ["presidio-block-guard"]
}'
기대되는 응답:
{
"error": {
"message": "Blocked PII entity detected: US_SSN by Guardrail: presidio-block-guard."
}
}
출력 파싱(언마스킹)
출력 파싱을 활성화해 LLM 응답의 마스킹된 토큰을 원래 값으로 자동 대체하세요:
guardrails:
- guardrail_name: "presidio-output-parse"
litellm_params:
guardrail: presidio
mode: "pre_call"
output_parse_pii: true # Enable output parsing
pii_entities_config:
PERSON: "MASK"
PHONE_NUMBER: "MASK"
동작 방식:
- 사용자 입력: "Hello, my name is Jane Doe. My number is 555-1234"
- LLM이 받는 것: "Hello, my name is
<PERSON>. My number is<PHONE_NUMBER>" - LLM 응답: "Nice to meet you,
<PERSON>!" - 사용자가 받는 것: "Nice to meet you, Jane Doe!" ✨
다중 언어 지원
다른 언어에 대한 PII 감지를 구성하세요:
guardrails:
- guardrail_name: "presidio-spanish"
litellm_params:
guardrail: presidio
mode: "pre_call"
presidio_language: "es" # Spanish
pii_entities_config:
CREDIT_CARD: "MASK"
PERSON: "MASK"
- guardrail_name: "presidio-german"
litellm_params:
guardrail: presidio
mode: "pre_call"
presidio_language: "de" # German
pii_entities_config:
CREDIT_CARD: "MASK"
PERSON: "MASK"
요청별로 언어를 재정의할 수도 있어요:
curl -X POST http://localhost:4000/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ***" \
-d '{
"model": "gpt-5.6-luna",
"messages": [
{"role": "user", "content": "Mi tarjeta de crédito es 4111-1111-1111-1111"}
],
"guardrails": ["presidio-spanish"],
"guardrail_config": {"language": "fr"}
}'
로깅 전용 모드
PII 마스킹을 실제 LLM 요청이 아니라 로그에만 적용하세요:
guardrails:
- guardrail_name: "presidio-logging"
litellm_params:
guardrail: presidio
mode: "logging_only" # Only mask in logs
pii_entities_config:
CREDIT_CARD: "MASK"
EMAIL_ADDRESS: "MASK"
이것은 다음 경우에 유용해요:
- 프로덕션 요청에서 PII를 허용하고 싶을 때
- 하지만 로깅 규정을 준수해야 할 때
- Langfuse, Datadog 등과 통합할 때
5부: 모니터링과 추적
LiteLLM UI에서 Guardrail 실행 보기
LiteLLM Admin UI를 사용 중이라면 상세 guardrail 추적을 볼 수 있어요:
- Logs 페이지로 이동
- guardrail을 사용한 요청 아무거나 클릭
- 자세한 정보 보기:
- 감지된 엔티티
- 각 감지의 신뢰도 점수
- Guardrail 실행 시간
- 원본 대 마스킹된 콘텐츠
Langfuse와 통합
Langfuse에 로깅한다면 guardrail 정보가 자동으로 포함됩니다:
litellm_settings:
success_callback: ["langfuse"]environment_variables:
LANGFUSE_PUBLIC_KEY: "your-public-key"
LANGFUSE_SECRET_KEY: "your-secret-key"
Guardrail 메타데이터에 대한 프로그래매틱 접근
커스텀 콜백에서 guardrail 메타데이터에 접근할 수 있어요:
import litellmdef custom_callback(kwargs, result, **callback_kwargs): # Access guardrail metadata
metadata = kwargs.get("metadata", {})
guardrail_results = metadata.get("guardrails", {})
print(f"Masked entities: {guardrail_results}")
litellm.callbacks = [custom_callback]
6부: 프로덕션 모범 사례
1. 성능 최적화
pre-call guardrails에 병렬 실행 사용:
guardrails:
- guardrail_name: "presidio-guard"
litellm_params:
guardrail: presidio
mode: "during_call" # Runs in parallel with LLM call
2. 사용 사례별 엔티티 유형 구성
의료(Healthcare) 애플리케이션:
pii_entities_config:
PERSON: "MASK"
MEDICAL_LICENSE: "BLOCK"
US_SSN: "BLOCK"
PHONE_NUMBER: "MASK"
EMAIL_ADDRESS: "MASK"
DATE_TIME: "MASK" # May contain appointment dates
금융(Financial) 애플리케이션:
pii_entities_config:
CREDIT_CARD: "BLOCK"
US_BANK_NUMBER: "BLOCK"
US_SSN: "BLOCK"
PHONE_NUMBER: "MASK"
EMAIL_ADDRESS: "MASK"
PERSON: "MASK"
고객 지원 애플리케이션:
pii_entities_config:
EMAIL_ADDRESS: "MASK"
PHONE_NUMBER: "MASK"
PERSON: "MASK"
CREDIT_CARD: "BLOCK" # Should never be shared
3. 고가용성 설정
프로덕션 배포에서는 여러 Presidio 인스턴스를 실행하세요:
version: '3.8'services: presidio-analyzer-1: image: mcr.microsoft.com/presidio-analyzer:latest ports: - "5002:3000" deploy: replicas: 3
presidio-anonymizer-1: image: mcr.microsoft.com/presidio-anonymizer:latest ports: - "5001:3000" deploy: replicas: 3
nginx, HAProxy 같은 로드 밸런서를 사용해 요청을 분산하세요.
4. 커스텀 엔티티 인식
도메인 특화 PII(예: 내부 직원 ID)에 대해 커스텀 인식기를 만드세요:
custom_recognizers.json 생성:
[
{
"supported_language": "en",
"supported_entity": "EMPLOYEE_ID",
"patterns": [
{
"name": "employee_id_pattern",
"regex": "EMP-[0-9]{6}",
"score": 0.9
}
]
}
]
LiteLLM에서 구성:
guardrails:
- guardrail_name: "presidio-custom"
litellm_params:
guardrail: presidio
mode: "pre_call"
presidio_ad_hoc_recognizers: "./custom_recognizers.json"
pii_entities_config:
EMPLOYEE_ID: "MASK"
5. 테스트 전략
PII 마스킹 테스트 케이스를 만드세요:
import pytestfrom litellm import completiondef test_pii_masking_credit_card(): """Test that credit cards are properly masked"""
response = completion(
model="gpt-5.6-luna",
messages=[{
"role": "user",
"content": "My card is 4111-1111-1111-1111"
}],
api_base="http://localhost:4000",
metadata={
"guardrails": ["presidio-pii-guard"]
}
)
# Verify the card number was masked
metadata = response.get("_hidden_params", {}).get("metadata", {})
assert "CREDIT_CARD" in str(metadata.get("guardrails", {}))def test_pii_masking_allows_normal_text(): """Test that normal text passes through"""
response = completion(
model="gpt-5.6-luna",
messages=[{
"role": "user",
"content": "What is the weather today?"
}],
api_base="http://localhost:4000",
metadata={
"guardrails": ["presidio-pii-guard"]
}
)
assert response.choices[0].message.content is not None
7부: 트러블슈팅
문제: Guardrail failure: non-JSON response from Presidio
증상: expected application/json Content-Type but received text/html 같은 오류를 받습니다.
근본 원인: 인그레스 컨트롤러나 리버스 프록시가 /analyze 또는 /anonymize POST 요청을 JSON 대신 일반 텍스트를 반환하는 헬스 엔드포인트(예: /health 또는 /presidio-analyzer/health)로 라우팅하고 있을 수 있어요.
해결: PRESIDIO_ANALYZER_API_BASE와 PRESIDIO_ANONYMIZER_API_BASE가 Presidio API 엔드포인트를 직접 올바르게 가리키는지, 또는 인그레스가 경로를 제거하거나 실수로 일반 텍스트 헬스 체크 엔드포인트로 전달하지 않도록 경로를 올바르게 라우팅하는지 확인하세요.
검증: curl로 엔드포인트를 검증할 수 있어요. text/html이 아니라 JSON 배열을 반환해야 합니다:
curl -sv -X POST http://your-analyzer-endpoint/analyze \
-H "Content-Type: application/json" \
-d '{"text":"test","language":"en"}'
문제: Presidio가 PII를 감지하지 못함
확인 1: 언어 구성
# Verify language is set correctlycurl -X POST http://localhost:5002/analyze \
-H "Content-Type: application/json" \
-d '{
"text": "Meine E-Mail ist [email protected]",
"language": "de"
}'
확인 2: 엔티티 유형
찾는 엔티티 유형이 설정에 있는지 확인하세요:
pii_entities_config:
CREDIT_CARD: "MASK" # Add all entity types you need
지원되는 모든 엔티티 유형 보기
문제: Presidio 컨테이너가 시작되지 않음
로그 확인:
docker-compose logs presidio-analyzerdocker-compose logs presidio-anonymizer
흔한 문제:
- 포트 충돌(5001, 5002가 이미 사용 중)
- 메모리 할당 부족
- Docker 네트워크 문제
문제: 높은 지연 시간
해결 1: during_call 모드 사용
mode: "during_call" # Runs in parallel
해결 2: Presidio 컨테이너 스케일링
deploy:
replicas: 3
해결 3: 캐싱 활성화
litellm_settings:
cache: true
cache_params:
type: "redis"
결론
축하합니다! 🎉 Presidio와 LiteLLM으로 PII 마스킹을 성공적으로 설정했어요. 이제 다음을 갖췄습니다:
✅ 프로덕션 준비 완료된 PII 마스킹 솔루션 ✅ 민감한 정보의 자동 감지 ✅ 여러 구성 옵션(마스킹 vs 차단) ✅ 모니터링 및 추적 기능 ✅ 다중 언어 지원 ✅ 프로덕션 배포를 위한 모범 사례
다음 단계
- 지원되는 모든 PII 엔티티 유형 보기
- 다른 LiteLLM guardrails 탐색
- 여러 guardrails 설정
- 키별 guardrails 구성
- 커스텀 guardrails 알아보기
추가 리소스
- Presidio Documentation
- LiteLLM Guardrails Reference
- LiteLLM GitHub Repository
- Report Issues
도움이 필요하세요? Discord 커뮤니티에 참여하거나 GitHub에 이슈를 여세요!
오탐(False Positive) 억제
Presidio는 때때로 오탐 감지를 트리거할 수 있어요. 예를 들어 짧은 영숫자 문자열이 US_DRIVER_LICENSE로 잘못 플래그될 수 있습니다.
presidio_score_thresholds 또는 presidio_entities_deny_list로 이런 오탐을 억제할 수 있어요.
guardrails:
- guardrail_name: presidio-pii
litellm_params:
guardrail: presidio
mode: "pre_call"
presidio_analyzer_api_base: "http://localhost:5002/"
presidio_anonymizer_api_base: "http://localhost:5001/"
# Use high score thresholds to reduce false positives
presidio_score_thresholds:
US_DRIVER_LICENSE: 0.85
ALL: 0.5
# Or exclude certain entity types entirely from detection
presidio_entities_deny_list:
- US_DRIVER_LICENSE