사내 문의 대응이나 고객 이메일 요약 등을 Dify로 만들려고 할 때, '고객의 이름이나 전화번호를 그대로 LLM에 보내도 되는지'라는 문제에 부딪힐 수 있습니다.
이 글에서는 Dify의 챗 플로우(Chatflow) 내에서 개인 정보를 마스킹(masking)한 후 LLM에 전달하고, 답변을 원래의 이름이나 번호로 복원하는 방법을 소개합니다. 마스킹 부분에는 이전 기사에서 소개했던 일본어 개인 정보 마스킹 API인 '개인정보흑누리군'을 사용합니다.
흑누리군은 Dify 공식 마켓플레이스의 플러그인이 되었기 때문에, 설치하고 블록만 연결하면 사용할 수 있습니다. 플러그인을 사용할 수 없는 환경을 위한 예시 챗 플로우도 HTTP 요청(HTTP Request) 블록으로 만들어 준비했습니다.
구현할 수 있는 것
사용자 문장: 야마다 타로님의 전화는 090-1234-5678입니다. 회신 문안을 작성해 주세요.
↓ 흑누리군: 마스킹 처리
LLM에 전달되는 문장: [PERSON_1]님의 전화는 [PHONE_1]입니다. 회신 문안을 작성해 주세요.
...
LLM에는 기호로 대체된 문장만 전달되고, 사용자에게는 원래 이름이 포함된 답변이 반환됩니다. 같은 사람에게는 동일한 기호가 부여되므로, LLM은 '누가 누구인지'를 구분한 채 문장을 이해할 수 있습니다.
플러그인으로 사용하기
- https://kuronurikun.com 에서 'Free 플랜 신청'을 통해 API 키(
pmk_... )를 무료로 발급받습니다 (월 10만 자까지) - Dify의 '마켓플레이스(Marketplace)'에서 '흑누리군'을 검색하고, '개인정보흑누리군'을 설치합니다 (플러그인 페이지)
- '도구(Tool)'에서 '개인정보흑누리군'을 열고 API 키를 입력하여 저장합니다
- 챗 플로우에서 다음 순서로 블록을 연결합니다
시작 → 개인 정보 마스킹 → LLM → 개인 정보 복원 → 답변
| 블록 | 설정 |
|---|
| 개인 정보 마스킹 | 문장: sys.query |
| LLM | 프롬프트: '마스킹된 텍스트(masked_text)' (대화 기억은 꺼짐) |
| 개인 정보 복원 | 문장: LLM의 text, mapping_token: '마스킹된'의 mapping_token |
| 답변 | '복원된 텍스트(restored_text)' |
- '미리보기(Preview)'에서 이름이나 전화번호가 포함된 문장을 보내봅니다.
실행 결과의 'LLM' 블록 입력이 기호로 대체되어 있다면 성공입니다.
예시 챗 플로우로 사용하기 (플러그인 없음)
- API 키를 발급받습니다 (위와 동일)
- 예시 파일을 다운로드합니다: kuronurikun-chatflow.yml
- Dify의 '스튜디오(Studio)'에서 'DSL 파일 가져오기'를 선택하고, 다운로드한 파일을 불러옵니다
- 우측 상단의 '환경 변수(Environment Variable)'에
KURONURI_API_KEY 에 API 키를 넣습니다 (시크릿으로 저장됨) - 'LLM(마스킹된 문장만 전달하기)' 블록을 열고, 사용할 모델을 선택합니다 (예시는 어떤 Dify에서도 읽을 수 있도록 모델을 비워두었기 때문에, 선택하지 않고 실행하면 'LLM provider and model are required.'라고 표시됩니다)
- '미리보기(Preview)'에서 이름이나 전화번호가 포함된 문장을 보내봅니다.
실행 결과의 'LLM' 블록 입력이 기호로 대체되어 있다면 성공입니다.
예시 챗 플로우 내용
시작 → 흑누리군: 마스킹 → LLM → 복원 준비 → 흑누리군: 복원 → 결과 추출 → 답변
| 블록 | 종류 | 수행하는 작업 |
|---|
| 흑누리군: 마스킹 | HTTP 요청 | 사용자 문장을 그대로 POST /v1/mask/text?output=text 로 전송합니다. 마스킹된 문장이 본문에, 복원 정보를 위한 정보(mapping_token)가 헤더로 반환됩니다 |
| ... | POST /v1/unmask 로 기호를 원래 값으로 되돌립니다 | |
| 결과 추출 | 코드 | 복원된 문장을 가져옵니다. 실패했을 때는 마스킹된 상태의 답변을 반환합니다 (내용을 추측하여 채우지 않습니다) |
API 키는 DSL 파일이나 각 블록에 직접 작성하지 않고, 환경 변수(시크릿)에서 읽어오도록 했습니다.
만들 때 막히기 쉬운 부분
1. 사용자 문장을 JSON에 삽입하면 깨짐
Dify의 HTTP 요청 블록에서 본문을 {"text": "{{#sys.query#}}"}와 같이 작성하면, 사용자 문장에 따옴표(")나 줄 바꿈이 포함되는 순간 JSON이 깨집니다.
예제에서는 문장을 JSON 안에 넣지 않고 본문 그대로 전송할 수 있는 /v1/mask/text를 사용하고 있습니다. 복원하는 데 필요한 값은 코드 블록에서 객체로 조립한 후 전달해야 합니다.
2. 대화 기억(Memory)을 비활성화하지 않기
챗 플로우의 LLM 블록에서 대화 기억 기능을 활성화하면, 과거 대화의 원문이 LLM에 전달됩니다. 예제에서는 이 기능을 비활성화했습니다. 대화 흐름을 유지하고 싶다면, 과거 대화 역시 마스킹 처리된 것을 전달하는 노력이 필요합니다.
3. 에이전트에게 도구로만 제공해서는 부족하다
에이전트에게 '마스킹 도구'를 제공하여 사용하는 방식으로는, LLM이 도구를 호출할지 여부를 판단하는 시점부터 원문이 이미 LLM에 전달되어 있습니다. 마스킹 처리는 워크플로우 순서상 반드시 LLM보다 앞에 배치해 주십시오.
servers가 필요합니다
- 커스텀 도구로 불러올 때는 Dify의 '커스텀 도구'가 OpenAPI 스키마를 읽을 수는 있지만, 스키마에
servers가 없으면 등록할 수 없습니다. 저희 Kuronurikun에서는 Dify용으로 servers를 포함한 스키마를 https://kuronurikun.com/dify/openapi.json에서 공개하고 있습니다.
워크플로우의 '도구' 블록에서 사용하려면 이 URL에서 가져와 주십시오. 인증은 'API 키', 헤더는 '커스텀'으로 설정하고, 헤더 이름은 X-API-Key로 지정합니다.
다만, 커스텀 도구를 사용한 챗 플로우는 DSL 파일 내에 워크스페이스별 도구 ID가 포함되기 때문에 다른 사람의 Dify에서는 그대로 불러올 수 없습니다. 예제를 HTTP 요청 블록으로 만든 이유도 이 때문입니다.
주의사항
- Kuronurikun은 규칙과 사전(dictionary)을 사용하여 개인 정보를 찾습니다. 흔히 쓰이는 패턴은 발견할 수 있지만, 놓치지 않는 것은 아닙니다. 중요한 데이터는 사람의 눈으로 확인하는 것과 병행해 주십시오.
- 복원을 위한 정보(
mapping_token)는 마스킹 처리했을 때와 동일한 API 키로만 사용할 수 있으며, 24시간이 지나면 무효화됩니다.
끝으로
Dify로 사내용 봇을 만들 때 개인정보에 대한 불안감을 줄이는 하나의 방법으로 도움이 되기를 바랍니다. 놓친 예시나 '이런 방식으로 사용하고 싶다'는 요청사항이 있다면 피드백으로 알려주십시오.
Discussion