LLM 비용 라우팅 가드레일 실전기

개요: $705 청구서로 시작된 이야기

2026년 6월 1일, Anthropic 청구 대시보드에 하루 비용으로 약 $705가 찍혔습니다.

9개 세션 전부가 Opus로 돌아가고 있었습니다. 그 중 단일 모니터링 세션 하나가 9.4시간, 1,145턴, ScheduleWakeup 138회를 기록하며 $381을 소진했습니다. 전체 하루 비용의 54%가 모니터링 루프 하나에서 나온 것입니다. cache_read가 195M 토큰이었는데, 이것이 비용의 42%를 차지했습니다.

원인을 파악하는 데 오래 걸리지 않았습니다. 캐시 미스 문제가 아니었습니다. 거대한 컨텍스트에 반복 턴이 쌓이고, 거기에 Opus 단가(입력 $15/MTok, 출력 $75/MTok)가 곱해진 결과였습니다.

이 글은 그날의 사고를 계기로 1개월간 진행한 비용 감사 결과와, 실제로 운영 중인 4가지 통제 메커니즘을 솔직하게 공개합니다. 마지막에는 이 운영 노하우를 ThakiCloud Praxis와 AI Platform에 어떻게 제품화했는지 설명합니다.


비용은 어디서 새는가: 1개월 감사 결과

사고 이후 1개월치 청구 데이터를 cost_audit.pyrouter_audit.py로 분석했습니다. 총 비용 $4,691에서 가장 충격적인 발견은 이것이었습니다.

비용의 90.3%가 Opus 메인 세션 turn에서 나왔습니다. 서브에이전트가 아니라 세션 기본 모델이 최대 누수 지점이었습니다.

직관적으로는 “서브에이전트를 많이 띄우면 비싸지는 것 아닌가”라고 생각하기 쉽습니다. 그러나 현실은 달랐습니다. 서브에이전트들은 이미 Sonnet을 쓰고 있었고, 서브 LLM 라우팅 준수율이 96.4%였습니다. 문제는 오케스트레이터인 세션 메인 모델이 Opus로 고정되어 있다는 것이었습니다. 파일 검색, 단순 Q&A, 파이프라인 오케스트레이션처럼 Sonnet으로 충분한 수백 번의 턴이 전부 Opus 비용($15/MTok)으로 처리됐습니다.

router_audit.py가 계산한 반사실적 절감 가능액은 명확했습니다. 메인을 Sonnet으로 전환하면 $3,388 헤드룸이 생깁니다. 서브 라우팅 개선으로 추가 $1,533/월이 줄고, 총 80.4% 절감이 가능하다는 결론이었습니다. $4,691 중 $3,800 이상이 불필요한 지출이었다는 뜻입니다.

비용을 좌우하는 것은 단가가 아니라 빈도입니다. Opus를 하루 10턴 쓰는 것과 1,000턴 쓰는 것의 차이가, Opus와 Sonnet 단가 차이($15 vs $3)보다 훨씬 큽니다.

모델 단가를 표로 정리하면 이렇습니다.

모델 입력 ($/MTok) 출력 ($/MTok) 캐시 읽기 ($/MTok) 상대 비용
Haiku $0.80 $4.00 $0.08 ~1x
Sonnet $3.00 $15.00 $0.30 ~4x
Opus $15.00 $75.00 $1.50 ~19x

Opus와 Haiku의 입력 단가 차이는 약 19배입니다. 탐색성 작업 하나를 Opus 대신 Haiku로 돌리면, 그 비용 차이가 18번의 추가 Haiku 호출을 가능하게 합니다.


모델 라우팅: 작업 성격이 모델 등급을 결정합니다

감사 결과를 바탕으로 세운 첫 번째 원칙은 단순합니다. 모델 파라미터를 반드시 명시하고, 작업 성격에 맞는 등급을 쓴다.

Agent tool 호출에서 model 파라미터를 생략하면, 세션 기본 모델(Opus이면 $15/MTok)로 실행됩니다. 이것이 서브에이전트 비용 누수의 근원입니다. Claude Code 환경에서 유효한 값은 "haiku", "sonnet", "opus" 세 가지입니다. 별도 슬러그 매핑이 필요 없습니다.

