LLM 엔지니어링21장 · AI 거버넌스와 보안, 온프레미스 인프라17 / 22← 이전목차다음 →MetaContents

21장. AI 거버넌스와 보안, 온프레미스 인프라

실습 4 — 가리고 가명으로 바꿔 LLM 에 보내기

실습 3의 패턴으로 상담 메모의 개인정보를 가린 뒤 LLM 에 요약을 맡깁니다. 같은 값은 늘 같은 가명이 되도록 비밀 키로 만든 지문(HMAC)을 쓰고, 권한 있는 사람만 원래 값으로 되돌립니다. 비밀 키는 실행할 때 secrets 로 만든 시험용 값을 환경 변수에 넣어 읽습니다. 실습 3과 Ollama 가 있어야 합니다.

1단계. 가리기(마스킹)

Python
import hashlib
import hmac
import json
import os
import re
import secrets
from pathlib import Path

import pandas as pd

PATTERNS = json.loads(Path("out_pii_patterns.json").read_text(encoding="utf-8"))
records = pd.read_csv("cs_records.csv")


def mask(text):
    """찾은 개인정보를 [종류] 로 바꾼다. 누구의 것인지는 남기지 않는다"""
    for kind, p in PATTERNS.items():
        text = re.sub(p, f"[{kind}]", text)
    return text


for m in records["메모"].iloc[[2, 3]]:
    # 결과: 원래 메모와 가린 메모
    print(m, "\n→", mask(m))
출력
본인 확인용으로 주민등록번호 720402-2345678 를 불러 줌(받으면 안 되는 정보). 연락처 010-0000-3681.
→ 본인 확인용으로 주민등록번호 [주민번호] 를 불러 줌(받으면 안 되는 정보). 연락처 [전화].
카드 4111-1111-1111-1111 결제 취소 요청. 환불 계좌 501-73-525265. 담당 상담원 메모.
→ 카드 [카드] 결제 취소 요청. 환불 계좌 [계좌]. 담당 상담원 메모.

2단계. 가명 처리와 되돌리기 금고

Python
# 시험용 비밀 키. 실제로는 사내 비밀 저장소에서 읽는다(13차시)
os.environ.setdefault("PII_KEY", secrets.token_hex(16))
KEY = os.environ["PII_KEY"].encode()
vault = {}                                     # 가명 → 원래 값. 권한 있는 사람만 연다


def pseudonymize(text):
    def repl(kind):
        def inner(m):
            value = re.sub(r"[\s-]", "", m.group(0))      # 꼴이 달라도 같은 번호면 같은 가명이 되게 다듬는다
            token = f"[{kind}_{hmac.new(KEY, value.encode(), hashlib.sha256).hexdigest()[:4]}]"
            vault[token] = m.group(0)
            return token
        return inner
    for kind, p in PATTERNS.items():
        text = re.sub(p, repl(kind), text)
    return text


records["가명"] = records["메모"].map(pseudonymize)
print(records["가명"].iloc[2])
# 결과: 같은 전화번호는 다른 메모에서도 같은 가명이다(같은 사람의 문의를 이어 볼 수 있다)
phones = [t for t in vault if t.startswith("[전화")]
print("서로 다른 전화 가명", len(phones), "개 · 금고 크기", len(vault))
출력
본인 확인용으로 주민등록번호 [주민번호_45b4] 를 불러 줌(받으면 안 되는 정보). 연락처 [전화_b31f].
서로 다른 전화 가명 16 개 · 금고 크기 49

3단계. 가린 메모를 LLM 에 요약시키기

Python
import sys

sys.path.insert(0, ".")
import out_secbot as bot                       # 실습 1의 chat() 을 쓴다

SYSTEM = "상담 메모를 한 문장으로 요약하세요. [종류_기호] 꼴의 표시는 바꾸지 말고 그대로 쓰세요."
rows = []
for i in [2, 3, 7]:
    summary = bot.chat(SYSTEM, records["가명"].iloc[i])
    found = [g for kind, p in PATTERNS.items() for g in re.findall(p, summary)]
    rows.append({"번호": int(records["번호"].iloc[i]), "요약": summary, "요약에 남은 개인정보": len(found)})
out = pd.DataFrame(rows)
# 결과: LLM 은 가명만 보았으므로, 요약에도 원래 개인정보가 없다
print(out.to_string(index=False))
출력
 번호                                                       요약  요약에 남은 개인정보
  3 본인 확인용으로 주민등록번호 [주민번호_45b4]와 연락처 [전화_b31f]를 불러와서는 안 됩니다.            0
  4       카드 [카드_0564] 결제 취소 요청, 환불 계좌 [계좌_1adf]로 변경. 상담 요청.            0
  8              주민등록번호 [주민번호_7179]와 연락처 [전화_e1bb] 불러오지 말 것.            0

4단계. 권한 있는 사람만 되돌리기

Python
def reveal(text, user_role):
    """상담 팀장만 가명을 원래 값으로 되돌린다. 되돌린 사람과 개수를 기록한다"""
    if user_role != "상담팀장":
        return text, 0
    n = 0
    for token, value in vault.items():
        if token in text:
            text, n = text.replace(token, value), n + 1
    return text, n


