본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 22. 04:56

에이전트 루프 비용: 호출당 견적보다 11배 높은 비용, 40줄의 코드로 확인하기

요약

에이전트 루프 비용이 호출당 견적보다 훨씬 높게 발생하는 원인을 분석하고, 이를 예측하는 40줄의 파이썬 스크립트를 소개합니다. 시스템 프롬프트와 도구 설명, 누적된 컨텍스트가 매 호출마다 재청구되어 발생하는 비용 격차를 다룹니다.

핵심 포인트

  • 에이전트 비용은 호출당 단위가 아닌 작업(task)당 단위로 계산해야 함
  • 시스템 프롬프트와 도구 설명의 반복 전송이 비용 상승의 주원인
  • 컨텍스트가 길어질수록 비용이 초선형적으로 증가하는 곡률 발생
  • CI 게이트로 활용 가능한 오프라인 비용 예측 스크립트 구현

**에이전트 루프 비용 (Agent loop cost)**은 호출당 비용이 아니라 작업(task)당 지불하는 비용을 의미합니다. 모든 도구 호출(tool-call) 시 시스템 프롬프트 전체와 모든 도구 설명(tool description)에 대해 다시 비용이 청구되기 때문에, 호출당 견적보다 훨씬 높게 나타납니다. 40줄로 구성된 오프라인 예측기(offline forecaster)는 하나의 JSONL 트레이스(trace)를 읽어 제품을 출시하기 전에 전체 루프의 가격을 산출합니다. 제가 사용한 비대한 테스트 환경(fixture)에서 측정 결과 11.29배의 격차가 발생했습니다.

저의 비대한 테스트 환경에서, 예측기는 호출당 견적이 $0.20인 것에 비해 작업당 실질 비용을 $2.26로 측정하여 11.29배의 격차를 나타냈습니다. 이때 누적 비용 곡률(cumulative-cost curvature)은 k = 2.14였으며, 이는

  • 한 팀이 에이전트의 비용을 단 한 번의 호출 가격으로 예산 책정합니다. 하지만 작업은 N번의 호출을 수행하며, 각 호출마다 고정된 시스템 세금(시스템 프롬프트 (system prompt) + 모든 도구 설명 (tool description))과 이전 결과들의 점점 길어지는 꼬리(tail) 부분이 다시 청구됩니다. 누적 청구 금액은 해당 곡선 아래의 면적입니다.
  • loop_forecast.py는 약 40줄로 구성된 오프라인, 키리스 (keyless), 읽기 전용 (read-only) 스크립트입니다. JSONL 트레이스 (trace)를 입력하면 태스크당 실질 비용 (effective $/task), 호출당 견적 대비 예측 격차 (forecast_gap), 그리고 곡률 k (curvature k) (누적 청구액 ~ 호출 횟수^k)를 출력합니다.
  • 나의 비대해진 테스트 케이스(fixture)에서는: $0.20 견적 대비 $2.26/태스크 = 11.29배, k = 2.14, 종료 코드 1. 깔끔한 3단계 루프에서는: 격차 0.04배, k 1.18, 종료 코드 0 — 반대 의견이 스스로의 논리에 의해 반박되었습니다.
  • 종료 코드는 실행 전 CI 게이트 (pre-execution CI gate) 역할을 합니다: 루프 비용이 저렴하거나 선형적이면 0, 초선형적(super-linear)이면서 견적을 초과하면 1, 사용 오류 시 2를 반환합니다.
  • 이것은 트레이스 (trace)를 기반으로 한 예측기이며, 런타임 제한 (runtime cap)이 아닙니다. 에이전트의 실행을 차단하지는 않지만, 빌드 (build)를 차단합니다.

호출당 견적이 잘못된 단위인 이유

