이번 시간에는 응답을 짧게 만든다는 7줄짜리 CLAUDE.md, claude-token-efficient가 우리 질문에서도 출력 토큰을 줄이는지를 claude -p로 측정하며 배워가는 시간입니다. claude -p는 대화 화면 없이 질문 하나를 보내고 결과를 받는 실행 방식이에요.
자 우리 같은 버그 질문을 CLAUDE.md가 없는 디렉터리와 이 파일을 둔 디렉터리에서 번갈아 보내 볼게요. 그리고 줄어든 출력과, 대신 늘어난 입력을 같이 보겠습니다.
모델 출력은 비결정적이라 다시 돌리면 숫자가 달라질 수 있어요. --model opus가 가리키는 모델도 시간이 지나면 달라질 수 있습니다.
claude-token-efficient가 줄이는 것과 공개 수치
claude-token-efficient는 GitHub 저장소 drona23/claude-token-efficient에 있는 CLAUDE.md 한 장이에요. 이 도구가 노리는 곳은 모델이 생성하는 응답, 즉 출력 토큰이에요. 대신 CLAUDE.md는 요청마다 컨텍스트에 함께 실리니 입력 토큰은 그만큼 늘어납니다.
파일 내용은 이게 전부예요.
## Approach
- Read existing files before writing. Don't re-read unless changed.
- Thorough in reasoning, concise in output.
- Skip files over 100KB unless required.
- No sycophantic openers or closing fluff.
- No emojis or em-dashes.
- Do not guess APIs, versions, flags, commit SHAs, or package names. Verify by reading code or docs before asserting.
README가 내세우는 숫자는 63%입니다. 질문 4개의 답 단어 수를 더해 465단어가 170단어로 줄었다는 표인데, 저자는 반복 실행이 없는 방향성 지표라고 적어 두었어요. 저자가 claude -p --output-format json으로 다시 잰 값은 지금의 7줄 파일로 opus -7%였습니다.
실습 준비
이 글의 명령은 모두 실습 코드의 claude-token-efficient-claude-md/ 디렉터리에서 실행합니다.
prompts/: 버그 원인과 수정을 묻는 질문 3개(p1.txt~p3.txt)run.sh: 두 조건을 섞은 순서로 18번 호출해out/<조건>/에 결과 JSON을 남기는 스크립트summarize.py: 결과 JSON에서 입력·출력 토큰, 단어 수, 비용 추정을 조건별로 모으는 스크립트judge.py: 답이 버그 원인을 짚고 수정 코드를 담았는지 판정하는 스크립트
조건은 두 가지예요.
base: 아무 파일도 없는 빈 디렉터리입니다. 스크립트가 처음 돌 때 만들어요.cte: claude-token-efficient의 CLAUDE.md 하나만 둔 디렉터리입니다.
cte 디렉터리와 CLAUDE.md는 저장소에서 바로 받아 올게요. 나중에 파일이 바뀌어도 같은 내용을 받도록 커밋을 고정한 주소를 씁니다.
mkdir -p cte
curl -sS -o cte/CLAUDE.md https://raw.githubusercontent.com/drona23/claude-token-efficient/0d30a6db75af983b8ababf585f28faefdfc87895/CLAUDE.md
wc -lc cte/CLAUDE.md
7 352 cte/CLAUDE.md
결과 JSON은 macOS 15부터 기본으로 들어 있는 jq로 읽고(그 전 버전이면 따로 설치해야 해요), Python 스크립트는 호스트에 아무것도 설치하지 않도록 python:3.12-slim 컨테이너에서 돌려요.
py: 현재 디렉터리를 컨테이너의/w로 붙여 Python 스크립트를 실행합니다.excerpt: 결과 JSON에서 토큰 네 필드, 비용 추정, 턴 수, 모델별 출력·생각 토큰만 골라 JSON 모양 그대로 보여 줍니다.
아래 블록은 파일로 만들지 말고 실습 디렉터리에서 연 터미널에 그대로 붙여 넣어 주세요. 이후 명령은 모두 이 터미널에서 실행하고, 터미널을 새로 열면 다시 붙여 넣으시면 됩니다.
py() { docker run --rm --network none -v "$PWD":/w -w /w python:3.12-slim python "$@"; }
excerpt() { jq '{usage: (.usage | {input_tokens, cache_creation_input_tokens, cache_read_input_tokens, output_tokens}), total_cost_usd, num_turns, modelUsage: (.modelUsage | map_values({outputTokens, thinkingTokens}))}' "$@"; }
claude -p를 그냥 실행하면 홈의 ~/.claude/CLAUDE.md, 사용자 설정, 계정에 연결된 MCP 서버가 함께 들어가요. 그래서 모든 호출에 다음 플래그를 붙였어요.
--setting-sources project: 사용자 설정과 사용자 CLAUDE.md를 빼고, 작업 디렉터리(와 그 상위 디렉터리)의 프로젝트 CLAUDE.md만 읽게 합니다. 저자의 벤치마크도 같은 방식으로 쟀어요.--strict-mcp-config:--mcp-config로 준 MCP 서버만 씁니다. 아무것도 주지 않았으니 MCP 서버가 하나도 붙지 않아요.--tools "": 내장 도구를 모두 끕니다. 모델은 파일을 읽지 못하고 답을 텍스트로만 내요.--model opus,--effort low: 모델과 effort를 고정합니다. effort를 비워 두면 사용자 설정이나 환경 변수 값이 끼어들 수 있어요.--no-session-persistence: 대화 기록 파일을 남기지 않습니다.
실습 1: 같은 질문을 두 조건으로 반복하기
질문은 작은 코드 조각에 버그 하나를 넣고 원인과 수정을 묻는 형태로 3개를 만들었어요. 답에 들어가야 할 내용이 정해져 있어서, 짧아진 답이 필요한 걸 빠뜨렸는지 확인하기 좋습니다.
p1.txt: JavaScript 합계 함수가i <= prices.length때문에 600 대신 NaN을 돌려주는 버그p2.txt: Python 함수의 기본 인자tags=[]가 호출 사이에 공유되는 버그p3.txt:forEach안의async콜백을 기다리지 않아 "done"이 먼저 찍히는 버그
run.sh는 질문 3개를 3회차에 걸쳐 두 조건으로 위 플래그를 붙여 보내고, 호출마다 결과 JSON을 out/<조건>/<질문>-r<회차>.json에 남겨요. 한 조건을 몰아서 돌리지 않도록 회차마다 base와 cte의 순서를 바꿉니다.
- 결과 JSON에
"is_error":false가 있으면 끝난 호출로 보고 건너뜁니다. 한도 오류로 멈췄다면 같은 명령을 다시 돌려 남은 호출만 이어 가면 돼요. - macOS에는
timeout명령이 기본으로 없어서perl로 300초 제한을 겁니다.
zip에서 푼 스크립트는 실행 권한이 없어서 먼저 chmod +x로 붙여 줍니다. 실제로 18번 호출하니 몇 분 걸립니다.
chmod +x run.sh
./run.sh
round 1 p1 base
round 1 p1 cte
round 1 p2 base
round 1 p2 cte
round 1 p3 base
round 1 p3 cte
round 2 p1 cte
round 2 p1 base
round 2 p2 cte
round 2 p2 base
round 2 p3 cte
round 2 p3 base
round 3 p1 base
round 3 p1 cte
round 3 p2 base
round 3 p2 cte
round 3 p3 base
round 3 p3 cte
두 번째 질문의 1회차 결과를 두 조건에서 나란히 볼게요. excerpt로 고른 필드만 보인 발췌입니다.
excerpt out/base/p2-r1.json out/cte/p2-r1.json
{
"usage": {
"input_tokens": 2,
"cache_creation_input_tokens": 693,
"cache_read_input_tokens": 2322,
"output_tokens": 546
},
"total_cost_usd": 0.0169364,
"num_turns": 1,
"modelUsage": {
"claude-opus-5-5": {
"outputTokens": 546,
"thinkingTokens": 0
}
}
}
{
"usage": {
"input_tokens": 2,
"cache_creation_input_tokens": 1017,
"cache_read_input_tokens": 2323,
"output_tokens": 510
},
"total_cost_usd": 0.0188086,
"num_turns": 1,
"modelUsage": {
"claude-opus-5-5": {
"outputTokens": 510,
"thinkingTokens": 0
}
}
}
output_tokens는 546에서 510으로 줄었어요. 그런데 total_cost_usd는 오히려 0.0169에서 0.0188로 올랐죠?
왜 그럴까요? cache_creation_input_tokens를 보시면 됩니다. 두 조건 모두 요청 앞부분 2,322토큰 남짓은 캐시에서 읽었지만, cte는 CLAUDE.md와 질문이 든 1,017토큰을 캐시에 새로 썼어요. base는 693토큰이었습니다.
캐시에 새로 쓰는 토큰은 캐시에서 읽는 토큰보다 훨씬 비싸게 계산돼요. 그래서 출력 36토큰을 아낀 것보다 입력 324토큰을 새로 쓴 비용이 더 컸습니다.
실습 2: 출력 토큰과 단어 수 집계하기
summarize.py는 조건마다 결과 JSON 9개를 읽어 입력 토큰(세 필드의 합)의 중앙값과 범위, 출력 토큰·단어 수·비용 추정의 합계를 찍어요. 단어 수는 답을 공백으로 나눈 개수예요. 마지막에는 합계가 몇 퍼센트 바뀌었는지 보여 주는데, 질문을 처음 보낸 1회차만 따로 더한 비용도 같이 냅니다.
py summarize.py
base: 9회
입력 토큰: 중앙값 3,017, 범위 3,003~3,020
출력 토큰 합계: 5,284
단어 수 합계: 1,292
비용 추정 합계: $0.1273 (1회차만 $0.0493)
cte: 9회
입력 토큰: 중앙값 3,342, 범위 3,328~3,345
출력 토큰 합계: 4,390
단어 수 합계: 1,077
비용 추정 합계: $0.1176 (1회차만 $0.0551)
질문별 중앙값 (base -> cte)
p1: 출력 토큰 405 -> 381, 단어 113 -> 106
p2: 출력 토큰 590 -> 494, 단어 143 -> 116
p3: 출력 토큰 685 -> 570, 단어 156 -> 133
출력 토큰 합계 변화: -16.9%
단어 수 합계 변화: -16.6%
비용 추정 합계 변화: -7.6%
1회차 비용 추정 합계 변화: +11.6%
입력 토큰 중앙값은 3,017에서 3,342로, 호출마다 325토큰씩 늘었어요. 352바이트짜리 영어 7줄치고는 많아 보이죠? 파일 내용 말고도 CLAUDE.md를 감싸는 안내 문구와 파일 경로가 같이 들어가기 때문으로 보여요.
출력 토큰 합계는 5,284에서 4,390으로 16.9% 줄었어요. 세 질문 모두 줄었고, 답이 길었던 p2와 p3에서 많이 줄었습니다.
비용 추정은 조건 전체로 보면 7.6% 줄었어요. 하지만 질문을 처음 보낸 1회차 3번만 더하면 $0.0493에서 $0.0551로 11.6% 늘었습니다. 2회차와 3회차는 같은 질문을 다시 보내 입력 전체를 캐시에서 읽은 호출이라, 늘어난 325토큰이 싸게 계산됐을 뿐이에요.
그럼 63%는 어떻게 됐을까요? 우리 질문에서는 16.9%였어요. 다만 9번씩의 작은 표본이라, 이 16.9%도 우리 질문에서 나온 하나의 값으로만 보셔야 해요.
실습 3: 짧아진 답에서 빠진 것 확인하기
출력이 줄었다고 끝내면 안 되죠. 답이 짧아지면서 버그 원인이나 수정이 빠졌다면 아낀 게 아니니까요. judge.py는 답을 코드 블록과 그 밖의 설명으로 나눈 다음, 질문마다 두 가지를 봐요.
- 원인: 설명에 버그의 핵심이 들어 있는지 봅니다. p1은
undefined, p2는 기본값이 "한 번"만 만들어진다는 말, p3는forEach가 기다리지 않는다는 말이에요. - 수정: 코드 블록에 고친 코드가 들어 있는지 봅니다. p1은
i < prices.length,for...of,reduce중 하나, p2는tags=None, p3는for (const item of items)나Promise.all이에요.
p3에는 하나를 더 봅니다. 원래 코드는 save가 실패해도 그 오류가 saveAll까지 올라오지 않는 문제가 하나 더 있어서, 답이 이걸 짚었는지(rejection이라는 말이 있는지)도 셉니다. 아래는 p3 줄과 조건별 요약 줄만 옮긴 발췌예요.
py judge.py
base p3-r1: 원인 O, 수정 O, 오류 전달 언급 O, 설명 619자, 코드 243자
base p3-r2: 원인 O, 수정 O, 오류 전달 언급 O, 설명 678자, 코드 243자
base p3-r3: 원인 O, 수정 O, 오류 전달 언급 O, 설명 879자, 코드 243자
base: 9/9 통과, p3 오류 전달 언급 3/3, 설명 중앙값 523자, 코드 중앙값 250자
cte p3-r1: 원인 O, 수정 O, 오류 전달 언급 X, 설명 567자, 코드 243자
cte p3-r2: 원인 O, 수정 O, 오류 전달 언급 O, 설명 562자, 코드 243자
cte p3-r3: 원인 O, 수정 O, 오류 전달 언급 X, 설명 533자, 코드 243자
cte: 9/9 통과, p3 오류 전달 언급 1/3, 설명 중앙값 451자, 코드 중앙값 243자
두 조건 모두 9개 답이 원인과 수정을 다 담았어요. 줄어든 건 코드가 아니라 설명입니다. 코드 중앙값은 250자와 243자로 거의 같고, 설명 중앙값이 523자에서 451자로 줄었어요.
그런데 p3에서 차이가 보이죠. base는 3번 모두 오류가 saveAll로 올라오지 않는다는 점을 짚었고, cte는 1번만 짚었어요. 3번씩이라 우연일 수도 있지만, 줄어든 쪽이 cte라는 점은 기억해 둘 만해요.
빠진 내용을 다시 묻는 질문도 입력과 출력 토큰을 다 씁니다. 그러니 첫 답에서 아낀 100토큰 안팎이 다시 묻는 비용보다 작다면, 짧아진 답이 더 싼 답은 아닌 거예요.
정리
범위: Claude Code 2.1.309의 claude -p, Opus 5.5(claude-opus-5-5), effort low, 도구를 끈 한 턴 응답, 한국어 버그 질문 3개를 두 조건에서 3회씩 18번 호출, claude-token-efficient 루트 CLAUDE.md(커밋 0d30a6d).
- 출력 토큰 합계는 5,284에서 4,390으로 16.9% 줄었습니다. README의 63%(단어 수, 반복 없음)도 저자 재측정의 opus -7%도 아닌 값이니, 자기 질문으로 직접 재 보세요.
- CLAUDE.md는 요청마다 입력을 325토큰 늘렸습니다. 352바이트 파일보다 커 보이는 값이라, 파일 크기가 아니라 결과 JSON의 입력 세 필드로 확인하세요.
- 질문을 처음 보낸 1회차만 더하면 비용 추정이 11.6% 늘었고, 같은 질문을 되풀이한 회차까지 넣어야 7.6% 줄었습니다. 매번 다른 질문을 새 세션으로 보내는 자동화라면 이 파일로 비용이 줄지 않을 수 있어요.
- 9개 답 모두 버그 원인과 수정 코드를 담았고, 줄어든 건 코드가 아니라 설명(중앙값 523자에서 451자)이었습니다.
- p3에서 오류가
saveAll까지 올라오지 않는다는 지적이 3번 중 3번에서 1번으로 줄었습니다. 짧아진 답에서 빠진 내용을 다시 묻게 되는 비용까지 더해서 판단하세요.