프롬프트 캐싱 적용했는데 cache_read_input_tokens가 계속 0이에요
RAG 서비스에서 시스템 프롬프트에 큰 문서(약 4만 토큰)를 박아두고 질문만 바꿔가며 호출합니다. 비용 줄이려고 cache_control을 붙였는데 두 번째, 세 번째 호출에서도 usage.cache_read_input_tokens가 0으로만 나옵니다.
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=4000,
system=[
{"type": "text", "text": f"오늘은 {datetime.now()}. 아래 문서를 참고...\n{doc}",
"cache_control": {"type": "ephemeral"}}
],
messages=[{"role": "user", "content": question}],
)
캐시 쓰기(cache_creation_input_tokens)는 매번 잡히는데 읽기가 0입니다. 뭐가 문제일까요?
답변 2개
- 채택된 답변에디터 검증
범인은 system 프롬프트 맨 앞의
{datetime.now()}입니다. 저도 똑같은 거 한번 겪었어요ㅋㅋ프롬프트 캐싱은 prefix 매칭이에요. 캐시 키는 breakpoint까지 렌더된 바이트가 정확히 일치해야 잡힙니다.
datetime.now()는 매 호출 다른 값이라 prefix가 매번 달라지고, 그 뒤 4만 토큰 전체가 통째로 무효화됩니다. 그래서 매번 새로 쓰기만(cache_creation) 하고 읽기는 0인 거예요.고치는 법: 변하는 값을 prefix에서 빼서 뒤로 옮기세요. system은 동결시키고, 날짜 같은 휘발성 컨텍스트는 user 메시지로:
system=[ {"type": "text", "text": f"아래 문서를 참고...\n{doc}", "cache_control": {"type": "ephemeral"}} ], messages=[{"role": "user", "content": f"오늘은 {today}.\n질문: {question}"}],같은 식으로 캐시 조용히 깨먹는 것들 — 한 번씩 다 밟아봄:
uuid4()나 request id를 prefix에 끼워넣기json.dumps(d)를sort_keys=True없이 직렬화 (dict 키 순서가 매번 바뀜)- user/session id를 system 프롬프트에 f-string으로 보간
- 도구 목록이 호출마다 바뀜 (tools는 position 0이라 여기 바뀌면 전체 무효)
고치고 나서
cache_read_input_tokens채워지는지 확인하세요. 읽기는 기본 입력의 약 0.1배라 두 번째 호출부터 바로 이득 봅니다. 위 게 99% 정답이고, 혹시 그래도 안 되면 캐시 최소 길이도 한번 보세요.
4만 토큰이면 문제없는데, 모델마다 캐시 걸리는 최소 prefix 길이가 있어서 그보다 짧으면
cache_control붙여도 조용히 캐싱 안 되고cache_creation: 0만 나옵니다. Opus 계열은 1024 토큰이 최소였던 걸로 기억하는데 정확힌 현재 docs 확인하시고요.그리고 디버깅 팁 — 두 요청의 렌더된 프롬프트를 파일로 떠서
diff떠보면 invalidator가 바로 보입니다. 눈으로 안 보이는 공백 하나, 키 순서 차이까지 다 잡혀요. 저는 이걸로 한참 헤매던 거 5분 만에 찾았던 적 있음.