과금과 투명성
AI 게이트웨이 투명성 감사: 프롬프트 주입·가짜 캐시와 실제 API 비용
저가형 프록시의 숨겨진 프롬프트 주입과 가짜 캐시 수법을 검증합니다. 멀티턴 대화의 토큰 부풀리기를 비교하고 오픈소스 테스트 스크립트로 실제 청구를 확인하세요.
“저가”의 이면에 숨겨진 실제 청구서: 숨은 프롬프트 주입과 가짜 캐시의 덫
AI 게이트웨이 및 LLM 중계 프록시 시장에서는 공식 요금 대비 50%에서 최대 80%에 달하는 파격적인 “할인”이 흔히 광고됩니다. 그러나 업계 전반에 걸친 벤치마크 조사 결과, 표면상 저렴해 보이는 다수의 중계 공급자가 백그라운드에서 대량의 숨겨진 시스템 지침을 주입하고 가짜 캐시 수치를 조작하여 개발자의 과금 토큰을 막대하게 부풀리고 있는 것으로 나타났습니다.
업스트림 프록시가 요청에 임의 지침을 주입하거나 토큰 메트릭을 조작하면, 실제 1,500토큰짜리 단일 요청이 15,000에서 28,000토큰으로 보고되어 청구됩니다. 멀티턴 대화 워크플로에서는 이러한 부풀리기가 턴을 거듭할수록 눈덩이처럼 불어나 8번째 턴에 이르면 실제 컨텍스트 크기의 4배 이상으로 폭증합니다. “낮은 배율”로 보였던 공급자가 실제로는 200%에서 400% 더 비싸지며 대화가 길어질수록 실질 비용은 기하급수적으로 치솟습니다.
| 지표 / 항목 | 일반적인 저가형 프록시 | API Route 게이트웨이 |
|---|---|---|
| 프롬프트 주입 | 미고지된 숨은 시스템 토큰 5,000~20,000개 주입 | 0토큰 주입 (요청 바이트 단위 완전 투과) |
| 첫 세션 캐시 | 신규 세션 턴 0부터 20,000개 이상의 가짜 캐시 읽기 보고 | 0 가짜 캐시 읽기 (접두사 트리 완전 일치 엄격 준수) |
| 캐시 쓰기 재현성 | 경직된 수식 (약 18%의 고정 쓰기 비율 유지) | 순수 프롬프트 증분만 반영 (신규 추가 컨텍스트만 쓰기) |
| 멀티턴 컨텍스트 증분 | 턴당 10K+ 토큰 이상 폭증 (8턴에서 4.2배 팽창) | 실제 컨텍스트 증가와 정확히 일치 (턴당 약 1.46K 토큰 증가) |
| 토크나이저 일치성 (GPT/Claude) | 조직적인 1.30배 할증 및 토큰 부풀리기 | 1.00배 완벽 일치 (공식 토크나이저와 100% 일치) |
| 검증용 엔드포인트 | 완전 차단 또는 HTTP 404 반환 | /v1/messages/count_tokens 네이티브 제공 |
| 실질적인 이용 비용 | 표면상 저가이나 종합 청구액은 2~4배 고가 | 투명하고 검증 가능하며 수학적으로 정확한 과금 |
API Route 게이트웨이는 조작 없는 완전 무변조 투과 기준을 엄격하게 준수합니다. 요금표에서 투명한 요율을 확인하고, 계정 생성 후 빠른 시작 가이드에 따라 안전한 소액 검증부터 시작하세요.
프록시 과금 트랩의 기술적 구조 심층 분석
타사 중계 및 프록시 공급자를 대상으로 광범위한 감사를 진행한 결과, 개발자에게 부당한 토큰 비용을 청구하는 대표적인 세 가지 수법이 확인되었습니다.
트랩 1: 턴 0의 “불가능한 캐시 읽기”
Anthropic 프롬프트 캐싱 기술 문서 및 OpenAI 프롬프트 캐싱 가이드에 명시된 대로, 접두사 기반 프롬프트 캐싱은 완벽한 바이트 단위 접두사 일치를 요구합니다. 프롬프트 시작 부분에 암호학적으로 고유한 난수 UUID를 배치한 경우, 서버의 기존 캐시에 적중하는 것은 물리적으로 불가능합니다. 그러나 경쟁사 프록시는 완전히 새로운 세션의 턴 0에서 21,800~22,372개의 cache_read_input_tokens를 보고합니다. 이는 과금 기준을 끌어올리기 위해 지어낸 가짜 수치입니다.
트랩 2: 고정 비율에 의한 가짜 캐시 산출식
정상적인 프롬프트 캐싱 체계에서는 새로 추가된 텍스트만 cache_creation_input_tokens에 기록되고, 변경 없는 기존 접두사는 cache_read_input_tokens에 기록됩니다. 그러나 경쟁사 프록시는 사용자가 50토큰을 추가했든 1,500토큰을 추가했든 상관없이 쓰기 비율이 전체 입력 토큰의 약 18.00%(Cache Write / Total Tokens ≡ 18.00%)로 일정하게 고정되어 있습니다. 이는 실제 하드웨어 캐시 동작과 무관하게 하드코딩된 수식으로 과금 데이터를 만들어내고 있음을 증명합니다.
트랩 3: 멀티턴 대화에서의 복리적 토큰 폭발
에이전트나 연속 대화 워크플로에서는 턴마다 일정한 양의 새 컨텍스트가 추가되어야 합니다. 그러나 숨겨진 주입과 가짜 캐시가 결합되면 토큰 수가 비정상적인 눈덩이 형태로 급증합니다.
Expected Growth: [Context: 13.2K] ──(+1.46K)──► [14.6K] ──(+1.46K)──► [16.1K] ──(+1.46K)──► [17.6K] ... Turn 8: [23.4K]
Inflated Growth: [Reported: 28.7K] ──(+10.7K)──► [33.4K] ──(+10.7K)──► [44.1K] ──(+10.7K)──► [54.7K] ... Turn 8: [97.4K] (4.2x)매 턴마다 10,600개 이상의 토큰이 누적되어, 실제 컨텍스트가 23,404토큰에 불과한 8턴 대화가 경쟁사 프록시에서는 97,400토큰으로 청구됩니다. 이로 인해 명목상의 할인은 완전히 사라지고 장기 에이전트 작업은 극도로 비싼 비용을 치르게 됩니다.
완전 오픈소스 Python 감사 테스트 스크립트
진정한 투명성은 독립적인 개발자의 객관적인 검증을 견뎌낼 수 있어야 합니다. 아래 Python 3 테스트 스크립트는 외부 라이브러리 의존성 없이 표준 라이브러리만으로 동작하며, 멀티턴 감사 과정을 그대로 재현합니다. https://global.api-route.com 또는 원하는 타사 프록시를 대상으로 직접 실행하여 토큰 진실성을 확인할 수 있습니다.
#!/usr/bin/env python3
"""
AI Gateway Token Usage & Cache Fidelity Audit Harness
Open Source Tool by API Route (https://www.api-route.com)
Usage:
BASE_URL=https://global.api-route.com API_KEY=sk-... python3 usage_test.py claude-sonnet-5-5 gpt-6-sol
Optional Environment Variables:
DIALOGS=2 Number of independent conversation sessions (default: 2)
TURNS=8 Number of consecutive turns per session (default: 8)
GAP=30 Seconds between turns within ephemeral cache window (default: 30)
"""
import json
import os
import sys
import time
import urllib.error
import urllib.request
import uuid
BASE = os.environ.get('BASE_URL', 'https://global.api-route.com').rstrip('/')
KEY = os.environ.get('API_KEY', '')
DIALOGS = int(os.environ.get('DIALOGS', 2))
TURNS = int(os.environ.get('TURNS', 8))
GAP = int(os.environ.get('GAP', 30))
def post(path, body):
"""Executes an authenticated JSON POST request."""
req = urllib.request.Request(
BASE + path,
data=json.dumps(body).encode('utf-8'),
headers={
'Content-Type': 'application/json',
'User-Agent': 'api-route-transparency-audit/1.0',
'x-api-key': KEY,
'Authorization': f'Bearer {KEY}',
'anthropic-version': '2023-06-01',
},
)
try:
with urllib.request.urlopen(req, timeout=180) as r:
return r.status, json.load(r)
except urllib.error.HTTPError as e:
return e.code, {'error': e.read().decode('utf-8', errors='replace')[:200]}
def claude_turn(model, system, msgs):
"""Executes a Claude turn, inspecting both /v1/messages and /v1/messages/count_tokens."""
body = {
'model': model,
'max_tokens': 30,
'system': [
{'type': 'text', 'text': system, 'cache_control': {'type': 'ephemeral'}}
],
'messages': msgs,
}
count_status, count = post(
'/v1/messages/count_tokens',
{k: body[k] for k in ('model', 'system', 'messages')},
)
status, d = post('/v1/messages', body)
u = d.get('usage') or {}
text = ''.join(b.get('text', '') for b in d.get('content', []) if isinstance(b, dict)) or '-'
counted = count.get('input_tokens') if count_status == 200 else f'HTTP {count_status}'
inp = u.get('input_tokens', 0)
read = u.get('cache_read_input_tokens', 0)
write = u.get('cache_creation_input_tokens', 0)
out = u.get('output_tokens', 0)
return status, counted, inp, read, write, out, text, d.get('error')
def gpt_turn(model, system, msgs, cache_key):
"""Executes a GPT turn on /v1/responses with prompt_cache_key."""
body = {
'model': model,
'instructions': system,
'input': msgs,
'max_output_tokens': 40,
'prompt_cache_key': cache_key,
}
status, d = post('/v1/responses', body)
u = d.get('usage') or {}
det = u.get('input_tokens_details') or {}
read = det.get('cached_tokens', 0)
write = det.get('cache_write_tokens', 0)
raw_input = u.get('input_tokens', 0)
inp = max(0, raw_input - read - write)
out = u.get('output_tokens', 0)
text = ''
for item in d.get('output', []):
if isinstance(item, dict):
for c in item.get('content') or []:
if isinstance(c, dict):
text += c.get('text', '')
return status, None, inp, read, write, out, text or '-', d.get('error')
def run_audit(model):
is_claude = model.startswith('claude')
print(f"AUDITING MODEL: {model} | Sessions: {DIALOGS} | Turns/Session: {TURNS} | Turn Gap: {GAP}s")
for dialog in range(DIALOGS):
nonce = uuid.uuid4().hex
system = f'Session {nonce}. ' + ' '.join(
f'Rule {i}: service number {i} returns code {i * 7} and writes log to data{i}.'
for i in range(1, 320)
)
msgs = []
for turn in range(TURNS):
if turn > 0:
time.sleep(GAP)
extra = ' '.join(
f'Note {turn}.{j}: audit service {j + turn} and record status.'
for j in range(60)
)
question = f'{extra} What is the code for service {turn + 3}? Output number only.'
if is_claude:
msgs.append({
'role': 'user',
'content': [{'type': 'text', 'text': question, 'cache_control': {'type': 'ephemeral'}}],
})
status, counted, inp, read, write, out, text, error = claude_turn(model, system, msgs)
msgs[-1]['content'][0].pop('cache_control', None)
else:
msgs.append({'role': 'user', 'content': question})
status, counted, inp, read, write, out, text, error = gpt_turn(
model, system, msgs, f'audit-{nonce}'
)
total = inp + read + write
print(
f"[{model}] S{dialog} T{turn} | HTTP {status} | "
f"CountTokens: {str(counted):>10} | Input: {inp:5d} | "
f"CacheRead: {read:5d} | CacheWrite: {write:5d} | Total: {total:6d} | Out: {out:3d}"
+ (f" | ERROR: {error}" if status != 200 else "")
)
if status == 200:
msgs.append({'role': 'assistant', 'content': text})
else:
msgs.pop()
if __name__ == '__main__':
if not KEY:
print("ERROR: API_KEY environment variable is required.", file=sys.stderr)
sys.exit(1)
models_to_test = sys.argv[1:] or ['claude-sonnet-5-5', 'gpt-6-sol']
for m in models_to_test:
run_audit(m)
전체 오픈소스 테스트 도구 및 실행 가이드는 GitHub 저장소 GitHub benchmark repository에 공개되어 있습니다.
멀티턴 대화 실측 벤치마크 결과 비교
동일한 프롬프트 시퀀스를 사용하여 일반적인 “저가형” 프록시와 API Route 공식 프로덕션 게이트웨이를 비교 측정했습니다. Claude Opus 및 Sonnet 모델을 대상으로 한 연속 8턴 대화의 실측 결과는 다음과 같습니다.
| 턴 | 실제 컨텍스트 증가량 | 타사 보고 합계 | 타사 캐시 읽기 | 타사 캐시 쓰기 | API Route 보고 합계 | API Route 캐시 읽기 | API Route 캐시 쓰기 |
|---|---|---|---|---|---|---|---|
| 턴 0 | 13,177 토큰 | 28,682 | 22,372 (가짜) | 5,162 (18.0%) | 13,177 (정확) | 0 (충실) | 13,173 |
| 턴 1 | 14,638 토큰 | 33,397 | 26,718 | 6,011 (18.0%) | 14,638 (정확) | 0 | 14,634 |
| 턴 2 | 16,099 토큰 | 44,060 | 35,249 | 7,931 (18.0%) | 16,099 (정확) | 14,634 | 1,461 |
| 턴 3 | 17,560 토큰 | 54,726 | 43,234 | 9,850 (18.0%) | 17,560 (정확) | 16,095 | 1,461 |
| 턴 4 | 19,021 토큰 | 65,393 | 52,315 | 11,770 (18.0%) | 19,021 (정확) | 17,556 | 1,461 |
| 턴 5 | 20,482 토큰 | 76,063 | 60,850 | 13,692 (18.0%) | 20,482 (정확) | 19,017 | 1,461 |
| 턴 6 | 21,943 토큰 | 86,730 | 65,914 | 15,612 (18.0%) | 21,943 (정확) | 20,478 | 1,461 |
| 턴 7 | 23,404 토큰 | 97,400 | 74,023 | 17,532 (18.0%) | 23,404 (정확) | 21,939 | 1,461 |
실측 데이터가 증명하는 핵심 사실:
- 턴 0 가짜 캐시 읽기 0건: API Route는 신규 세션에서 엄격하게 0을 반환하여 콜드 스타트를 충실히 보고합니다. 반면 경쟁사는 22,000건 이상의 가짜 읽기를 청구했습니다.
- 정확한 증분 일치: 매 턴 추가되는 텍스트는 정확히 1,461토큰입니다. API Route의 쓰기량은 1,461토큰을 정확히 반영하는 반면, 경쟁사는 매 턴 10,660토큰 이상 부풀려졌습니다.
- 실질 청구액 역전: 7턴 시점에서 실제 컨텍스트는 23,404토큰이지만 경쟁사는 97,400토큰을 청구합니다. 경쟁사가 50% 할인을 적용하더라도 청구 단위는 48,700이 되어 API Route 정가 23,404단위보다 2배 이상 비쌉니다.
OpenAI GPT 토크나이저 1.00배 완벽 일치
gpt-6-sol 등의 모델 테스트에서도 경쟁사 프록시는 입력 프롬프트에 1.30배 할증 계수를 적용하여 청구했습니다(턴 0에서 기준 8,258 대비 10,724 청구). API Route는 패딩이나 임의 배수를 일절 배제하며 공식 토크나이저와 100% 일치합니다.
개발자 자가진단 체크리스트: 부정 프록시 판별 4단계
현재 사용 중인 AI 프록시에서 과다 청구가 발생하고 있는지 4단계로 자가진단할 수 있습니다.
- 턴 0 콜드 캐시 확인: 시스템 프롬프트 맨 앞에 32자리 난수 UUID를 넣고 요청합니다. 첫 요청에서
cache_read_input_tokens > 0이 반환되면 가짜 캐시입니다. - 캐시 쓰기 비율 확인: 연속된 턴에서 쓰기량과 총 입력의 비율을 계산합니다. 추가된 텍스트 크기와 상관없이 18.00% 안팎의 고정 비율을 보인다면 조작된 공식이 사용되고 있습니다.
- 컨텍스트 증분과 토크나이저 대조: N번째 턴에서 추가한 텍스트의 실제 토큰 수를 측정합니다. 실제 증가량이 1,000토큰인데 프록시 보고가 5,000~10,000토큰 증가했다면 숨은 프롬프트가 주입된 것입니다.
- 공식 사전 계산 엔드포인트 호출: 페이로드를
/v1/messages/count_tokens엔드포인트로 전송하여 확인합니다.
curl -X POST https://global.api-route.com/v1/messages/count_tokens \
-H "Content-Type: application/json" \
-H "x-api-key: $API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-sonnet-5-5",
"messages": [{"role": "user", "content": "Hello, world!"}]
}'{
"input_tokens": 10
}이 엔드포인트가 HTTP 404를 반환하거나 청구된 토큰 수와 불일치한다면 독립적인 감사가 불가능한 공급자입니다. API Route를 사용할 때는 사용 로그에서 모든 요청의 상세 내역을 실시간으로 대조할 수 있습니다.