실수는 단위의 혼동에서 비롯되며, 이는 저지르기 쉬운 실수입니다. 가격 페이지를 열어 $3.00 / 1M 입력 토큰 (input tokens)을 확인하고, 한 번의 호출이 약 8k 토큰의 입력을 사용한다고 추정하여 에이전트 동작당 $0.20라고 적습니다. 여기에 일일 호출량을 곱하면 예산이 나옵니다. 매우 엄격해 보이지만, 이는 자릿수(order of magnitude) 단위로 틀린 계산입니다. 호출당 가격이 틀렸기 때문이 아닙니다. 그 부분은 맞습니다. 오류는 *태스크 (task)*가 단 한 번의 호출이 아니라는 점에 있습니다.

실제로 청구되는 방식은 다음과 같습니다. 에이전트 루프는 매 단계마다 전체 작업 컨텍스트 (working context)를 입력으로 다시 전송합니다. 시스템 프롬프트 (system prompt)가 다시 전송됩니다. 인벤토리에 있는 모든 도구 설명 (tool description)이 다시 전송됩니다. 그리고 에이전트가 지금까지 수행한 모든 내용의 기록 — 즉, 이전의 모든 도구 결과 (tool result) — 가 매 턴마다 커지면서 다시 전송됩니다. 따라서 1단계는 작은 페이로드 (payload)를 청구하고, 8단계는 큰 페이로드를 청구하며, 태스크 비용은 이 모든 것의 합계입니다. 그 합계는 상승하는 곡선 아래의 면적입니다. 상승하는 직선 아래의 면적은 이차 함수(quadratic) 형태를 띱니다.

Muskan의 글은 이 재현(replay) 과정에 구체적인 형태를 부여했습니다. 약 200개의 토큰을 가진 8개의 도구는 매 단계마다 1,600개의 인벤토리 토큰이 재현됨을 의미하며, 8단계에 이르면 단일 단계의 컨텍스트(context)는 대략 83,000개의 입력 토큰으로 급증합니다. 에이전트 루프 토큰 비용에 관한 Augment Code의 가이드 또한 독립적으로 동일한 구조적 주장을 하고 있습니다. 즉, 단순한(naive) 루프는 "매 호출마다 이전 컨텍스트를 다시 청구(rebill)"하므로, 입력 비용은 이차 함수(quadratic) 형태로 상승하며 "20단계 루프"는 "단순한 단계별 추정치보다 10배 이상 더 많은 비용"이 발생할 수 있다는 것입니다. 이 두 가지는 제 수치가 아니라 제3자 지원 자료로 인용된 _그들_의 측정값입니다. 저의 기여는 여러분이 이미 가지고 있는 트레이스(trace)로부터 여러분만의 버전을 계산할 수 있는 도구를 만드는 것입니다.

틀릴 수도 있도록 명시된 반대 의견(contrarian claim)

논쟁의 여지가 있을 만큼 날카로운 입장은 다음과 같습니다: 당신은 에이전트의 예산을 단일 호출 가격으로 책정하지만, 실제로는 곡선 아래의 면적만큼 비용을 지불하게 됩니다. 그리고 그 면적은 도구 호출(tool-calls) 횟수에 따라 이차 함수(quadratic) 형태를 띱니다. 왜냐하면 결과의 꼬리(result tail)는 계속 커지는 반면, 고정된 시스템 세금(system tax)은 매 단계마다 재현되기 때문입니다. 또한, 제공업체의 빌링(billing) API를 전혀 건드리지 않고도 프로덕션에서 실행하기 전 트레이스(trace)를 통해 전체 곡선을 계산할 수 있습니다.

이 주장에는 명확한 실패 모드(failure mode)가 있으며, 저는 이를 도구에 내장했습니다. 만약 루프가 짧고, 도구 인벤토리가 작으며, 모델이 조기에 종료된다면, 단계별 곡선은 거의 상승하지 않고 누적 비용은 거의 선형(linear)에 가깝게 나타나며, 견적과의 격차도 작습니다. 이 경우 해당 주장은 _당신에게는 틀린 것_이 되며, loop_forecast.pyexit 0을 반환하며 이를 알려줍니다. 저는 정확히 그 사례를 실행해 보았습니다. 깨끗한 고정값(fixture) — 420토큰 시스템, 3개의 도구 설명, 3단계, 작은 결과값 — 을 사용했을 때, gap 0.04x 및 k 1.18이라는 결과가 나왔습니다. 위반 사항이 없었습니다. 만약 당신의 실제 트레이스가 모두 이와 같다면, 이 논제 전체가 당신에게는 적용되지 않으며, 저는 당신이 제 말을 믿기보다 도구가 직접 말해주기를 바랍니다.

