시리즈 이전 편: [Part 3] 시스템 프롬프트 분석 — 클로드 코드가 매번 보내는 거대한 지시문의 구조
목차
- API 호출마다 시스템 프롬프트를 보내면 비용이 얼마나 될까
- 프롬프트 캐싱이란
- 정적/동적 경계 — SYSTEM_PROMPT_DYNAMIC_BOUNDARY
- splitSysPromptPrefix() — 캐시 스코프 결정 로직
- buildSystemPromptBlocks() — 최종 조립
- 실제 비용 절감 효과
- 자신의 LLM 앱에 적용하는 법
- 다음 편 예고
API 호출마다 시스템 프롬프트를 보내면 비용이 얼마나 될까
Claude Code의 시스템 프롬프트는 수만 토큰에 달합니다. 사용자가 엔터를 칠 때마다 이 전체가 API로 전송된다면, 한 세션에 수십 번 호출하는 것만으로 토큰 비용이 눈덩이처럼 불어납니다. Anthropic이 이 문제를 어떻게 해결했는지, 소스코드를 직접 열어보겠습니다.
프롬프트 캐싱이란
Anthropic API에는 cache_control이라는 파라미터가 있습니다. 시스템 프롬프트 블록에 이 파라미터를 붙이면, 서버가 해당 블록을 캐시해두고 다음 요청에서 동일한 내용이 오면 재계산 없이 재사용합니다. 캐시된 토큰은 입력 비용의 약 10%만 청구되기 때문에, 같은 프롬프트를 반복 전송하는 구조에서는 비용 절감 효과가 극적입니다.
핵심은 "어디까지 캐시하고, 어디부터 캐시하지 않을 것인가"를 정확히 나누는 것입니다.
정적/동적 경계 — SYSTEM_PROMPT_DYNAMIC_BOUNDARY
Claude Code는 시스템 프롬프트를 "정적 영역"과 "동적 영역"으로 나눕니다. 그 경계를 표시하는 상수가 바로 이것입니다.
// 경로: src/constants/prompts.ts (line 105~115)
/**
* Boundary marker separating static (cross-org cacheable)
* content from dynamic content.
* Everything BEFORE this marker can use scope: 'global'.
* Everything AFTER contains user/session-specific content
* and should not be cached.
*/
export const SYSTEM_PROMPT_DYNAMIC_BOUNDARY =
'__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__'
실제 시스템 프롬프트 배열에서 이 마커가 어떻게 쓰이는지 보겠습니다.
// 경로: src/constants/prompts.ts (line 560~576)
return [
// --- Static content (cacheable) ---
getSimpleIntroSection(outputStyleConfig),
getSimpleSystemSection(),
getSimpleDoingTasksSection(),
getActionsSection(),
getUsingYourToolsSection(enabledTools),
getSimpleToneAndStyleSection(),
getOutputEfficiencySection(),
// === BOUNDARY MARKER - DO NOT MOVE OR REMOVE ===
...(shouldUseGlobalCacheScope()
? [SYSTEM_PROMPT_DYNAMIC_BOUNDARY] : []),
// --- Dynamic content (registry-managed) ---
...resolvedDynamicSections,
].filter(s => s !== null)
마커 위쪽은 모든 사용자에게 동일한 내용이라 전역 캐시가 가능합니다. 마커 아래쪽은 세션별로 달라지는 MCP 설정, 스킬 정보 등이 들어가서 캐시 대상에서 제외됩니다.
splitSysPromptPrefix() — 캐시 스코프 결정 로직
실제로 이 경계를 파싱해서 각 블록에 캐시 스코프를 부여하는 함수가 splitSysPromptPrefix()입니다.
// 경로: src/utils/api.ts (line 296~320, 요약)
// 동작 모드 3가지:
// 1. MCP 도구 있음 (skipGlobalCacheForSystemPrompt=true)
// → cacheScope='org' (조직 단위 캐시)
// 2. 글로벌 캐시 모드 + boundary 발견 (1P 전용)
// → 정적 블록: cacheScope='global'
// → 동적 블록: cacheScope=null (캐시 안 함)
// 3. 기본 모드 (3P 또는 boundary 없음)
// → cacheScope='org'
스코프별 의미를 정리하면 이렇습니다.
- global: 모든 조직, 모든 사용자가 공유하는 캐시. 시스템 프롬프트의 정적 부분에 적용됩니다. 캐시 적중률이 가장 높아 비용 절감이 극대화됩니다.
- org: 같은 조직 내에서 공유하는 캐시. MCP 도구가 연결되어 있으면 시스템 프롬프트가 사용자마다 달라지므로, 전역 캐시 대신 조직 단위로 범위를 좁힙니다.
- null: 캐시하지 않음. 동적 콘텐츠나 빌링 헤더처럼 매번 달라지는 부분입니다.
buildSystemPromptBlocks() — 최종 조립
splitSysPromptPrefix()가 나눈 블록들은 buildSystemPromptBlocks()에서 API 요청 형태로 변환됩니다.
// 경로: src/services/api/claude.ts (line 3213~3237)
export function buildSystemPromptBlocks(
systemPrompt, enablePromptCaching, options
) {
return splitSysPromptPrefix(systemPrompt, {
skipGlobalCacheForSystemPrompt:
options?.skipGlobalCacheForSystemPrompt,
}).map(block => ({
type: 'text',
text: block.text,
...(enablePromptCaching && block.cacheScope !== null
&& { cache_control: getCacheControl({
scope: block.cacheScope,
}) }),
}))
}
cacheScope가 null이 아닌 블록에만 cache_control이 붙습니다. 단순하지만 정확한 분기입니다.
실제 비용 절감 효과
Claude Code의 시스템 프롬프트는 대략 1.5만
2만 토큰 규모입니다. 이 중 정적 영역이 약 70
80%를 차지합니다. 캐시된 입력 토큰은 원래 가격의 10%만 청구되므로, 단순 계산으로도 시스템 프롬프트 비용이 한 자릿수로 떨어집니다.
한 세션에서 20번 API를 호출한다고 가정하면, 첫 호출만 전체 비용을 내고 나머지 19번은 정적 영역에 대해 90% 할인을 받는 셈입니다. 세션이 길어질수록 절감 효과는 더 커집니다.
또 하나 주목할 점은 getSessionSpecificGuidanceSection() 함수의 주석입니다. 조건부 블록을 경계 마커 앞에 놓으면 "2^N 변형"이 생겨 캐시 적중률이 급락한다고 경고합니다. 캐시 효율을 지키려면 정적 영역의 내용이 진짜로 불변이어야 합니다.
자신의 LLM 앱에 적용하는 법
이 패턴은 Claude Code에만 해당하는 것이 아닙니다. Anthropic API를 쓰는 어떤 앱이든 동일하게 적용할 수 있습니다.
첫째, 시스템 프롬프트를 "모든 사용자에게 동일한 부분"과 "세션마다 달라지는 부분"으로 물리적으로 나누세요. 둘째, 정적 부분에 cache_control: { type: 'ephemeral' }을 붙이세요. 셋째, 동적 부분은 정적 부분 뒤에 별도 블록으로 배치하세요. 순서가 바뀌면 캐시가 무효화됩니다.
조건부 토글(A/B 테스트, 기능 플래그 등)은 동적 영역으로 밀어내야 합니다. Claude Code 소스에서도 이 실수로 캐시가 깨진 버그가 두 번이나 발생했습니다(PR #24490, #24171).
다음 편 예고
이번 편에서는 시스템 프롬프트의 정적 영역을 다뤘습니다. 하지만 경계 마커 아래의 동적 영역 — 매 세션, 매 환경마다 달라지는 부분 — 에도 흥미로운 구조가 숨어 있습니다. 다음 편에서는 그 동적 프롬프트가 어떻게 조립되는지 파헤쳐 보겠습니다.
다음 편 예고: [Claude Code 해부학 Part 5] 클로드 코드(Claude Code) 동적 프롬프트 분석 — 매번 다르게 말하는 비밀
'AI 코딩 에이전트' 카테고리의 다른 글
| Claude Code 도구 시스템 분석: AI에게 손발을 달아주는 40개 Tool (해부학 Part 6) (0) | 2026.04.01 |
|---|---|
| Claude Code 동적 프롬프트 분석: 매번 다르게 말하는 비밀 (해부학 Part 5) (0) | 2026.04.01 |
| Claude Code 시스템 프롬프트 분석: 915줄은 어떻게 조립되는가 (해부학 Part 3) (0) | 2026.04.01 |
| Claude Code 부팅 과정 분석: 실행 후 0.5초 안에 벌어지는 일들 (해부학 Part 2) (0) | 2026.04.01 |
| Claude Code 프로젝트 구조 분석: 1,900개 파일의 지도 그리기 (해부학 Part 1) (2) | 2026.04.01 |