s = out["요약"].iloc[0]
for role in ["인턴", "상담팀장"]:
    shown, n = reveal(s, role)
    # 결과: 인턴에게는 가명 그대로, 상담팀장에게는 원래 값. 기록에는 되돌린 개수만 남긴다
    print(f"{role}: {shown} (되돌림 {n}개)")
출력
인턴: 본인 확인용으로 주민등록번호 [주민번호_45b4]와 연락처 [전화_b31f]를 불러와서는 안 됩니다. (되돌림 0개)
상담팀장: 본인 확인용으로 주민등록번호 720402-2345678와 연락처 010-0000-3681를 불러와서는 안 됩니다. (되돌림 2개)

가명으로 바꾼 메모를 받은 LLM 은 가명 표시를 그대로 옮겨 쓰고, 요약에 원래 개인정보를 하나도 남기지 않았습니다. 다만 요약의 뜻은 따로 보아야 합니다. 3번과 8번 메모는 "고객이 주민등록번호를 불러 주었다"는 기록인데, 요약은 "불러와서는 안 된다"는 지시처럼 바뀌었습니다. 개인정보 점검과 요약 품질 점검은 서로를 대신하지 않습니다.

이 실습이 뜻하는 것

이 실습은 상담 기록을 바깥 전문가에게 맡길 때, 이름과 번호를 "고객 A-45b4"처럼 바꿔 보내고 바꾼 짝표는 금고에 두는 일과 같습니다. 개인정보 자리를 종류만 남기고 가리고(1단계), 가명으로 바꾸며 짝표를 금고에 넣고(2단계), 가명으로 된 기록을 바깥 전문가에게 요약시킨 뒤(3단계), 짝표 금고는 팀장만 열게 합니다(4단계). 여기서 바깥 전문가가 LLM(Large Language Model, 대규모 언어 모델), 짝표 금고가 vault, 팀장이 권한 있는 사람(상담팀장)입니다. 비유와 다른 점은, 가명이 아무렇게나 붙인 번호가 아니라 비밀 키로 만든 지문(HMAC, Hash-based Message Authentication Code, 해시 기반 메시지 인증 코드)이라서 같은 번호는 늘 같은 가명이 된다는 것입니다.

단계출력뜻
1. 가리기[주민번호], [전화], [카드], [계좌]개인정보 자리에 종류만 남았습니다. 누구의 번호인지는 알 수 없습니다
2. 가명[주민번호_45b4], [전화_b31f]종류 뒤에 비밀 키로 만든 네 글자가 붙었습니다. 같은 번호면 다른 메모에서도 같은 가명입니다
2. 금고서로 다른 전화 가명 16 개 · 금고 크기 49전화번호가 서로 다른 가명 16개가 되었고, 금고에는 가명과 원래 값의 짝이 49개 들어 있습니다
3. 요약세 요약 모두 요약에 남은 개인정보 0LLM 은 가명만 보았으므로 요약에도 원래 번호가 없습니다
3. 요약의 뜻3번 "불러와서는 안 됩니다"원래는 "고객이 불러 주었다"는 기록인데 지시처럼 바뀌었습니다. 개인정보는 지켰지만 요약의 품질은 따로 봐야 합니다
4. 되돌리기인턴 되돌림 0개, 상담팀장 되돌림 2개같은 요약이 인턴에게는 가명 그대로, 상담팀장에게는 원래 번호로 보였습니다

한 줄로 줄이면, LLM 에는 가명만 보내고 원래 값은 금고에 두어 권한 있는 사람만 꺼내게 하는 흐름을 끝까지 돌려 본 실습입니다. 다만 실습 3의 패턴이 찾은 것만 가려지므로, 이름처럼 패턴이 못 잡는 개인정보는 그대로 LLM 에 갑니다(실습 3에서 24건). 가명 처리 키는 실행할 때 secrets 로 만든 시험용 값이라, 환경 변수 PII_KEY 를 정해 두지 않으면 다시 실행할 때 가명의 네 글자가 바뀝니다.

코드 설명

  • mask() 는 종류만 남기고 가립니다. 누구의 것인지 알 필요가 없는 통계나 로그에 씁니다.
  • hmac.new(KEY, 값, sha256) 은 비밀 키가 있어야 같은 지문을 만들 수 있는 해시입니다. 키가 없으면 가명에서 원래 번호를 짐작하기 어렵습니다.
  • 값을 다듬은 뒤(빈칸, 하이픈 지우기) 지문을 만들어, 010 0000 4487 과 010-0000-4487 이 같은 가명이 되게 합니다.
  • vault 는 가명과 원래 값의 짝입니다. 실제로는 접근이 제한된 저장소에 두고, 되돌린 사람과 때를 기록합니다.
  • LLM 에는 가명만 보내므로, 요약이 어디에 남거나 새더라도 원래 개인정보는 들어 있지 않습니다.
← 이전 절21장 · 실습 3 — 개인정보 찾기다음 절 →21장 · 실습 5 — 접근 제어가 있는 검색