AI 에이전트 비용 드리프트 (Cost Drift): 하루 0.35%의 변화는 대시보드에 보이지 않습니다
요약
AI 에이전트의 시스템 프롬프트나 도구 스키마 증가로 인해 발생하는 '비용 드리프트' 현상을 분석합니다. 이동 평균 기반의 기존 모니터링 방식은 점진적인 비용 상승을 감지하지 못하므로, 고정된 앵커(frozen anchor)를 활용한 새로운 탐지 방식의 필요성을 제안합니다.
핵심 포인트
- 이동 평균 방식은 점진적인 컨텍스트 하한선 상승을 포착하지 못함
- 하루 0.35%의 미세한 비용 증가는 60일간 기존 탐지기에서 감지되지 않음
- 고정된 앵커(frozen anchor) 방식은 9일 차에 해당 드리프트를 차단 가능
- 비용 모니터링 시 속도(speed)와 수준(level)의 차이를 구분해야 함
**AI 에이전트 비용 드리프트 (AI agent cost drift)**는 시스템 프롬프트 (system prompt), 도구 스키마 (tool schemas), CLAUDE.md, MCP 서버와 같은 입력 하한선 (input floor)이 서서히 증가하는 현상을 말합니다. 이동 평균 기준선 (rolling baseline)은 이 하한선과 함께 상승하기 때문에 결코 이를 포착할 수 없습니다. drift_anchor_gate.py는 0일 차에 고정된 카나리 (canary)를 고정하고 비교합니다. 하루 0.35%의 점진적인 증가가 60일 동안 단 한 번의 경보도 울리지 않았지만, 고정된 앵커 (anchor)는 9일 차에 이를 차단했습니다.
여러분의 플릿 대시보드 (fleet dashboard)는 오늘의 평균 실행 비용이 지난주보다 20% 급증할 때만 경보를 울립니다. 하지만 여러분의 컨텍스트 하한선 (context floor)은 하루에 0.35%씩 성장합니다. 이 두 숫자는 결코 만나지 않습니다. 저는 6개의 60일 분량의 가상 세계를 구축하고 그 위에서 4개의 이동 탐지기 (rolling detectors)를 실행했습니다. 각 탐지기는 평탄한 플릿 상태에서 조용히 유지될 수 있는 가장 엄격한 임계값 (threshold)을 적용했습니다. 그 결과, 하한선이 22.6% 상승하는 동안 이 느린 증가세는 60일 내내 4개의 탐지기 모두에서 단 한 번의 경보도 울리지 않았습니다. 반면 고정된 앵커는 9일 차에 동일한 증가세를 포착했습니다.
AI 공개 (AI disclosure): 저는 AI 어시스턴트와 함께
drift_anchor_gate.py및make_worlds.py를 작성했으며, 직접 실행했습니다: Python 3.13.5, 오프라인, 표준 라이브러리만 사용, 네트워크 없음, 키(keys) 없음. 아래의 모든 숫자, 종료 코드(exit code) 및 sha256은 실제 로컬 실행 결과에서 복사되었습니다. 저는 깨끗한rm -rf worlds상태에서 전체 데모를 두 번 실행했으며, 두 개의output.txt파일은 바이트 단위로 동일합니다 (sha256 1772e695cb75f79d9e3f162ed4c49477a329703610cdf5ddff54cec2cc4da62a). 데이터 시리즈는 합성된 것이며, 아래에 어떻게 생성되었는지 정확히 명시합니다. 합성되지 않은 유일한 것은 산술 연산이며, 이것이 실질적인 역할을 수행하는 부분입니다.
요약하자면:
- 최근 이력(fleet 전체에 대한 rolling median, rolling mean, EWMA)으로부터 계산된 베이스라인(baseline)은 **속도(speed)**를 탐지하는 도구입니다. 반면 인보이스(invoice)는 **수준(level)**에 따라 청구됩니다. 이 둘은 서로 다른 양이며, 느린 비용 드리프트(cost drift)는 그 사이의 간극에 존재합니다.
- 감지되지 않는 구간(blind band)은 발견 사항이 아니라 산술적인 결과입니다: 길이가
w이고 비율이T인 윈도우는g* = T^(2/w) - 1미만의 모든 균일한 일일 성장(daily growth)을 볼 수 없습니다.w=7, T=1.20인 경우 이는 하루 5.35 퍼센트입니다. 실제 컨텍스트 크리프(context creep)는 이의 10분의 1 수준으로 발생합니다. - 제 설정값으로 측정한 결과: 하루 0.35 퍼센트의 크리프는 60일 동안 4개의 rolling detector 모두에서 0개의 알람을 발생시켰습니다. 반면, 2 퍼센트 허용 오차를 가진 frozen anchor는 평탄한 fleet 상태에서 오탐(false alarm) 없이 9일째에 이를 차단했습니다.
- 중요한 데모: 모든 로컬 파일이 0일 차와 바이트 단위로 동일한 세상(
diff -rq결과가 없고,git diff결과도 없는 상황)에서, 벤더(vendor)가 조용히 하네스 스캐폴드(harness scaffold)를 높였을 때, fleet 대시보드의 short-window detector들은 침묵을 지켰고(long window는 58일째에 단 한 번 작동하여 46일이나 늦었습니다), anchor는 성장의 원인을vendor: +2600 B로 식별하여 12일째에 차단했습니다. - 대시보드 역시 승리하는 세상이 있으며, 이를 보여드리겠습니다: 실행당 **작업(work)**량이 두 배로 늘어나고 바닥(floor)이 변하지 않을 때, anchor는 60일 내내 통과하지만 rolling detector는 30일째에 작동합니다. 둘 다 유지하십시오.
- 이 글이 틀린 경우: 바닥(floor)이 일반적인 요청의 90 퍼센트 이상일 때, rolling detector는 크리프를 감지합니다. Sweep(스윕)을 포함해서 말이죠.
측정으로 증명해야 했던 코멘트
7월 13일, Dipankar Sarkar는 전달된 텍스트에 대해 0개의 토큰을 보고하는 사용 로그에 관한 나의 포스트에 날카로운 교정 의견을 남겼습니다. 그의 논지는 다음과 같습니다. 임계값(threshold)만 모니터링한다면, 제공업체의 토크나이저(tokenizer) 업데이트와 실제 억제(suppression) 현상은 동일해 보이기 때문에, 판별기(disambiguator)는 임계값이 아닌 모집단(population)을 기준으로 삼아야 한다는 것입니다. "따라서 차단 조건은 '델타(delta)가 X를 넘어 넓어졌다'가 아니라, '이 스트림의 델타가 전체 플릿(fleet)의 델타와 갈라졌다'가 되어야 합니다. 즉, 다른 모든 이들의 이동 평균 기준선(rolling baseline)에 대비한 스트림별 이상 징후(per-stream anomaly)를 보는 것입니다."
그의 말이 맞으며, 저 또한 그렇게 말했습니다. 그 후 저는 이 글의 주제가 된 논거를 제시했습니다. 이동 평균 기준선(rolling baseline)은 적응(adapt)하기 때문에, 적응 창(adaptation window)보다 느리게 성장하는 모든 것은 플릿(fleet)으로부터 결코 벗어나지 않습니다. 그것은 플릿과 함께 움직입니다. 끓는 물 속의 개구리(Boiling frog)와 같습니다. 우리에게 필요한 것은 이미 검증된 날짜에 고정된 두 번째 닻(anchor)이며, 플릿이 없는 개인 개발자에게는 결정론적 입력(deterministic input)을 가진 카나리 요청(canary request)이 곧 '1명으로 구성된 모집단'이 됩니다.
그리고 저는 저 자신의 댓글을 다음과 같이 끝맺었습니다.
"상태에 대해 솔직히 말씀드리자면, 이것은 설계에 관한 논쟁이며 제가 실제로 측정한 것은 아닙니다. 재토큰화된 하한선(re-tokenized floor)은 실행해 보았지만, 플릿 대 카나리(fleet-versus-canary) 판별은 해보지 않았습니다."
그것은 이틀 전의 일이었습니다. 이 포스트는 그 약속을 이행하는 과정입니다. 도메인은 바뀌었지만(저는 사용 억제가 아닌 입력 비용 드리프트(input cost drift)를 측정하고 있습니다), 질문의 구조는 Dipankar와 제가 논쟁했던 것과 동일합니다. 자신의 최근 이력으로 구축된 기준선이, 자신의 최근 이력을 변화시키는 현상을 포착할 수 있는가?
데이터가 존재하기 전, 산술(arithmetic)부터 시작하기
이 부분은 발견(discovery)이 아닙니다. 이는 유도된(derivative) 결과이며, 장치를 만지기 전에 종이 위에서 수행할 가치가 있습니다. 왜냐하면 이 과정이 측정값이 무엇을 찾아낼 수 있는지 알려주기 때문입니다.
이동 창(Rolling window)은 오늘을 이전 w일 동안 구축된 베이스라인(Baseline)과 비교합니다. 해당 베이스라인은 대략 w/2일 전을 중심으로 설정됩니다. 일일 성장률 g가 균일할 때, 베이스라인 수준 대비 오늘의 수준은 약 (1+g)^(w/2)입니다. 이 비율이 임계값 T를 초과하면 알람이 발생합니다. g에 대해 풀면 다음과 같습니다:
g* = T^(2/w) - 1
g*보다 느린 모든 성장은 영원히 보이지 않습니다.
| field | bytes | origin |
|---|---|---|
tools_json | 26,000 | local file |
| ... |
실행당 작업량(Work per run)은 로그정규 분포 (lognormal draw)를 따르며, 중앙값은 18,000 바이트, 시그마 ($\sigma$)는 0.8이고, 여기에 일일 작업 믹스 계수 (daily job-mix factor, 로그정규 분포, $\sigma$ 0.15)가 곱해집니다. 따라서 전형적인 실행은 86,033 바이트이며, 하한선 (floor)은 그 값의 71.0 퍼센트입니다. 모든 월드 (world)는 하나의 노이즈 실현 (noise realization)과 하나의 앵커 (anchor)를 공유합니다: 오직 하한선 궤적 (floor trajectory)만이 다릅니다. 대시보드에는 앵커가 받는 것보다 더 어려운 드로우 (draw)가 절대 전달되지 않습니다.
여섯 개의 월드:
| world | 발생하는 일 |
|---|---|
control | 아무것도 변하지 않음 |
| ... |
각 정책 섹션 (policy section)은 고유하게 생성되며, 자체적인 헤딩 (heading)과 본문을 포함하여 380에서 620 바이트 사이입니다. 제가 이 점을 언급하는 이유는, 저의 지난 비용 관련 기사가 한 단락을 30번 반복하는 고정 요소 (fixture) 때문에 리뷰 과정에서 반려되었기 때문이며, 이는 타당한 지적이었습니다.
그 결과로 나타나는 성장률은 다음과 같습니다: creep은 일일 +1.044 퍼센트로 실행되어 112,700 바이트에 도달합니다. slowcreep은 일일 +0.347 퍼센트로 실행되어 74,885 바이트에 도달하며, 이는 22.6 퍼센트 증가한 수치입니다. 육안으로 봐서는 그 어느 것도 알아차릴 수 없을 것입니다.
여섯 개의 월드, 하나의 앵커, 하나의 명령
모든 이동 탐지기 (rolling detector)는 자신만의 최선의 조건을 부여받습니다: 도구 (tool)는 1.05부터 위로 임계값 사다리 (threshold ladder)를 걷으며, 평탄한 월드 (flat world)에서 오탐 (false alarms)을 전혀 일으키지 않는 가장 낮은 임계값을 선택합니다. 이것은 알림 규칙 (alerting rule)에 정직하게 부여할 수 있는 가장 관대한 보정 (calibration)입니다. 왜냐하면 현실 세계에서 노이즈가 심한 규칙에 발생하는 첫 번째 일은, 누군가가 규칙이 조용해질 때까지 임계값을 높여버리는 것이기 때문입니다.
실제 출력값에서 요약된 결과는 다음과 같습니다:
| world | rolling median w=7 | rolling mean w=7 | EWMA a=0.3 | rolling median w=28 | frozen anchor, tol 2% |
|---|---|---|---|---|---|
control | 0 alarms (T=1.20) | 0 (T=1.20) | 0 (T=1.20) | 0 (T=1.15) | PASS, all 60 days |
| ... | |||||
짧은 윈도우(short windows)에 대한 creep 행을 보십시오. 각각 한 번의 알람이 발생하며, 이는 도구 스키마(tool schemas)가 설치된 12일째로부터 28일이 지난 40일째에 발생합니다. creep 때문이 아닙니다. 단계적 변화(step) 때문입니다. 탐지기(detector)는 불연속성(discontinuity)을 감지하도록 설계된 목적 그대로 작동하고 있으며, 그 과정에서 바닥(floor) 값이 51 KB만큼 상승한 것은 탐지기에 보이지 않습니다. |
이제 긴 윈도우(long window)인 T=1.15에서의 w=28을 보겠습니다. 산술적으로 이 탐지기는 일일 1.00% 미만에서는 눈이 멀게 되어 있습니다. creep은 일일 1.044%로 작동하여 기준선 바로 위에 위치하며, 탐지기는 30일째부터 15번 작동합니다. slowcreep은 일일 0.347%로 작동하여 기준선 아래에 위치하며, 60일 동안 0번 작동합니다. 이 공식은 데이터가 존재하기 전부터 적중(hit)과 미탐(miss)을 모두 예측했습니다. 이것이 단 두 줄로 요약된 논거의 전부이며, 제가 산술적 근거를 가장 먼저 배치한 이유입니다.
참고로, 긴 윈도우를 사용하는 데는 공짜가 없습니다. 그 민감도를 얻는 대신 spike에서 15번, workload에서 14번의 알람을 대가로 지불했습니다.
제가 이것을 만들게 된 데모: git diff는 비어있는데 비용은 더 지불하고 있다
harness 환경은 제가 회의론자들에게 보여주고 싶은 것입니다.
저장소(repository) 내의 그 어떤 것도 변하지 않습니다. 단 1바이트도 말이죠. 이 데모는 요청을 생성하는 모든 로컬 파일에 대해 0일 차 스냅샷과 59일 차 스냅샷 사이의 diff -rq를 실행합니다:
diff -rq repo_day000 repo_day059: no differences. git diff would print nothing.
sha256 of every local file, day 0 vs day 59:
...
그동안 벤더(vendor)는 12일 차에 모든 요청에 2,600바이트의 도구 호출 프로토콜 서문(tool-call protocol preamble)을 추가했고, 31일 차에 3,100바이트를, 48일 차에 3,400바이트를 추가했습니다. 59일 차에 이르면, 렌더링된 요청은 고정 시점(pin time)에 비해 모든 단일 호출에서, 그리고 영구적으로 14.9% 더 높은 바닥(floor) 값을 갖게 됩니다.
세 가지 측정 도구가 그 상황을 살펴보았습니다. git diff는 아무것도 보고하지 않았습니다. 사용 로그(usage log)의 이동 탐지기(rolling detectors)도 아무것도 보고하지 않았습니다 (긴 윈도우(long window)는 결국 58일째에야 작동했는데, 이는 46일이나 늦었으며 세 번째 급증이 발생한 이후였습니다). 앵커(anchor)는 12일째에 동일한 명령어를 통해 이를 보고했습니다:
$ drift_anchor_gate.py check worlds/anchor.json worlds/harness/canary/day012.json
anchor 61060 B pinned day 0 tolerance 2.0%
today 63660 B day 12 drift +4.26%
...
대조군(control world)에서의 동일한 명령어, 동일한 앵커, 동일한 날짜 결과는 다음과 같습니다:
$ drift_anchor_gate.py check worlds/anchor.json worlds/control/canary/day012.json
today 61060 B day 12 drift +0.00%
verdict: PASS -> exit 0 (drift +0.00% is within the 2.0% tolerance)
...
from local files: +0 B from the vendor: +2600 B라는 이 줄이 바로 앵커가 당신이 목록에 올리는 것을 기억한 파일들이 아니라, **렌더링된 요청 (rendered request)**을 측정하는 이유입니다. 당신의 리포지토리(repo)가 비용을 지불하는 대상이 아닙니다. 렌더링된 요청이 바로 당신이 비용을 지불하는 대상입니다.
대시보드가 승리하는 지점과 나의 앵커가 눈먼 지점
만약 이 글에 이 섹션이 없었다면 저는 이 글을 신뢰하지 않았을 것입니다. 그러니 바로 설명하겠습니다.
workload 월드: 바닥(floor) 값은 절대 변하지 않으며, 30일째에 실행당 작업량이 두 배로 늘어납니다 (검색(retrieval)이 두 배의 청크(chunks)를 반환하기 시작하거나, 사용자가 더 큰 입력을 붙여넣는 등 어떤 이유에서든 말입니다). 이동 탐지기는 30일째에 작동하며, 이는 완전히 정확한 동작입니다. 하지만 앵커는 **60일 전체를 통과(pass)**합니다. 왜냐하면 고정된 카나리(canary) 작업은 더 많은 작업을 수행하고 있지 않기 때문입니다. 그 작업의 입력값은 실제로 드리프트(drift)되지 않았습니다. 앵커는 워크로드(workload)에 존재하는 비용 증가의 전체 범주(컨텍스트 세금(context tax), 즉 모든 단계에서 전체 트랜스크립트(transcript)에 대해 다시 비용이 청구되는 현상)에 대해 눈이 멀어 있으며, 그 범주는 실재하며 종종 바닥 값보다 더 큽니다.
이제 그 결과 테이블에서 spike와 workload를 나란히 놓아보세요. 이동 컬럼(rolling columns)을 살펴보십시오. 동일한 탐지기(detector), 동일한 30일째, 거의 동일한 알람 횟수(짧은 윈도우에서는 4, 2, 2, 긴 윈도우에서는 15 대 14)를 보입니다. 사용 로그(usage log) 내부에서 보면 이 두 세계는 구분할 수 없습니다 (indistinguishable). 하나는 누군가 삭제할 때까지 매 호출마다 비용을 지불하게 될, 지침 파일(instruction file)에 붙여넣은 40 KB짜리 문서입니다. 다른 하나는 당신이 원했던 대로 제품이 더 많은 작업을 수행하고 있는 것입니다. 대시보드는 두 경우 모두에 대해 동일한 알람을 발생시킵니다.
앵커(anchor)가 이 둘을 구분해 줍니다. 붙여넣기에는 BLOCK(차단), 워크로드(workload)에는 PASS(통과)를 부여합니다. 이것은 앵커가 더 민감해진 것이 아닙니다. 앵커가 더 다르고 좁은 질문에 답하고 있으며, 그 질문에 깔끔하게 답하고 있는 것입니다.
따라서 솔직한 요약은 대체가 아닌 역할 분담입니다. 이동 탐지기(rolling detector)를 유지하십시오. 그것은 붙여넣기, 단계(step), 그리고 워크로드 변화를 잡아냅니다. 여기에 앵커(anchor)를 추가하십시오. 그것은 연간 추세(the year)를 잡아냅니다.
이 글이 틀린 부분
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기