사주 룰 + AI 해석 하이브리드: 비용과 품질의 균형점 찾기

반응형

2026.02.17 - [개발] - 사주 앱 개발 4편 - 룰 엔진 + AI 해석 하이브리드 설계

 

사주 룰 + AI 해석 하이브리드 설계과정에  대한 좀 상세한 분석이다

 

 

서비스를 오픈하고 나서 처음 한 일이 API 비용 계산이었다.

사주 해석 하나에 카테고리 7개. 카테고리마다 Claude API 호출 한 번. 사용자 한 명이 전체를 다 펼치면 호출 7번. 여기에 종합 해석은 Sonnet, 나머지는 Haiku로 모델을 분기했는데 — 실제로 한 달 운영하면 얼마가 나올지 감이 없었다.

최적화하기 전에 먼저 측정했다.


실측 비용 데이터

사주 한 명 분석 기준, 프롬프트 토큰과 응답 토큰을 실측했다.

 
 
[종합 해석 — claude-sonnet-4-5]
입력 토큰: 약 1,800 (시스템 프롬프트 + 데이터 패키지 + 궁통보감)
출력 토큰: 약 600
비용: $0.0054 + $0.0090 = 약 $0.015

[개별 카테고리 — claude-haiku-4-5 × 6]
입력 토큰: 약 1,600 (종합보다 약간 작음)
출력 토큰: 약 350
비용(1회): $0.00032 + $0.00105 = 약 $0.0014
비용(6회): 약 $0.008

[사용자 1명 전체 호출 시]
종합: $0.015
카테고리 전체: $0.008
합계: 약 $0.023 (약 33원)

33원. 예상보다 낮았다. 하지만 트래픽이 붙으면 얘기가 달라진다. 월 1,000명이 전체 카테고리를 다 펼치면 $23. 월 10,000명이면 $230. AdSense 수익이 그만큼 나오기 전까지는 적자 구조다.

그리고 현실에서 사용자가 카테고리 7개를 전부 펼치는 경우는 많지 않다. Analytics를 보니 종합 해석과 성격, 직업 카테고리 클릭이 대부분이었다. 나머지 4개는 20% 미만. 평균 호출 수는 3~4회 수준이었다.

실제 평균 비용: 사용자 1명당 약 $0.012 (17원)


캐싱 전략

같은 사주를 두 번 조회할 때 API를 다시 호출하는 건 낭비다. 사주는 생년월일시가 같으면 결과가 동일하다. 캐시 키로 쓰기 딱 좋다.

 
 
typescript
// 캐시 키 생성
function generateCacheKey(
  birthDate: string,    // "1985-03-15"
  birthTime: string,   // "10:30"
  gender: 'M' | 'F',
  calendar: 'solar' | 'lunar',
  category: string     // "personality" | "career" | ...
): string {
  return `saju:${birthDate}:${birthTime}:${gender}:${calendar}:${category}`
}

// 조회 전 캐시 확인
async function getInterpretation(params: InterpretationParams) {
  const cacheKey = generateCacheKey(
    params.birthDate,
    params.birthTime,
    params.gender,
    params.calendar,
    params.category
  )

  // 로컬 스토리지 캐시 (Cloudflare Workers KV로 교체 가능)
  const cached = localStorage.getItem(cacheKey)
  if (cached) {
    return JSON.parse(cached)
  }

  // 캐시 미스 — API 호출
  const result = await callClaudeAPI(params)
  
  // 24시간 만료 캐시 저장
  localStorage.setItem(cacheKey, JSON.stringify({
    result,
    expiredAt: Date.now() + 24 * 60 * 60 * 1000
  }))
  
  return result
}

캐시 만료를 24시간으로 설정한 이유가 있다. 사주 해석은 자주 바뀔 필요가 없지만, 프롬프트를 업데이트하면 기존 캐시를 갱신해야 한다. 영구 캐시는 프롬프트 개선의 효과가 사용자에게 전달되지 않는 문제가 생긴다.

Cloudflare Workers 환경에서는 KV 스토리지로 교체했다. 로컬 스토리지는 사용자 디바이스에 종속되지만 KV는 서버 사이드 캐시라 다른 디바이스에서 같은 사주를 조회해도 캐시가 적용된다.


