LLM 토큰 절약·비용 최적화 실전 가이드
TL;DR — 입력·출력·추론 토큰, 프롬프트 캐싱, 모델 라우팅, 배치 API까지 — LLM 비용을 실제로 줄이는 6가지 실무 전략을 코드와 체크리스트로 정리했습니다. Claude·OpenAI·Gemini 공식 문서 기반, 3-AI 교차검증.
LLM 비용 최적화는 "프롬프트를 짧게 쓰는 것"이 아니라, 입력 토큰·출력 토큰·추론 토큰·도구 호출·캐시 적중·배치 처리·모델 라우팅을 한꺼번에 관리하는 엔지니어링 작업입니다. 프로덕션에서 LLM 청구서가 예상보다 3~5배 부풀어 오르는 가장 흔한 원인은 프롬프트 길이가 아니라, 캐시가 매 요청마다 깨지거나, 단순 작업에 고비용 모델과 높은 추론 강도를 그대로 두거나, 실시간이 필요 없는 작업을 실시간 API로 돌리는 데 있습니다.
이 가이드는 벤더 중립적으로 작성했으되 Claude·OpenAI·Gemini의 실제 동작을 구체적으로 다룹니다. 내용은 세 계열의 AI(Anthropic Claude, OpenAI 계열, Google 계열)로 교차검증했으며, 특히 용어 정확성에 주의했습니다 — 현행 Claude 추론은 고정 budget_tokens가 아니라 적응형 사고(adaptive thinking) + effort 레벨을 쓰고, Claude의 토큰 카운팅은 tiktoken이 아니라 count_tokens API를 써야 합니다.
핵심 원칙: 비용은 "토큰 수 × 단가"입니다. 둘 다 줄일 수 있지만, 가장 큰 절감은 보통 같은 토큰을 다시 처리하지 않는 것(캐싱)과 작업 난이도에 맞춰 단가를 낮추는 것(라우팅)에서 나옵니다.
핵심 개념: 비용을 결정하는 토큰의 종류
LLM 청구서를 줄이려면 먼저 무엇에 돈을 내고 있는지 분해해야 합니다.
| 토큰 종류 | 설명 | 통제 수단 |
|---|---|---|
| 입력(input) | 시스템 프롬프트·도구 정의·대화 히스토리·문서 | 캐싱, 컨텍스트 편집/압축 |
| 출력(output) | 모델이 생성하는 답변 | max_tokens, 구조화 출력, 분량 지시 |
| 추론(reasoning/thinking) | 모델 내부 사고 — 숨겨져도 과금됨 | effort 레벨, 적응형 사고 |
| 도구 호출(tool calls) | 함수/도구 정의와 결과 왕복 | 도구 수 제한, 프로그래매틱 호출 |
여기서 가장 자주 놓치는 것이 추론 토큰입니다. 답변 텍스트가 짧아도 모델이 내부적으로 길게 추론하면 그만큼 과금됩니다. "출력을 짧게 하면 추론 비용도 준다"는 흔한 오해이며, 추론 비용은 effort 같은 별도 레버로 통제해야 합니다.
베스트 프랙티스 1 — 프롬프트 캐싱: 반복되는 접두부를 재사용하라
프롬프트 캐싱은 반복되는 긴 접두부(prefix) — 시스템 프롬프트, 도구 정의, 공유 컨텍스트 — 의 prefill 연산을 재사용합니다. 캐시는 입력 prefill을 재사용할 뿐, 출력(답변)은 재사용하지 않습니다. "캐싱이 답변을 재사용한다"는 것은 가장 흔한 오해입니다.
벤더별 구현:
- Anthropic:
cache_control브레이크포인트 - OpenAI/Azure: 정확한 접두부 일치 +
cached_tokens자동 - Gemini: 암묵적(implicit) / 명시적(explicit) 캐싱
캐싱의 절대 원칙은 접두부 일치(prefix match) 입니다. 접두부 어디든 1바이트라도 바뀌면 그 뒤 전체가 무효화됩니다. 따라서 캐시 친화적 순서가 핵심입니다.
# 좋은 순서: 안정적인 내용 먼저, 변동 내용 나중
client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
system=[{
"type": "text",
"text": LARGE_STABLE_INSTRUCTIONS, # 지시문·few-shot·도구 스키마
"cache_control": {"type": "ephemeral"},
}],
messages=[{"role": "user", "content": user_question}], # 변동 부분은 끝에
)
흔한 실패는 시스템 프롬프트 맨 앞에 datetime.now(), 요청 ID, 사용자 이름을 넣는 것입니다. 그러면 매 요청마다 접두부가 달라져 캐시가 0% 적중합니다. 날짜·ID·사용자 데이터 같은 변동 값은 항상 맨 끝으로 옮기세요.
검증은 응답의 사용량 필드로 합니다. 동일 접두부를 반복 호출했는데 cache_read_input_tokens가 계속 0이면 어딘가에 "조용한 무효화"가 있는 것입니다.
print(resp.usage.cache_read_input_tokens) # 캐시에서 읽음(~0.1x 단가)
print(resp.usage.cache_creation_input_tokens) # 캐시에 씀(~1.25x 단가)
print(resp.usage.input_tokens) # 미적중(정상 단가)
베스트 프랙티스 2 — 긴 세션은 압축하되, 결정은 보존하라
에이전트나 멀티턴 세션은 컨텍스트가 계속 누적됩니다. 컨텍스트 편집(오래된 도구 결과 제거)이나 압축(요약)으로 입력 토큰을 줄이되, 반드시 다음 것들을 보존하세요: 내려진 결정, 식별자(ID), 도구 결과 요약, 미해결 블로커, 다음 목표. 이것들을 날리면 모델이 같은 작업을 다시 하면서 오히려 토큰을 더 씁니다.
베스트 프랙티스 3 — 추론 강도를 작업에 맞춰 라우팅하라
현행 Claude(Opus 4.8/4.7 등)는 고정 토큰 예산 대신 적응형 사고와 effort 레벨(low/medium/high/xhigh/max)을 씁니다. budget_tokens는 폐기되었으며 일부 모델에서는 400 에러를 냅니다.
# 단순 분류·요약 → 낮은 effort
client.messages.create(
model="claude-opus-4-8", max_tokens=256,
output_config={"effort": "low"},
messages=[{"role": "user", "content": "이 리뷰를 긍정/부정으로 분류"}],
)
# 복잡한 코드·수학·계획 → 높은 effort + 적응형 사고
client.messages.create(
model="claude-opus-4-8", max_tokens=16000,
thinking={"type": "adaptive"},
output_config={"effort": "high"},
messages=[{"role": "user", "content": COMPLEX_REFACTOR_TASK}],
)
핵심: 추론 토큰은 숨겨져도 과금됩니다. 사소한 작업에 높은 effort를 켜둔 채 방치하지 마세요. 단순 작업은 low, 복잡한 작업만 high 이상으로 올리는 것이 가장 직접적인 절감 레버 중 하나입니다.
베스트 프랙티스 4 — 작은 모델로 라우팅하라
쉽거나 반복적인 요청은 저가·고속 모델로, 어렵거나 불확실하거나 고위험인 요청만 대형 모델로 에스컬레이션하세요. 예를 들어 분류·간단 요약은 Haiku 4.5나 Gemini 3.5 Flash 같은 저가 모델로, 복잡한 추론은 Opus 4.8이나 GPT-5.5 같은 플래그십으로 보냅니다. 모델 간 단가 차이가 5배 이상인 경우가 많으므로, 트래픽의 대부분을 차지하는 쉬운 요청을 저가 모델로 빼는 것만으로 전체 비용이 크게 떨어집니다.
베스트 프랙티스 5 — 실시간이 아니면 배치 API를 써라
실시간 UX가 필요 없는 작업(야간 분류, 대량 임베딩, 백필 요약)은 배치 API로 처리하세요. 보통 표준 대비 약 50% 저렴하고, 최대 24시간 안에 완료됩니다(대부분 1시간 내).
batch = client.messages.batches.create(requests=[
Request(custom_id=f"item-{i}", params=MessageCreateParamsNonStreaming(
model="claude-haiku-4-5", max_tokens=50,
messages=[{"role": "user", "content": f"분류: {text}"}],
)) for i, text in enumerate(items)
])
배치에도 프롬프트 캐싱·도구·비전 등 모든 기능이 그대로 적용됩니다. 다만 실시간 채팅 UX에 배치를 쓰면 안 됩니다 — 응답 지연이 수십 분이 될 수 있습니다.
베스트 프랙티스 6 — 출력을 제약하고, 실제 비용을 측정하라
출력 토큰은 입력보다 단가가 보통 5배 높으므로 통제 가치가 큽니다. max_tokens를 합리적으로 잡고, 구조화 출력(JSON 스키마)이나 "불릿 N개만" 같은 지시로 분량을 제한하세요.
토큰 추정은 절대 글자수/4 같은 로컬 어림으로 신뢰하지 마세요. 이미지·파일·도구 스키마·구조화 출력은 추정과 크게 다릅니다. Claude는 count_tokens API로 정확히 셉니다. tiktoken은 OpenAI 토크나이저라 Claude에서는 부정확합니다(코드·비영어에서 더 심함).
n = client.messages.count_tokens(
model="claude-opus-4-8",
messages=messages, system=system,
).input_tokens
print(f"예상 입력 비용: ${n * 5e-6:.4f}")
최적화 체크리스트
- 시스템 프롬프트·도구 정의·few-shot을 앞에, 타임스탬프·사용자 데이터를 뒤에 배치했는가
- 시스템 프롬프트 앞에
datetime.now()·UUID·사용자 ID가 들어가 있지 않은가 -
cache_read_input_tokens로 캐시 적중을 실제로 확인했는가 - 단순 작업의 effort를
low로 내렸는가 (추론 토큰은 숨겨져도 과금) - 쉬운 요청을 저가 모델로 라우팅하고, 어려운 것만 에스컬레이션하는가
- 실시간이 아닌 작업을 배치 API(약 50% 절감)로 돌리는가
-
max_tokens와 출력 형식(JSON/불릿)으로 출력을 제약했는가 - 토큰을
글자수/4가 아니라 count_tokens API로 측정했는가 - 긴 세션 압축 시 결정·ID·블로커·다음 목표를 보존했는가
흔한 오해와 실수 정리
- "캐싱이 답변을 재사용한다" — 아니요. 입력 접두부의 prefill만 재사용합니다. 출력은 매번 새로 생성·과금됩니다.
- 날짜·요청 ID·사용자명을 프롬프트 맨 앞에 둠 — 캐시가 매 요청 깨집니다. 끝으로 옮기세요.
- "출력을 줄이면 추론 비용도 준다" — 추론 토큰은 별도 레버(effort/적응형 사고)로 통제해야 합니다.
- 로컬
글자수/4추정만 믿음 — 이미지·도구 스키마·구조화 출력에서 크게 어긋납니다. - 고정
budget_tokens사용 — 폐기된 방식입니다. 적응형 사고 + effort를 쓰세요. - Claude 토큰을 tiktoken으로 셈 — OpenAI 토크나이저라 부정확합니다. count_tokens API를 쓰세요.
- 실시간 UX에 배치 API 사용 — 응답이 수십 분 지연됩니다. 배치는 비실시간 전용입니다.
마무리
LLM 비용 최적화의 핵심은 한 줄로 요약됩니다 — 같은 토큰을 다시 처리하지 말고(캐싱), 작업 난이도에 단가를 맞추라(라우팅·effort). 여기에 배치 처리, 출력 제약, 정확한 토큰 측정을 더하면 대부분의 워크로드에서 비용을 절반 이하로 줄일 수 있습니다. 모든 권장 사항은 Anthropic·OpenAI·Gemini의 공식 문서(프롬프트 캐싱, 컨텍스트 편집, 배치, 토큰 카운팅)를 근거로 하며, 세 계열 AI로 교차검증했습니다.
이 가이드는 시리즈의 일부입니다. 에이전트를 실제로 설계·운영하는 방법은 AI 에이전트 스킬 가이드와 에이전트 하네스 엔지니어링 가이드에서 이어서 확인하세요. 다음 단계로, 여러분의 프로덕션 프롬프트 한 개를 골라 위 체크리스트를 적용하고 cache_read_input_tokens를 측정해 보세요 — 첫 절감 효과를 바로 눈으로 확인할 수 있습니다.