라우팅 테이블은 이렇게 운용합니다.

  • 탐색, 파일 읽기, grep, 단순 조회: haiku. read-only이고 구조화된 출력만 필요합니다. 파일 3개 이상을 요약/대조하는 작업도 여기에 속합니다.
  • 코드베이스 연구, 요약, 번역: haiku. 판단이 필요 없는 작업입니다.
  • 분석, 구현, 코드 생성, 리뷰: sonnet. 품질과 비용의 균형점이며 대부분의 작업이 여기 해당됩니다.
  • 보고서 생성, 콘텐츠 작성: sonnet. 생성 작업의 기본값입니다.
  • 아키텍처 결정, 복잡한 다단계 추론, 심층 디버깅: opus. 이 경우에만 정당화됩니다.
Agent({
  description: "코드베이스에서 사용 패턴 탐색",
  model: "haiku",   # 탐색/검색 = haiku, model 명시 필수
  prompt: "..."
})

세션 기본 모델도 작업 성격에 따라 라우팅합니다. 파이프라인 오케스트레이션, 일상 코딩, 파일 검색은 /model sonnet으로 세션을 엽니다. 아키텍처 결정이 필요한 순간에만 /model opus로 전환합니다.

검색/조회성 작업을 메인 Opus 세션에서 직접 grep하지 말고, Haiku 서브에이전트로 위임하는 것이 “2K 토큰 룰”의 핵심입니다. 2K 토큰을 넘을 것으로 예상되는 도구 출력은 서브에이전트가 읽고 요약만 메인으로 반환합니다. 원본 덤프를 메인 컨텍스트에 올리는 것이 금지입니다.

이 라우팅을 일관되게 적용한 결과, 서브 라우팅 준수율 96.4%라는 수치가 나왔습니다. 탐색성 작업 55개 에이전트 중 대부분이 Haiku로 돌아갑니다. 개별 작업당 절감액은 작아 보이지만, 하루 수백 번의 탐색 작업이 누적되면 월 $1,533 차이를 만들어냅니다.

또한 세션 라이프사이클 관리도 중요합니다. 세션이 50턴을 넘거나 컨텍스트가 40%를 초과하면 /clear로 새로 시작합니다. 메가세션은 컨텍스트가 비대해질수록 매 턴 cache_read가 선형으로 증가하기 때문입니다. 6월 1일 사고에서 cache_read 비용이 전체의 42%였던 것이 이 때문입니다.


회고 기반 자동 에스컬레이션: 데이터가 모델을 결정합니다

“모든 스킬을 Sonnet으로 고정하면 품질이 무너진다”는 우려가 현실적인 반대 논거입니다. 이 우려는 타당합니다. 콘텐츠 분류, enrichment, 스레드 작성처럼 출력 품질이 산출물 자체인 스킬은 Sonnet 품질이 실제로 부족한 경우가 있습니다. 그래서 선택한 방식이 회고 기반 에스컬레이션입니다.

핵심 원칙은 “싸게 시작하되, 회고가 품질/실패를 감지하면 그 스킬만 Opus로 올린다”입니다. 전역 Opus 금지와 모순이 아닙니다. 폴링이 아닌 품질 산출 스킬에 한해, 데이터 기반으로만 승격합니다.

기본값은 Sonnet으로 시작합니다. 연속 bad-run이 2회를 초과하면 그 스킬만 Opus로 자동 승격합니다. clean run이 이어지면 streak을 리셋하지만, 자동 강등은 없습니다. 한번 Opus가 필요했던 스킬은 수동으로만 낮춥니다. 실패/승격 시 Slack 알림이 갑니다.

구현은 두 파일로 이루어집니다.

scripts/skills/skill_model_policy.json이 중앙 레지스트리 역할을 합니다.

{
  "skills": {
    "twitter-timeline-to-slack": {
      "model": "opus",
      "pinned": true,
      "reason": "classification+enrichment+thread writing 품질 임계",
      "fail_streak": 0
    }
  },
  "_default": {
    "model": "sonnet",
    "escalate_to": "opus",
    "max_fail_streak": 2,
    "fail_streak": 0
  }
}

러너는 실행 전에 모델을 정책에서 받고, 종료 시 회고를 기록합니다.

SKILL_NAME="bespin-news-digest"
MODEL="${MY_MODEL:-$(python3 scripts/skills/skill_retro.py get-model "$SKILL_NAME" 2>/dev/null || echo sonnet)}"

# ... claude -p --model "$MODEL" 실행 ...

python3 scripts/skills/skill_retro.py record "$SKILL_NAME" "$RC" "$RUN_LOG" 2>/dev/null || true

