AI 상담을 직접 호출 방식으로 전환하고 남용 차단 구조 강화
목차
외부 서비스를 끼워 쓰던 AI 상담 기능을 Claude API 직접 호출 방식으로 전환했다. 작업 규모 자체는 크지 않았지만, 전환 과정에서 구조를 다시 들여다보게 됐고 보안 쪽 개선을 몇 가지 함께 묶었음.
중간 레이어가 있으면 편한 점도 있다. 인증, 오류 처리, 응답 포맷 정규화 같은 걸 위임할 수 있으니까. 근데 그 대신 잃는 게 있음. 모델 파라미터 제어가 제한되고, 시스템 프롬프트를 어디까지 커스터마이징할 수 있는지도 서비스마다 다르다. 특히 FAQ 주입처럼 도메인 특화 컨텍스트를 정교하게 넣어야 하는 케이스에서 외부 래퍼는 발목을 잡음.
레이턴시도 있었다. 요청이 외부 서비스 서버를 경유하면 왕복 구간이 하나 더 생기는 거라, 내부 기준으로는 체감이 됐음. 사용자 입장에서 AI 상담 응답이 2-3초 더 오래 걸리면 기다린다는 느낌이 꽤 달라진다.
전환 후 구조는 단순해졌다:
사용자 질문
→ 내부 클래스 (API 호출)
→ Claude API (messages endpoint)
→ 응답 파싱
→ 채팅 UI 렌더링
내부 클래스에서 system 파라미터를 직접 구성하니까, FAQ 목록을 어떤 형식으로 주입할지, 상담사 페르소나를 어떻게 설정할지 자유롭게 조정할 수 있게 됐음. 모델 버전 고정, temperature 조정, max_tokens 제한도 코드 수준에서 명시적으로 관리할 수 있고.
보안 레이어 설계
직접 호출로 바꾸면서 보안 처리도 같이 손봤다. 이전에는 외부 서비스 측에서 어느 정도 걸러줬는데, 직접 호출이 되면 그 보호막이 없어지는 거라 내 쪽에서 챙겨야 했음.
추가한 것들:
- 로그인 필수 처리: 비로그인 상태에서의 AI 상담 요청은 애초에 차단. API 비용이 발생하는 엔드포인트니까 인증 없이 열어두는 건 위험함.
- 경고 후 30분 정지: 부적절한 요청을 감지하면 바로 차단하지 않고 경고를 먼저 줌. 실수인 경우를 고려한 거고, 경고 누적 시 일시 정지.
- DB 영속화: 경고 횟수, 정지 시각, 사유를 DB에 저장. 서버 재시작이나 세션 초기화로 상태가 날아가면 안 되니까.
경고-정지 흐름을 정리하면 이렇다:
| 상태 | 조건 | 다음 단계 |
|---|---|---|
| 정상 | - | 요청 처리 |
| 경고 1회 | 부적절 요청 감지 | 경고 메시지 반환, DB 기록 |
| 경고 2회 | 재발 | 30분 정지 시작 |
| 정지 중 | 만료 전 요청 | 차단 메시지 반환 |
| 정지 만료 | 30분 경과 | 경고 초기화, 정상 복귀 |
경고를 먼저 주는 방식이 단순 차단보다 나은 건, 봇이 아닌 실제 사용자가 의도치 않게 감지 로직에 걸렸을 때 복구가 가능하기 때문임. 1회 경고에서 자기 행동을 인지하고 멈추는 케이스가 꽤 있다.
블랙리스트 자동 등록 구조
수동 대응은 느리다. 공격성 요청이 들어왔을 때 관리자가 확인하고 처리하는 사이에 요청이 계속 들어올 수 있음. 그래서 감지 즉시 DB에 등록하고 다음 요청부터 바로 차단하는 자동 등록 구조를 붙였다.
근데 자동화에는 반드시 오탐 대비가 따라와야 한다. 내가 가장 신경 쓴 부분이 여기였음.
첫째, 예외 처리. 로드 테스트나 개발 환경에서의 배치 작업이 자동 차단에 걸리면 운영이 꼬인다. 내부 IP 대역, 특정 테스트 계정은 감지 로직에서 제외해야 함. 이걸 빠뜨리면 스테이징 테스트 돌릴 때마다 자기 자신을 차단하는 상황이 생김.
둘째, TTL 설정. 영구 차단 대신 만료 시간을 설정해두면 오탐으로 차단된 사용자가 자동으로 복구됨. 심각한 케이스는 관리자가 수동으로 기간을 연장할 수 있고, 가벼운 케이스는 TTL로 알아서 해소되게. 트레이드오프는 있다. TTL이 짧으면 진짜 악용자도 잠깐 기다렸다 재시도할 수 있음. 운영 패턴을 보면서 값을 조정해야 하는 부분이고, 처음부터 정답을 알 수 없다.
셋째, 알림 연동. 자동 차단이 발생하면 슬랙이나 디스코드로 알림이 가게 해뒀음. 차단 건수가 갑자기 치솟으면 오탐 가능성이 높은 거고, 특정 사용자만 계속 걸린다면 진짜 악용 시도일 가능성이 큼. 숫자를 모니터링하지 않으면 자동화가 오히려 조용히 문제를 키울 수 있음.
FAQ 주입과 첫 접속 UX
시스템 프롬프트에 FAQ를 주입하는 방식은 생각보다 효과가 좋다. 자주 묻는 질문에 대한 답변이 일관되게 나오고, 모델이 즉흥적으로 답을 생성하는 경우를 줄일 수 있음. 구조는 단순하다. FAQ를 DB나 파일에서 로드한 뒤 포맷팅해서 system 파라미터에 붙이는 방식:
faq_block = "\n".join([f"Q: {q}\nA: {a}" for q, a in faq_list])
system_prompt = f"{BASE_PERSONA}\n\n아래 FAQ를 우선 참고해서 답변하세요:\n{faq_block}"
FAQ 수가 많아지면 토큰 비용 문제가 생기니까, 전체를 다 넣기보다 카테고리 태깅을 해두고 사용자 질문과 연관된 항목만 선택적으로 주입하는 방향이 더 낫다. 지금은 전체 주입 방식이지만, FAQ가 늘어나면 바꿔야 할 부분임.
첫 접속 UX도 손봤음. 상담사 이름이랑 "어떤 걸 물어볼 수 있어요"를 가이드하는 문구가 없으면 채팅창 앞에서 사용자가 멈추는 경우가 많다. 빈 인풋 박스만 있으면 어색하거든. 간단한 소개와 예시 질문 몇 개를 초기 메시지로 보여주는 것만으로도 첫 입력까지의 마찰이 줄었음. 이건 코드 몇 줄 수준인데 UX 체감 변화는 꽤 됐다.
작업 단위는 작지만, 직접 호출 전환, 보안 레이어 추가, 자동 차단 구조, FAQ 주입이 묶이면 운영 중에 덜 신경 써도 되는 구조가 만들어진다. 각각 따로 보면 별거 아닌 것 같아도, 이게 쌓이는 거라고 본다.
댓글 0
첫 댓글 달아줘.