AI 에이전트에 타이머를 설정했더니, 밤새 아무것도 안 하고 1억 3,600만 토큰을 소모했습니다.
요약
AI 에이전트에 예약된 깨우기 기능을 설정했을 때 발생한 막대한 토큰 소모 사례를 분석합니다. API의 상태 비저장 특성, 짧은 프롬프트 캐시 수명, 지속적으로 증가하는 스레드 크기가 결합되어 비용이 기하급수적으로 증폭되는 원인을 설명합니다.
핵심 포인트
- API의 Stateless 특성으로 인해 매 턴마다 전체 대화 기록을 재전송해야 함
- 프롬프트 캐시 만료(Claude 기준 약 5분)로 인해 캐시 할인 혜택을 받지 못함
- 세션이 길어질수록 읽어야 할 컨텍스트가 늘어나며 비용이 복리로 증가함
- 단순 모델 교체가 아닌 아키텍처 차원의 접근으로 해결해야 함
제가 직접 개입하지 않고도 저희 AI 에이전트 프로젝트 중 하나를 계속 진행시키기 위해, 저는 당연한 선택을 했습니다. 오케스트레이션 에이전트 (orchestrating agent)에게 예약된 깨우기 (scheduled wake-up) 기능을 부여한 것입니다. 몇 분마다 에이전트가 스스로를 재호출하여, 작업 큐 (task queue)를 확인하고, 한 단계를 수행한 뒤 다시 잠드는 방식이었습니다.
이것은 밤새 실행되었습니다. 아침이 되었을 때, 단 하나의 세션이 1억 3,600만 토큰을 소모했습니다. 그리고 에이전트는 실행 중이던 저의 다른 에이전트들을 몇 시간 동안 조용히 종료시키고 있었습니다. 이는 5시간의 토큰 한도가 엄격하게 적용되는 구독 계정이었기 때문에, 예상치 못한 청구서가 날아오지는 않았습니다. 대신, 이 하트비트 (heartbeat) 작업이 공유 속도 제한 (rate limit)을 모두 잡아먹어 다른 모든 작업들을 굶겨 죽였습니다. 단 한 번의 5시간 블록에서만 1억 1,600만 토큰을 사용했는데, 이는 계정 한도의 76%에 달하는 양이었습니다.
시스템이 충돌한 것은 아니었습니다. 제 코드에 제어 불능 루프 (runaway loop)가 있었던 것도 아니었습니다. 에이전트는 제가 지시한 대로 정확히 수행했습니다. 세션을 분석했을 때, 이 현상을 설명해 주는 숫자는 다음과 같았습니다.
처리된 토큰의 97.7%는 에이전트가 자신의 대화 기록을 다시 읽는 데 사용되었습니다.
20시간 동안 지속된 1,297회의 턴 (turns)에 걸쳐, 실제 새로운 작업에 사용된 1,190만 토큰에 비해 5억 800만 토큰의 컨텍스트 (context)를 다시 읽었습니다.
에이전트는 거의 아무것도 하지 않았습니다. 거의 모든 시간을 자신의 과거를 다시 읽는 데 보냈습니다. 합리적으로 보이는 설정이 왜 이런 결과를 초래하는지 그 이유를 설명해 드리겠습니다.
함정: 증폭을 일으키는 세 가지 평범한 요소
1. API는 상태가 없습니다 (stateless). 모델은 당신의 대화를 기억하지 못합니다. 매 턴마다 전체 스레드 (system prompt, 이전의 모든 메시지, 모든 도구 호출 및 결과)를 입력값으로 다시 전송해야 합니다. 20번째 턴은 1~19번째 턴을 다시 읽는 비용을 지불하는 셈입니다. 이를 자동화하기 전까지는 피할 수 없는 기본 조건입니다.
2. 프롬프트 캐시 (prompt cache)의 수명이 짧습니다. 제공업체들은 재사용 비용을 낮추기 위해 컨텍스트를 캐싱합니다. 하지만 캐시는 빠르게 만료됩니다. Claude의 경우 약 5분입니다. 저의 하트비트는 (완곡하게 표현하자면) 5분 이상의 간격으로 실행되었습니다. 따라서 매번 깨어날 때마다 캐시가 만료된 후에 작동했고, 할인된 캐시 가격이 아닌 거의 전체 입력 가격으로 전체 스레드를 처음부터 다시 처리해야 했습니다.
3. 스레드는 계속해서 늘어납니다. 매번 깨어날 때마다 동일한 세션에 내용이 추가되었습니다. 첫 번째 실행(Fire #1)은 짧은 스레드를 다시 읽었지만, 50번째 실행(Fire #50)은 거대한 스레드를 다시 읽었습니다. 밤새도록 매 하트비트(heartbeat)마다 이전보다 더 많은 비용이 발생했습니다.
상태 비저장(Stateless) + 캐시 미적용(cache-cold) + 단조 증가(monotonically growing) = 한계 없이 치솟는 턴당 비용. 타이머는 당신이 잠든 동안 그 치솟는 비용을 지불하는 과정을 자동화할 뿐입니다. 이 세 가지 요소 중 어느 것도 개별적으로는 버그가 아니지만, 루프(loop) 안에서 함께 작동하면 복리로 증폭됩니다.
해결책 (아키텍처적 접근 — 단순한 튜닝으로는 해결할 수 없습니다)
여기서는 더 저렴한 모델을 사용한다고 해서 비용이 절감되지 않습니다. 구조 자체가 잘못되었기 때문입니다. 저희에게 영향력이 컸던 순서대로 나열하면 다음과 같습니다:
1. 프론티어 모델(frontier model)에 자동 실행 타이머를 설정하지 마세요. 반복적인 자율 작업은 비싼 모델 '외부'에서 실행되어야 합니다: 저렴한 플래너(planner) + 로컬/무료 워커(worker) + 결정론적 검증 게이트(deterministic verify-gate)의 조합을 사용하세요. 밤새 비용을 태웠던 루프는 이제 프론티어 모델이 포함되지 않으므로 약 €0에 실행됩니다. 프론티어 모델은 실제로 의사결정이 필요할 때만 호출됩니다. 자율성은 동일하게 유지하면서, 비용의 복리 증폭은 제거할 수 있습니다.
2. 짧은 세션(Short sessions)이 저희에게는 가장 큰 해결책이었습니다 — 모델 선택보다 더 효과적이었습니다. 왜냐하면 비용 소모의 90% 이상이 과거 기록을 다시 읽는 데 발생했기 때문입니다. 영구적인 상태(durable state)는 디스크의 파일에 저장하고, 하나의 스레드를 20시간 동안 계속 키우는 대신 **새로운 세션(fresh session)**으로 넘겨주세요. 연속성은 모델이 매 턴마다 다시 읽고(그리고 다시 비용을 지불하는) 끝없이 길어지는 컨텍스트(context)가 아니라, 파일 속에 존재해야 합니다.
3. 저렴한 노동력, 프론티어 검증. 일상적인 작업 — 조사, 추출, 초안 작성, 스캐닝 — 은 저렴하거나 로컬 모델에서 실행합니다. 프론티어 모델은 판단과 최종 검증을 위해서만 예약해 둡니다.
4. 엄격한 상한선(hard cap)을 강제하고, 소모량을 측정하세요. 강제하지 않는 예산은 그저 바람일 뿐입니다. 저희는 한도를 초과하면 작업을 연기하는 상한선을 추가했으며, 피해를 보고 나서야 비용을 파악하는 대신 세션당 소모량을 모니터링합니다.
여러분의 에이전트를 점검하는 방법
거창한 것이 필요하지는 않습니다. Claude Code는 세션 기록(session transcripts)을 ~/.claude/projects/**/*.jsonl에 작성하며, 각 턴(turn)마다 usage 블록(input, output, cache_creation_input_tokens, cache_read_input_tokens)을 포함한 하나의 JSON 객체가 생성됩니다. 이 값들을 모두 합산하여 턴당 *새로운 입력(new input)*과 *재읽기 컨텍스트(re-read context)*를 비교해 보세요. 만약 두 번째 숫자가 첫 번째 숫자보다 압도적으로 크고 세션이 진행됨에 따라 계속 상승한다면, 당신은 함정에 빠진 것입니다. npx ccusage를 사용하면 설정한 한도 대비 5시간 단위 블록별 총 사용량도 확인할 수 있습니다.
저는 JSONL 파일을 일일이 눈으로 확인하는 것에 지쳐서, 이를 tokenscope (npx @wartzar-bee/tokenscope)로 감싸서 만들었습니다. 이 도구는 해당 기록들을 읽어 세션에 실제로 얼마의 비용이 들었는지 보여줍니다: 새로운 작업(new work) 대 캐시된 데이터(cached) 대 재읽기-콜드(re-read-cold)의 비율, 그리고 어떤 세션에서 비용이 새고 있는지(bleeding)를 보여줍니다. 제가 97.7%라는 수치를 얻은 방법도 바로 이것입니다. 만약 타이머를 설정해 에이전트를 실행하거나 세션을 몇 시간 동안 방치한다면, 여러분의 기록에도 이 도구를 적용해 보세요. 여러분의 비율이 제 것만큼 끔찍한지 저도 알고 싶군요.
— wartzar-bee. 우리는 비용 효율적인 자율 에이전트(autonomous agents)를 구축하고 운영합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기