Back

LLM API 비용 90% 절감하는 법: 캐싱, 라우팅, 프롬프트 최적화 실전 가이드

이번 달 LLM API 청구서, 2,400찍혔어요.지난달2,400 찍혔어요. 지난달 800, 그 전달 $300이었거든요. 프로덕트는 잘 크고 있고, 유저들은 AI 기능 좋아하는데, 재무팀이 슬슬 눈치를 주기 시작했어요.

공감되죠? 여러분만 그런 거 아니에요. 2026년 기준으로 AI 달린 SaaS는 인프라 예산의 30-50%를 LLM API에 갖다 붓고 있어요. 근데 그 돈 대부분은 안 써도 되는 거예요.

불편한 진실 하나. 대부분의 앱이 같은 쿼리를 반복해서 보내고, 간단한 작업에 비싼 모델을 태우고, 필요 이상으로 토큰을 쏟아내고 있어요. 답은 AI를 덜 쓰는 게 아니라, 똑똑하게 쓰는 거예요.

이 글에서 LLM API 비용을 70-90% 깎을 수 있는 5가지 실전 전략을 풀어볼게요. 이론 얘기 아니에요. 전부 프로덕션 코드, 실제 비용 계산, 솔직한 트레이드오프 분석까지 들어 있어요.

비용 구조부터 파악하기

최적화 전에, 과금이 어떻게 돌아가는지부터 알아야 해요. "토큰에 돈이 든다" 정도는 알아도, 정확히 어디서 얼마나 빠지는지 모르는 분들이 대부분이에요.

토큰 이코노믹스

주요 LLM 프로바이더들이 다 토큰 단위로 과금하는데, 생각보다 계산이 단순하지 않아요:

2026년 가격 스냅샷 (1M 토큰 기준):

┌─────────────────────────┬──────────┬───────────┐
│ 모델                     │  입력     │  출력     │
├─────────────────────────┼──────────┼───────────┤
│ GPT-4.1                 │  $1.00   │   $4.00   │
│ GPT-4.1 mini            │  $0.20   │   $0.80   │
│ GPT-4.1 nano            │  $0.05   │   $0.20   │
│ Claude Opus 4.6         │  $5.00   │  $25.00   │
│ Claude Sonnet 4.6       │  $3.00   │  $15.00   │
│ Claude Haiku 4.5        │  $1.00   │   $5.00   │
│ Gemini 2.5 Pro          │  $1.25   │  $10.00   │
│ Gemini 2.5 Flash        │  $0.30   │   $2.50   │
│ DeepSeek V4             │  $0.30   │   $0.50   │
└─────────────────────────┴──────────┴───────────┘

바로 눈에 들어오는 게 두 가지 있어요:

  1. 출력 토큰이 입력보다 3-5배 비싸요. 200토큰짜리 프롬프트 넣었는데 2,000토큰 응답이 나오면, 생각보다 청구서가 훨씬 세요.
  2. 모델 급 차이가 장난 아니에요. Claude Opus 4.6 출력 토큰이 GPT-4.1 nano보다 125배 비싼데, 막상 해보면 싼 모델로도 충분한 작업이 수두룩해요.

돈이 실제로 어디서 새는지

보통 AI가 들어간 앱에서 비용 분포를 까보면:

LLM 비용이 빠지는 곳:

  반복/비슷한 쿼리:           35-45%  ← 캐싱으로 잡을 수 있음
  싼 모델로도 될 걸 비싼 걸로: 20-30%  ← 라우팅으로 해결
  뻥튀기된 프롬프트:          15-20%  ← 압축 가능
  진짜 필요한 복잡한 쿼리:     10-20%  ← 출력 길이 조절

정리하면 LLM 비용의 60-80%는 깎을 수 있는 부분이에요. 프로덕트 품질 안 건드리고요. 하나씩 파볼게요.

전략 1: 시맨틱 캐싱

뽕을 가장 많이 뽑을 수 있는 최적화예요. 고객 지원, 콘텐츠 생성, 코드 어시스턴트, 검색 — 이런 반복 패턴이 있는 서비스를 만들고 있다면, 같은 API 호출을 수천 번 반복하고 있을 확률이 높아요.

그냥 캐싱의 한계

