콘텐츠로 이동

비용 최적화와 모델 라우팅

소규모 프로젝트에서 에이전트 비용은 무시할 수 있는 수준입니다. 하지만 팀이 에이전트를 적극 활용하기 시작하면 비용이 빠르게 증가합니다.

개인 사용: $10~50/월 → 무시 가능
소규모 팀: $200~500/월 → 최적화 고려
프로덕션 팀: $2,000+/월 → 전략적 최적화 필수

비용 최적화는 단순히 돈을 아끼는 것이 아닙니다. 올바른 도구를 올바른 작업에 사용하는 엔지니어링 판단의 문제입니다.

| 모델 | 특성 | 적합한 작업 | 상대 비용 | |---|---|---|---| | Haiku | 빠름, 저렴, 간단한 추론 | 분류, 요약, 포맷 변환 | 1x | | Sonnet | 균형, 표준 코딩 | 일반 코드 작성, 디버깅 | 5x | | Opus | 느림, 고비용, 깊은 추론 | 아키텍처 설계, 복잡한 분석 | 15x |

harness/model-router.ts
type TaskComplexity = 'simple' | 'standard' | 'complex'
interface RoutingRule {
keywords: string[]
maxTokens: number
complexity: TaskComplexity
}
const ROUTING_RULES: RoutingRule[] = [
{
keywords: ['classify', 'summarize', 'format', 'extract', 'translate'],
maxTokens: 1000,
complexity: 'simple'
},
{
keywords: ['implement', 'fix', 'refactor', 'test', 'debug'],
maxTokens: 4000,
complexity: 'standard'
},
{
keywords: ['architect', 'design', 'analyze', 'review', 'optimize'],
maxTokens: 8000,
complexity: 'complex'
}
]
function routeTask(task: string, contextSize: number): string {
const lowerTask = task.toLowerCase()
for (const rule of ROUTING_RULES) {
const matches = rule.keywords.some(kw => lowerTask.includes(kw))
if (matches && contextSize <= rule.maxTokens) {
return modelForComplexity(rule.complexity)
}
}
return 'claude-sonnet-4-5' // 기본값
}
function modelForComplexity(complexity: TaskComplexity): string {
const models: Record<TaskComplexity, string> = {
simple: 'claude-haiku-4-5',
standard: 'claude-sonnet-4-5',
complex: 'claude-opus-4-5'
}
return models[complexity]
}

KV-cache(Key-Value Cache)는 반복되는 컨텍스트를 캐싱하여 API 비용을 대폭 줄이는 기술입니다.