이것이 아닌 것

이 시리즈에서 다루는 세 가지 인접한 비용은 이미 각각의 도구를 가지고 있으며, 이번 도구는 그 중 어느 것도 아닙니다.

  • 전사(transcript)에 대한 재청구 세금(re-bill tax)이 아닙니다. 매 단계마다 전사를 재청구하는 컨텍스트 세금 (context tax that re-bills your transcript every step)은 대화 턴(conversation turns)의 평면적인 목록을 가져와서, 1..N번의 턴이 입력으로 다시 전송됨에 따라 발생하는 _메시지 히스토리 (message history)_의 복리 효과를 측정합니다. 해당 도구의 입력은 role/content 턴이지만, 이 도구의 입력은 매니페스트(manifest)를 포함한 _도구 호출 기록 (tool-call records)_입니다. 또한 이 도구는 루프의 구조 (structure) (고정된 세금으로서의 시스템 + 도구 설명)의 재생(replay)과 결과 꼬리(result tail)를 모델링하여 외부 견적과 비교합니다. 입력이 다르고, 지표가 다르며, 질문이 다릅니다.
  • 정적 MCP 인벤토리 세금(static MCP inventory tax)이 아닙니다. 연결된 MCP 서버 인벤토리의 토큰 세금 (token tax of your connected MCP server inventory)은 도구가 _연결 (connected)_되어 있을 때 발생하는 일회성 컨텍스트 비용, 즉 에이전트가 무엇인가를 수행하기 전에 컨텍스트 윈도우(context window)에 놓여 있는 설명들을 측정합니다. 이 도구는 동일한 인벤토리를 가져와서, 그것이 다중 호출 루프(multi-call loop)의 모든 단계에서 재생(replayed)될 때 어떤 비용이 발생하는지 보여줍니다. 인벤토리 세금이 고정 비용(standing charge)이라면, 이것은 사용량 기반 과금(metered usage)입니다.
  • 지출 한도(spend cap)가 아닙니다. 이것은 완료된 트레이스(trace)로부터 도출된 예측치입니다. 런타임(runtime)에서 아무것도 차단하지 않습니다. 만약 1달러 상한선에 도달했을 때 실행을 실제로 중단하고 싶다면, 그것은 런타임에서의 슬라이딩 윈도우 지출 가드 (sliding-window spend guard at runtime)을 사용해야 합니다. loop_forecast.pyAI 에이전트를 위한 실행 전 게이트 (pre-execution gate for AI agents)의 변형입니다. 즉, 비용이 많이 드는 루프가 배포되기 전에 CI 빌드를 실패하게 만듭니다.

도구: loop_forecast.py

제약 사항부터 말씀드리겠습니다. 이 제약 사항들 덕분에 보안 검토 없이도 프로덕션 트레이스(production trace)에서 이 도구를 실행할 수 있습니다: 오프라인(offline), 키리스(keyless), 읽기 전용(read-only), 네트워크 사용 없음(zero network). 벤더 SDK도, API 키도 필요 없으며, 아무것도 사용자의 머신을 벗어나지 않습니다. 하나의 JSONL 파일을 읽어서 출력할 뿐입니다. 토큰화(Tokenization)는 실제 방식으로 이루어지며, tiktokeno200k_base 인코딩을 사용합니다. 만약 tiktoken이 설치되어 있지 않다면 정직하게 len/4를 대체값(fallback)으로 사용합니다. 이 대체값은 실제 BPE(Byte Pair Encoding)와 대략 ±15% 정도의 오차가 있으며, 출력 결과에 어떤 방식이 실행되었는지 표시됩니다.