기존 캐싱은 문자열이 100% 똑같아야 히트가 나요. 근데 LLM 쿼리가 글자 하나 안 다르고 들어올 리가 없잖아요. 이것 좀 보세요:

"비밀번호 어떻게 재설정해요?"
"비밀번호 변경하고 싶어요"
"비밀번호를 잊어버렸는데 초기화 방법이 뭐예요?"

다 같은 질문인데, 정확 매칭 캐시한테는 3건의 개별 유료 API 호출이에요. 돈이 3배로 나가는 거죠.

시맨틱 캐싱이 뭔데

쿼리를 임베딩 벡터로 바꾼 다음, 코사인 유사도로 "의미가 비슷한" 캐시 응답을 찾아오는 방식이에요:

import { OpenAI } from 'openai'; import { createClient } from 'redis'; import { Index } from '@upstash/vector'; const openai = new OpenAI(); const redis = createClient(); const vectorIndex = new Index({ url: process.env.UPSTASH_VECTOR_URL!, token: process.env.UPSTASH_VECTOR_TOKEN!, }); interface CachedResponse { response: string; model: string; timestamp: number; hitCount: number; } const SIMILARITY_THRESHOLD = 0.92; // 이 값 튜닝이 핵심 const CACHE_TTL = 86400; // 24시간 async function queryWithSemanticCache( prompt: string, systemPrompt: string, model: string = 'gpt-4.1-mini' ): Promise<{ response: string; cached: boolean; savings: number }> { // 1. 들어온 쿼리의 임베딩 생성 const embedding = await openai.embeddings.create({ model: 'text-embedding-3-small', input: `${systemPrompt}\n${prompt}`, }); const vector = embedding.data[0].embedding; // 2. 의미적으로 유사한 캐시 검색 const results = await vectorIndex.query({ vector, topK: 1, includeMetadata: true, }); // 3. 충분히 유사한 매치가 있는지 확인 if (results.length > 0 && results[0].score >= SIMILARITY_THRESHOLD) { const cacheKey = results[0].id; const cached = await redis.get(`llm:${cacheKey}`); if (cached) { const parsed: CachedResponse = JSON.parse(cached); parsed.hitCount++; await redis.set(`llm:${cacheKey}`, JSON.stringify(parsed), { EX: CACHE_TTL, }); return { response: parsed.response, cached: true, savings: estimateCost(prompt, parsed.response, model), }; } } // 4. 캐시 미스 — 실제 API 호출 const completion = await openai.chat.completions.create({ model, messages: [ { role: 'system', content: systemPrompt }, { role: 'user', content: prompt }, ], }); const response = completion.choices[0].message.content!; // 5. 벡터 인덱스와 Redis 양쪽에 저장 const cacheId = crypto.randomUUID(); await vectorIndex.upsert({ id: cacheId, vector, metadata: { prompt: prompt.slice(0, 200) }, }); await redis.set( `llm:${cacheId}`, JSON.stringify({ response, model, timestamp: Date.now(), hitCount: 0, } satisfies CachedResponse), { EX: CACHE_TTL } ); return { response, cached: false, savings: 0 }; }

유사도 임계값이 핵심이에요

SIMILARITY_THRESHOLD 이 값 하나가 성패를 가른다고 봐도 돼요. 잘못 잡으면:

  • 너무 높으면 (>0.97): 아무것도 안 걸려요. 히트율 제로. 비싼 삽질만 한 꼴이에요.
  • 너무 낮으면 (<0.85): 엉뚱한 쿼리한테 캐시 응답이 나가요. 유저가 이상한 답을 받고, 프로덕트 신뢰가 깨져요.

어디에 쓰느냐에 따라 적정값이 달라요:

유스케이스권장 임계값이유
FAQ / 고객 지원0.90 - 0.93질문이 주제별로 뭉치니까
코드 생성0.95 - 0.97프롬프트 살짝만 달라져도 코드가 완전히 달라짐
콘텐츠 요약0.88 - 0.92같은 문서면 어떻게 물어봐도 같은 요약이 나와야 하니까
창작 글쓰기캐싱 ㄴㄴ매번 새로운 결과가 나와야 함

실제로 얼마나 아낄 수 있나

월 10만 건 쿼리 처리하는 고객 지원 챗봇 기준으로 뜯어볼게요:

