🤖AI

Anthropic → Gemini 마이그레이션 후 발견한 4가지 함정

비용 80% 절감하려고 Claude Sonnet 4.6 → Gemini 2.5 Flash로 갈아타다가 만난 실제 버그들. 같은 실수 안 하길 바라며.

📅 2026년 5월 3일·📖 7분 읽기·👁 41

왜 갈아탔나

Anthropic Claude Sonnet 4.6 운영 중 월 API 비용이 부담스러운 수준까지 올라왔다. 같은 모델군에서 Haiku로 다운그레이드해도 토큰당 단가는 여전히 비쌌다.

가격 비교를 다시 해보니:

모델InputOutput
Claude Sonnet 4.6$3.00 / 1M$15.00 / 1M
Claude Haiku 4.5$0.80 / 1M$4.00 / 1M
Gemini 2.5 Flash (비사고)$0.15 / 1M$0.60 / 1M
Gemini Flash-Lite$0.075 / 1M$0.30 / 1M

Sonnet 대비 Gemini 2.5 Flash는 20배 저렴하면서 한국어 품질이 비슷하다는 자체 테스트 결과가 나왔다. 갈아타기로 결정.

이론은 깔끔했다. 실제로는 4개의 함정이 기다리고 있었다.

함정 1: thinking_budget을 0으로 안 박으면 검색이 죽는다

gemini-2.5-flash는 기본적으로 thinking 모드가 켜져 있다. 이게 켜져 있으면:

  • 응답 속도가 느려진다 (~2배)
  • 가격이 올라간다 ($0.60 → $3.50 / 1M output)
  • 그리고 가장 짜증나는 건, google_search tool 트리거가 약해진다

증상: "오늘 환율 알려줘" 같은 시간 기반 질문에도 검색을 안 띄우고 자체 학습 데이터로 답함.

3시간 삽질 끝에 발견한 해결책:

config = gtypes.GenerateContentConfig(
    system_instruction=system_prompt,
    tools=[gtypes.Tool(google_search=gtypes.GoogleSearch())],
    max_output_tokens=8192,
    temperature=0.7,
    thinking_config=gtypes.ThinkingConfig(thinking_budget=0),  # ← 이거
)

thinking_budget=0을 명시적으로 박으면 thinking이 완전히 꺼지고, 모델이 Flash-Lite처럼 빠르게 응답하면서 검색 트리거도 정상 작동한다.

함정 2: 야간 배치 잡이 신규 유저 매 턴마다 분석 실행

이건 우리 서비스에만 있는 코드 버그였지만, 비슷한 패턴 자주 본다.

문제 코드:

last_count = (existing or {}).get("message_count_at_analysis") or 0
if last_count > 0 and len(messages) - last_count < 5:
    return  # ← 5턴 미만이면 스킵

논리적으로 보이지만 함정이 있다. 신규 유저는 last_count == 0이라 조건이 항상 False가 된다. 그래서 매 채팅 턴마다 분석 함수가 돌아간다.

분석 함수는 Gemini API를 2번 호출 (프로필 JSON 생성 + injection 텍스트 생성). 200개 메시지를 입력으로 넣으니 호출당 비용이 작지 않다.

이틀 동안 신규 유저 몇 명이 활발히 채팅하면: - 1유저 × 20턴 × 2 API 호출 × ~3원 = 120원/유저 - 야간 배치도 동일 인터벌 체크 없이 모든 유저 매일 재분석 → 수백 원 추가

이틀에 1,000원 넘게 빠져나갔다.

수정:

if last_count == 0:
    if len(messages) < 10:    # 첫 분석은 메시지 10개 이상일 때만
        return
else:
    if len(messages) - last_count < 20:   # 그 후엔 20턴 인터벌
        return

추가로 메시지 입력 한도를 200 → 60으로, 메시지당 truncate를 300 → 200으로 줄였다. 약 80~90% 비용 절감.

함정 3: gemini-2.5-flash 가격을 잘못 박아둠

