개발 slecs

은행 셀렉터 AI 상담을 직접 API 호출로 전환해 품질·속도 개선

목차

외부 서비스를 통해 AI 상담을 제공하던 방식엔 항상 찜찜한 부분이 있었다. 응답을 내 맘대로 조정할 수가 없고, 중간 레이어가 하나 더 있다는 건 장애 포인트도 하나 더 생긴다는 뜻이다. 서드파티 래퍼 서비스가 버전 업데이트를 늦추면 우리도 그 타임라인을 기다려야 하고, 프롬프트 제어 폭이 그쪽 API 설계에 종속된다. 이런 이유들이 쌓여서 은행 셀렉터 AI 상담을 Claude API 직접 호출 방식으로 전환했다.

결과부터 얘기하면 응답 품질, 커스터마이징 자유도, 레이턴시 세 가지 모두 나아졌다. 외부 래퍼를 거칠 때는 시스템 프롬프트 조작에 제약이 있었는데, 직접 호출하면 messages endpoint 파라미터를 전부 내가 통제할 수 있다. temperature, max_tokens, system 블록 구성, 멀티턴 컨텍스트 관리까지 전부.

구조를 바꾼 이유와 설계 방향

기존 방식의 문제를 구체적으로 짚으면 이렇다. 서드파티 래퍼는 보통 자기네 방식으로 추상화를 한 겹 씌운다. 이게 편할 때도 있지만, 그 추상화가 내가 원하는 제어 지점과 맞지 않으면 오히려 발목을 잡는다. 시스템 프롬프트를 원하는 구조로 구성하려 해도 래퍼가 자체 포맷으로 변환해버리거나, 특정 파라미터를 아예 노출하지 않는 경우가 있었다.

직접 호출로 바꾸면서 외부 의존성이 Claude API 하나로 줄었다. 흐름은 아래와 같다:

사용자 질문
  → 내부 클래스 (API 호출)
    → Claude API (messages endpoint)
  → 응답 파싱
  → 채팅 UI 렌더링

내부 클래스를 별도로 두는 건 중요한 설계 선택이다. 호출 지점이 여기저기 흩어져 있으면 나중에 모델을 바꾸거나 파라미터를 수정할 때 손대야 할 곳이 너무 많아진다. 클래스 하나에 모아두면 변경이 한 군데에서 끝난다. 지금은 Claude가 대상이지만, 인터페이스를 잘 정의해두면 다른 모델로 교체하거나 멀티 모델 라우팅을 나중에 얹는 것도 어렵지 않다.

레이턴시 개선도 체감됐다. 래퍼 서비스를 거치면 네트워크 홉이 하나 더 생기고 그쪽 서버에서 요청을 처리하는 시간도 있다. 직접 연결하면 이 오버헤드가 없어진다. AI 상담처럼 응답을 기다리는 UX에서는 수백 ms 차이도 사용자가 체감한다. 특히 스트리밍 응답을 쓰지 않는 구조면 첫 바이트까지 걸리는 시간이 UX 전체 인상을 좌우하는데, 중간 레이어가 없어지는 게 여기서 꽤 효과가 있었다.

FAQ 주입과 시스템 프롬프트 설계

직접 호출의 가장 큰 장점이 여기서 드러난다. 시스템 프롬프트에 FAQ 목록을 주입해서 자주 묻는 질문엔 일관된 답변이 나오게 했다. FAQ는 DB나 파일에서 로드해서 동적으로 주입하는 구조다. 콘텐츠를 코드 밖에서 관리할 수 있어서 비개발자도 수정이 가능하다.

시스템 프롬프트 설계에서 챙긴 것들:

  • AI 상담사 이름과 역할 소개 문구: 처음 접속 시 무엇을 물어볼 수 있는지 안내가 있어야 사용자가 어색하지 않다
  • FAQ 블록을 고정 위치에 두고 대화 이력을 그 아래에 붙이는 순서 유지
  • "모른다"고 대답해야 하는 영역 명시: 범위 밖 질문에서 환각 답변이 나오는 걸 줄이기 위해
  • 답변 포맷 지침: 마크다운 헤딩을 남발하지 않고 간결하게 쓰도록 명시

