OpenClaw 에이전트를 위한 토큰 라우팅 결정 트리 구축: 비용 절감 및 3건의 Cron 오류 해결
요약
OpenClaw 에이전트의 비용 절감과 안정성 향상을 위해 호출 전 모델을 선택하는 토큰 라우팅 결정 트리 구축 사례를 소개합니다. 단순 재시도 방식이 아닌, 작업의 성격에 따라 모델을 사전에 분류하여 지연 시간, 비용, 신뢰성 문제를 해결하는 방법을 다룹니다.
핵심 포인트
- 단순 재시도(Retry)와 사전 라우팅(Routing)의 차이점 설명
- 작업 성격에 따른 결정론적 함수 기반의 모델 선택 필요성
- 라우팅 최적화를 통한 비용 절감 및 Cron 작업 오류 방지
- 지연 시간과 모델 신뢰성을 고려한 라우팅 전략
저는 지난 3월 단 한 번의 주말 동안 87달러를 낭비했습니다. 제 OpenClaw 에이전트가 무언가 잘못해서가 아닙니다. 모든 도구 호출(tool call)이 유료 기본 모델(primary model)을 사용했기 때문입니다. 심지어 필요하지도 않은 Cron 작업의 날씨를 확인하는 호출까지도 말이죠.
그러다 이번 주 OmniRoute가 10억 개의 토큰을 0달러에 라우팅했다는 피드를 보여주었고, 저는 제가 몇 달 동안 라우팅을 잘못해 왔다는 사실을 깨달았습니다. '메커니즘(mechanics)'이 잘못된 것은 아니었습니다. 그것들은 괜찮았습니다. '결정(decisions)'이 틀렸던 것입니다. 저는 "모델이 작동 중인가?"를 기준으로 라우팅을 하고 있었지, "이 호출이 실제로 무엇을 배우려고 하는가?"를 기준으로 하지 않았습니다.
제가 구축한 라우팅 결정 트리(routing decision tree), 이로 인해 방지할 수 있었던 세 가지 Cron 오류, 그리고 주당 87달러에서 한 자릿수 달러로 줄어든 청구서를 소개합니다.
누구나 저지르는 라우팅 실수
OpenClaw의 폴백 체인(fallback chain)은 호출당(per-call) 순차적으로 작동하며 첫 번째 성공 지점에서 멈춥니다. 이것이 대부분의 사람들이 가지고 있는 사고 모델입니다. 기본 모델(Primary model) → 실패 → 다음 시도 → 실패 → 다음 시도 → 성공.
이것은 라우팅이 아닙니다. 그것은 재시도 목록(retry list)입니다. **라우팅(Routing)**이란 호출이 수행하는 작업에 따라 호출 *전(before)*에 적절한 모델을 선택하는 것을 의미합니다.
그 차이는 세 가지 측면에서 나타납니다:
- 지연 시간 (Latency). 로컬 9B 모델에 대한 6단계 추론(reasoning) 호출은 14초가 걸립니다. 유료 클라우드 모델의 경우 1.8초가 걸립니다. 호출이 "작다"는 이유로 해당 호출을 "아래(down)"로 라우팅하면 전체 대기 시간(wall time)이 세 배로 늘어날 수 있습니다.
- 비용 (Cost). 하트비트 상태 확인(heartbeat health check)을 유료 기본 모델로 라우팅하면 호출당 0.002달러가 듭니다. 이를 하루에 200번 수행하면 지능이 필요 없는 확인 작업을 위해 한 달에 14.40달러가 소요됩니다.
- 신뢰성 (Reliability). OpenRouter의
*:free모델들은 깔끔한 429(Too Many Requests) 오류를 반환하지 않는 소프트 속도 제한(soft rate limits)을 가지고 있으며, 대신 잘린 출력(truncated output)을 반환합니다. 저는 무슨 일이 일어나고 있는지 이해하기 전까지, 지난 5월에 정확히 이러한 실패 모드로 인해 세 건의 Cron 작업을 놓쳤습니다.
결정 트리 (The Decision Tree)
저는 에이전트의 래퍼 계층(wrapper layer)에 상주하는 아주 작은 호출 전 라우터(pre-call router)로서 이것을 작성했습니다. 모든 도구 호출은 먼저 한 단어로 분류되며, 이 분류가 모델을 선택합니다.
// routes.js — 호출 전 분류기 (pre-call classifier)
const ROUTES = {
heartbeat: { provider: 'ollama-local', model: 'qwen3.5:9b', max_tokens: 200 },
...
분류는 에이전트 내부가 아닌, 한 단계 위인 호출 지점 (call site)에서 발생합니다. 이는 의도된 설계입니다. 모델이 스스로 어떻게 라우팅할지 결정하게 해서는 안 됩니다. 대신, _호출 (call)_이 무엇을 수행하는지에 따라 버킷 (bucket)을 선택하는 결정론적 함수 (deterministic function)가 필요합니다.
// wrapper.js — 모든 도구 호출은 라우팅됨
async function callLLM(callType, prompt, context) {
const r = route(callType, context);
...
핵심 통찰: 폴백 (fallback)은 안전망이지, 전략이 아닙니다. 기본 모델이 다운되었다면 괜찮습니다, 체인을 따라 이동하면 됩니다. 하지만 라우팅 설정 때문에 200토큰짜리 하트비트 (heartbeat) 체크가 유료 기본 모델로 전달된다면, 폴백 체인이 도움을 줄 기회조차 얻지 못합니다. 이미 비용이 쌓이고 있기 때문입니다.
이를 통해 방지한 세 가지 Cron 오류
실패 사례 #1: VRAM 연쇄 오류 (VRAM cascade). 토요일 아침 메모리 유지보수 Cron 작업에는 제가 로컬 27B qwen 모델로 라우팅해 둔 38K 토큰 요약 단계가 있었습니다. M3가 바쁠 때, 래퍼 (wrapper)는 해당 로컬 모델로 폴백되었습니다. 로컬 모델은 나머지 런타임 (runtime)과 함께 38K 토큰을 여유롭게 수용할 수 없었습니다. 출력은 약 500토큰으로 급감하더니 결국 중단되었습니다. 90분 동안 세 번의 타임아웃이 발생했습니다. 이제 제 라우터는 입력이 50K 토큰을 초과하는 모든 로컬 호출을 자동으로 유료 기본 모델로 격상시킵니다. 토요일 아침 Cron은 5주째 정상(green) 상태입니다.
실패 사례 #2: 무료 티어 절단 (Free-tier truncation). DEV.to 댓글을 분류하기 위해 OpenRouter의 gpt-oss-20b:free를 사용하는 오후 참여형 Cron 작업이 있었습니다. 무료 티어가 조용히 절단된 응답(2,000토큰 대신 1,200토큰)을 반환하기 시작했습니다. 에이전트는 정상 작동 중이라고 판단했습니다. 댓글에는 답글이 달리지 않았습니다. 저의 해결책은 다음과 같습니다: 모든 분류 호출에 실제 출력 스키마 (schema)와 일치하는 엄격한 max_tokens 상한선을 설정하고, 성공을 선언하기 전에 JSON을 파싱하는 건전성 검사 (sanity check)를 추가했습니다. 이제는 6시간이 걸리는 대신 200ms 만에 절단 문제를 잡아냅니다.
실패 사례 #3: 콜드 스타트 폭풍 (Cold-start storm). 서브 에이전트 (Sub-agent)가 순수 왕복 시간 (round-trip) 기준으로 약 3초 지점에서 콜드 스타트 (cold-start)를 발생시킵니다. 리서치 작업을 위해 8개의 서브 에이전트를 병렬로 생성했는데, 각각이 유료 기본 모델 (paid primary)에 요청을 보냈습니다. 콜드 스타트로 인해 1.40달러가 소모되었지만, 첫 3초 동안 유용한 작업은 전혀 이루어지지 않았습니다. 저는 서브 에이전트 생성을 배치 (batch) 처리하고 (최대 3개까지 병렬 생성, 나머지는 대기열에 추가), 초기 "시작 (kickoff)" 프롬프트를 로컬 9B 모델로 라우팅 (routing)했습니다. 그 결과 콜드 스타트 비용이 0.31달러로 감소했습니다. 로컬 시작 방식이 유료 기본 모델의 슬롯 (slot)을 기다리는 것보다 빠르기 때문에 전체 실행 시간 (wall time)은 거의 변하지 않았습니다.
현재 청구서 현황
이전: 주당 87달러. 하트비트 체크 (heartbeat check)에만 월 약 14달러, 서브 에이전트 콜드 스타트에 26달러, 나머지는 실제 작업에 소요되었습니다.
이후: 주당 9~12달러. 동일한 작업량, 동일한 출력, 동일한 신뢰성을 유지하면서도, 각 호출 전에 적절한 모델로 라우팅했을 뿐입니다.
계산법은 복잡하지 않습니다. "필요하지 않은 지능에 비용을 지불하는 것을 중단하는 것"입니다. 두 개의 타임스탬프 (timestamp)를 비교하는 하트비트 체크에는 프론티어 모델 (frontier model)이 필요하지 않습니다. "이 폴더를 요약해"라고 말하는 서브 에이전트의 시작 단계도 마찬가지입니다. 하지만 두 개의 PR (Pull Request)이 충돌하는지에 대한 6단계 추론 체인 (reasoning chain)은 프론티어 모델이 필요합니다.
내가 배운 점
- 라우팅(Routing)은 목록이 아니라 결정입니다. 호출이 어떤 종류의 작업을 수행하는지 결정한 다음 모델을 선택하세요. 사용 가능한 모델을 기반으로 모델이 스스로를 선택하게 두지 마세요.
- 무료 티어(Free-tier) 모델은 주요 재료가 아니라 크론(cron) 독약입니다. 분류(classification), 요약(summarization), 또는 검증 가능한 엄격한 출력 스키마(output schema)가 있는 작업에만 사용하세요. 조용한 잘림(silent truncation)이 발생해도 알아차릴 수 없는 작업에는 사용하지 마세요.
- 폴백 체인(Fallback chains)은 안전망이지 전략이 아닙니다. 만약 정상적인 운영을 위해 라우팅이 폴백 체인에 의존하고 있다면, 당신의 라우팅은 잘못된 것입니다.
- 로컬 모델(Local models)은 공짜가 아닙니다. VRAM, 실제 소요 시간(wall time), 그리고 신뢰성 비용이 발생합니다. 지연 시간(latency)이 중요하지 않고 입력값이 적절하게 들어맞을 때만 로컬 모델로 라우팅하세요.
- 모든 것을 측정하세요. 저는 모든 호출의 제공자(provider), 모델, 입력/출력 토큰(input/output tokens), 그리고 실제 소요 시간(wall time)을 기록하는 래퍼(wrapper)를 사용했기 때문에 이 모든 실패를 잡아낼 수 있었습니다. 이 로그가 없다면, "에이전트가 가끔 느린 것 같다"는 신호 외에는 아무것도 얻을 수 없습니다.
이번 주의 OmniRoute 글은 대규모 환경에서 $0 라우팅이 어떤 모습인지 보여주었습니다. 제 설정은 더 작지만(단일 에이전트, 단일 사용자) 원칙은 동일합니다. 모델이 결정하게 두지 마세요. 결정하고, 그다음에 라우팅하세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기