AI API를 쓰다 보면 모델 가격보다 더 신경 쓰이는 게 있습니다. 매번 같은 내용을 보내면서 비용을 내고 있지는 않은가 하는 문제입니다.
AI 에이전트를 만든다고 생각해 보죠. 시스템 프롬프트, 업무 규칙, 도구 설명, 참고 문서까지 붙이면 사용자 질문은 짧아도 실제 API에 보내는 내용은 꽤 길어집니다.
그런데 이 중 상당 부분은 다음 요청에서도 그대로 쓰입니다.
이럴 때 확인해 볼 기능이 프롬프트 캐싱(Prompt Caching)입니다.

1. 프롬프트 캐싱이 뭔가요?
간단합니다. 이전 요청에서 처리한 프롬프트의 앞부분을 저장해 두었다가 다음 요청에서 재사용하는 기능입니다.
예를 들어 고객 문의를 처리하는 AI를 운영한다고 가정해 보겠습니다.
| 입력 내용 | 토큰 수 | 변경 여부 |
|---|---|---|
| 시스템 지침 | 2,000 | 거의 동일 |
| 업무 규칙과 참고 문서 | 5,000 | 거의 동일 |
| 사용자 질문 | 100 | 매번 변경 |
질문은 매번 달라지지만 앞의 7,000토큰은 그대로일 수 있습니다. 프롬프트 캐싱은 이런 공통 부분을 다시 활용합니다.
중요한 점은 앞부분이 같아야 한다는 겁니다. 같은 내용을 보내더라도 순서가 바뀌면 캐시 적중에 불리할 수 있습니다.
2. GPT-6에서는 얼마나 저렴할까요?
2026년 11월 기준, GPT-6 Luna의 Standard 요금은 다음과 같습니다.
| 구분 | 100만 토큰당 요금 |
|---|---|
| 일반 입력 | $0.10 |
| 캐시 읽기 | $0.01 |
| 캐시 쓰기 (30분) | $0.125 |
| 출력 | $0.50 |
캐시 읽기 요금은 일반 입력보다 90% 저렴합니다.
다만 여기서 놓치면 안 되는 게 있습니다. 캐시에 처음 기록하는 비용은 일반 입력보다 비쌉니다. 30분 캐시 쓰기는 일반 입력 요금의 1.25배입니다.
그러니까 요청 한두 번으로 무조건 비용을 절약하는 구조는 아닙니다. 같은 내용을 여러 번 재사용해야 이득이 생깁니다.
3. 직접 테스트해 보기
이번에는 실제 API 응답에서 캐시 읽기와 쓰기 토큰을 확인해 보겠습니다.
Python 3.10 이상, OpenAI API 키, API 사용 가능한 계정이 필요합니다. 소액의 사용료가 발생할 수 있습니다.
① OpenAI SDK 설치
python -m pip install --upgrade openai설치된 버전도 확인해 봅니다.
python -m pip show openai② API 키 설정
macOS 또는 Linux 터미널에서 실행합니다.
export OPENAI_API_KEY="YOUR_API_KEY"Windows PowerShell이라면 다음 명령을 사용합니다.
$env:OPENAI_API_KEY="YOUR_API_KEY"API 키를 Python 코드에 직접 적거나 Git 저장소에 올리지는 마세요.
③ Python 테스트 코드
아래 코드를 cache_test.py로 저장합니다.
GPT-6 Luna를 사용하고, 공통 지침 뒤에 명시적인 캐시 경계를 설정했습니다. 요청마다 달라지는 질문은 그 뒤에 배치합니다.
from openai import OpenAI
client = OpenAI()
MODEL = "gpt-6-luna"
# GPT-6 Luna Standard, 100만 토큰당 USD
INPUT_PRICE = 0.10
CACHE_READ_PRICE = 0.01
CACHE_WRITE_PRICE = 0.125
OUTPUT_PRICE = 0.50
# 여러 요청에서 재사용할 공통 내용
base_rules = """
당신은 SaaS 고객지원 분석가입니다.
다음 규칙에 따라 고객 문의를 분석하세요.
1. 확인된 사실과 추정을 구분합니다.
2. 가능한 원인을 우선순위로 정리합니다.
3. 고객이 직접 확인할 항목을 안내합니다.
4. 개발팀에서 확인할 항목을 따로 정리합니다.
5. 근거가 부족하면 단정하지 않습니다.
6. 답변은 간결하고 구체적으로 작성합니다.
"""
# 캐싱을 관찰하기 위한 긴 공통 프롬프트
# 실제 서비스에서는 업무 문서나 도구 정의가 들어갑니다.
shared_context = (
base_rules
+ "\n공통 운영 지침:\n"
+ (base_rules * 100)
)
questions = [
"로그인 버튼을 눌러도 반응이 없습니다.",
"결제했는데 구독이 활성화되지 않습니다.",
"비밀번호 재설정 메일이 도착하지 않습니다."
]
def calculate_cost(usage):
details = usage.input_tokens_details
cached = getattr(details, "cached_tokens", 0) or 0
written = getattr(details, "cache_write_tokens", 0) or 0
ordinary = usage.input_tokens - cached - written
input_cost = (
ordinary * INPUT_PRICE
+ cached * CACHE_READ_PRICE
+ written * CACHE_WRITE_PRICE
) / 1_000_000
output_cost = (
usage.output_tokens * OUTPUT_PRICE
/ 1_000_000
)
return {
"ordinary": ordinary,
"cached": cached,
"written": written,
"input_cost": input_cost,
"output_cost": output_cost,
"total_cost": input_cost + output_cost
}
total_actual = 0
total_without_cache = 0
for index, question in enumerate(questions, 1):
response = client.responses.create(
model=MODEL,
reasoning={"effort": "none"},
prompt_cache_options={
"mode": "explicit",
"ttl": "30m"
},
input=[
{
"role": "developer",
"content": [
{
"type": "input_text",
"text": shared_context,
"prompt_cache_breakpoint": {
"mode": "explicit"
}
}
]
},
{
"role": "user",
"content": question
}
],
max_output_tokens=300
)
usage = response.usage
result = calculate_cost(usage)
# 동일한 토큰 수가 모두 일반 요금으로
# 처리되었다고 가정한 비교 비용
no_cache_cost = (
usage.input_tokens * INPUT_PRICE
+ usage.output_tokens * OUTPUT_PRICE
) / 1_000_000
total_actual += result["total_cost"]
total_without_cache += no_cache_cost
print(f"\n===== 요청 {index} =====")
print("질문:", question)
print("전체 입력:", usage.input_tokens)
print("일반 입력:", result["ordinary"])
print("캐시 읽기:", result["cached"])
print("캐시 쓰기:", result["written"])
print("출력:", usage.output_tokens)
print(
"추정 API 비용:",
f'${result["total_cost"]:.8f}'
)
print("응답:")
print(response.output_text[:300])
print("\n===== 전체 결과 =====")
print(f"캐시 적용 추정 비용: ${total_actual:.8f}")
print(f"일반 요금 비교 비용: ${total_without_cache:.8f}")
if total_without_cache > 0:
saving = (
1 - total_actual / total_without_cache
) * 100
print(f"비용 차이: {saving:.2f}%")여기서 반복되는 긴 지침은 테스트를 위해 일부러 만든 것입니다. 실제 서비스에서는 시스템 프롬프트, 업무 문서, 도구 정의 등이 이 자리에 들어갑니다.
④ 실행
python cache_test.py세 번의 API 요청을 순서대로 보내고, 각각의 토큰 사용량과 비용 추정치를 출력합니다.
4. 결과는 어떻게 읽어야 할까요?
출력에서 볼 항목은 세 가지입니다.
| 항목 | 의미 |
|---|---|
ordinary | 일반 입력 요금이 적용되는 토큰 |
cached | 기존 캐시에서 읽은 토큰 |
written | 새로 캐시에 기록한 토큰 |
첫 요청에서는 공통 지침을 캐시에 기록하고, 이후 요청에서는 같은 지침을 재사용하는 흐름을 기대할 수 있습니다.
하지만 매번 캐시가 적중하는 것은 아닙니다. 기존 캐시의 존재 여부, 요청 간격, 프롬프트의 일치 여부 등에 따라 결과가 달라질 수 있습니다.
코드의 비용 계산은 공개된 Standard 단가를 적용한 추정치입니다. 실제 청구액과 차이가 있을 수 있고, 별도 도구 사용료나 특수 요금은 포함하지 않았습니다.
5. 여러 번 호출하면 얼마나 차이가 날까요?
공통 프롬프트가 7,000토큰이고, 이를 100번 사용한다고 가정해 보겠습니다.
비교를 단순하게 하기 위해 공통 프롬프트만 계산하고, 첫 요청에서 한 번 기록한 뒤 나머지 99번은 전부 캐시에서 읽는다고 가정합니다.
| 구분 | 비용 |
|---|---|
| 캐시 없이 100번 입력 | $0.070000 |
| 첫 캐시 쓰기 | $0.000875 |
| 이후 99번 캐시 읽기 | $0.006930 |
| 캐시 적용 합계 | $0.007805 |
이 가정에서는 공통 프롬프트의 입력 비용이 약 88.9% 줄어듭니다.
물론 현실에서는 모든 요청이 적중하지 않을 수 있습니다. 캐시가 만료되어 다시 쓰는 비용도 발생할 수 있고요. 사용자 질문과 출력 토큰 비용도 별도로 계산해야 합니다.
그래서 실제 서비스에서는 캐시 적중률뿐 아니라 캐시 쓰기 비용까지 포함한 전체 비용을 비교해야 합니다.
6. 캐시가 잘 적용되도록 프롬프트 구성하기
코드를 보면 공통 지침을 먼저 보내고 사용자 질문을 뒤에 배치했습니다.
이 순서가 중요합니다.
| 앞에 배치할 내용 | 뒤에 배치할 내용 |
|---|---|
| 시스템 프롬프트 | 사용자 질문 |
| 공통 업무 규칙 | 현재 날짜와 시간 |
| 고정 참고 문서 | 요청별 데이터 |
| 재사용하는 도구 정의 | 현재 작업 상태 |
특히 현재 시각이나 요청 ID처럼 매번 바뀌는 값을 공통 프롬프트 앞에 넣지 않는 게 좋습니다.
GPT-6 계열에서는 명시적인 캐시 브레이크포인트를 사용할 수도 있습니다. 공통 부분의 끝을 직접 지정하는 방식이라, 매번 바뀌는 사용자 질문까지 불필요하게 캐시에 기록하지 않도록 구성할 수 있습니다.
7. 실제 서비스에 적용한다면
저라면 먼저 API 사용 로그에 다음 항목을 남기겠습니다.
- 모델별 전체 입력 토큰
- 캐시 읽기 토큰
- 캐시 쓰기 토큰
- 출력 토큰
- 요청별 추정 비용
그다음 반복 호출이 많은 기능부터 확인해 보겠습니다. 고객지원 챗봇, 문서 분석, 코드 리뷰, 여러 도구를 사용하는 AI 에이전트 같은 기능이 후보가 될 수 있겠죠.
캐시 적중률이 높아도 쓰기 비용이 자주 발생하면 생각보다 절감 효과가 작을 수 있습니다. 반대로 긴 공통 지침을 짧은 간격으로 반복 사용하는 서비스라면 차이가 꽤 날 수 있습니다.
마무리
API 비용이 늘어나면 보통 모델부터 바꿀 생각을 합니다.
그런데 모델을 바꾸면 응답 품질도 달라질 수 있습니다. 그전에 지금 보내는 프롬프트에서 같은 내용을 얼마나 반복하고 있는지 확인해 보는 것도 좋겠습니다.
프롬프트 캐싱은 복잡한 기술은 아니지만, 제대로 비용을 계산하려면 캐시 읽기와 쓰기를 모두 봐야 합니다.
위 코드는 직접 실행해서 확인할 수 있도록 작성했습니다. 제가 API를 호출해 측정한 결과는 아니니, 각자 환경에서 나온 수치를 기준으로 판단하시면 됩니다.
참고 자료
지난번에 배치가 많은 작업을 시켰는데 다 하고 나더니
"프롬프트 캐싱을 까먹어서 돈을 낭비했습니다 죄송합니다?"
이런 식으로 답변하더라구요...
의식적으로 프롬프트 캐싱을 잘 하도록 구성하는거 중요한 것 같아요!