AI 상담을 Claude API 직접 호출로 전환해 품질·레이턴시 개선
목차
외부 래퍼 서비스를 걷어내고 Claude API를 직접 때리는 구조로 바꿨다. 작업 자체는 크지 않았는데, 바꾸고 나서 체감 차이가 꽤 있어서 기록으로 남긴다.
왜 직접 호출로 바꿨나
기존 구조는 중간 서비스가 API 키를 관리하고 모델 호출을 대신 해주는 형태였다. 초기엔 빠르게 붙이기 좋았는데, 시간이 지나면서 불편한 지점이 쌓였다. 시스템 프롬프트 제어가 제한적이었고, 응답 스트리밍 방식이나 에러 핸들링도 중간 서비스 스펙에 종속됐다. 레이턴시도 문제였다. 요청이 우리 서버 → 중간 서버 → Anthropic 순으로 두 홉을 타는 구조라 체감이 느렸다.
직접 호출로 바꾸면 당연히 구현 부담이 늘어난다. API 키 관리, 재시도 로직, 토큰 소진 처리 같은 걸 직접 챙겨야 한다. 그럼에도 바꾼 이유는 커스터마이징 자유도 때문이었다. 시스템 프롬프트를 완전히 통제할 수 있어야 상담사 페르소나를 제대로 잡을 수 있고, FAQ 주입 방식도 우리 데이터 구조에 맞게 설계할 수 있다.
결과적으로 중간 레이어가 사라지니 레이턴시가 줄었고, 응답 품질 조정도 훨씬 수월해졌다.
구현 구조와 시스템 프롬프트 설계
호출 흐름은 단순하다.
사용자 질문
→ 내부 클래스 (API 호출)
→ Claude API (messages endpoint)
→ 응답 파싱
→ 채팅 UI 렌더링
내부 클래스로 한 번 감싼 건 나중에 모델을 바꾸거나 파라미터를 조정할 때 호출부를 건드리지 않기 위해서다. API 클라이언트를 직접 여기저기 뿌리면 나중에 수정 범위가 넓어진다.
시스템 프롬프트 구성이 이번 작업에서 가장 신경 쓴 부분이었다. 상담사 이름과 역할 소개 문구를 먼저 정리했다. 처음 접속했을 때 "뭘 물어봐야 할지 모르겠다"는 어색함을 줄이려면 초기 안내가 있어야 한다. 단순히 이름만 붙이는 게 아니라 어떤 주제를 다루는지, 어떤 질문에 답할 수 있는지를 명시해줘야 사용자가 바로 쓸 수 있다.
FAQ 주입은 DB나 파일에서 목록을 로드해서 시스템 프롬프트에 포함시키는 방식으로 했다. 자주 묻는 질문에 대해 일관된 답변을 내보내는 게 목적인데, 이걸 파인튜닝 없이 프롬프트 수준에서 해결한다는 점이 장점이다. 수정도 훨씬 빠르다. FAQ 내용을 바꾸려면 DB 값만 업데이트하면 되고, 재배포가 필요 없다.
# FAQ 주입 예시 (개념 코드)
faqs = load_faqs_from_db()
faq_block = "\n".join([f"Q: {f['question']}\nA: {f['answer']}" for f in faqs])
system_prompt = f"""
당신은 {CONSULTANT_NAME}입니다. {ROLE_DESCRIPTION}
자주 묻는 질문에 대해서는 아래 답변을 우선 참고하세요:
{faq_block}
"""
프롬프트에 FAQ를 넣을 때 토큰이 늘어나는 건 감수해야 한다. FAQ가 많아지면 컨텍스트 길이가 커지고 비용도 늘어난다. 이 부분은 운영하면서 필요한 항목만 선별적으로 주입하는 방식으로 조정할 수 있다.
보안 처리와 자동 차단 구조
로그인 없이 쓸 수 있는 AI 상담은 어뷰징 타깃이 되기 쉽다. 이번에 몇 가지를 추가했다.
| 처리 항목 | 내용 |
|---|---|
| 접근 제한 | 로그인 필수, 비인증 요청 즉시 차단 |
| 경고 처리 | 부적절 요청 감지 시 경고 발송, DB에 횟수 저장 |
| 일시 정지 | 경고 누적 시 30분 정지, 정지 횟수도 DB 기록 |
| 자동 블랙리스트 | 공격성 요청 감지 시 즉시 DB 등록 및 차단 |
자동 블랙리스트 등록은 수동 대응의 속도 한계를 보완하기 위한 것이다. 감지 → 등록 → 차단이 요청 단위로 바로 이뤄져야 실시간 대응이 된다. 수동으로 신고 받고 확인하고 처리하는 사이에 이미 반복 요청이 여러 번 들어온다.
다만 자동 차단은 오탐 관리를 함께 고려해야 한다:
- 오탐 예외 처리: 배치 작업이나 내부 로드 테스트가 공격으로 잘못 분류되지 않도록 IP나 사용자 ID 기반 화이트리스트를 둔다.
- TTL 설정: 영구 차단 대신 만료 시간을 설정한다. 오탐 복구가 가능하고, 오래된 차단 기록이 쌓여서 관리 부담이 커지는 것도 막는다.
- 알림 연동: 자동 차단이 발생하면 슬랙이나 디스코드로 알림을 보낸다. 자동화가 되어 있어도 모니터링은 사람이 해야 한다. 알림 없이 돌아가면 오탐이 생겨도 모르고 지나친다.
경고와 정지 횟수를 DB에 저장하는 건 단순 집계 이상의 의미가 있다. 나중에 특정 사용자의 이력을 보면서 패턴을 파악할 수 있고, 정책을 조정할 때 데이터 근거가 생긴다. 로그만 남기는 것과 DB에 상태로 관리하는 건 운영 편의에서 차이가 크다.
이번 작업에서 배운 건, API 직접 호출로 전환할 때 기능 자체보다 주변 처리-보안, 에러 핸들링, 모니터링-에 공이 더 많이 든다는 거다. 기능 구현 시간보다 이쪽을 챙기는 시간이 길었다. 그게 맞는 방향이라고 생각한다. 기능은 빠르게 붙여도 되는데, 운영 안정성은 나중에 고치면 이미 문제가 터진 뒤다.
댓글 0
첫 댓글 달아줘.