컴퓨터 액세스 시스템

에이전트 비용 최적화: 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,200800$0.0126$0.0120$0.025
2웹 검색 호출4,800200$0.0144$0.0030$0.017
3검색 결과 처리8,500600$0.0255$0.0090$0.035
4심층 읽기 (페이지 1)12,000500$0.0360$0.0075$0.044
5심층 읽기 (페이지 2)15,200500$0.0456$0.0075$0.053
6추가 검색16,800200$0.0504$0.0030$0.053
7결과 처리20,100600$0.0603$0.0090$0.069
8종합22,5001,200$0.0675$0.0180$0.086
9검증24,000400$0.0720$0.0060$0.078
10최종 응답25,5001,500$0.0765$0.0225$0.099
합계153,6006,500$0.461$0.098$0.558

입력 비용이 지배적이며, 대화 기록이 축적되면서 각 단계마다 증가한다는 점에 주목하세요. 단계 8~10은 전체 단계의 30%에 불과하지만 총 비용의 47%를 차지합니다.

비용 추적 코드

처음부터 추적 래퍼를 구현하세요:

import anthropic
import time
from dataclasses import dataclass, field
from typing import Optional
@dataclass
class 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},
}
@dataclass
class 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/)