AI 에이전트를 위한 지출 게이트웨이를 구축했습니다. 그리고 부하 상황에서 그것이 버티지 못한다는 것을 증명했습니다.
요약
AI 에이전트 간 작업 위임 시 발생하는 비용 문제를 해결하기 위한 지출 게이트웨이 구축 사례를 다룹니다. 비용 추정 방식과 예산 집행 메커니즘을 설명하며, 시스템 설계 과정에서 발견된 한계와 버그를 분석합니다.
핵심 포인트
- 에이전트 간 작업 위임 시 무분별한 비용 발생을 막는 게이트웨이 설계
- 자가 보고, 토크나이저 추정, 일반 폴백을 활용한 3단계 비용 추정 방식
- 작업 완료 전에는 실제 비용을 알 수 없는 구조적 한계 직면
- 투명한 프록시 형태를 통한 트래픽 가시성 확보 및 예산 통제
A2A — 한 AI 에이전트가 다른 에이전트에게 작업을 넘길 수 있게 해주는 개방형 프로토콜 (open protocol) — 는 발견 (discovery)과 메시징 (messaging) 문제를 잘 해결합니다. 하지만 돈이 개입되는 순간 중요한 질문 하나를 해결하지 못합니다. 에이전트 A가 에이전트 B에게 작업을 위임할 때, 아무도 한도를 설정하지 않은 비용이 조용히 쌓이는 것을 무엇이 막아줄 수 있을까요?
저는 그 질문에 답하기 위해 작은 게이트웨이 (gateway)를 구축했습니다. 이 게이트웨이는 두 에이전트 사이에 위치하여, 위임된 각 작업의 비용을 추정하고, 에이전트의 지출 한도가 소진되면 요청 전달을 거부할 수 있습니다. 작동은 잘 됩니다. 하지만 정확히 사용자를 보호해야 하는 부분에 실제적이고 해결되지 않은 버그 (bug)가 존재하며 — 저는 설계와 버그 모두를 살펴보고자 합니다. 왜냐하면 버그가 기능보다 더 흥미롭기 때문입니다.
설정 (The setup)
두 개의 연습용 에이전트가 실제 A2A를 통해 대화합니다. 작업을 위임하는 코디네이터 (Coordinator)와 이를 완료하는 스페셜리스트 (Specialist)입니다. 게이트웨이는 그들 사이에 위치하여 트래픽을 투명하게 전달하지만, 한 가지 의도적인 예외를 둡니다. 게이트웨이는 스페셜리스트가 광고하는 주소를 자기 자신을 가리키도록 재작성하여, 코디네이터가 실제 에이전트 대신 게이트웨이를 발견하게 만듭니다. 이것이 이 "투명한" 프록시 (proxy)가 의도적으로 네트워크 선에 개입하는 유일한 지점이며, 그 외의 모든 것은 바이트 단위로 그대로 통과합니다.
이 위치에 자리 잡음으로써, 게이트웨이는 모든 요청 (request)과 응답 (response)을 볼 수 있습니다. 이러한 가시성 (visibility) 덕분에 비용 추정 (cost estimation)과 예산 집행 (budget enforcement)이 가능해집니다.
정밀도에 대해 거짓말하지 않고 비용 추정하기
게이트웨이는 스페셜리스트가 내부적으로 무엇을 하는지는 결코 알 수 없습니다. 시스템 프롬프트 (system prompts), 재시도 (retries), 또는 도구 호출 (tool calls)에 대한 가시성은 전혀 없습니다. 게이트웨이가 보는 것은 오직 입력 메시지 (input message)와 출력 결과물 (output artifact)뿐입니다. 따라서 여기서 "비용 추정"은 신뢰도에 따라 순위가 매겨지고 모든 로그 기록에 적절히 라벨링된 세 가지 중 하나를 의미합니다:
- Self-reported (자가 보고) — 협력하는 에이전트가 실제 사용 데이터를 자발적으로 첨부합니다. 사용 가능한 경우 Ground truth (실측값) 역할을 합니다.
- Provider-tokenizer estimate (제공자 토크나이저 추정치) — 게이트웨이는 어떤 모델이 에이전트를 지원하는지 알고 있으며, 해당 제공자의 실제 토크나이저 (OpenAI의
tiktoken, Mistral의 Tekken 토크나이저, Google의 실험적인 로컬 Gemini 토크나이저를 대상으로 검증되었으며, 문서를 통해 추정하지 않고 실제로 라이브러리를 설치하여 실행함으로써 확인됨)를 실행합니다. - Generic fallback (일반 폴백) — 다른 방법이 적용되지 않을 때 사용하는, 제공자에 구애받지 않는 대략적인 글자 수 계산 방식입니다.
모든 로그 기록에는 어떤 티어(tier)에서 생성되었는지가 명시됩니다. 추정치는 절대 측정치처럼 보일 수 없습니다. 이 구분은 수치를 바탕으로 실제 요청을 거부할지 여부를 결정해야 하는 상황에서 매우 중요합니다.
미래가 아닌 과거를 기준으로 한 게이팅 (Gating)
직접 고민해 보기 전까지는 명확하지 않은 부분이 있습니다: 게이트웨이는 작업을 전달하기 전에는 해당 작업의 실제 비용을 알 수 없습니다. 비용은 Specialist (전문가)가 응답한 후에야 계산할 수 있으며, 이것이 바로 앞서 설명한 추정 시스템의 전제 조건입니다. 따라서 예산 확인(budget check) 단계에서 "이 작업이 너무 많은 비용을 발생시킬 것인가?"라고 물을 수는 없습니다. 그 질문에 대한 답은 아직 존재하지 않기 때문입니다.
예산 확인이 할 수 있는 유일하게 정직한 질문은 다음과 같습니다: "이 에이전트가 이미 새로운 요청을 시작하지 못할 정도로 충분한 비용을 지출했는가?" 게이트웨이는 실제 완료된 작업의 추정치로부터 도출된 에이전트의 누적 지출 총액을 계속 추적하며, 이 총액에 (전송 전 확인 가능한) 새로운 요청의 입력 측면에 대한 저렴한 사전 점검(pre-flight) 추정치를 더하여 설정된 한도(ceiling)와 비교합니다.
이는 단일 작업의 실제 응답 비용이 입력값이 시사했던 것보다 훨씬 더 비싼 것으로 판명될 경우, 여전히 예산을 초과할 수 있음을 의미합니다. 외부에서는 이를 방지할 수 있는 방법이 없습니다. 초과 지출은 응답이 이미 전달된 후에야 알 수 있기 때문입니다. 게이트웨이는 자원이 고갈되면 새로운 고비용 작업이 들어오는 것을 차단하지만, 이미 진행 중인(in flight) 요청이 얼마만큼의 비용을 발생시킬지는 제한할 수 없습니다. 저는 이것을 숨겨야 할 결함이라고 생각하지 않습니다. 이는 네트워크 수준의 프록시(proxy)가 알 수 있는 것과 알 수 없는 것에 대한 구조적인 사실이며, 그렇지 않은 척하는 것이 차라리 인정하는 것보다 더 나쁠 것입니다.
버그: 동시성(concurrency) 상황에서 "과거"의 의미가 모호해짐
예산 확인은 세 가지 단계를 순차적으로 수행합니다: 에이전트의 현재 지출액을 읽고, 전달 여부를 결정한 다음, 응답이 돌아오면 새로운 지출액을 기록합니다. 읽기(read)와 기록(record) 사이에는 Specialist로의 네트워크 호출에 대한 실제 await가 존재합니다. Python의 asyncio는 모든 await 동안 다른 작업을 인터리빙(interleave)할 수 있습니다.
이는 '확인 후 실행(check-then-act)' 방식의 레이스 컨디션(race condition)이며, 저는 단순히 이것이 존재할 것이라고 의심만 한 것이 아니라 tests/test_budget_race_condition.py에서 의도적으로 재현했습니다. 저는 정확히 하나의 요청만 허용할 수 있는 크기로 예산을 설정한 다음, 동일한 에이전트에게 5개의 동시 위임(delegation)을 보냈습니다. 순차적으로 계산하면 결과는 명확합니다: 첫 번째 요청이 예산을 소진하며, 그 이후의 모든 요청은 거부되어야 합니다.
하지만 동시적으로 실행했을 때, 5개 모두 통과되었습니다.
5개의 요청 각각은 다른 요청이 기록을 마치기 전에 "현재 지출액" 값을 읽습니다. 따라서 각 요청은 예산이 아직 사용되지 않은 것처럼 인식하여 확인 과정을 통과하고 진행됩니다. 게이트웨이가 개별적인 결정 자체를 틀린 것은 아닙니다. 다만 결정이 중요해지는 시점에는 이미 오래된(stale) 상태를 바탕으로 추론하고 있을 뿐입니다.
이번 포스트에서 이를 수정하지 않는 이유
진정한 해결책은 설명하기는 간단하지만 올바르게 구현하기는 쉽지 않습니다. 즉, 전체 '확인-전달-기록 (check-forward-record)' 시퀀스 주위에 에이전트별 잠금 (per-agent lock) 또는 비교 및 설정 (compare-and-set)을 적용하여, 동일한 에이전트에 대한 동시 요청이 현재 경합 (race)이 발생하는 바로 그 지점에서 직렬화 (serialize)되도록 하는 것입니다. 저는 해결책이 무엇인지 알고 있습니다. 하지만 의도적으로 아직 배포하지 않았습니다. 이 프로젝트의 전체적인 원칙은 "구축하기 전에 검증하라"였으며, 저는 버그를 방지해야 할 코드를 건드리기 전에 해당 버그가 문서화되고, 재현되며, 실재함이 증명되기를 원했습니다. 현재 안전하게 실패하는 테스트가 존재합니다. 이 테스트는 현재 버그가 존재함을 확인함으로써 통과하고 있으며, 경합을 해결하면 해당 단언 (assertion)이 뒤집히도록 작성되었습니다. 이것이 핵심입니다. 만약 이 프로젝트의 향후 버전에서 해당 테스트가 실패하기 시작한다면, 그것은 회귀 (regression)가 아니라 해결책이 제대로 작동하고 있다는 뜻입니다.
해결되지 않았음에도 불구하고 이것을 읽을 가치가 있는 이유
대부분의 "AI 도구를 만들었습니다"라는 포스트들은 해피 패스 (happy path)만을 보여줍니다. 저는 시스템의 자체 로직이 의지만으로는 넘을 수 없는 벽에 부딪히는 지점을 공개하는 것이 더 유용하다고 생각합니다. 왜냐하면 비슷한 것을 만드는 사람 역시 실제로 마주하게 될 부분이기 때문이며, 그것이 다가오고 있다는 것을 아는 것이 또 다른 다듬어진 데모를 보는 것보다 더 가치 있기 때문입니다.
만약 여러분이 A2A를 기반으로 구축 중이며 이 문제에 부딪혔거나, 혹은 해결했다면, 어떻게 했는지 진심으로 듣고 싶습니다.
코드: github.com/AliAbdallah21/a2a-cost-gateway. 직접 버그 재현을 확인하고 싶다면 경합 조건 (race-condition) 테스트가 tests/test_budget_race_condition.py에 있습니다. 왜 현재 상태로도 괜찮은지를 포함한 전체적인 설계 근거는 리포지토리의 arch.md에 기술되어 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기