내부 비용 추적용 MODEL_PRICING 딕셔너리에 가격을 잘못 넣어놨다:

# 잘못된 값 (사고 모드 가격)
"gemini-2.5-flash": {"input": 0.30, "output": 2.50},

올바른 값 (비사고 모드, thinking_budget=0 적용 시)

"gemini-2.5-flash": {"input": 0.15, "output": 0.60},

Google 가격 페이지가 thinking/non-thinking 두 가격을 같이 표기하는데 헷갈렸다. 함정 1에서 thinking을 껐으니 비사고 가격을 적용해야 맞다.

이거 안 잡으면 관리자 페이지의 비용 그래프가 실제보다 4배 높게 나온다. 의사결정에 직접 영향을 준다.

함정 4: 마이그레이션 했는데 크레딧 차감 단가는 그대로

유료 사용자에게서 차감하는 단가도 별도 상수로 박혀 있었다:

# 옛날 — Flash-Lite 기준
PAID_IN_KRW_PER_TOKEN  = 0.075 * 1400 / 1_000_000 * 3
PAID_OUT_KRW_PER_TOKEN = 0.30  * 1400 / 1_000_000 * 3

메인 모델은 2.5 Flash로 올렸는데 차감은 Flash-Lite 가격. 유저는 실비보다 적게 차감되고, 우리는 손해를 보고 있었다. 한참 동안 모르고 있었다.

수정:

# 2.5 Flash + 3x 마진
PAID_IN_KRW_PER_TOKEN  = 0.15 * 1400 / 1_000_000 * 3
PAID_OUT_KRW_PER_TOKEN = 0.60 * 1400 / 1_000_000 * 3

거기에 마이그레이션 이전 Claude 시절의 비용 기록이 usage_logs에 남아 있어서 통계가 일관되지 않았다. 관리자 페이지에 "Claude 비용 초기화" 버튼을 만들어서 한 번에 정리.

정리: 모델 마이그레이션 체크리스트

같은 작업 할 사람을 위한 체크리스트.

  • [ ] 모델별 가격 페이지 두 번 확인: thinking / non-thinking 가격이 다를 수 있음 (Gemini 2.5 Flash 같은 경우)
  • [ ] thinking_budget 명시적으로 박기: 기본값에 의존하지 말 것. 끄려면 0, 켜려면 정확한 토큰 수
  • [ ] 검색/툴 트리거 회귀 테스트: 같은 입력에 같은 행동 하는지 모델 변경 후 재확인
  • [ ] 내부 가격표 동기화: MODEL_PRICING 딕셔너리, 크레딧 차감 단가 — 두 곳 다
  • [ ] 이전 모델 비용 데이터 처리 정책: 그대로 둘지 / 삭제할지 / 별도 통계로 분리할지
  • [ ] 신규 유저 코드 경로 점검: count == 0 조건이 인터벌 체크를 무력화시키는 버그 없는지
  • [ ] 배치 잡과 실시간 트리거 중복 점검: 같은 작업이 두 곳에서 돌면 비용 2배

결과

마이그레이션 + 4개 함정 수정 후:

  • 평균 응답 속도: 1.7배 빨라짐 (Sonnet 대비)
  • 운영 비용: 약 80% 절감
  • 검색 트리거: 정상 작동
  • 한국어 품질: 자체 테스트에선 차이 없음 (블라인드 비교)

thinking_budget=0을 알아내는 데 가장 오래 걸렸다. 같은 함정에 빠지지 않길.


※ 이 시스템은 Riel 챗봇에 실제 적용되어 있고, 비용 기록은 관리자 대시보드에서 실시간으로 보고 있다.

태그

#gemini#anthropic#cost-optimization#live-bug

📨 박주니에게 한마디

스팸·악성 메시지 방지를 위해 구글 로그인 후 메시지를 보낼 수 있어요. 비공개로 전달되며, 운영자 외에는 볼 수 없습니다.

Google 로그인 후 메시지 남기기