모델 선택 기준

Sonnet과 Haiku의 실질적 차이를 카테고리별로 비교했다.

카테고리SonnetHaiku채택
종합 해석 맥락 연결 풍부, 문장 깊이 있음 나열식, 연결이 약함 Sonnet
성격 차이 크지 않음 충분한 품질 Haiku
직업 차이 크지 않음 충분한 품질 Haiku
재물 차이 크지 않음 충분한 품질 Haiku
건강 차이 크지 않음 충분한 품질 Haiku
대인관계 차이 크지 않음 충분한 품질 Haiku
대운 시기 시기별 흐름 연결에서 Sonnet이 나음 단편적 Sonnet 고려

처음엔 종합 해석만 Sonnet으로 쓰다가, 대운 시기 해석도 Sonnet으로 바꿨다. 대운은 10년 단위 흐름을 연결해서 설명해야 하는데 Haiku가 각 시기를 나열하는 수준에 그쳤다. 반면 성격/직업/재물은 단일 카테고리 해석이라 Haiku도 충분했다.


on-demand 호출 구조

모든 카테고리를 한꺼번에 호출하지 않는다. 사용자가 탭을 클릭할 때만 호출한다.

 
 
사주 입력 제출
    ↓
룰 엔진 계산 (즉시)
    ↓
명식표 + 오행 차트 표시 (즉시)
    ↓
종합 해석 자동 호출 (스트리밍 시작)
    ↓
사용자가 "직업 적성" 탭 클릭
    ↓
직업 해석 호출 (스트리밍 시작)
    ↓
캐시 저장

명식표를 먼저 보여주는 게 중요하다. 룰 엔진 계산은 수 밀리초면 끝난다. AI 해석을 기다리는 동안 사용자가 명식표와 오행 차트를 보고 있다. 대기 시간이 같아도 빈 화면을 보는 것과 내용을 보는 것은 체감이 다르다.


토큰 최적화

프롬프트가 길수록 비용이 올라간다. 궁통보감 데이터를 전부 넣으면 입력 토큰이 급증한다. 필요한 조합만 추출해서 넣는 게 핵심이다.

 
 
typescript
// 전체 궁통보감 데이터를 넣지 않는다
// 일간-월지 조합에 해당하는 항목만 추출
function getGungtongboGam(
  dayStem: HeavenlyStem,    // 일간
  monthBranch: EarthlyBranch // 월지
): string {
  return GUNGTONGBO_GAM[dayStem][monthBranch] ?? ''
}

전체 궁통보감 데이터를 프롬프트에 넣으면 입력 토큰이 수천 개 늘어난다. 해당 조합 하나만 넣으면 200~300토큰으로 해결된다. 품질은 동일하다. AI는 관련 있는 데이터만 있으면 된다.

시스템 프롬프트도 최소화했다. 초기엔 명리학 이론 설명을 길게 넣었는데, Claude는 이미 명리학 지식을 갖고 있다. 역할 정의와 제약 조건만 있으면 충분했다. 이론 설명을 빼고 나서 입력 토큰이 약 400개 줄었다.


실제 운영 비용 정리

최적화 전후 비교:

 
 
[최적화 전]
사용자 1명 전체 호출: $0.023
모델: 전체 Sonnet
캐시: 없음

[최적화 후]
사용자 1명 평균 호출(3~4회): $0.008
모델: 종합/대운 Sonnet, 나머지 Haiku
캐시: 24시간 로컬/KV

월 1,000명 기준:
최적화 전: $23
최적화 후: $8
절감: 65%

비용보다 중요한 게 응답 속도다. Haiku가 Sonnet보다 체감상 2~3배 빠르다. 사용자가 탭을 클릭했을 때 스트리밍이 빠르게 시작되면 만족도가 올라간다. 비용과 UX를 동시에 잡는 게 모델 분기의 진짜 이유다.


다음 편에서는 실전 트러블슈팅을 다룬다. 운영 중 실제로 터진 케이스들 — 프롬프트 제약을 뚫고 나오는 할루시네이션, 캐시가 오히려 독이 된 상황, 스트리밍 도중 연결이 끊기는 엣지 케이스까지.

반응형