일반 요청:
시스템 프롬프트 (2,000 토큰) → 매번 전체 처리
비용: 2,000 × $0.003/1K = $0.006/요청
캐시 적용:
시스템 프롬프트 첫 요청 → 캐시 저장 (일반 비용)
이후 요청 → 캐시 히트 (10% 비용)
비용: 2,000 × $0.0003/1K = $0.0006/요청
→ 10배 비용 절감
harness/cached-client.ts
import Anthropic from '@anthropic-ai/sdk'
const client = new Anthropic()
async function runWithCache(
systemPrompt: string,
userMessage: string
): Promise<string> {
const response = await client.messages.create({
model: 'claude-sonnet-4-5',
max_tokens: 4096,
system: [
{
type: 'text',
text: systemPrompt,
// 시스템 프롬프트를 캐시 대상으로 지정
cache_control: { type: 'ephemeral' }
}
],
messages: [
{ role: 'user', content: userMessage }
]
})
// 캐시 사용 통계 로깅
const usage = response.usage
console.info('Cache stats:', {
inputTokens: usage.input_tokens,
cacheCreationTokens: usage.cache_creation_input_tokens,
cacheReadTokens: usage.cache_read_input_tokens
})
return response.content[0].type === 'text' ? response.content[0].text : ''
}
높은 캐시 히트율을 위한 설계:
1. 정적 컨텐츠를 앞에 배치
[시스템 프롬프트 - 캐시] + [사용자 입력 - 비캐시]
2. AGENTS.md를 시스템 프롬프트에 포함
매 요청마다 동일한 컨텐츠 → 높은 히트율
3. 대화 히스토리 전략
오래된 메시지 압축/요약 후 캐시 경계 이전으로 이동
harness/token-budget.ts
interface TokenBudget {
maxInputTokens: number
maxOutputTokens: number
warningThreshold: number // 경고 발생 비율
}
const TOKEN_BUDGETS: Record<string, TokenBudget> = {
codeGeneration: {
maxInputTokens: 8_000,
maxOutputTokens: 4_000,
warningThreshold: 0.8
},
codeReview: {
maxInputTokens: 16_000,
maxOutputTokens: 2_000,
warningThreshold: 0.8
},
architectureAnalysis: {
maxInputTokens: 32_000,
maxOutputTokens: 8_000,
warningThreshold: 0.9
}
}
function validateTokenUsage(
taskType: string,
inputTokens: number
): { allowed: boolean; warning: string | null } {
const budget = TOKEN_BUDGETS[taskType]
if (!budget) return { allowed: true, warning: null }
if (inputTokens > budget.maxInputTokens) {
return {
allowed: false,
warning: `토큰 예산 초과: ${inputTokens} > ${budget.maxInputTokens}`
}
}
if (inputTokens > budget.maxInputTokens * budget.warningThreshold) {
return {
allowed: true,
warning: `토큰 예산 ${Math.round(inputTokens / budget.maxInputTokens * 100)}% 사용 중`
}
}
return { allowed: true, warning: null }
}
harness/cost-tracker.ts
interface CostRecord {
timestamp: Date
model: string
taskType: string
inputTokens: number
outputTokens: number
cacheReadTokens: number
estimatedCost: number
}
const COST_PER_1K: Record<string, { input: number; output: number; cacheRead: number }> = {
'claude-haiku-4-5': { input: 0.00025, output: 0.00125, cacheRead: 0.000025 },
'claude-sonnet-4-5': { input: 0.003, output: 0.015, cacheRead: 0.0003 },
'claude-opus-4-5': { input: 0.015, output: 0.075, cacheRead: 0.0015 }
}
function calculateCost(record: Omit<CostRecord, 'estimatedCost'>): number {
const rates = COST_PER_1K[record.model]
if (!rates) return 0
return (
(record.inputTokens / 1000) * rates.input +
(record.outputTokens / 1000) * rates.output +
(record.cacheReadTokens / 1000) * rates.cacheRead
)
}
.github/workflows/cost-alert.yml
name: Daily Cost Alert
on:
schedule:
- cron: '0 9 * * *' # 매일 오전 9시
jobs:
check-cost:
runs-on: ubuntu-latest
steps:
- name: Check daily spend
run: |
DAILY_COST=$(node scripts/get-daily-cost.js)
BUDGET=50 # $50/일 예산
if (( $(echo "$DAILY_COST > $BUDGET" | bc -l) )); then
echo "::warning::일일 비용 초과: $${DAILY_COST} (예산: $${BUDGET})"
# Slack 알림 전송
curl -X POST $SLACK_WEBHOOK \
-d "{\"text\": \"AI 에이전트 일일 비용 경고: \$${DAILY_COST}\"}"
fi

| 항목 | 확인할 지표 | |---|---| | 모델 라우팅 | 작업별 성공률, 재시도율, 입력·출력 토큰 비용 | | 프롬프트 캐싱 | 캐시 적중률, 캐시 읽기·쓰기 단가, 지연시간 | | 컨텍스트 압축 | 정답률·근거 누락률, 전달 토큰 수 | | 배치 처리 | 대기시간, 처리량, 실패한 재시도의 비용 | | 토큰 예산 | 예산 초과 차단 횟수와 중단된 작업의 품질 |

비용 최적화는 모델 라우팅, 프롬프트 캐싱, 토큰 예산 관리의 세 축으로 구성됩니다. 반복되는 컨텍스트가 있다고 해서 항상 비용 이점이 생기는 것은 아니므로, 모델·프롬프트·캐시 정책·예산을 바꿀 때마다 성공률, 지연시간, 토큰 비용을 같은 작업 묶음에서 비교합니다.