입력값은 JSONL 트레이스(trace)입니다. 하나의 매니페스트(manifest) 레코드와 각 도구 호출(tool-call)당 하나의 레코드로 구성됩니다:

{"type":"manifest","system_tokens":1200,"tool_descriptions":[210,190,230,200,180,220,205,195,215,185],"quoted_usd_per_invocation":0.20,"input_usd_per_mtok":3.0}
{"type":"call","tool":"read_file","result_tokens":5400}
{"type":"call","tool":"run_tests","result_tokens":8100}

모든 필드는 이미 보유하고 있거나 계산할 수 있는 것들입니다: 시스템 프롬프트(system-prompt) 크기, 각 도구 설명(tool description)의 크기, 예산으로 책정했던 호출당 가격(per-invocation price), 그리고 백만 토큰당 입력 가격(input price per million tokens)입니다. 만약 호출 레코드에 result_tokens가 없다면, 도구가 result 텍스트 자체를 토큰화합니다.

예측(forecast)은 네 가지 결정론적(deterministic) 규칙으로 이루어지며, 루프 내에 모델이 포함되지 않고 무작위성(randomness)도 없습니다:

fixed = sys_t + desc                       # 매 단계마다 반복되는 시스템 세금(system tax)
billed, cum, tail, run = [], [], 0, 0
for rt in tails:                           # n번째 단계는 고정 세금 + 누적된 이전 결과들을 청구함
...

규칙 1: 단계별 청구되는 입력(input)은 고정 세금(fixed tax)에 누적된 꼬리(accumulated tail)를 더한 값입니다. 규칙 2: 유효한 작업당 비용($/task)은 모든 단계의 합, 즉 면적입니다. (한 가지 솔직한 참고 사항: 이는 입력(input) 토큰만을 계산하므로, 비교 대상인 호출당 견적(per-invocation quote) 또한 입력 예산일 때만 이 격차가 공정합니다. 출력(output) 토큰 가격 책정은 범위 외이며, 제한 사항 섹션을 참조하십시오.) 규칙 3: 격차(gap)는 해당 유효 비용을 예산으로 잡았던 호출당 견적으로 나눈 값입니다. 규칙 4: 누적(cumulative) 청구액을 calls^k에 맞추고 이를 기준으로 게이트(gate)를 설정하십시오. 곡률(curvature)에 대한 솔직한 설명: 저는 단계별 비용(per-step cost)이 아니라 누적(cumulative) 비용을 맞추었습니다. 단계별 곡선은 대략 선형적입니다(나선형에서 k ≈ 1.4). 그 적분값, 즉 실제로 지불하는 누적 총합은 이차 함수 형태(k = 2.14)를 띱니다. 처음 시도했을 때는 단계별 곡선을 맞추어 k ≈ 1.0으로 읽었는데, 이 때문에 명백한 이차 함수 형태의 루프가 선형처럼 보였습니다. 누적 급수(cumulative series)를 맞추는 것이 해결책입니다.

전체 파일은 CLI 인자 처리, 토크나이저 심(tokenizer shim), 출력 포맷팅을 포함하여 72줄입니다. 예측 부분 자체(위의 내용과 매니페스트 파싱 포함)는 약 40줄입니다. 헤드라인의 의미를 맞추기 위해 코드를 지나치게 축소(golfing)하기보다는 읽기 쉬운 버전을 유지하겠습니다.

정상적인 루프에서 출력되는 내용 (exit 0)

깨끗한 피스처(fixture) — 세 번의 도구 호출(tool-calls), 작은 인벤토리, 조기 종료 — 에서 실행해 보겠습니다:

$ python3 loop_forecast.py fixtures/loop_clean.jsonl
loop_forecast.py | tokenizer: tiktoken/o200k_base
steps: 3 | system: 420t | tool_descriptions: 360t (매 단계마다 재재생됨)
...

