에이전트 비용 최적화: API 지출 절감을 위한 실전 가이드
에이전트 비용 최적화: API 지출 절감을 위한 실전 가이드
에이전트가 잘 작동하고 있습니다. 하루 200건의 요청을 처리하고, 사용자들도 만족합니다. 그런데 API 청구서를 확인해 보니 이번 달 $3,400이 청구되었습니다. 세부 내역을 살펴보니, 에이전트가 요청당 평균 12번의 API 호출을 하고, 각 호출마다 4,000토큰짜리 시스템 프롬프트가 포함되어 있습니다. 시스템 프롬프트만으로 하루에 입력 토큰이 960만 개 소비됩니다. 100만 토큰당 $3 기준으로, 반복되는 콘텐츠만으로 월 $864가 지출되는 셈입니다.
비용 문제는 프로덕션 에이전트 배포가 축소되거나 완전히 중단되는 가장 큰 이유입니다. 최적화는 섣부른 행동이 아니라 생존의 문제입니다. 희소식은, 대부분의 에이전트 배포에서 시스템 전체를 재설계하지 않고도 50~80%의 비용 절감이 가능하다는 점입니다.
이 가이드에서는 ROI(투자 대비 수익률) 순으로 정렬된 7가지 비용 절감 전략을 소개합니다. 상단부터 시작하여 예산 목표를 달성할 때까지 순서대로 적용하세요. 각 섹션에는 구체적인 수치가 포함되어 있어, 코드를 한 줄도 작성하기 전에 예상 절감액을 추정할 수 있습니다.
1. 토큰 회계: 비용이 어디로 가는지 파악하기
측정하지 않으면 최적화할 수 없습니다. 무엇이든 변경하기 전에, 에이전트 워크플로우에서 모든 비용이 어디에 쓰이는지 전체 그림을 파악하세요.
입력 토큰 분류
Claude에 대한 모든 API 호출에는 입력 측면에서 여러 토큰 카테고리가 포함됩니다:
- 시스템 프롬프트 — 지시사항, 페르소나, 제약 조건. 종종 1,000~5,000 토큰이며 모든 호출마다 반복됩니다.
- 도구 정의 — 에이전트가 사용할 수 있는 각 도구의 JSON 스키마. 도구 10개만으로도 쉽게 2,000~3,000 토큰을 소비합니다.
- 대화 기록 — 대화의 모든 이전 메시지. 각 단계마다 증가합니다.
- 도구 결과 — 이전 도구 호출의 출력이 컨텍스트에 다시 주입됩니다. 매우 방대해질 수 있습니다(전체 웹 페이지, 데이터베이스 결과 등).
출력 토큰 분류
출력 토큰은 입력 토큰보다 3~5배 더 비싸기 때문에 중요한 최적화 대상입니다:
- 에이전트 추론 — 내부 사고 과정(특히 확장 사고 사용 시).
- 도구 호출 생성 — 도구 호출을 위한 JSON.
- 최종 응답 — 사용자에게 보여지는 답변.
작업별 비용 계산
단일 에이전트 작업의 총 비용은 다음과 같습니다:
총 비용 = Σ (입력_토큰 × 입력_가격 + 출력_토큰 × 출력_가격) 작업의 각 API 호출에 대해비용 분류 예시
다음은 Claude Sonnet($3/백만 입력, $15/백만 출력)을 사용하는 10단계 리서치 에이전트 작업의 현실적인 분류입니다:
| 단계 | 구성 요소 | 입력 토큰 | 출력 토큰 | 입력 비용 | 출력 비용 | 합계 |
|---|---|---|---|---|---|---|
| 1 | 초기 계획 | 4,200 | 800 | $0.0126 | $0.0120 | $0.025 |
| 2 | 웹 검색 호출 | 4,800 | 200 | $0.0144 | $0.0030 | $0.017 |
| 3 | 검색 결과 처리 | 8,500 | 600 | $0.0255 | $0.0090 | $0.035 |
| 4 | 심층 읽기 (페이지 1) | 12,000 | 500 | $0.0360 | $0.0075 | $0.044 |
| 5 | 심층 읽기 (페이지 2) | 15,200 | 500 | $0.0456 | $0.0075 | $0.053 |
| 6 | 추가 검색 | 16,800 | 200 | $0.0504 | $0.0030 | $0.053 |
| 7 | 결과 처리 | 20,100 | 600 | $0.0603 | $0.0090 | $0.069 |
| 8 | 종합 | 22,500 | 1,200 | $0.0675 | $0.0180 | $0.086 |
| 9 | 검증 | 24,000 | 400 | $0.0720 | $0.0060 | $0.078 |
| 10 | 최종 응답 | 25,500 | 1,500 | $0.0765 | $0.0225 | $0.099 |
| 합계 | 153,600 | 6,500 | $0.461 | $0.098 | $0.558 |
입력 비용이 지배적이며, 대화 기록이 축적되면서 각 단계마다 증가한다는 점에 주목하세요. 단계 8~10은 전체 단계의 30%에 불과하지만 총 비용의 47%를 차지합니다.
비용 추적 코드
처음부터 추적 래퍼를 구현하세요:
import anthropicimport timefrom dataclasses import dataclass, fieldfrom typing import Optional
@dataclassclass CostRecord: step: int model: str input_tokens: int output_tokens: int cache_read_tokens: int = 0 cache_creation_tokens: int = 0 input_cost: float = 0.0 output_cost: float = 0.0 total_cost: float = 0.0 duration_ms: float = 0.0
# Pricing per million tokens (as of early 2026)MODEL_PRICING = { "claude-haiku": {"input": 0.25, "output": 1.25, "cache_read": 0.025, "cache_write": 0.30}, "claude-sonnet": {"input": 3.00, "output": 15.00, "cache_read": 0.30, "cache_write": 3.75}, "claude-opus": {"input": 15.00, "output": 75.00, "cache_read": 1.50, "cache_write": 18.75},}
@dataclassclass TaskCostTracker: task_id: str budget_limit: Optional[float] = None records: list = field(default_factory=list) total_cost: float = 0.0
def record_call(self, step: int, model: str, usage) -> CostRecord: pricing = MODEL_PRICING.get(model, MODEL_PRICING["claude-sonnet"])
input_cost = (usage.input_tokens / 1_000_000) * pricing["input"] output_cost = (usage.output_tokens / 1_000_000) * pricing["output"] cache_read_cost = (getattr(usage, 'cache_read_input_tokens', 0) / 1_000_000) * pricing["cache_read"] cache_write_cost = (getattr(usage, 'cache_creation_input_tokens', 0) / 1_000_000) * pricing["cache_write"]
total = input_cost + output_cost + cache_read_cost + cache_write_cost
record = CostRecord( step=step, model=model, input_tokens=usage.input_tokens, output_tokens=usage.output_tokens, cache_read_tokens=getattr(usage, 'cache_read_input_tokens', 0), cache_creation_tokens=getattr(usage, 'cache_creation_input_tokens', 0), input_cost=input_cost + cache_read_cost + cache_write_cost, output_cost=output_cost, total_cost=total, )
self.records.append(record) self.total_cost += total
print(f" Step {step} [{model}]: {usage.input_tokens} in / {usage.output_tokens} out = ${total:.4f} (cumulative: ${self.total_cost:.4f})")
if self.budget_limit and self.total_cost > self.budget_limit: raise BudgetExceededError( f"Task {self.task_id} exceeded budget: ${self.total_cost:.4f} > ${self.budget_limit:.4f}" )
return record
def summary(self) -> dict: return { "task_id": self.task_id, "total_steps": len(self.records), "total_input_tokens": sum(r.input_tokens for r in self.records), "total_output_tokens": sum(r.output_tokens for r in self.records), "total_cost": self.total_cost, "cost_by_model": self._cost_by_model(), }
def _cost_by_model(self) -> dict: by_model = {} for r in self.records: if r.model not in by_model: by_model[r.model] = {"calls": 0,
---
## 관련 기사
- [AI 에이전트를 위한 캐싱 전략: 품질 타협 없이 비용 절감하기](/ko/blog/caching-strategies-for-ai-agents-cutting-costs-without-cutting-corners/)- [에이전트 워크플로우에서 추론 모델 활용: 확장 사고가 효과를 발휘할 때](/ko/blog/reasoning-models-in-agent-workflows-when-extended-thinking-pays-off/)- [멀티에이전트 패턴: 오케스트레이터, 워커, 파이프라인](/ko/blog/multi-agent-patterns/)- [자율 에이전트 시스템의 디버깅과 관찰 가능성](/ko/blog/debugging-agent-observability/)