bad-run 판정은 보수적입니다. rc != 0 또는 로그에 Failed to authenticate, API Error:, Traceback 마커가 있을 때만 카운트합니다. 일시적인 네트워크 오류 하나로는 승격이 일어나지 않습니다. streak이 쌓여야 합니다. 이 보수성이 중요합니다. 과감하게 판정하면 일시적 blip마다 Opus로 올라가 비용 통제 효과가 사라집니다.

plist의 EnvironmentVariables에서 해당 스킬의 모델 키를 제거하면 정책이 구동합니다. 수동 오버라이드가 필요할 때만 환경 변수를 다시 넣습니다. 이 구조 덕에 새 스킬을 추가할 때 “어떤 모델을 쓸까”를 사전에 결정하지 않아도 됩니다. Sonnet으로 시작하고, 데이터가 모델을 올려줍니다.

품질이 미달이라고 느껴질 때, 먼저 해야 할 일은 검증 단계를 추가하는 것입니다. 모델을 올리기 전에 fan-out 결과에 adversarial verify 스텝을 먼저 달아보십시오. 품질 문제의 더 흔한 원인은 “모델이 약해서”가 아니라 검증 스테이지 부재입니다. 워커를 싸게 구성하고 최종 검증 스텝만 Opus로 두는 것이 비용 대비 품질을 극대화합니다.


루프 cron화와 컨텍스트 위생

6월 1일 사고의 54%를 차지한 모니터링 세션은 사실 Claude가 매 틱 판단할 필요가 없는 작업이었습니다. 가격 스냅샷, 상태 비교, 헬스체크는 Claude 없이도 돌릴 수 있습니다.

반복 모니터링은 Claude 핫루프에서 빼고 cron으로 전환합니다. 비용은 $0이 됩니다.

# 5분마다 실행, 특이사항 감지 시에만 Slack 알림
# com.thaki.toss-monitor.plist
scripts/toss_monitor_tick.sh

이 패턴의 핵심은 “사람이나 이벤트가 있을 때만 Claude를 호출한다”는 원칙입니다. 모든 상태 폴링은 셸 스크립트가 담당하고, 임계를 넘거나 이상 신호가 감지될 때만 Claude를 깨웁니다. 그날 $381을 소진한 ScheduleWakeup 138회는 대부분 단순 상태 확인이었는데, 이 작업들을 cron으로 빼내면 Claude 비용 자체가 발생하지 않습니다.

Claude 루프가 꼭 필요한 경우라면 다음 세 가지를 지킵니다.

첫째, 모델은 Haiku 또는 Sonnet으로만 씁니다. 모니터링에 Opus는 정당화되지 않습니다. 19배 비싼 모델이 단순 상태 비교에 쓰이는 것은 낭비입니다. 둘째, 상태는 파일(monitor-state.json)에 두고 매 wakeup이 최소 컨텍스트로 시작합니다. 이전 루프의 전체 대화 기록을 가져오는 것이 아니라, 상태 파일만 읽고 판단합니다. 셋째, 한 세션 50턴 또는 컨텍스트 40% 초과 전에 /clear합니다.

컨텍스트 위생에서 6월 1일 사고가 드러낸 구체적인 반패턴들을 열거합니다. 같은 파일을 세션 내에서 10회 재읽기했습니다. cd 명령을 153회 실행했는데, git은 현재 작업 디렉터리에서 바로 동작하므로 cd 프리픽스가 필요 없습니다. 큰 출력을 메인 컨텍스트에 그대로 올렸습니다. 이 세 가지가 cache_read를 비용의 42%까지 끌어올렸습니다.

세션 내에서 한번 읽은 파일은 다시 읽지 않습니다. 대용량 도구 출력은 /tmp/scratch-{task}.md에 써두고 경로만 메인으로 반환합니다. 반복 구조의 JSON 배열 출력이 많다면 headroom SmartCrusher 압축을 사용합니다. [추정] 50% 이상 절감이 가능하다는 벤치마크가 있습니다. rtk 접두사를 붙이면 셸 출력도 60~90% 압축됩니다.


Praxis, AI Platform: 운영 노하우의 제품화

이 운영 경험은 ThakiCloud 제품 두 곳으로 이어졌습니다.

Praxis의 비용인식 LLM 라우터는 예산 잔여에 따라 저비용 모델로 자동 전환합니다. 위에서 설명한 단계별 모델 자동선택 로직이 제품 레벨에서 구현된 것입니다. 월 예산의 70%가 소진되면 기본 모델을 Sonnet으로 내리고, 90%를 넘으면 Haiku로 강등합니다. 품질 임계 스킬은 핀으로 고정해 강등에서 제외합니다.

