AI Sparkup

최신 AI 쉽게 깊게 따라잡기⚡

OpenAI Privacy Filter 튜토리얼 – gradio.Server로 PII 처리 웹앱 3종 만들기

openai-privacy-filter와 Gradio의 gradio.Server를 조합하면 문서·이미지·텍스트의 개인식별정보(PII)를 처리하는 세 가지 앱을 빠르게 구현할 수 있다. Privacy Filter는 1.5B 파라미터(활성 50M)로 128K 컨텍스트를 단일 순전파에서 처리하며, Apache 2.0으로 무료 사용 가능하다.

gradio.Server 구조 이해

세 앱 모두 동일한 패턴을 사용한다.

용도데코레이터특징
모델 연산@server.api(name="...")Gradio 큐, ZeroGPU 지원, Python·JS 양쪽에서 호출 가능
정적 페이지·라우팅@server.get(...)일반 FastAPI 라우트, 큐 불필요
import gradio as gr

server = gr.Server()

@server.api(name="analyze")
def analyze(text: str) -> dict:
    # Privacy Filter 호출
    return run_privacy_filter(text)

@server.get("/", response_class=HTMLResponse)
async def homepage():
    return FRONTEND_HTML

브라우저에서는 @gradio/client JS SDK로, Python 클라이언트에서는 gradio_client로 동일 엔드포인트에 접근한다.

앱 1: Document Privacy Explorer

목적: PDF 또는 DOCX를 업로드하면 탐지된 PII 스팬을 카테고리별로 하이라이트해서 보여준다.

핵심 아이디어: 128K 컨텍스트 덕분에 파일 전체를 청킹 없이 단일 패스로 처리한다. 스팬 오프셋이 렌더링된 텍스트와 직접 정렬된다.

@server.api(name="analyze_document")
def analyze_document(file: FileData) -> dict:
    text = extract_text(file["path"])          # PyMuPDF / python-docx
    source_text, spans = run_privacy_filter(text)
    return {
        "text":  source_text,
        "spans": spans,                        # [{start, end, label}, ...]
        "stats": compute_stats(source_text, spans),
    }
<script type="module">
import { Client, handle_file } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";
const client = await Client.connect(window.location.origin);

async function uploadFile(file) {
  const result = await client.predict("/analyze_document", { file: handle_file(file) });
  renderResults(result.data[0]);  // { text, spans, stats }
}
</script>

프론트엔드는 순수 HTML/JS로 작성해 카테고리 필터 토글, CSS 클래스 전환 등 인터랙션을 서버 재요청 없이 처리한다.

앱 2: Image Anonymizer

목적: 이미지(스크린샷, 영수증, 슬랙 화면 등)를 업로드하면 PII가 검출된 영역에 블랙바를 씌워 돌려준다. 캔버스에서 수동 조정 후 다운로드 가능.

핵심 아이디어: Tesseract OCR로 단어별 바운딩 박스를 추출 → Privacy Filter로 텍스트 스팬 탐지 → 캐릭터 오프셋을 픽셀 좌표로 변환

@server.api(name="anonymize_screenshot")
def anonymize_screenshot(image: FileData) -> dict:
    img = Image.open(image["path"]).convert("RGB")
    full_text, char_to_box = ocr_image(img)    # 단어별 박스 + 캐릭터 맵
    spans = run_privacy_filter(full_text)
    boxes = spans_to_pixel_boxes(spans, char_to_box)
    return {
        "image_data_url": pil_to_base64(img),
        "width":  img.width,
        "height": img.height,
        "boxes":  boxes,  # [{x, y, w, h, label, text}, ...]
    }

토글·드래그·새 박스 그리기·PNG 내보내기는 모두 브라우저 캔버스에서 처리되며 서버에 재요청하지 않는다.

앱 3: SmartRedact Paste

목적: 민감한 텍스트를 붙여넣으면 리댁션된 공개 URL과 원본을 볼 수 있는 비공개 URL 두 개를 발급한다.

핵심 아이디어: Privacy Filter가 스팬을 <CATEGORY> 플레이스홀더로 교체 → FastAPI 라우트로 공개/비공개 뷰를 분리

@server.api(name="create_paste")
def create_paste(text: str, ttl: str = "never") -> dict:
    source_text, spans = run_privacy_filter(text)
    redacted = redact(source_text, spans)      # <PRIVATE_PERSON>, <PRIVATE_EMAIL> 등
    pid = secrets.token_urlsafe(6)
    reveal_token = secrets.token_urlsafe(22)
    PASTES[pid] = Paste(pid, reveal_token, source_text, redacted, spans, ...)
    return {
        "view_path":   f"/view/{pid}",
        "reveal_path": f"/view/{pid}?token={reveal_token}",
    }

@server.get("/view/{pid}", response_class=HTMLResponse)
async def view_paste(pid: str, token: str | None = None):
    p = PASTES.get(pid)
    revealed = bool(token) and secrets.compare_digest(token, p.reveal_token)
    return HTMLResponse(render_view(p, revealed))

전체 서비스(스토리지 포함)가 약 200줄의 애플리케이션 코드로 구현된다. 만료된 페이스트는 30초 주기 데몬 스레드가 자동 삭제한다.

탐지 카테고리

Privacy Filter는 8가지 범주를 탐지한다.

private_person · private_address · private_email · private_phone · private_url · private_date · account_number · secret

다국어 텍스트(영어·스페인어·프랑스어·중국어·힌디어 등)도 동일한 호출로 처리한다.

직접 체험

참고 자료



AI Sparkup 구독하기

최신 게시물 요약과 더 심층적인 정보를 이메일로 받아 보세요! (무료)