여기서 유효 비용은 견적보다 낮습니다. 결과값이 매우 작은 3단계 루프는 예산이 과다 책정되었기 때문에, 가정된 단일 호출 예산보다 저렴합니다. 격차 0.04x, k 1.18, 게이트 통과(PASS). 이것이 반증(falsification)이 작동하는 방식입니다. 저렴한 루프는 저렴하게 읽힙니다.

나선형(spiral)에서 출력되는 내용 (exit 1)

이제 비대해진 피스처(fixture)를 살펴보겠습니다. 1,200토큰의 시스템 프롬프트(system prompt), 10개의 도구 설명(매 단계마다 재현되는 2,030토큰의 인벤토리), 그리고 에이전트가 파일을 읽고, 테스트를 실행하며, 출력을 다시 파싱(re-parse)함에 따라 도구 결과값이 수천 개로 늘어나는 14개의 단계로 구성됩니다:

$ python3 loop_forecast.py fixtures/loop_spiral.jsonl
loop_forecast.py | tokenizer: tiktoken/o200k_base
steps: 14 | system: 1200t | tool_descriptions: 2030t (replayed every step)
...

단계별 목록을 읽어보십시오. 1단계는 3,230토큰을 청구합니다. 8단계는 54,430토큰을 청구합니다. 이는 Muskan이 제시한 8단계 약 83k 수치와 비슷한 규모이며, 저는 그들의 극적인 효과를 빌려오지 않기 위해 의도적으로 그들의 수치보다 낮게 유지했습니다. 14단계에 이르면 단일 단계에서 120,030개의 입력 토큰(input tokens)을 청구하며, 이 중 거의 대부분은 다시 전송된 컨텍스트(context)입니다. 이 작업의 비용은 $2.26입니다. 당신은 $0.20라고 인용했습니다. 그것이 제목에 언급된 11.29배입니다. 이것은 제가 역설계(reverse-engineered)한 목표치가 아니라, 도구가 실제로 산출한 숫자입니다.

게이트(gate)가 작동하는 이유는 두 조건이 모두 충족되기 때문입니다: 격차가 8배를 초과하고, 누적 k(cumulative k)가 1.3 이상인 경우입니다. 비용은 많이 들지만 선형적인(linear) 루프(길고 단순한 단일 도구 작업)는 게이트를 작동시키지 않을 것이며, 곡선형이지만 저렴한 루프 역시 마찬가지입니다. 누군가의 빌드(build)를 실패시키기 전에 당신은 두 가지 신호를 모두 확인해야 합니다.

결정론(Determinism), 불안정한 게이트는 게이트가 없는 것보다 나쁘기 때문에

동일한 입력에 대해 서로 다른 숫자를 반환하는 CI 게이트는 쓸모가 없습니다. 첫 주 만에 무시당할 것입니다. 따라서 산술 방식은 무작위성(randomness)과 네트워크 없이 정수 토큰 수(integer token counts)를 사용합니다. 저는 각 피스처에 대해 stdout을 두 번 해싱(hash)했습니다:

clean  run1: 455a86ce7e1df9cdca74f072c5d5e2919dac8f91889d950769673e7998bd506d
clean  run2: 455a86ce7e1df9cdca74f072c5d5e2919dac8f91889d950769673e7998bd506d
spiral run1: 450d51f471b747c224c3782c6d8b4af8acddc1db677b073389e5de0a09ff74f3
...

바이트 단위로 일치합니다. 동일한 트레이스(trace)가 입력되면 동일한 게이트 결과가 나옵니다. 잘못된 JSON이 반환되면 파서(parser)의 오류와 함께 exit 2를 반환하며, 인자가 없으면 사용법을 출력하고 exit 2로 종료됩니다. 따라서 게이트는 깨진 트레이스에 대해 조용히 통과하는 대신, 명확하게 실패를 알립니다.

이 부분이 틀린 곳, 그리고 제가 추측하고 있는 부분

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0