처음 접속 시 안내 문구가 있고 없고의 차이는 생각보다 크다. 사용자가 AI 상담에 익숙하지 않으면 뭘 물어봐야 할지 막막해서 그냥 나간다. "어떤 은행 적금이 이자율이 높나요?" 같은 예시 질문 두세 개만 보여줘도 진입 장벽이 확 낮아진다. 이 부분은 개발 비용이 거의 없는데 UX 임팩트는 꽤 큰 편이다.

FAQ 주입 방식엔 트레이드오프가 있다. FAQ가 길어질수록 컨텍스트 윈도우를 많이 소비하고 토큰 비용이 올라간다. 모델이 정작 사용자 질문에 집중해야 할 부분에서 프롬프트 앞부분에 쌓인 FAQ를 처리하는 데 주의가 분산될 수 있다는 논의도 있다. 지금은 카테고리별로 FAQ를 잘라서 사용자 질문과 관련된 카테고리만 주입하는 방식을 검토 중이다. 첫 턴에 분류 비용이 생기는 게 단점이라 아직 적용은 안 했다.

보안 레이어와 Rate Limit

AI 상담 기능을 오픈할 때 놓치기 쉬운 게 보안이다. 로그인 필수 처리는 기본이고, 부적절 이용 시 경고 후 30분 정지 로직, 경고/정지 횟수를 DB에 저장하는 것까지 같이 넣었다.

AI API 호출은 일반 REST 요청보다 비용이 훨씬 크기 때문에 Rate Limit 설정을 소홀히 하면 비용 폭탄으로 이어진다. 설계할 때 핵심은 "얼마나 많이 막을 것인가"보다 "정상 사용자를 막지 않으면서 공격을 차단하는가"였다. 너무 타이트하게 잡으면 정상 사용자가 피해를 보고, 너무 느슨하면 의미가 없다.

Redis 슬라이딩 윈도우와 고정 윈도우를 비교하면:

방식 경계 버스팅 취약성 구현 복잡도 Redis 의존도
고정 윈도우 높음 (경계에서 2배 요청 가능) 낮음 낮음
슬라이딩 윈도우 낮음 중간 높음

슬라이딩 윈도우가 경계 구간 버스팅 공격에 강한 건 맞다. 고정 윈도우는 윈도우가 끊기는 순간 직전/직후 요청을 합산하면 제한치의 두 배를 단시간에 밀어넣을 수 있다. 슬라이딩 윈도우는 이 허점이 없다.

단, Redis가 단일 장애점이 되는 문제가 있다. 연결 풀과 타임아웃 설정을 같이 챙겨야 하고, Redis가 다운됐을 때 fallback 전략도 미리 정해두는 게 좋다. 완전히 차단할지, 로컬 인메모리 카운터로 대체할지, 일단 통과시킬지 - 서비스 성격에 따라 다르지만 "생각 안 해뒀다"가 제일 위험하다.

경고/정지 횟수를 DB에 저장하는 것도 이 맥락에서 중요하다. 메모리나 Redis에만 두면 서버 재시작 때 초기화된다. 누적 이력이 없으면 반복 어뷰징 패턴을 뒤늦게 인지하게 된다. DB에 이력을 쌓아두면 "이 계정이 지난달에도 여러 차례 경고 받았다"는 컨텍스트를 갖고 더 강한 제재 여부를 판단할 수 있다.

작업 규모 자체는 크지 않았다. 하지만 이런 인프라성 변경이 쌓이는 게 결국 운영 안정성이 된다. 외부 의존성 하나 줄이고, 내가 통제할 수 있는 영역을 하나 늘리는 것. 당장 눈에 띄는 변화는 아니지만, 뭔가 문제가 생겼을 때 원인을 내 코드 안에서 찾을 수 있다는 게 생각보다 큰 차이다.

댓글 0

첫 댓글 달아줘.