AI Platform의 비용 추적 및 할당량 제어는 팀·프로젝트별로 LLM 비용을 분리 집계합니다. cost_audit.py 수준의 감사를 플랫폼이 자동으로 수행하고, 특정 팀의 Opus 사용 비중이 임계를 넘으면 알림을 보냅니다. 쿠버네티스 Kueue로 GPU 워크로드를 조율하는 것과 동일한 사고방식을 LLM 토큰 예산에 적용한 것입니다.

claude-code-ccr-cost-routing은 다른 방향의 최적화입니다. 메인 모델은 Anthropic에 두되, 서브에이전트를 서드파티(MiniMax, GLM, DeepSeek, Qwen)로 라우팅합니다. Sonnet 대비 훨씬 저렴한 단가로 탐색·요약·번역을 처리하면서 메인 오케스트레이터의 일관성을 유지합니다.

세 접근법의 공통점은 하나입니다. 비용을 우연에 맡기지 않고, 코드가 결정론적으로 통제한다.


한계 및 교훈

솔직하게 공개해야 할 한계가 있습니다.

회고 기반 에스컬레이션에는 자동 강등이 없습니다. Opus로 올라간 스킬은 수동으로만 내릴 수 있습니다. 시간이 지나면 pinned Opus 스킬이 늘어날 수 있습니다. 주기적인 수동 검토가 필요합니다. 이는 설계 선택입니다. “한번 Opus가 필요했던 스킬은 데이터 없이 강등하지 않겠다”는 보수적 판단인데, 그 트레이드오프로 drift 가능성을 안고 갑니다.

품질 게이트를 너무 빡빡하게 잡으면 재배포 루프가 길어집니다. 임계값은 과거 통과분 기준으로 현실적으로 설정해야 합니다. 처음부터 완벽한 게이트를 설계하려 하면 운영이 멈춥니다. bad-run 판정을 보수적으로 설계한 이유도 이것입니다. 일시적인 네트워크 오류 하나가 Opus 자동 승격을 유발하면 비용 통제 효과가 반감됩니다.

비용 가드레일만으로 품질을 대체할 수 없습니다. 이 글에서 설명한 메커니즘은 비용을 줄이면서 품질을 유지하는 것이지, 품질을 포기하고 비용을 줄이는 것이 아닙니다. 특정 스킬에서 품질이 떨어지면 streak을 통해 자연스럽게 Opus로 올라가는 것이 설계 의도입니다.

가장 큰 교훈은 이것입니다. 모델 비용은 모델 단가가 아니라 모델 선택 빈도가 결정합니다. Opus를 $15/MTok에 팔더라도, Opus를 거의 쓰지 않으면 비용은 내려갑니다. 거꾸로, Haiku가 $0.80/MTok이라도 1,145턴 루프로 돌리면 상당한 비용이 나옵니다.

두 번째 교훈은 이것입니다. 품질 문제의 원인을 먼저 진단하십시오. “Sonnet이 부족하다”는 느낌이 들 때, 먼저 확인할 것은 모델 등급이 아니라 검증 단계의 유무입니다. fan-out 결과를 adversarial verify 없이 그대로 쓰면, Opus를 써도 품질이 불안정합니다. 워커는 싸게, 게이트만 비싸게 구성하는 것이 올바른 방향입니다.

올바른 도입 순서는 이렇습니다.

  1. 루프/모니터는 cron으로 빼내 Claude 비용 자체를 없앱니다.
  2. 세션 기본 모델을 Sonnet으로 내립니다. 전체 비용의 가장 큰 레버입니다.
  3. 서브에이전트에 model 파라미터를 필수 지정합니다.
  4. cost_audit.py로 1주일 비용을 감사하고 누수 지점을 찾습니다.
  5. 품질이 미달인 스킬만 회고 기반으로 Opus로 올립니다.
  6. 주기적으로 감사를 반복하고 숫자를 보면서 조정합니다.

이 순서를 지키면 $705/일 사고는 재현되지 않습니다. 동시에 Sonnet 품질이 충분한 작업은 Sonnet으로, Haiku로 충분한 탐색은 Haiku로 돌아가는 체계가 자리를 잡습니다.


ThakiCloud는 이 운영 경험을 Praxis와 AI Platform에 반영하고 있습니다. 비용인식 LLM 라우팅이 어떻게 제품으로 동작하는지 궁금하시다면 hello@thakicloud.co.kr로 문의 주십시오.