
에이전트 가드레일: 루프 제한, 비용 상한 및 인간 승인 게이트
요약
자율 에이전트의 무한 루프와 비용 폭증을 방지하기 위한 세 가지 핵심 가드레일을 소개합니다. 단계 상한선, 실행당 예산 제한, 그리고 중요 작업에 대한 인간 승인 게이트 구축의 중요성을 강조합니다.
핵심 포인트
- 단계 상한선(Step Ceiling)을 설정하여 에이전트의 무한 루프를 방지해야 함
- 실행당 달러 단위 예산(Per-run Budget)을 지정하여 예상치 못한 비용 발생 차단
- 중요한 시스템 동작에는 반드시 인간의 승인 게이트(Approval Gate)를 도입
- 프레임워크의 기본값에 의존하지 말고 작업 성격에 맞는 제한값 설정 필요
- 도서: Agents in Production — Building, Tracing, and Shipping Multi-Step AI You Can Trust
- 저자의 다른 저서: Observability for LLM Applications — The AI Engineer's Library (2권 시리즈)의 동반 도서
- 내 프로젝트: Hermes IDE | GitHub — Claude Code 및 기타 AI 코딩 도구를 사용하여 작업하는 개발자를 위한 IDE
- 자기소개: xgabriel.com | GitHub
당신은 금요일에 에이전트(agent)를 배포합니다. 소규모 코호트(cohort)를 대상으로 플래그를 활성화하고, 트레이스(traces)가 정상인 것을 확인한 뒤 노트북을 닫습니다. 일요일, 온콜(on-call) 채널에 재무팀으로부터 조용한 메시지가 옵니다: "이 청구서가 맞나요?" 맞지 않습니다. 한 세션이 고장 난 도구(tool)를 호출했고, 모델은 마지막 호출이 거의 성공했다고 판단하여 36시간 동안 재시도(retry)를 반복했습니다. 아무도 지켜보고 있지 않았습니다. 아무도 지켜보라고 알려주는 것이 없었기 때문입니다.
이것이 바로 가드레일(guardrails)이 존재하는 이유이자, 방지해야 할 실패 사례입니다. 보안 논문에서 다루는 이색적인 프롬프트 인젝션(prompt-injection) 체인이 아닙니다. 지루하지만 치명적인 문제, 즉 한계치가 없는 자율 루프(autonomous loop)가 자체적인 재시도 속도로 돈을 쓰고 시스템을 건드리는 문제입니다.
에이전트는 모델을 호출하고, 모델이 요청하는 도구를 실행하며, 그 결과를 다시 피드백하는 while 루프입니다. 이 루프가 곧 제품입니다. 또한 이것은 책임(liability)이기도 합니다. 세 가지 가드레일은 이 책임을 다시 제품으로 돌려놓습니다: 단계 상한선(step ceiling), 달러 단위로 지정된 실행당 예산(per-run budget), 그리고 세상을 변화시키는 동사(verbs)들에 대한 승인 게이트(approval gate)입니다. 루프에 트래픽이 유입되기 전에 이 세 가지를 모두 구축하십시오.
단계 상한선: 에이전트가 빠르게 실패하도록 만들기
언제 멈춰야 할지 모르는 에이전트는 영원히 실행될 것입니다.
모든 프레임워크에는 루프 제한 (loop limit) 기능이 포함되어 있지만, 대부분의 기본값은 너무 관대합니다. LangGraph는 recursion_limit의 기본값을 25로 설정하며, 이 한계에 도달하면 GraphRecursionError를 발생시킵니다. OpenAI Agents SDK는 max_turns를 사용하며 MaxTurnsExceeded를 발생시킵니다. 두 방식 모두 괜찮습니다. 다만, 한계에 도달했을 때 사용자가 스택 트레이스 (stack trace)를 보는 대신 구조화된 결과 (structured result)를 반환할 수 있도록 두 방식 모두 래핑 (wrapping)이 필요합니다.
프레임워크의 기본값이 아닌, 작업의 형태에 따라 숫자를 선택하십시오. 사용자 대상 채팅 에이전트는 812회 이상의 턴 (turns)이 지나면 거의 항상 문제가 발생합니다. 백그라운드 조사 에이전트는 2030회 정도를 유지합니다. 코딩 에이전트는 100회에 가깝게 실행되지만, 이는 단계 카운터 (step counter)가 도달하기 훨씬 전에 실행 시간 제한 (wall-clock cap)과 토큰 예산 (token budget)이 먼저 작동하기 때문입니다. 기본값이 존재하는 이유는 새로운 사용자들을 놀라게 하지 않기 위함이지, 여러분의 청구서를 보호하기 위함이 아닙니다.
단계 카운터는 하나의 축입니다. 실행 시간 (Wall-clock time)과 총 토큰 (total tokens)이 나머지 두 축입니다. 이 중 무엇이든 먼저 도달하는 것이 우선하며, 모든 상한선은 동일한 경로를 통해 종료되어야 하므로 다운스트림 (downstream)의 모든 요소가 하나의 실패 형태를 이해할 수 있어야 합니다.
from dataclasses import dataclass
from time import monotonic
...
다음 모델 호출 전, 루프의 최상단에서 tripped를 호출하십시오. 먼저 확인하고, 그다음에 비용을 지불하십시오. 만약 확인을 나중에 한다면, 이미 한계를 초과하게 만든 호출에 대한 비용을 지불한 상태가 됩니다.
단계 상한선만으로는 12단계를 수행하면서도 진전이 없는 에이전트, 즉 동일한 도구, 동일한 인자, 동일한 결과를 12번 반복하는 에이전트를 놓칠 수 있습니다. 매 단계마다 (tool_name, arguments) 튜플 (tuple)을 해시 (hash)하고, 마지막 3개가 동일할 경우 중단하십시오.
def no_progress(steps: list[tuple[str, dict]]) -> bool:
if len(steps) < 3:
return False
...
비용 상한선: 예산은 달러로 책정됩니다
에이전트가 단계(step) 제한을 준수하더라도 수천 달러를 지출할 수 있습니다. 각 턴마다 10만 토큰 규모의 문서를 컨텍스트(context)로 불러와 Claude Opus에게 질문을 던진다면 더욱 그렇습니다. 단순한 토큰 수(token count)는 이를 숨기게 되는데, 토큰의 가격이 모두 동일하지 않기 때문입니다.
캐시된 입력 토큰(Cached input tokens)은 저렴합니다. 새로운 입력 토큰(Fresh input tokens)은 중간 수준입니다. 출력 토큰(Output tokens)은 비쌉니다. 확장된 사고(extended-thinking) 모델의 추론 토큰(Reasoning tokens)은 이 중 가장 비쌉니다. 토큰 단위로 예산을 계산하는 것은 이러한 차이를 무시하는 거짓된 방식입니다. 달러 단위로 계산하십시오.
PRICE_PER_MTOK = {
"cache_read": 1.50,
"input": 15.00,
...
위 수치는 Opus급 모델의 백만 토큰당 대표적인 수치입니다 (2026년경 Anthropic 공개 가격 기준 — 현재 요율을 확인하십시오). 사용 중인 벤더의 공개된 수치로 대체하여 사용하십시오. 중요한 점은 상한선이 '달러 금액'이어야 한다는 것입니다. 왜냐하면 프론티어 모델(frontier model)에서의 1,000개 추론 토큰 비용이 소형 모델에서의 10,000개 캐시된 입력 토큰 비용보다 더 비싸기 때문입니다.
비용 제어는 두 곳에서 이루어져야 합니다. 하네스(harness) 내부의 실행당(per-run) 예산과 게이트웨이(gateway)에서의 키당(per-key) 예산입니다. 프로세스 내부 예산은 구현하기 가장 쉬우므로 가장 먼저 작성하십시오. 모든 호출에 걸쳐 usage를 누적하고, 실행이 상한선을 초과하면 중단하도록 합니다.
게이트웨이 예산은 하네스에 버그가 발생하더라도 살아남는 방어선입니다. LiteLLM Proxy는 사용자별, 키별, 모델별로 엄격한 상한선(hard caps)을 적용하는 예산 관리자를 제공합니다. Portkey 역시 관리형 대시보드를 통해 동일한 기능을 제공합니다. 프로세스 내부 상한선은 합리적인 최악의 작업 비용에 2를 곱한 값으로 설정하십시오. 게이트웨이 상한선은 사후 분석(postmortem) 보고서를 작성할 용의가 있는 금액으로 설정하십시오. 만약 두 수치가 같다면, 당신은 매우 힘든 오후를 보내게 될 것입니다.
하나의 가드, 하나의 탈출구, 하나의 스팬(span)
산발적인 체크 방식도 작동은 하겠지만, 프로덕션 환경에서는 단계(steps), 시간(seconds), 토큰(tokens), 그리고 비용(dollars)을 한 곳에서 강제하고 현재 트레이스 스팬(trace span)에 모든 경로를 기록하는 단일 객체를 원하게 됩니다. 사후 분석(postmortem) 시 실행이 왜 중단되었는지 물었을 때, 로그를 뒤지는 것이 아니라 스팬 쿼리(span query)로 답을 얻을 수 있어야 합니다.
from time import monotonic
from opentelemetry import trace
...
하네스(harness)는 각 모델 턴(model turn) 전에 check()를 호출하고, 각 응답 후에 charge(response.usage)를 호출합니다. 네 줄의 코드, 하나의 탈출 경로, 그리고 각 사유별 하나의 스팬 속성(span attribute)으로 구성됩니다.
guard = AgentGuard(max_steps=10, max_usd=2.0)
while not done:
guard.check()
...
핵심은 코드가 아니라 그 형태(shape)입니다. 여러분은 여러분의 프레임워크에 맞춰 자신만의 형태를 작성하게 될 것입니다.
승인 게이트: 파괴적인 동사를 위한 인간의 클릭
상한선(ceilings)과 예산(budgets)은 폭주를 막아줍니다. 하지만 전선을 배송하거나, 테이블을 삭제하거나, 고객에게 이메일을 보내는 단일 도구 호출(tool call)에 대해서는 아무런 역할을 하지 못합니다. 저렴한 비용으로 되돌릴 수 없는 방식으로 상태를 변경하는 모든 동사에 대해, 올바른 체크 방식은 버튼을 클릭하는 사람입니다. LLM 판사(judge)도, 규칙 엔진(rule engine)도 아닙니다. 정확한 인자(arguments)를 보고 있는 사람이어야 합니다.
모든 프레임워크는 동일한 형태(shape)로 수렴했습니다. LangGraph는 이를 interrupt()라고 부릅니다. 그래프가 일시 중지되고, 체크포인터(checkpointer)가 상태를 유지하며, 호출자가 제안된 작업을 전달받은 뒤, Command(resume=...)를 통해 실행이 재개됩니다. Agents SDK는 이를 도구(tool)의 needs_approval 플래그로 모델링합니다. 메커니즘 자체는 지루할 수 있지만, 이를 둘러싼 규율(discipline)은 그렇지 않습니다.
from functools import wraps
from uuid import uuid4
...
게이트가 단순한 보여주기식(theater)이 아닌 실질적인 역할을 하게 하려면 두 가지 규칙이 필요합니다.
페이로드(payload)를 있는 그대로 보여주어야 합니다. 사람은 모델이 요약한 내용이 아니라, 도구에 전달된 정확한 인자들을 고정폭 글꼴(monospace font)로 확인해야 합니다. 모델은 _"회의를 확인하기 위해 앨리스에게 보내는 짧은 메모"_라고 렌더링하지만, 실제 원본 인자에는 모델이 언급하지 않기로 선택한 주소로의 bcc가 포함되어 있는 승인 UI를 상상해 보십시오. 사람이 승인을 클릭합니다. 요약은 바로 그 피해가 숨어 있는 지점에서 충실도(fidelity)를 잃게 됩니다.
"내 선택 기억하기"를 절대 추가하지 마십시오. 이는 승인 피로 (approval fatigue)를 해결하기 위한 임시방편처럼 느껴집니다. 6주가 지나면 대부분의 승인은 자동 승인되고, 게이트는 장식품이 되며, 사고가 발생하기 전까지는 아무도 이를 알아차리지 못합니다. 피로가 실재한다면(그리고 피로는 항상 실재합니다), 승인을 트리거하는 도구의 집합을 좁히십시오. 승인 자체를 약화시키지 마십시오. 게이트를 통과하기에 너무 느린 도구는 에이전트의 손에 들려 있어서는 안 되는 도구입니다.
게이트에 타임아웃 (timeout)을 설정하십시오. 인간을 위해 일시 중지하고 영원히 기다리는 에이전트는 이를 호스팅하는 어떤 런타임 (runtime)에서든 스레드 (thread)를 누수시킵니다. 마감 기한을 정하고, 만료를 approval_timeout이라는 사유를 가진 거절로 처리하십시오. 그리고 호출자가 여전히 해당 동작을 원한다면 다시 시작할 수 있도록 하십시오.
이 순서대로 구축하십시오
첫 번째 데모를 선보이기 전에 이 모든 것이 다 필요하지는 않습니다. 필요한 것은 올바른 순서이며, 각 레이어 (layer)는 그것이 방지하고자 하는 실패가 실제 사용자에게 도달하기 전에 구축되어야 합니다.
- 단계 상한선 (Step ceiling) 및 달러 예산 (dollar budget). 첫 번째 데모 전, 첫날에 구축하십시오. 이는 여러분이 실제로 1주 차에 맞닥뜨리게 될 실패, 즉 테스트한 입력값에는 작동하지만 테스트하지 않은 20%의 입력값에 대해서는 무한 루프를 도는 에이전트를 잡아냅니다.
- 도구 허용 목록 (Tool allow-list). 무언가를 변경(mutate)하는 첫 번째 도구가 도입되기 전에 구축하십시오. 에이전트의 도구 세트는 생성 시점에 선언된 폐쇄된 우주입니다. 모델은 런타임 (runtime) 중에 도구를 발견하지 않습니다.
- 파괴적인 동사에 대한 승인 게이트 (Approval gate on destructive verbs). 여러분이 아닌 첫 번째 사용자가 나타나기 전에 구축하십시오. 개발자는 에이전트가 무엇을 하는지 알고 있습니다. 하지만 첫 번째 외부인은 아무도 계획하지 않은 일을 에이전트에게 요청할 것입니다.
가드레일 (Guardrails)은 능력에 부과되는 세금이 아닙니다. 가드레일은 그 능력을 출시 가능한 상태로 만드는 이유입니다. 상한선이 없는 코딩 에이전트는 데모에 불과합니다. 단계 상한선, 달러 예산, 허용 목록, 그리고 git push 및 프로덕션 (production)에 접촉하는 모든 것에 대한 인간 게이트를 갖춘 동일한 에이전트는 제품입니다. 이 둘 사이에는 단 한 번의 인프라 (infrastructure) 스프린트 (sprint)가 놓여 있으며, 그 이후에는 매일 밤의 숙면이 보장됩니다.
오늘 단계 상한선을 작성하십시오. 오늘 달러 예산을 작성하십시오. 어떤 동사에 인간의 개입이 필요한지 오늘 결정하십시오. 그런 다음 플래그 (flag)를 켜십시오.
가드레일 (Guardrails)은 혼란에 빠진 에이전트가 영원히 비용을 소모하는 것을 방지합니다. 더 어려운 작업은 실행이 왜 중단되었는지, 그리고 정상적인 실행은 어떤 모습인지 파악하는 것이며, 이는 트레이싱 (tracing) 문제입니다. _Agents in Production_은 가드레일 스택을 엔드 투 엔드 (end to end)로 다루며 (상한선, 예산, 허용 목록, 승인 대기열), The AI Engineer's Library의 동반서인 _Observability for LLM Applications_는 어떤 트리프와이어 (tripwire)가 부하를 담당하고 있는지 알려주는 스팬 (spans), 평가 (evals), 비용 텔레메트리 (cost telemetry)를 다룹니다. 이 두 권은 데모와, 누군가에게 호출 (page)을 보낼 수 있는 실제 서비스 사이의 차이를 만들어냅니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기