캐싱 없이:
  100,000 쿼리 × ~500 입력 토큰 × ~800 출력 토큰
  = 50M 입력 + 80M 출력 토큰
  GPT-4.1 mini 기준: (50 × $0.20) + (80 × $0.80) = $74/월

시맨틱 캐싱 적용 (65% 히트율):
  35,000 실제 API 호출 + 65,000 캐시 히트
  임베딩 비용: 100,000 × ~20 토큰 × $0.02/1M = $0.04
  API 호출: (17.5M × $0.20 + 28M × $0.80) / 1M = $25.90/월
  벡터 DB: ~$10/월 (Upstash, Pinecone 등)
  Redis: ~$5/월

  총계: ~$41/월 → 45% 절감

이거 65% 히트율로 보수적으로 잡은 거예요. 반복 패턴 뚜렷한 서비스(서포트 봇, 문서 검색)는 80% 넘는 게 보통이라, 절감 폭이 70%를 훌쩍 넘어요.

전략 2: 모델 라우팅

"괜히 복잡해질 것 같아서" 대부분 팀이 안 하는 최적화인데, 숫자를 보면 생각이 바뀌어요. 지금 15/M토큰모델한테시키고있는일중에,15/M 토큰 모델한테 시키고 있는 일 중에, 0.05/M 모델로도 충분한 게 수두룩할 거예요.

라우팅 구조

개념은 간단해요. 비싼 모델한테 보내기 전에, 이 쿼리가 얼마나 어려운지 먼저 판단해서 감당할 수 있는 제일 싼 모델로 배정하는 거예요.

