시스템 프롬프트가 매 요청마다 다시 처리되고 있습니다. 캐싱 하나로 비용의 90%를 날릴 수 있습니다.
[핵심 요약]
→ 문제: LLM API는 같은 시스템 프롬프트도 매번 토큰 비용 청구
→ 해결: 프롬프트 캐싱 — 한 번 처리된 컨텍스트를 재사용
→ 절감: 캐시 히트 시 입력 토큰 비용 90% 절감 (Claude 기준)
→ 지원: Claude (Anthropic), GPT-4o (OpenAI), Gemini 3.1 (Google)
→ 적합한 곳: 긴 시스템 프롬프트, 문서 분석, RAG, 멀티턴 대화
→ 주의: TTL 있음 (Claude 5분, GPT 1시간) — 전략적 설계 필요
프롬프트 캐싱이 왜 필요한가
# 캐싱 없을 때 — 매 요청마다 전체 토큰 과금
system_prompt = """
당신은 법률 전문 AI 어시스턴트입니다.
다음 법률 조항을 참고하여 질문에 답변하세요.
[민법 제1조~제200조 전문]
[상법 제1조~제150조 전문]
[특허법 제1조~제100조 전문]
... (총 50,000 토큰)
"""
# 사용자 질문 100개 → 100번 × 50,000 토큰 = 5,000,000 토큰 과금
# Claude Sonnet 4.6 기준: $3/1M tokens → $15.00
# 캐싱 있을 때
# 첫 번째 요청: 50,000 토큰 처리 (전액 과금)
# 2~100번째 요청: 캐시 히트 → 90% 할인
# 절감액: $15.00 → ~$1.65 (89% 절감)
[캐싱이 효과적인 상황]
→ 긴 시스템 프롬프트 (1,000 토큰 이상)
→ 반복 사용되는 문서/코드베이스 컨텍스트
→ RAG에서 동일 청크가 반복 참조될 때
→ 멀티턴 대화에서 누적되는 히스토리
→ 배치 처리 (같은 컨텍스트로 여러 사용자 서비스)
[캐싱이 효과 없는 상황]
→ 매 요청마다 다른 시스템 프롬프트
→ 짧은 시스템 프롬프트 (1,000 토큰 미만)
→ 요청 간격이 TTL 초과 (Claude 5분, GPT 1시간)
실전 1 — Claude 프롬프트 캐싱
Claude는 cache_control 파라미터로 캐싱을 명시적으로 제어합니다.
import anthropic
client = anthropic.Anthropic()
# ===== 기본 캐싱 =====
def ask_with_cache(question: str, legal_docs: str) -> str:
"""
긴 법률 문서를 캐싱해서 반복 질문 처리
"""
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system=[
{
"type": "text",
"text": "당신은 법률 전문 AI입니다. 다음 법률 문서를 참고하세요.",
},
{
"type": "text",
"text": legal_docs, # 긴 문서
"cache_control": {"type": "ephemeral"} # ← 여기까지 캐싱
}
],
messages=[
{"role": "user", "content": question} # 매번 다른 질문
]
)
# 캐시 사용 여부 확인
usage = response.usage
print(f"입력 토큰: {usage.input_tokens}")
print(f"캐시 생성 토큰: {usage.cache_creation_input_tokens}")
print(f"캐시 읽기 토큰: {usage.cache_read_input_tokens}")
return response.content[0].text
# 첫 번째 호출 — 캐시 생성
result1 = ask_with_cache(
question="계약 해지 조건이 뭔가요?",
legal_docs=LEGAL_DOCS # 50,000 토큰짜리 문서
)
# cache_creation_input_tokens: 50,000
# cache_read_input_tokens: 0
# 두 번째 호출 — 캐시 히트 (5분 이내)
result2 = ask_with_cache(
question="손해배상 청구 기간은?",
legal_docs=LEGAL_DOCS # 동일한 문서
)
# cache_creation_input_tokens: 0
# cache_read_input_tokens: 50,000 ← 90% 할인 적용
# ===== 다중 캐시 포인트 =====
def analyze_codebase(question: str, codebase: str, guidelines: str) -> str:
"""
코드베이스 + 가이드라인 동시 캐싱
캐시 포인트는 최대 4개까지 가능
"""
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=2048,
system=[
{
"type": "text",
"text": "당신은 시니어 개발자입니다."
},
{
"type": "text",
"text": guidelines, # 코딩 가이드라인
"cache_control": {"type": "ephemeral"} # 첫 번째 캐시 포인트
},
{
"type": "text",
"text": codebase, # 전체 코드베이스
"cache_control": {"type": "ephemeral"} # 두 번째 캐시 포인트
}
],
messages=[{"role": "user", "content": question}]
)
return response.content[0].text
[Claude 캐싱 스펙]
→ 최소 캐시 크기: 1,024 토큰 (미만 시 캐싱 안 됨)
→ TTL: 5분 (기본값)
→ 캐시 생성 비용: 일반 입력 토큰의 25% 추가
→ 캐시 히트 비용: 일반 입력 토큰의 10%
→ 캐시 포인트: 시스템 프롬프트당 최대 4개
→ 지원 모델: Claude Haiku 4.5, Sonnet 4.6, Opus 4.7
실전 2 — OpenAI 프롬프트 캐싱
OpenAI는 자동 캐싱을 지원합니다. 별도 설정 없이 동일한 프롬프트 앞부분이 캐싱됩니다.
from openai import OpenAI
client = OpenAI()
def ask_gpt_with_cache(question: str, context: str) -> str:
"""
OpenAI 자동 캐싱 활용
별도 설정 없이 동일 프롬프트 앞부분 자동 캐싱
"""
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{
"role": "system",
"content": context # 동일한 컨텍스트 → 자동 캐싱
},
{
"role": "user",
"content": question
}
]
)
# 캐시 사용 확인
usage = response.usage
cached_tokens = usage.prompt_tokens_details.cached_tokens
total_tokens = usage.prompt_tokens
print(f"전체 프롬프트 토큰: {total_tokens}")
print(f"캐시된 토큰: {cached_tokens}")
print(f"캐시 적중률: {cached_tokens/total_tokens*100:.1f}%")
return response.choices[0].message.content
# 멀티턴 대화에서 캐싱 극대화
class CachedConversation:
"""캐싱을 고려한 멀티턴 대화"""
def __init__(self, system_prompt: str):
self.system_prompt = system_prompt # 항상 동일 → 캐싱
self.history = []
def chat(self, user_message: str) -> str:
self.history.append({
"role": "user",
"content": user_message
})
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": self.system_prompt},
*self.history # 누적 히스토리
]
)
assistant_message = response.choices[0].message.content
self.history.append({
"role": "assistant",
"content": assistant_message
})
return assistant_message
[OpenAI 캐싱 스펙]
→ 방식: 자동 (별도 설정 불필요)
→ 최소 캐시 크기: 1,024 토큰
→ TTL: 1시간 (Claude보다 훨씬 김)
→ 캐시 히트 비용: 일반 가격의 50%
→ 지원 모델: gpt-4o, gpt-4o-mini, o1 시리즈
→ 캐싱 범위: 프롬프트 앞부분부터 정확히 일치하는 부분
실전 3 — Gemini 프롬프트 캐싱
Gemini는 CachedContent 객체로 명시적으로 캐시를 생성합니다.
import google.generativeai as genai
from google.generativeai import caching
import datetime
genai.configure(api_key="YOUR_API_KEY")
# ===== 캐시 생성 =====
def create_document_cache(document_text: str, ttl_minutes: int = 60):
"""
긴 문서를 캐시로 저장
"""
cache = caching.CachedContent.create(
model="gemini-3.1-flash",
display_name="법률문서 캐시",
system_instruction="당신은 법률 전문가입니다. 다음 문서를 참고하세요.",
contents=[
{
"role": "user",
"parts": [{"text": document_text}]
}
],
ttl=datetime.timedelta(minutes=ttl_minutes)
)
print(f"캐시 생성됨: {cache.name}")
return cache
# ===== 캐시 사용 =====
def ask_with_cached_doc(cache, question: str) -> str:
"""캐시된 문서로 질문 처리"""
model = genai.GenerativeModel.from_cached_content(cached_content=cache)
response = model.generate_content(question)
# 사용량 확인
print(f"캐시 토큰: {response.usage_metadata.cached_content_token_count}")
print(f"전체 토큰: {response.usage_metadata.total_token_count}")
return response.text
# 실전 사용
legal_cache = create_document_cache(
document_text=LEGAL_DOCS,
ttl_minutes=60
)
# 같은 캐시로 여러 질문 처리
questions = [
"계약 해지 조건이 뭔가요?",
"손해배상 청구 기간은?",
"계약 위반 시 패널티는?"
]
for q in questions:
answer = ask_with_cached_doc(legal_cache, q)
print(f"Q: {q}\nA: {answer[:100]}...\n")
# ===== 캐시 관리 =====
def list_caches():
"""현재 생성된 캐시 목록 조회"""
for cache in caching.CachedContent.list():
print(f"이름: {cache.name}")
print(f"만료: {cache.expire_time}")
def delete_cache(cache):
"""캐시 삭제"""
cache.delete()
print("캐시 삭제됨")
[Gemini 캐싱 스펙]
→ 방식: 명시적 (CachedContent 객체 생성)
→ 최소 캐시 크기: 32,768 토큰 (다른 모델보다 훨씬 큼)
→ TTL: 기본 1시간, 최대 1일
→ 캐시 히트 비용: 일반 가격의 25%
→ 캐시 저장 비용: 토큰당 시간당 요금 추가
→ 지원 모델: Gemini 3.1 Pro, Flash, Flash-Lite
실전 4 — RAG 파이프라인에서 캐싱 전략
RAG에서 캐싱을 적용하면 특히 큰 효과를 볼 수 있습니다.
import anthropic
from typing import Optional
class CachedRAGPipeline:
"""캐싱 최적화된 RAG 파이프라인"""
def __init__(self):
self.client = anthropic.Anthropic()
self.cached_chunks = {} # 청크별 캐시 추적
def build_cached_prompt(
self,
retrieved_chunks: list[str],
query: str
) -> dict:
"""
검색된 청크를 캐싱 구조로 배치
자주 등장하는 청크를 앞에 배치해 캐시 히트율 극대화
"""
content_blocks = []
# 시스템 안내 (항상 캐싱)
content_blocks.append({
"type": "text",
"text": "다음 문서를 참고하여 질문에 답변하세요.",
"cache_control": {"type": "ephemeral"}
})
# 검색된 청크들 (자주 나오는 것 앞에 배치)
for i, chunk in enumerate(retrieved_chunks):
block = {"type": "text", "text": chunk}
# 마지막 청크에 캐시 포인트 설정
# (앞에서부터 일치하는 부분이 캐싱됨)
if i == len(retrieved_chunks) - 1:
block["cache_control"] = {"type": "ephemeral"}
content_blocks.append(block)
return content_blocks
def query(self, retrieved_chunks: list[str], question: str) -> str:
content_blocks = self.build_cached_prompt(retrieved_chunks, question)
response = self.client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system=content_blocks,
messages=[{"role": "user", "content": question}]
)
# 캐시 효율 출력
usage = response.usage
total_input = usage.input_tokens
cache_read = usage.cache_read_input_tokens or 0
cache_created = usage.cache_creation_input_tokens or 0
if total_input > 0:
hit_rate = cache_read / (total_input + cache_read) * 100
print(f"캐시 히트율: {hit_rate:.1f}%")
return response.content[0].text
# 사용 예시
rag = CachedRAGPipeline()
# 같은 문서 청크로 여러 질문 처리
common_chunks = [
"Chapter 1: Introduction to AI...",
"Chapter 2: Machine Learning...",
"Chapter 3: Deep Learning..."
]
queries = [
"AI의 기본 개념은?",
"머신러닝과 딥러닝의 차이는?",
"딥러닝 모델 학습 방법은?"
]
for q in queries:
answer = rag.query(common_chunks, q)
[RAG 캐싱 최적화 팁]
→ 공통 청크 앞 배치: 여러 쿼리에 공통으로 사용되는 청크를 앞에 배치
→ 청크 순서 고정: 청크 순서가 바뀌면 캐시 미스 발생
→ 배치 처리: 비슷한 쿼리를 묶어서 처리 (캐시 TTL 안에 처리)
→ 청크 크기: 1,024 토큰 이상 청크만 캐싱 효과 있음
실전 5 — 비용 모니터링 및 최적화
from dataclasses import dataclass
from collections import defaultdict
@dataclass
class CacheMetrics:
total_requests: int = 0
cache_hits: int = 0
total_input_tokens: int = 0
cache_creation_tokens: int = 0
cache_read_tokens: int = 0
total_cost_usd: float = 0.0
saved_cost_usd: float = 0.0
class CostOptimizedClient:
"""비용 추적 + 캐싱 최적화 Claude 클라이언트"""
# Claude Sonnet 4.6 가격
PRICES = {
"input": 3.00 / 1_000_000,
"output": 15.00 / 1_000_000,
"cache_create": 3.75 / 1_000_000, # input × 1.25
"cache_read": 0.30 / 1_000_000, # input × 0.10
}
def __init__(self):
self.client = anthropic.Anthropic()
self.metrics = CacheMetrics()
def complete(self, system_blocks: list, user_message: str, **kwargs) -> str:
response = self.client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system=system_blocks,
messages=[{"role": "user", "content": user_message}],
**kwargs
)
# 비용 계산
usage = response.usage
input_tokens = usage.input_tokens
output_tokens = usage.output_tokens
cache_created = getattr(usage, "cache_creation_input_tokens", 0) or 0
cache_read = getattr(usage, "cache_read_input_tokens", 0) or 0
actual_cost = (
input_tokens * self.PRICES["input"] +
output_tokens * self.PRICES["output"] +
cache_created * self.PRICES["cache_create"] +
cache_read * self.PRICES["cache_read"]
)
# 캐싱 없었을 때 예상 비용
no_cache_cost = (
(input_tokens + cache_read) * self.PRICES["input"] +
output_tokens * self.PRICES["output"]
)
saved = no_cache_cost - actual_cost
# 메트릭 누적
self.metrics.total_requests += 1
self.metrics.total_input_tokens += input_tokens
self.metrics.cache_creation_tokens += cache_created
self.metrics.cache_read_tokens += cache_read
self.metrics.total_cost_usd += actual_cost
self.metrics.saved_cost_usd += saved
if cache_read > 0:
self.metrics.cache_hits += 1
return response.content[0].text
def print_report(self):
m = self.metrics
hit_rate = (m.cache_hits / m.total_requests * 100
if m.total_requests > 0 else 0)
print(f"""
=== 캐싱 효율 보고서 ===
총 요청 수: {m.total_requests:,}
캐시 히트: {m.cache_hits:,} ({hit_rate:.1f}%)
총 비용: ${m.total_cost_usd:.4f}
절감 비용: ${m.saved_cost_usd:.4f}
절감률: {m.saved_cost_usd/(m.total_cost_usd+m.saved_cost_usd)*100:.1f}%
""")
# 사용
client = CostOptimizedClient()
system = [
{"type": "text", "text": LONG_SYSTEM_PROMPT,
"cache_control": {"type": "ephemeral"}}
]
for question in questions:
answer = client.complete(system, question)
client.print_report()
# === 캐싱 효율 보고서 ===
# 총 요청 수: 100
# 캐시 히트: 97 (97.0%)
# 총 비용: $0.1823
# 절감 비용: $1.4177
# 절감률: 88.6%
3사 캐싱 스펙 비교
항목 Claude OpenAI GPT-4o Gemini 3.1
| 방식 | 명시적 (cache_control) | 자동 | 명시적 (CachedContent) |
| 최소 크기 | 1,024 토큰 | 1,024 토큰 | 32,768 토큰 |
| TTL | 5분 | 1시간 | 1시간~1일 |
| 캐시 히트 비용 | 입력의 10% | 입력의 50% | 입력의 25% |
| 캐시 생성 비용 | 입력의 125% | 없음 | 별도 저장 요금 |
| 최대 캐시 포인트 | 4개 | 자동 | 1개 |
| 설정 복잡도 | 중간 | 쉬움 | 높음 |
마무리
✅ 프롬프트 캐싱 써야 할 때
→ 시스템 프롬프트가 1,000 토큰 이상
→ 같은 문서/코드베이스를 반복 참조
→ RAG 파이프라인에서 동일 청크가 자주 검색
→ 멀티턴 대화 서비스 (누적 히스토리)
→ 배치 처리 (수백~수천 요청을 같은 컨텍스트로)
❌ 효과 없는 경우
→ 시스템 프롬프트가 매번 다름
→ 1,000 토큰 미만 짧은 프롬프트
→ TTL 초과 간격으로 요청 (Claude 5분, GPT 1시간)
→ 단발성 요청 (캐시 생성 비용이 절감액보다 큼)
[Claude 캐싱 빠른 설정 요약]
→ 시스템 프롬프트 마지막 블록에 cache_control 추가
→ 최소 1,024 토큰 이상인지 확인
→ 5분 이내 반복 요청 패턴인지 확인
→ usage.cache_read_input_tokens로 히트 여부 확인
관련 글:
https://cell-devlog.tistory.com/106
Claude Opus 4.7 토크나이저 함정 — 같은 가격, 더 많은 비용
Anthropic이 4월 16일 Opus 4.7을 출시하면서 이렇게 말했어요."가격 변동 없음. Opus 4.6과 동일한 $5/$25 per MTok"맞아요. 토큰당 가격은 그대로예요.근데 같은 텍스트가 더 많은 토큰으로 쪼개집니다.Anthro
cell-devlog.tistory.com
https://cell-devlog.tistory.com/107
Opus 4.7 에이전트 비용 제어 실전 — effort + Task Budget 완전 가이드
에이전트를 Opus 4.7로 돌리면 비용이 예측 불가예요.왜 예측이 안 되냐:→ 에이전트 루프: 생각 + 툴 호출 + 툴 결과 + 출력이 쌓임→ xhigh 기본값: 더 많이 생각함→ 새 토크나이저: 같은 텍스트도
cell-devlog.tistory.com
https://cell-devlog.tistory.com/91
LLM 모델 라우팅 완전 가이드 — 분류기, 캐스케이딩, 시맨틱 캐시 실전
LLM을 프로덕션에 올리면 첫 달 청구서가 이렇게 나와요.예상: $300/월실제: $2,400/월원인 분석해보면 이래요.고객: "배송 얼마나 걸려요?"→ Claude Opus 4.6 응답 ($0.015/1K토큰)고객: "안녕하세요"→ Claud
cell-devlog.tistory.com
'LLM' 카테고리의 다른 글
| IBM Granite 4.1 완전 분석 — 8B가 32B MoE를 이긴 이유, 파라미터보다 훈련이 중요하다 (0) | 2026.05.06 |
|---|---|
| 프롬프트 버전 관리 완전 가이드 — Git처럼 프롬프트를 관리하는 법 (0) | 2026.04.30 |
| Kimi K2.6 완전 분석 — 오픈소스가 GPT-5.4를 이기고 Claude 비용의 10%로 돌아간다 (0) | 2026.04.28 |
| Microsoft MAI 모델 3종 완전 분석 — OpenAI 없이 만든 음성·이미지 API 실전 가이드 (0) | 2026.04.27 |
| OpenAI Privacy Filter 완전 가이드 — LLM에 개인정보 넣기 전에 로컬에서 자동 마스킹하는 법 (0) | 2026.04.24 |