
프롬프트를 일부러 13,000 토큰으로 늘렸더니 AI 원가가 낮아졌다——prompt caching의 「4096 토큰의 벽」
요약
Anthropic 모델의 Prompt Caching 작동 시 발생하는 '최소 토큰 제한' 문제를 다룹니다. Haiku 모델 등에서 캐싱이 작동하지 않는 원인이 최소 캐시 길이 미달임을 밝히고, 이를 해결하기 위해 프롬프트 길이를 의도적으로 늘려 비용을 절감한 사례를 소개합니다.
핵심 포인트
- Anthropic 모델별로 캐싱 가능한 최소 토큰 수가 다름
- 최소 길이를 충족하지 못하면 에러 없이 캐싱만 작동하지 않음
- Haiku 모델의 경우 최소 4,096 토큰 이상이어야 캐싱 가능
- 의미 없는 데이터 대신 레시피 목록 등 유효한 데이터를 추가해 해결
개인적으로 AI 식단 앱을 준비하고 있습니다. 냉장고에서 사용하고 싶은 식재료를 꺼내 찍으면, AI가 식단을 제안해 주는 앱입니다. 운영은 저 혼자와 AI 한 명입니다. 구현은 전부 Claude Code (AI)가 수행하고, 저는 동작과 영수증을 확인하는 역할을 맡고 있습니다.
월 500엔의 구독 서비스로 성립시키고 싶기 때문에, AI의 원가는 1엔 단위로 신경 쓰고 있습니다. 그 절약의 핵심이 prompt caching(프롬프트 캐싱)——동일한 프롬프트의 전반부를 캐시하여, 2회차 이후의 입력 요금을 약 10분의 1로 줄이는 메커니즘이었습니다.
구현을 하고, 요청을 보내고, 응답의 usage를 확인해 보니 다음과 같았습니다.
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 0
에러는 없습니다. 식단도 제대로 돌아옵니다. 다만, 캐시만 조용히 작동하지 않고 있습니다.
이 기사는 그 원인(문서에 적혀 있는데도 알아차리지 못하는 사양)과, "프롬프트를 줄이는" 것이 아니라 일부러 13,000 토큰까지 늘려서 해결한 이야기입니다. 마지막에 실제 측정된 비용도 싣겠습니다.
무슨 일이 일어나고 있었나
작성한 코드 자체는 문서대로의 정직한 것이었습니다. 시스템 프롬프트에 cache_control을 붙이기만 하면 됩니다.
// Cloudflare Worker에서 Claude API (Haiku 4.5)를 호출
system: [
{ type: "text", text: MENU_SYSTEM_PROMPT, cache_control: { type: "ephemeral" } },
...
이렇게 하면 "첫 회는 캐시 쓰기(cache_creation_input_tokens가 붙음), 2회차 이후는 읽기(cache_read_input_tokens가 붙음)"가 되어야 합니다. 그런데 몇 번을 보내도 둘 다 0입니다. 마커를 붙이는 방식, 블록을 나누는 방식, TTL…… 한 차례 의심해 보았지만 모두 맞았습니다.
원인: 모델마다 「캐시되는 최소 토큰 수」가 있다
답은 Anthropic의 공식 문서에 적혀 있었습니다. 프롬프트 캐시에는 최소 캐시 가능 길이가 있으며, 프리픽스(prefix)가 이에 미치지 못할 경우 "에러가 발생하지 않고, 단지 캐시되지 않을 뿐"입니다.
| 모델 | 최소 캐시 길이 |
|---|---|
| Opus 4.5~4.8, Haiku 4.5 | 4096 토큰 |
| Sonnet 4.6 | 2048 토큰 |
| Sonnet 4.5 | 1024 토큰 |
저희의 식단 생성은 Haiku 4.5를 사용하고 있었고, 시스템 프롬프트는 4096 토큰에 전혀 미치지 못하는 규모였습니다.
이 사양의 까다로운 점은 아무런 신호도 주지 않는다는 것입니다. 400 에러도 경고도 반환하지 않고, cache_control을 붙인 요청은 정상적으로 성공하지만, 단지 write=0 / read=0이 됩니다. "작성 방식이 잘못된 것 아닌가"라며 구현 측을 계속 의심하게 만드는 구조가 됩니다. 게다가 최소 길이는 모델에 따라 4배나 차이가 나기 때문에, "Sonnet에서는 작동했는데 Haiku로 교체했더니 작동하지 않는다"는 일도 흔히 발생합니다.
해결: 줄이는 것이 아니라, 늘리기
보통의 튜닝은 "프롬프트를 짧게 해서 저렴하게 만드는" 방향입니다. 하지만 이 벽 앞에서는 반대로, 4096 토큰을 넘지 않는 한 단 1토큰도 캐시되지 않습니다.
그렇다고 의미 없는 문자열로 양을 늘리는 것은 최악입니다. 캐시 읽기에도 통상적인 10%의 요금이 들기 때문에, 쓰레기 데이터를 13,000 토큰이나 계속 읽게 하는 것은 단순한 낭비가 됩니다.
그래서 앱이 원래 가지고 있던 자산을 사용했습니다. 이 앱에는 내장 레시피가 113가지 있습니다. 그 목록(장르·주요리·부요리·조리 시간·칼로리·주재료)을 텍스트화하여 시스템 프롬프트에 심었습니다.
【일식】定番의 생강구이|주요리: 돼지 생강구이|부요리: 양배추 채썰기, 미소된장국|20분|약 450kcal/인|아이 OK|주재료: 돼지 등심, …
【양식】…(×113가지)
이로써 시스템 프롬프트는 약 13,000 토큰이 되었습니다. 벽을 넘었습니다.
이 방법의 좋은 점은 패딩(padding)이 실질적인 이득을 가진다는 것입니다. AI가 내장 레시피의 라인업을 알고 있는 상태에서 식단을 짤 수 있게 되므로, 앱 내의 레시피와 모순되지 않는 제안을 내놓기 쉬워집니다. "캐시를 위해 늘린 것"과 "제안 품질을 위해 문맥을 전달한 것"이 하나의 수단이 되었습니다.
레시피 목록은 수기로 작성하지 않고, 레시피 데이터로부터 자동 생성하는 스크립트로 만들었습니다.
// public/recipes.js 로부터 recipe-ref.js 를 재생성한다
// 레시피를 추가/변경하면 실행: node scripts/gen-recipe-ref.js
const lines = RECIPE_DB.map((r) =>
...
한편, 사용자마다 달라지는 부분(선택한 식재료, 알레르기 설정, 요리 가짓수)은 캐시를 깨뜨리기 때문에, 시스템 프롬프트(System Prompt)에는 넣지 않고 user 메시지 쪽으로 몰았습니다. 이 부분은 압축하여 1 요청당 약 80 토큰입니다. "변하지 않는 13,000 토큰은 캐시에, 변하는 80 토큰만 매번 지불한다"는 형태가 됩니다.
실측
수정 후의 usage는 다음과 같았습니다.
-
첫 번째 요청:
cache_creation_input_tokens: 13060
(쓰기) -
2회차 이후:
cache_read_input_tokens: 13060
(읽기)
0이 나열되어 있던 칸에 13060이 깔끔하게 이동했습니다.
요금에 미치는 영향은 다음과 같습니다 (Haiku 4.5는 입력 $1/100만 토큰. 캐시 읽기는 0.1배, 쓰기는 1.25배. 1달러 150엔 환산).
| 13,060 토큰 입력 비용 | |
|---|---|
| 캐시 없음 (매번 새로 읽기) | 약 2.0엔/회 |
| ... |
출력 토큰 등을 포함한 실측에서는, 식단 생성 1회당 캐시 히트(Cache Hit) 시 12엔, 미스(Miss) 시 35엔. 응답은 618초였습니다. 월 500엔 플랜에서 사용자가 매일 3번 사용하더라도, 원가는 월 100300엔 수준으로 수렴하여 채산성을 맞출 수 있습니다.
"짧은 프롬프트 상태라면 매번 0.1엔 이하가 아닌가?"라는 지적은 옳으며, 순수하게 금액만 따지면 그것이 최저가입니다. 하지만 그 경우에는 113가지 레시피의 문맥(Context)을 버리게 됩니다. 이 문맥을 전달하고 싶다는 전제에 선 순간, "4096의 벽을 넘어 캐시에 태우는 것"이 유일한 정답이 됩니다. 어중간하게 3,000 토큰만 전달하는 것이 가장 손해인 설계입니다.
실패하지 않기 위한 체크리스트
- 캐시가 작동하는지는 에러가 아니라
usage를 반드시 확인할 것.cache_creation_input_tokens/cache_read_input_tokens를 통해서만 알 수 있습니다. 둘 다 계속 0이라면 무언가 잘못된 것입니다. - 모델의 최소 캐시 길이를 확인할 것. Haiku 4.5와 Opus 계열은 4096 토큰입니다. Sonnet보다 벽이 높습니다. 모델을 교체했을 때 아무 말 없이 작동하지 않는다면 바로 이 부분 때문입니다.
- 프롬프트를 늘린다면 "안정적이고 유용한" 내용으로 채울 것. 캐시는 프리픽스(Prefix)의 바이트 단위 일치 여부로 판정되므로, 날짜나 사용자 ID를 심어두면 매번 무효화됩니다. 변하는 정보는 user 메시지 쪽으로 보내세요.
- 임베드된 참조 데이터의 업데이트는 캐시의 재작성을 의미함. 저희의 경우, 레시피를 한 가지 추가하면 재생성 스크립트를 돌리며, 그 직후의 1회에만 쓰기 비용(1.25배)이 발생합니다. 빈번하게 변하는 데이터는 이 수법에 적합하지 않습니다.
- TTL은 5분(기본값). 쓰기가 1.25배이므로, 5분 이내에 2번째 요청이 오면 본전을 뽑을 수 있습니다. 캐시는 조직 단위로 공유되므로, 다른 사용자의 요청이라도 프리픽스가 같다면 히트됩니다. 사용자가 늘어날수록 히트율이 올라가는, 몇 안 되는 "스케일할수록 저렴해지는" 구조입니다.
작성자: '우미유이'라는 작은 인터넷 생선 가게를 AI와 둘이서(그리고 강아지 한 마리와) 준비하고 있는 개인입니다. 가게를 만드는 전 과정을 책으로 묶고 있습니다 → 개인 EC 「우미유이」 운영 실록
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기