AI 에이전트 청구 비용이 단계 수의 제곱에 따라 증가하는 이유와 공개물 개인정보를 0으로 만드는 pre-commit 게이트 구현
요약
AI 에이전트의 비용은 처리 단계 수에 따라 입력 토큰이 제곱 형태로 증가하는 구조적 특성을 분석했습니다. 따라서 호출 전에 최악의 경우를 예측하여 상한 초과 시 실행을 막는 로직 구현이 필수입니다. 또한, 커밋/푸시 시점에 개인정보 유출을 원천 차단하는 pre-commit 게이트 구현 방법도 제시합니다.
핵심 포인트
- AI 에이전트 비용은 단계 수의 제곱에 비례하여 급증하므로 주의해야 합니다.
- 총 입력 토큰을 줄이는 가장 효과적인 방법은 도구 결과를 간소화하는 것입니다.
- 예산 알림 대신, 호출 전에 최악의 경우를 예측해 실행을 막는 게이트가 필요합니다.
- pre-commit 게이트를 통해 커밋 단계에서 개인정보 유출을 기계적으로 차단할 수 있습니다.
- AI 에이전트 이용료가 처리 단계(스텝 수)에 대해 왜 직선이 아닌 제곱에 가까운 형태로 증가하는지, 공식과 계산값으로 보여드립니다.
- '알림'에서 멈추지 않는 이유와 호출 전에 비용을 예측하여 멈출 수 있는 최소한의 구현 방법. - 리포지토리나 배포 ZIP 파일에 남아있기 쉬운 개인 흔적(사용자 이름이 포함된 경로, 비밀 키, 이메일 주소, 사진의 EXIF 등)을 커밋(commit)/푸시(push) 시점에서 기계적으로 막는 게이트 구현과 그 검증 결과.
본문 내 금액은 모두 공식에서 도출한 계산값이며 실제 청구서상의 숫자가 아닙니다. 단가는 예시로 둔 값이므로, 사용하시는 서비스의 현재 공식 가격으로 대체하여 읽어주시기 바랍니다.
브라우저만으로 테스트할 수 있는 데모를 마련했습니다(외부 통신 없음): https://zero-trace-ship-kit.zeus-lab-global.workers.dev/
에이전트는 한 단계마다 그동안의 대화 내용과 도구 실행 결과를 모두 입력으로 다시 전송합니다. 기호를 다음과 같이 정의하겠습니다.
B: 처음부터 내장된 양(지시문, 도구 정의) -A: 한 단계마다 증가하는 양(도구 결과와 직전 응답) -O: 한 단계의 출력 -S: 처리 단계 수
k단계째의 입력은 B + (k-1)·A 토큰입니다. S단계까지의 입력 합계는 다음과 같습니다.
입력 총합 = S·B + A·S·(S-1)/2
이때, 두 번째 항이 S의 제곱에 비례하여 증가합니다. 단계 수가 2배가 되면 이 항은 약 4배가 됩니다.
B=6,000, A=2,500, O=500이라는 예시 값을 사용하고, 입력 $3 / 100만 토큰, 출력 $15 / 100만 토큰, 재사용분(프롬프트 캐시 읽기) $0.30 / 100만 토큰으로 계산하면 다음과 같습니다.
| 단계 수 | 입력 총합 (토큰) | 총 비용 | 매번 재사용했을 경우 | 1단계 평균 ÷ 첫 번째 단계 |
|---|---|---|---|---|
| 10 | 172,500 | $0.59 | $0.20 | 2.3배 |
| ... |
단계 수가 10에서 160으로 16배가 되면 비용은 약 169배가 됩니다. '어제까지 수십 엔이던 처리가 오래 돌린 날만 자릿수가 달라지는' 것은 이 형태가 원인입니다.
파악할 수 있는 점은 세 가지가 있습니다.
- 비용을 결정하는 것은 단가보다 단계 수와 1단계당 증가분입니다. 단가가 절반이 되어도, 단계 수가 1.5배가 되면 상쇄됩니다. - 재사용(캐시)는 효과를 보지만, 제곱 형태 자체는 사라지지 않습니다. 위의 표에서도 재사용 있음 열은 여전히 가속하고 있습니다. 또한, 중간에 문맥의 앞부분을 수정하면 재사용이 풀리면서 그 단계부터 일반 단가로 돌아갑니다. 재사용 쓰기에 할증이 붙는 요금 체계에서는 이 열보다 더 높아집니다. - 도구 결과를 작게 만드는 것이 가장 효과적입니다.
A는 제곱의 계수입니다. 파일 전체나 로그 전체를 반환하는 도구를 필요한 행만 반환하도록 수정하면, 총합에 직접적으로 영향을 줍니다.
많은 관리 화면에 있는 '예산 알림'은 사용한 후에 도착합니다. 막으려면, 호출 전에 최악의 경우 비용을 예측하여 상한을 초과하면 호출하지 않도록 순서를 조정해야 합니다.
class BudgetExceeded(RuntimeError):
pass
class Guard:
...
핵심은 다음 세 가지입니다.
authorize는 출력의 최대 한도까지 사용된 경우에 예측합니다. 실적이 아닌 최악값으로 판정하면, 상한을 초과하는 호출은 단 한 번도 실행되지 않습니다. -record에는 응답에 포함된 실제 사용량을 전달해야 합니다. - 상한은 '1회 실행'뿐만 아니라 '월' 단위로도 가질 수 있습니다. 여러 처리가 같은 원장 파일을 읽도록 하면, 합계에서 막을 수 있습니다.
앞서 예시에서는, 상한을 $2에 두면 21단계에서 멈춥니다. 그대로 40단계를 돌렸을 경우의 $6.87과의 차이가, 1회 실행으로 지킬 수 있는 금액입니다.
같은 호출을 반복하는 상태(같은 파일을 여러 번 읽거나, 같은 검색을 반복하는 것)도 비용이 불어나는 전형적인 사례입니다. 도구 이름과 인수를 연결한 문자열을 직전의 것과 비교하여 연속적으로 같다면 멈추게 하는 몇 줄의 감시로 막을 수 있습니다.
비용 다음으로 사고가 나기 쉬운 것이 공개된 리포지토리나 배포 ZIP에 남는 개인 정보입니다. AI 에이전트에게 작업을 맡기면, 로그, 설정, 오류 출력, 시험 결과 등에 작업한 단말기의 정보가 그대로 기록될 수 있습니다. 남아있기 쉬운 것들을 꼽아보겠습니다.
⚠️ [IMG:N] 형식 토큰은 이미지 placeholder 입니다. 번역하지 말고 원래 위치에 그대로 유지하세요.
| 종류 | 예시 |
|---|---|
| 사용자명 포함 절대 경로 | /Users/<로그인명>/... ,/home/<로그인명>/... ,C:\Users\<로그인명>... |
| 비밀 키/비밀번호 | 각 서비스의 API 키, BEGIN ... PRIVATE KEY로 시작하는 키 |
| 이메일 주소 | README의 연락처, 커밋 명의 |
| 이미지 메타데이터 | JPEG의 EXIF(촬영 위치 포함 가능), PNG의 문자 정보 |
| ... | .DS_Store ,__MACOSX/ ,Thumbs.db ,.env |
| Git 명의 | 개인 이름과 이메일 주소로 생성된 커밋 |
ZIP 파일은 특히 놓치기 쉽습니다. 작업 폴더를 확인하더라도, 배포용으로 묶은 ZIP 내부나 그 안에 있는 또 다른 ZIP까지는 열어보지 않기 때문입니다.
정책은 '찾아내면 commit과 push를 실패시키는 것'뿐입니다. Python의 표준 기능만을 사용하여 작성합니다.
HOME_PATH = re.compile(
r"(?<![\w.])(?:/Users/|/home/|[A-Za-z]:\\{1,2}Users\\{1,2})([A-Za-z0-9][A-Za-z0-9._-]*)"
)
맨 앞의 (?<![\w.])는 https://example.com/home/dashboard와 같은 URL의 일부를 잘못 가져오는 것을 방지하기 위한 조건입니다. 추출된 이름이 you나 runner처럼 설명용 또는 CI(Continuous Integration)용 이름이라면 통과시킵니다.
검사 장비에 본명을 적어 넣으면, 검사 장비 자체가 유출의 원인이 됩니다. 따라서 로그인명, OS에 등록된 성명, Git의 전역 설정 명의를 실행할 때마다 터미널에서 읽어와 메모리 안에서만 대조합니다. 출력에서는 반드시 가립니다.
raw.add(getpass.getuser())
raw.add(Path.home().name)
full = run(["id", "-F"])
# macOS: 등록된 성명
문자열은 UTF-8과 UTF-16 두 가지 형식 모두에서 검색합니다. Office 문서나 일부 바이너리는 이름에 UTF-16을 사용하기 때문입니다.
def scan_archive(self, where, data, depth):
with zipfile.ZipFile(io.BytesIO(data)) as zf:
for info in zf.infolist():
...
scan_bytes는 내용물이 ZIP 파일인 경우 다시 scan_archive를 호출하므로, ZIP 안의 ZIP도 동일한 검사를 거칩니다. .docx나 .xlsx 역시 실체는 ZIP이므로 같은 경로로 작성자란까지 읽을 수 있습니다.
EXIF 유무는 JPEG 영역을 순차적으로 읽어 APP1 영역이 Exif로 시작하는지만 확인하면 알 수 있습니다. 위치 정보는 그 안에 있는 표식인 0x8825가 있는지 여부로 판정합니다. 외부 라이브러리는 필요 없습니다.
# .git/hooks/pre-commit
exec python3 gatekeeper/zero_trace_check.py --staged --identity
# .git/hooks/pre-push
...
커밋 전에는 앞으로 기록될 내용(스테이징된 내용 자체)을 조사합니다. 푸시 전에는 모든 파일, 과거 커밋의 명의와 메시지, 전송 대상 URL, 그리고 .gitignore로 제외한 배포 ZIP의 위치도 조사합니다. 동일한 검사를 GitHub Actions의 첫 번째 작업으로 실행하고, 통과했을 때만 후속 배포가 작동하도록 합니다.
검사 장비가 스스로 '합격'이라고 말해도 의미가 없기 때문에, 다른 프로그램에서 확인했습니다. 임시 폴더에 '유출을 심은 파일'과 '헷갈리지만 안전한 파일'을 만들고 검사 장비에 적용합니다. 심는 값은 실행 시점에 조합하므로, 시험 프로그램 자체는 검사를 통과합니다.
| 항목 | 결과 |
|---|---|
| 유출을 심은 파일 | 21건 중 21건 감지 |
| ... | |
유출시킨 21개 항목의 내역은 세 가지 OS의 절대 경로, 네 종류의 키와 비밀번호, 이메일 주소, 하드코딩된 비밀번호, EXIF와 위치 정보를 가진 JPEG, 문자 정보를 가진 PNG, .DS_Store ,.env ,작성자란이 있는 PDF, 경로를 포함하는 ZIP, 키가 포함된 'ZIP 안의 ZIP', __MACOSX |
포함하는 ZIP, 중간에 잘린 ZIP, 작성자 필드가 있는 .docx,
UTF-16으로 등록어를 포함하는 바이너리, 등록어를 포함하는 파일명입니다.
안전 측 12건에는 /Users/you/와 같은 설명용 경로, actions/checkout@v4, [email protected], example.com 주소, /home/을 포함하는 URL, 환경 변수에서 읽기만 하는 코드 등을 넣었습니다.
테스트 결과는 그대로 공개하고 있습니다: https://zero-trace-ship-kit.zeus-lab-global.workers.dev/evidence/selftest.json
- 형태가 정해지지 않은 정보는 수집할 수 없습니다. 문서 속의 실명이나 주소는 단말기에서 읽어낸 단어나, 사용자가 등록한 단어에 입력된 단어만 대상입니다.
- 키의 탐지는 주요 형식으로 제한됩니다. 자체 형식의 인증 정보는 대상에서 제외됩니다.
- 이미지 안에 찍힌 문자는 읽지 않습니다.
- "탐지 0건"은 이 검사기가 알고 있는 형태에 대해서는 남아있지 않다는 의미이며, 안전의 증명이 아닙니다.
- 에이전트 비용은
S·B + A·S·(S-1)/2형태로 증가합니다. 효과적인 것은 수고와 1수당 증분입니다. - 막으려면, 호출 전에 최악의 값으로 추정하여 거부해야 합니다. 알림은 중단하지 않습니다. - 개인의 흔적은 사람의 주의가 아니라
commit/push지점에서 막습니다. ZIP 파일은 내부까지 엽니다. 자신의 이름은 검사기에 적지 않고, 실행 시에 읽습니다.
브라우저에서 작동하는 스캐너와 문서 파일을 검사하는 경량 버전은 무료입니다: https://zero-trace-ship-kit.zeus-lab-global.workers.dev/
commit/push를 막는 완전판 검사기, 클린 배포 ZIP을 만드는 도구, 상한 가드(upper limit guard), 배포 자동화 일체, 상업용 라이선스를 모은 완전판은 여기입니다: https://zeuslab.gumroad.com/l/zero-trace-ship-kit
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기