type ComplexityLevel = 'trivial' | 'simple' | 'moderate' | 'complex'; interface RouteConfig { model: string; maxTokens: number; temperature: number; costPer1MInput: number; costPer1MOutput: number; } const MODEL_ROUTES: Record<ComplexityLevel, RouteConfig> = { trivial: { model: 'gpt-4.1-nano', maxTokens: 256, temperature: 0.1, costPer1MInput: 0.05, costPer1MOutput: 0.20, }, simple: { model: 'gpt-4.1-mini', maxTokens: 1024, temperature: 0.3, costPer1MInput: 0.20, costPer1MOutput: 0.80, }, moderate: { model: 'claude-haiku-4.5', maxTokens: 2048, temperature: 0.5, costPer1MInput: 1.00, costPer1MOutput: 5.00, }, complex: { model: 'claude-sonnet-4.6', maxTokens: 4096, temperature: 0.7, costPer1MInput: 3.00, costPer1MOutput: 15.00, }, }; async function classifyComplexity(prompt: string): Promise<ComplexityLevel> { // 가장 싼 모델로 분류 — 메타 최적화! const classification = await openai.chat.completions.create({ model: 'gpt-4.1-nano', max_tokens: 10, temperature: 0, messages: [ { role: 'system', content: `사용자 쿼리의 복잡도를 분류하세요. 한 단어로만 답하세요: - "trivial": 인사, Yes/No 질문, 단순 조회 - "simple": 단일 단계 작업, 기본 Q&A, 포맷팅 - "moderate": 다단계 추론, 비교, 분석 - "complex": 창작, 코드 생성, 심층 리서치 분류 단어만 응답하세요.`, }, { role: 'user', content: prompt }, ], }); const level = classification.choices[0].message.content! .trim() .toLowerCase() as ComplexityLevel; return MODEL_ROUTES[level] ? level : 'simple'; }

분류 비용 역설

"잠깐, 분류하려고 API를 한 번 더 쏘잖아요. 그거 오히려 손해 아니에요?"

계산 한 번 해보면 바로 답이 나와요. 분류기는 GPT-4.1 nano로 입력 ~100토큰, 출력 ~5토큰:

쿼리당 분류 비용:
  (100 / 1M) × $0.05 + (5 / 1M) × $0.20 = $0.000006

전부 Claude Sonnet 4.6으로 보낼 때 쿼리당 비용:
  (500 / 1M) × $3.00 + (800 / 1M) × $15.00 = $0.0135

60%를 저렴한 모델로 라우팅했을 때 절감 효과:
  라우팅 없이: 쿼리당 $0.0135
  라우팅 적용 (40% Sonnet, 30% Haiku, 20% Mini, 10% Nano):
    = (0.4 × $0.0135) + (0.3 × $0.0045) + (0.2 × $0.00074) + (0.1 × $0.000185) + $0.000006
    = 쿼리당 $0.0069

  절감률: 쿼리당 49%

분류기가 자기 몸값의 1000배를 뽑아와요. 이것도 보수적으로 잡은 거고, 실제로는 70% 넘는 쿼리가 최저가 모델로 빠지는 경우가 흔해요.

심화: 품질 체크 + 폴백

한 단계 더 가면 라우팅만 하는 게 아니라, 싼 모델 결과가 쓸 만한지 검증까지 해요:

async function routedQueryWithFallback( prompt: string, systemPrompt: string, qualityThreshold: number = 0.7 ): Promise<{ response: string; model: string; attempts: number }> { const complexity = await classifyComplexity(prompt); const route = MODEL_ROUTES[complexity]; const response = await callModel(route.model, systemPrompt, prompt, { maxTokens: route.maxTokens, }); // complex가 아닌 경우, 저렴한 체크로 품질 검증 if (complexity !== 'complex') { const qualityScore = await quickQualityCheck(prompt, response); if (qualityScore < qualityThreshold) { // 다음 등급으로 에스컬레이션 const nextTier = getNextTier(complexity); const betterResponse = await callModel( MODEL_ROUTES[nextTier].model, systemPrompt, prompt, { maxTokens: MODEL_ROUTES[nextTier].maxTokens } ); return { response: betterResponse, model: MODEL_ROUTES[nextTier].model, attempts: 2 }; } } return { response, model: route.model, attempts: 1 }; }

전략 3: 프롬프트 다이어트

프롬프트 대부분이 군살투성이예요. 특히 시스템 프롬프트는 시간이 지날수록 지침, 예시, 엣지 케이스가 덕지덕지 붙어요. 200토큰으로 시작한 게 2,000토큰이 되고 — 그게 매 요청마다 곱해지는 거예요.

시스템 프롬프트부터 손보기

실제 프로덕션에서 쓰던 시스템 프롬프트 Before/After예요:

Before (847 토큰):

당신은 우리 이커머스 플랫폼의 도움되는 고객 지원 어시스턴트입니다.
항상 정중하고 전문적인 응답을 해야 합니다.
고객이 주문에 대해 물어보면 주문 세부 정보를 조회하여
주문 상태에 대한 관련 정보를 제공해야 합니다. 가능한 경우
운송장 번호도 포함해주세요...
(400토큰 이상 계속)

After (189 토큰):

이커머스 지원 에이전트. 규칙:
- 주문 조회, 상태 + 운송장 제공
- 조회 불가 시 지원팀 안내
- 고객 언어에 맞춤
- 내부 시스템/가격/직원 정보 절대 비공개
- 불만 고객: 공감 → 해결책 제시
- 톤: 전문적, 따뜻하게, 간결하게

똑같이 동작하는데, 토큰 78% 절감. GPT-4.1 mini로 월 10만 건 기준:

토큰 절감: 658 토큰 × 100,000 = 65.8M 토큰/월
비용 절감: (65.8 / 1M) × $0.20 = 월 $13.16 (입력만)

13/월이별것아닌것같지만쌓여요.AI기능이10개인데다프롬프트가뚱뚱하면,13/월이 별것 아닌 것 같지만 쌓여요. AI 기능이 10개인데 다 프롬프트가 뚱뚱하면, 월 130 — 연간 $1,560 — 프롬프트 다이어트만으로 아끼는 거예요.

대화 히스토리 압축

RAG나 대화 기록처럼 동적으로 컨텍스트가 붙는 프롬프트는 보내기 전에 압축하세요:

function compressConversationHistory( messages: Array<{ role: string; content: string }>, maxTokens: number = 2000 ): Array<{ role: string; content: string }> { const estimated = messages.reduce( (sum, m) => sum + estimateTokens(m.content), 0 ); if (estimated <= maxTokens) return messages; // 전략: 첫 메시지(컨텍스트)와 마지막 N개는 유지 // 중간은 요약 const first = messages[0]; const last3 = messages.slice(-3); const middle = messages.slice(1, -3); if (middle.length === 0) return messages; const middleSummary = middle .map(m => `${m.role}: ${m.content.slice(0, 50)}...`) .join('\n'); return [ first, { role: 'system', content: `[대화 요약: ${middleSummary}]`, }, ...last3, ]; }

출력 토큰 함정

진짜 중요한 거 하나. 출력 토큰이 입력보다 3-5배 비싸요. max_tokens 거는 게 비용 잡는 제일 쉬운 방법이에요:

// ❌ 안 좋은 예: 모델이 길게 쓰도록 방치 const response = await openai.chat.completions.create({ model: 'gpt-4.1-mini', messages: [{ role: 'user', content: '이 기사 요약해줘' }], // max_tokens 없음 = 모델이 길이 결정 }); // ✅ 좋은 예: 출력 길이 제어 const response = await openai.chat.completions.create({ model: 'gpt-4.1-mini', messages: [{ role: 'user', content: '이 기사를 3문장으로 요약해줘.' }], max_tokens: 200, });

구조화된 출력이 필요하면 JSON 모드가 답이에요 — 짧고 예측 가능한 응답이 일관되게 나와요:

const response = await openai.chat.completions.create({ model: 'gpt-4.1-mini', response_format: { type: 'json_schema', json_schema: { name: 'sentiment_analysis', schema: { type: 'object', properties: { sentiment: { type: 'string', enum: ['positive', 'negative', 'neutral'] }, confidence: { type: 'number' }, keywords: { type: 'array', items: { type: 'string' }, maxItems: 5 }, }, required: ['sentiment', 'confidence', 'keywords'], }, }, }, messages: [{ role: 'user', content: `분석: "${text}"` }], max_tokens: 100, });

전략 4: 배치로 몰아서 처리

당장 결과가 필요한 게 아니면, 배치 API가 반값이에요. OpenAI Batch API는 실시간 호출 대비 50% 할인. 야간 콘텐츠 생성, 대량 분류, 데이터 보강 같은 건 이걸로 거저 처리할 수 있어요.

배치가 맞는 경우

실시간 필수:
  ✗ 유저한테 바로 보여주는 채팅
  ✗ 코드 자동완성
  ✗ 실시간 검색

배치로 돌려도 되는 것:
  ✓ 밤에 돌리는 콘텐츠 요약
  ✓ 이메일 대량 분류
  ✓ 주간 리포트
  ✓ 데이터 라벨링/보강
  ✓ 밀린 모더레이션
  ✓ 번역 몰아서

구현

import { OpenAI } from 'openai'; import * as fs from 'fs'; const openai = new OpenAI(); interface BatchItem { custom_id: string; method: 'POST'; url: '/v1/chat/completions'; body: { model: string; messages: Array<{ role: string; content: string }>; max_tokens: number; }; } async function submitBatchJob( items: Array<{ id: string; prompt: string }>, systemPrompt: string, model: string = 'gpt-4.1-mini' ): Promise<string> { // 1. JSONL 파일 생성 const batchLines: string[] = items.map(item => { const batchItem: BatchItem = { custom_id: item.id, method: 'POST', url: '/v1/chat/completions', body: { model, messages: [ { role: 'system', content: systemPrompt }, { role: 'user', content: item.prompt }, ], max_tokens: 1024, }, }; return JSON.stringify(batchItem); }); const filePath = `/tmp/batch-${Date.now()}.jsonl`; fs.writeFileSync(filePath, batchLines.join('\n')); // 2. 파일 업로드 const file = await openai.files.create({ file: fs.createReadStream(filePath), purpose: 'batch', }); // 3. 배치 생성 const batch = await openai.batches.create({ input_file_id: file.id, endpoint: '/v1/chat/completions', completion_window: '24h', }); return batch.id; }

비용 효과

매일 밤 10,000건을 처리하는 작업 기준:

실시간 API (GPT-4.1 mini):
  10,000 × (500 입력 + 800 출력 토큰)
  = (5M × $0.20 + 8M × $0.80) / 1M = $7.40

배치 API (50% 할인):
  = $3.70

월간 절감: ~$111 (매일 실행 가정)
연간 절감: ~$1,332

전략 5: LLM 게이트웨이와 모니터링

재볼 수 없으면 줄일 수도 없어요. 앱이랑 API 프로바이더 사이에 게이트웨이를 하나 끼우면:

  • 기능별 비용 추적 — 어떤 기능이 돈을 태우는지 한눈에
  • 자동 재시도 + 페일오버 — OpenAI 터지면 Anthropic으로 넘기기
  • 레이트 리미팅 — 버그나 어뷰징으로 비용 폭주 방지
  • 사용량 분석 — 다음 최적화 포인트 찾기

가벼운 게이트웨이 만들기

interface LLMRequest { featureId: string; prompt: string; systemPrompt: string; model?: string; maxTokens?: number; userId?: string; } interface LLMMetrics { featureId: string; model: string; inputTokens: number; outputTokens: number; cost: number; latencyMs: number; cached: boolean; timestamp: number; } class LLMGateway { private metrics: LLMMetrics[] = []; private rateLimiter: Map<string, { count: number; resetAt: number }> = new Map(); async query(request: LLMRequest): Promise<string> { // 1. 레이트 리미팅 this.enforceRateLimit(request.featureId); // 2. 예산 체크 await this.checkBudget(request.featureId); const startTime = Date.now(); // 3. 시맨틱 캐시 먼저 시도 const cached = await queryWithSemanticCache( request.prompt, request.systemPrompt, request.model ); if (cached.cached) { this.recordMetrics({ featureId: request.featureId, model: request.model || 'gpt-4.1-mini', inputTokens: 0, outputTokens: 0, cost: 0, latencyMs: Date.now() - startTime, cached: true, timestamp: Date.now(), }); return cached.response; } // 4. 최적 모델로 라우팅 const result = await routedQuery(request.prompt, request.systemPrompt); // 5. 메트릭 기록 this.recordMetrics({ featureId: request.featureId, model: result.model, inputTokens: estimateTokens(request.prompt + request.systemPrompt), outputTokens: estimateTokens(result.response), cost: result.cost, latencyMs: Date.now() - startTime, cached: false, timestamp: Date.now(), }); return result.response; } }

이 정도 대시보드는 있어야 해요

최소한 이것들은 기능별, 일별로 찍고 있어야 해요:

┌─────────────────────────────────────────────┐
│ LLM 비용 대시보드 - 2026년 3월              │
├──────────────┬────────┬────────┬────────────┤
│ 기능         │ 호출수  │  비용  │ 캐시 히트  │
├──────────────┼────────┼────────┼────────────┤
│ 채팅 지원    │ 45,231 │ $89.40 │    72%     │
│ 코드 리뷰    │ 12,847 │ $67.20 │    34%     │
│ 요약         │  8,392 │ $12.10 │    81%     │
│ 검색         │ 31,094 │ $23.40 │    68%     │
│ 번역         │  3,201 │  $8.90 │    45%     │
├──────────────┼────────┼────────┼────────────┤
│ 합계         │100,765 │$201.00 │    63%     │
└──────────────┴────────┴────────┴────────────┘

 최적화 전: $890
 최적화 후: $201
 아낀 돈: $689 (77%)

다 합치면: 최적화 스택

이 전략들은 겹겹이 쌓을수록 효과가 커져요:

들어오는 LLM 요청
        │
        ▼
  ┌─────────────┐
  │ 레이트 리미터 │─── 초과? → 에러
  └──────┬──────┘
         │
         ▼
  ┌──────────────┐
  │ 예산 체크     │─── 초과? → 에러
  └──────┬───────┘
         │
         ▼
  ┌────────────────────┐
  │ 시맨틱 캐시         │─── 히트? → 캐시 응답 반환
  └──────────┬─────────┘
             │ 미스
             ▼
  ┌────────────────────┐
  │ 프롬프트 압축       │─── 토큰 줄이기
  └──────────┬─────────┘
             │
             ▼
  ┌────────────────────┐
  │ 모델 라우터         │─── 처리 가능한 최저가 모델 선택
  └──────────┬─────────┘
             │
             ▼
  ┌────────────────────┐
  │ API 호출 + 메트릭   │─── 모든 것 기록
  └──────────┬─────────┘
             │
             ▼
  ┌────────────────────┐
  │ 응답 캐싱          │─── 다음 히트를 위해 저장
  └────────────────────┘

현실적인 시뮬레이션

실제 시나리오로 뜯어볼게요 — 월 20만 건 쿼리 처리하는 개발문서 어시스턴트가 지금 전부 Claude Sonnet 4.6으로 돌리고 있다면:

현재 (최적화 없음):
  200,000 쿼리 × 600 평균 입력 × 1,200 평균 출력 토큰
  입력: 120M × $3.00/1M = $360
  출력: 240M × $15.00/1M = $3,600
  합계: $3,960/월

전략 다 적용하면:
  1. 시맨틱 캐싱 (70% 히트율): 14만 건 날림
     남은 거: 6만 건 / 임베딩: $0.24 / 인프라: $15/월

  2. 6만 건 모델 라우팅:
     - 15% complex → Claude Sonnet 4.6 (9,000건)
     - 25% moderate → Claude Haiku 4.5 (15,000건)
     - 40% simple → GPT-4.1 mini (24,000건)
     - 20% trivial → GPT-4.1 nano (12,000건)

  3. 프롬프트 압축으로 토큰 30% 추가 절감

  계산:
     Sonnet: $124.74 / Haiku: $69.30
     Mini: $18.14 / Nano: $2.27
     분류기: $0.36 / 인프라: $15 / 임베딩: $0.24

  합계: ~$230/월

  절감: 월 $3,730 (94%)

3,9603,960 → 230. 연간 $44,760 절감. 솔직히 이 정도면 스타트업에서는 생존이냐 폐업이냐 수준의 차이예요.

하면 안 되는 것들

1. 캐시 걸어놓고 갱신 안 하기

캐시 응답은 시간 지나면 상해요. 상품 정보가 바뀌었는데 봇이 어제 답변을 뱉으면 안 되잖아요. TTL 설정하고, 콘텐츠 바뀔 때 수동으로 캐시 날리는 로직도 넣어두세요.

2. 싼 모델에 너무 많이 몰기

유저 5%가 잘못 분류된 쿼리 때문에 허접한 답변을 받으면, 돈은 아꼈는데 신뢰를 잃어요. 품질 점수 모니터링하면서 위에서 설명한 폴백 패턴 꼭 같이 쓰세요.

3. 프롬프트 너무 깎기

컨텍스트를 빼면 응답 품질이 은근히 떨어져요. 프롬프트 바꿀 때는 무조건 A/B 테스트로 품질 확인하고 나서 적용하세요.

4. 프로바이더 한 곳에 올인

프로바이더마다 잘하는 게 달라요. Claude는 긴 분석에 강하고, GPT-4.1 nano는 분류 가성비 최고, Gemini Flash는 범용에서 가격 대비 성능이 좋아요. 한 군데만 쓰지 마세요.

5. 임베딩 비용 까먹기

시맨틱 캐싱은 쿼리마다 임베딩을 해야 해요. 규모 커지면 이것도 만만치 않아요. text-embedding-3-small 쓰고 (large 말고), 임베딩도 배치로 묶어서 보내세요.

마무리

LLM API 비용은 자연법칙이 아니에요 — 엔지니어링으로 풀 수 있는 엔지니어링 문제예요. 여기 나온 5가지 — 시맨틱 캐싱, 모델 라우팅, 프롬프트 다이어트, 배치, 모니터링 — 다 적용하면 품질 안 건드리고 70-90% 깎을 수 있어요.

뭐부터 할지 모르겠으면 이 순서로:

  1. 시맨틱 캐싱 — 효과 최고, 제일 먼저
  2. 프롬프트 다이어트 — 오늘 당장 할 수 있음
  3. 모델 라우팅 — 절감 폭 크고, 난이도 중간
  4. 모니터링 — 다음에 뭘 고칠지 보려면 이게 있어야 함
  5. 배치 — 백그라운드 작업은 이걸로 공짜 절감

하나 적용하고, 효과 재고, 그 위에 다음 거 얹으세요. 다섯 개 다 적용한 팀들은 "시리즈 B를 OpenAI한테 갖다 바치나" 에서 "LLM 비용? 반올림 오차인데" 로 바뀌더라고요.

API 청구서, 무서워할 거 없어요. 재미없게 만들어버리면 돼요.

AILLMOpenAIAnthropiccost-optimizationsemantic-cachingprompt-engineeringTypeScript

관련 도구 둘러보기

Pockit의 무료 개발자 도구를 사용해 보세요