프롬프트 캐싱
프롬프트의 안정적 프리픽스를 캐시해 반복 호출 시 재처리를 건너뛴다. 반복 컨텍스트가 큰 워크로드에서 입력 비용을 크게 줄이는 1순위 레버다.
모든 캐싱을 지배하는 한 규칙: 안정적인 것 먼저, 변하는 것 나중에 (stable first, variable last).
캐싱은 프리픽스 일치다. 프리픽스의 한 바이트라도 바뀌면 그 뒤가 전부 무효화된다. 이 한 문장에서 나머지가 전부 따라 나온다.
프로바이더별 메커니즘 (verified 2026-07)
| 프로바이더 | 방식 | 할인 |
|---|---|---|
| Anthropic [HIGH] | 명시적 cache_control 브레이크포인트 |
읽기 0.1× · 쓰기 1.25×(5분 TTL) / 2.0×(1시간 TTL) |
| OpenAI [official] | ≥1,024 토큰 프리픽스 자동 (GPT-5.6+ 는 명시적 breakpoint 도 지원) | 읽기 ~90% off · 쓰기 1.25×(GPT-5.6+) |
| Gemini [official] | 암묵적 캐싱 기본 ON(2.5+) + 명시적 캐싱 · ≥2,048~4,096 토큰(모델별) | 캐시 입력 ~90% off + 저장 $1/1M·시간 |
손익분기 (Anthropic 기준): 5분 TTL 은 2회 요청이면 이득(1.25× + 0.1× = 1.35× vs 2×). 1시간 TTL 은 쓰기가 2× 라 최소 3회는 읽어야 본전이다. 트래픽이 뜸하게 몰리는 패턴에만 1시간을 써라.
배치 순서
[ 툴 정의 ] ← 렌더 순서상 맨 앞 ┐
[ 시스템 프롬프트 ] ← 가장 안정적 │ 캐시 가능 프리픽스
[ 긴 정적 컨텍스트 ] ← 안정 │
[ 천천히 바뀌는 문서 ] ← 반(半)안정 ┘
[ 현재 사용자 메시지 ] ← 가장 변동 ← 이 뒤는 캐시 안 됨
렌더 순서는 tools → system → messages. 마지막 system 블록에 브레이크포인트를 걸면 툴과 시스템이 함께 캐시된다.
response = client.messages.create(
model="claude-opus-5", max_tokens=16000,
system=[{
"type": "text", "text": large_document,
"cache_control": {"type": "ephemeral"}, # 또는 {"type":"ephemeral","ttl":"1h"}
}],
messages=[{"role": "user", "content": "요약해줘"}],
)
브레이크포인트는 최대 4개. 세밀한 배치가 필요 없으면 최상위 cache_control 하나로 마지막 캐시 가능 블록에 자동 배치시켜도 된다.
최소 캐시 프리픽스는 모델마다 다르고 단조증가가 아니다 (verified 2026-08). 이걸 모르면 마커를 달았는데 조용히 캐시가 안 된다 — 에러도 안 난다:
| 최소 토큰 | 모델 |
|---|---|
| 512 | Opus 5 · Fable 5 · Mythos 5 |
| 1024 | Opus 4.8 · Sonnet 5 · Sonnet 4.6 · Sonnet 4.5 |
| 2048 | Opus 4.7 |
| 4096 | Opus 4.6 · Opus 4.5 · Haiku 4.5 |
3K 토큰 프롬프트는 Opus 5 에선 캐시되고 Opus 4.6 에선 조용히 안 된다.
무엇이 무엇을 무효화하나 (verified 2026-08) [HIGH]
캐시는 3계층이고, 변경은 자기 계층과 그 아래만 무효화한다. 이걸 알면 과잉 걱정을 줄일 수 있다.
| 바꾼 것 | 툴 캐시 | 시스템 캐시 | 메시지 캐시 |
|---|---|---|---|
| 툴 정의 추가·삭제·순서변경 | ❌ | ❌ | ❌ |
| 모델 교체 | ❌ | ❌ | ❌ |
| 시스템 프롬프트 내용 | ✅ | ❌ | ❌ |
tool_choice, 이미지, thinking on/off |
✅ | ✅ | ❌ |
| 메시지 내용 | ✅ | ✅ | ❌ |
즉 tool_choice 를 요청마다 바꾸거나 thinking 을 껐다 켜도 툴+시스템 캐시는 살아 있다. 전면 재구축을 부르는 건 툴 정의 변경과 모델 교체 두 가지뿐이다.
시스템 프롬프트를 안 깨고 지시를 주입하는 법
세션 중간에 운영자 지시가 생겼을 때 최상위 system 을 고치면 대화 전체 프리픽스가 날아간다. 대신 messages[] 안에 {"role": "system", ...} 메시지를 붙여라 — 캐시된 히스토리 뒤에 놓이므로 프리픽스가 살아남는다. 베타 헤더 불필요.
지원: Opus 5 · Opus 4.8 · Fable 5 · Mythos 5. Sonnet 5 는 미지원 (400). 미지원 모델에선 user 턴 안의 <system-reminder> 텍스트로 대체한다.
조용한 무효화 범인들
프리픽스에 들어가는 코드에서 이걸 찾아라. 전부 에러 없이 캐시만 죽인다.
| 패턴 | 왜 깨지나 |
|---|---|
시스템 프롬프트의 datetime.now() / Date.now() |
매 요청 프리픽스가 달라짐 |
앞쪽에 박힌 uuid4() · 요청 ID |
같은 이유 |
sort_keys=True 없는 json.dumps · set 순회 |
직렬화 순서가 비결정적 |
| 시스템 프롬프트에 세션/유저 ID 보간 | 유저별 프리픽스 — 공유 불가 |
조건부 시스템 섹션 (if flag: system += ...) |
플래그 조합마다 별개 프리픽스 |
| 유저별로 달라지는 툴셋 | 툴은 위치 0 — 아무것도 공유 안 됨 |
히트 확인하는 법
| 필드 | 의미 |
|---|---|
usage.cache_creation_input_tokens |
이번에 캐시에 쓴 토큰 (1.25× 지불) |
usage.cache_read_input_tokens |
캐시에서 읽은 토큰 (0.1× 지불) |
usage.input_tokens |
캐시 안 된 나머지만 |
⚠️ input_tokens 는 전체가 아니라 잔여분이다. 총 프롬프트 크기 = 세 값의 합. 에이전트를 몇 시간 돌렸는데 input_tokens 가 4K 로 보인다면 나머지는 캐시에서 온 것이다 — 합을 봐라.
동일 프리픽스 반복 요청에서 cache_read_input_tokens 가 계속 0 이면 위 표의 범인이 있다. 히트율이 70% 아래로 떨어지면 프롬프트 드리프트 경보로 삼아라.
함정
- 20블록 룩백: 브레이크포인트는 뒤로 최대 20개 콘텐츠 블록만 훑어 이전 캐시를 찾는다. 툴 호출이 많은 에이전트 루프에서 한 턴에 20블록을 넘기면 다음 요청이 조용히 미스난다. 긴 턴에선 ~15블록마다 브레이크포인트를 추가하라.
- 동시 요청: 캐시는 첫 응답이 스트리밍을 시작한 뒤에야 읽을 수 있다. 같은 프리픽스로 N개를 동시에 쏘면 전부 제값을 낸다. 1개를 먼저 보내 첫 토큰을 받은 뒤 나머지를 쏴라.
- 포크 연산: 요약·압축·서브에이전트가 별도 호출을 만들 때
system·tools·model을 조금이라도 다르게 재조립하면 부모 캐시를 통째로 놓친다. 그대로 복사하고 뒤에만 덧붙여라. - 프리워밍:
max_tokens: 0요청은 프리필만 돌고 즉시 반환한다(출력 토큰 과금 없음). 앱 기동 시 캐시를 미리 데워 첫 요청 지연을 없앨 수 있다. 단 트래픽이 TTL 안에 계속 들어오는 서비스라면 불필요한 쓰기 비용일 뿐이다. - 캐시는 모델별이다. 모델을 바꾸면 우회로가 없다. 메인 루프는 한 모델로 고정하고 싼 작업은 서브에이전트로 분리하라.
참고
- claude-family — 모델별 최소 캐시 길이의 출처
- anthropic-messages-api —
cache_control을 실제 요청에 넣는 법 - context-engineering — 캐싱으로 안 풀리는 컨텍스트 문제
- api-quickstart — 처음 붙일 때
- model-choice-decision-guide — 캐시 최소 길이가 모델 선택에 영향을 줄 때
출처: Anthropic 메커니즘·무효화 계층·모델별 최소 길이는 first-party [HIGH] (verified 2026-08). OpenAI·Gemini 는 각 사 공식 문서 재확인 [official] (verified 2026-07) — 정확 할인율은 일부 미공개라 근사치. 원본 → 2026-07-25-anthropic-claude-deep