LLM 앱 운영 #2 비용 — 토큰 회계와 모델 라우팅
1편의 계측으로 어디서 얼마가 나가는지 보이기 시작했습니다. 이제 줄입니다. 비용 절감 수단은 여러 가지지만 효과의 크기가 다르므로, 이 글은 큰 지렛대부터 순서대로 갑니다. 입력을 줄이고, 출력을 줄이고, 그리고 가장 큰 것, 작업에 맞는 모델로 보내는 라우팅입니다.
보내기 전에 잽니다 — count_tokens #
토큰 회계의 기본 도구부터 챙깁니다. API에는 요청을 보내지 않고 토큰 수만 세는 count_tokens 엔드포인트가 있습니다.
count = client.messages.count_tokens(
model="claude-opus-4-8",
system=SYSTEM,
messages=messages,
)
print(count.input_tokens) # 이 요청의 입력 토큰 수쓰임은 두 가지입니다. 첫째, 상한 게이트입니다. 사용자가 올린 문서가 비정상적으로 크면, 보내기 전에 알고 거부하거나 분할할 수 있습니다. 둘째, 설계 단계의 견적입니다. 시스템 프롬프트를 고치거나 RAG의 조각 수를 바꿀 때, 입력이 몇 토큰 늘었는지 배포 전에 압니다. 주의할 점 하나, 토큰화는 모델별이므로 실제로 쓸 모델로 세야 정확합니다.
입력 다이어트 — 쌓이는 것들을 의심하기 #
1편의 로그에서 입력 토큰이 큰 기능을 열어 보면, 범인은 대개 본문이 아니라 딸려 들어가는 것들입니다.
- 대화 누적 — 챗봇이 대화 전체를 매번 다시 보냅니다. LLM 앱 개발 실전 9편의 요약·절단 기법이 비용 기법이기도 합니다.
- RAG 조각 과잉 — top_k를 늘려 둔 채 잊으면 매 요청이 그만큼 무겁습니다. RAG 심화 4편의 리랭킹은 품질과 비용을 같이 잡는 장치입니다.
- 도구 결과 비대 — 에이전트의 도구가 통째로 반환하는 JSON. AI 에이전트 개발 실전 4편의 결과 상한이 여기서도 일합니다.
이미 다른 시리즈에서 만든 장치들이 전부 비용 장치였다는 것이 이 절의 요지입니다. 새로 만들 것은 적고, 1편의 계측으로 어느 장치가 풀려 있는지 찾는 일이 대부분입니다.
출력 다이어트 — 단가 5배짜리 토큰 #
출력은 입력의 5배 단가이므로 같은 토큰 수를 줄여도 효과가 5배입니다. 손잡이는 두 개입니다.
- 프롬프트로 길이를 지시합니다. “3문장 이내로”, “JSON만 출력하고 설명은 생략” 같은 명시적 지시가 가장 효과적입니다. 특히 구조화된 출력을 쓰는 기능에서 모델이 붙이는 서론과 부연은 순수한 비용입니다.
- max_tokens는 안전망으로 씁니다. max_tokens는 길이를 유도하는 파라미터가 아니라 상한입니다. 너무 조이면 에이전트 1편에서 본 잘림(stop_reason: max_tokens)이 늘어나 재시도 비용이 오히려 붙습니다. 1편 로그의 잘림 비율을 보면서 여유 있게 둡니다.
모델 라우팅 — 가장 큰 지렛대 #
이 글의 본론입니다. 1편의 단가표에서 본 것처럼 모델 간 단가 차이는 5배에 이릅니다. 그런데 앱 안의 작업이 모두 같은 난이도는 아닙니다. 분류, 라우팅 판단, 요약 같은 작업은 작은 모델로도 충분하고, 복잡한 추론과 에이전트 작업은 큰 모델이 필요합니다. 작업별로 모델을 나눠 보내는 것이 모델 라우팅이고, 대부분의 서비스에서 가장 큰 비용 절감이 여기서 나옵니다.
ROUTES = {
# 단순 작업 — 빠르고 싼 모델
"classify_intent": {"model": "claude-haiku-4-5", "max_tokens": 256},
"rewrite_query": {"model": "claude-haiku-4-5", "max_tokens": 300},
"summarize_doc": {"model": "claude-sonnet-4-6", "max_tokens": 1024},
# 핵심 작업 — 품질 우선
"answer_question": {"model": "claude-opus-4-8", "max_tokens": 4096},
"agent_task": {"model": "claude-opus-4-8", "max_tokens": 16000},
}
def call(task: str, **kwargs):
route = ROUTES[task]
return call_llm(model=route["model"], max_tokens=route["max_tokens"],
metadata={"feature": task}, **kwargs)라우팅 설계의 원칙은 세 가지입니다.
- 품질 기준은 측정으로 정합니다. “이 작업은 haiku로 충분한가"는 감이 아니라 평가셋으로 판정합니다. RAG 심화 6편의 평가 파이프라인을 모델 비교에 그대로 쓰면 됩니다. 정확도가 같다면 싼 모델이 정답입니다.
- 이미 라우팅하고 있던 곳을 찾습니다. RAG 심화 4편의 쿼리 리라이팅이 haiku였던 것처럼, 보조 작업들은 이미 후보입니다. 1편 로그에서 기능별 비용을 보고 비싼 모델로 가는 단순 작업부터 옮깁니다.
- 경계 작업은 위로 보냅니다. 애매하면 비싼 모델로 두고, 측정이 충분해진 뒤에 내립니다. 라우팅의 실패 모드는 비용이 아니라 품질 저하이고, 그쪽이 복구가 더 비쌉니다.
effort — 같은 모델 안의 조절 손잡이 #
모델을 바꾸지 않고도 조절하는 손잡이가 하나 더 있습니다. effort 파라미터입니다. 에이전트 3편에서 품질 쪽으로 소개했지만, 운영 관점에서는 토큰 사용량의 손잡이이기도 합니다. effort를 낮추면 모델이 더 간결하게 생각하고 답해서 토큰이 줄고, 높이면 그 반대입니다.
# 단순 추출 — 낮은 effort 로 토큰 절약
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
output_config={"effort": "low"},
messages=[...],
)라우팅과 결을 맞춰서, 작업 정의에 모델과 함께 effort 를 두면 조절 지점이 한곳에 모입니다. 단 에이전트처럼 깊은 추론이 단계 수를 줄여 주는 작업에서는 effort 를 낮추는 것이 총비용을 올릴 수도 있다는 것을 3편에서 봤습니다. 여기서도 판정 기준은 측정입니다.
흔히 걸려 넘어지는 곳 #
- 모든 작업을 한 모델로 보낸다 — “그냥 제일 좋은 모델"은 데모의 선택입니다. 분류 한 번에 5배 단가를 쓰는 것이 쌓이면 청구서가 됩니다.
- 품질 검증 없이 모델을 내린다 — 비용은 즉시 보이고 품질 저하는 늦게 보입니다. 내리기 전에 평가셋 비교, 내린 뒤에 품질 지표 감시가 순서입니다.
- max_tokens로 출력을 줄이려 한다 — 잘림은 절약이 아니라 불량입니다. 길이는 프롬프트로 지시하고 max_tokens는 안전망으로 둡니다.
마무리 #
이번 글에서는 비용의 지렛대를 큰 것부터 당겼습니다.
- count_tokens로 보내기 전에 재고, 입력에 딸려 들어가는 것들(대화 누적, RAG 조각, 도구 결과)을 다이어트합니다.
- 출력은 단가 5배입니다. 길이는 프롬프트로 지시합니다.
- 가장 큰 지렛대는 작업 난이도별 모델 라우팅입니다. 품질 기준은 평가셋 측정으로 정하고, effort 로 한 번 더 조절합니다.
그런데 아직 손대지 않은 큰 덩어리가 하나 있습니다. 매 요청 똑같이 반복되는 시스템 프롬프트와 도구 정의입니다. 다음 글인 “LLM 앱 운영 #3 프롬프트 캐싱 실전"에서 그 반복분의 가격을 10분의 1로 만들겠습니다.