Published on

AutoGPT 무한루프·비용폭주 차단 가드레일 5가지

Authors

AutoGPT 계열 에이전트는 plan -> act -> observe 루프를 스스로 돌며 목표를 달성합니다. 문제는 이 루프가 조금만 조건이 어긋나도 끝없이 반복되거나, 외부 툴 호출을 과도하게 수행하면서 토큰과 API 비용이 폭주한다는 점입니다. 특히 검색, 브라우징, 코드 실행, DB 조회 같은 툴을 붙이면 에이전트가 실패를 만회하려고 더 많은 시도를 하면서 악순환이 생깁니다.

이 글에서는 AutoGPT 무한루프와 비용폭주를 막는 실전 가드레일 5가지를 정리합니다. 핵심은 “에이전트가 똑똑해지길 기다리는 것”이 아니라, 시스템이 안전하게 멈추게 만드는 것입니다.

1) 실행 예산(Budget) 하드 리밋: 토큰·시간·툴 호출 상한

가장 강력한 가드레일은 예산을 강제로 끊는 것입니다. 예산은 여러 축으로 잡아야 합니다.

  • 토큰 예산: 입력/출력 토큰 합산 상한
  • 비용 예산: 모델별 단가를 반영한 달러 상한
  • 시간 예산: wall clock 타임아웃
  • 툴 호출 예산: 전체 호출 횟수, 툴별 호출 횟수

예산은 “권고”가 아니라 하드 리밋이어야 합니다. 에이전트가 스스로 절약하길 기대하면 실패합니다.

예시: 런타임 예산 가드(파이썬)

import time

class BudgetExceeded(Exception):
    pass

class RunBudget:
    def __init__(self, max_seconds=120, max_steps=30, max_tool_calls=50):
        self.started_at = time.time()
        self.max_seconds = max_seconds
        self.max_steps = max_steps
        self.max_tool_calls = max_tool_calls
        self.steps = 0
        self.tool_calls = 0

    def check(self):
        if time.time() - self.started_at > self.max_seconds:
            raise BudgetExceeded("time budget exceeded")
        if self.steps >= self.max_steps:
            raise BudgetExceeded("step budget exceeded")
        if self.tool_calls >= self.max_tool_calls:
            raise BudgetExceeded("tool call budget exceeded")

    def on_step(self):
        self.steps += 1
        self.check()

    def on_tool_call(self):
        self.tool_calls += 1
        self.check()

# 사용 예
budget = RunBudget(max_seconds=90, max_steps=20, max_tool_calls=25)

for _ in range(10_000):
    budget.on_step()
    # tool을 호출한다면
    # budget.on_tool_call()

여기에 토큰 예산까지 포함하려면 모델 응답의 usage를 누적해 상한을 넘으면 중단합니다. OpenAI Responses API를 쓴다면, 스키마 검증 실패로 재시도 루프가 생기기도 하므로 입력 스키마를 엄격히 검증하는 것도 비용 절감에 직결됩니다. 관련해서는 OpenAI Responses API 422 스키마 검증 에러 해결 가이드를 같이 참고하면 좋습니다.

2) 단계 제한 + 종료 조건을 코드로 강제: “완료 정의”를 기계적으로

무한루프의 본질은 “완료”를 자연어로만 정의해두고, 시스템적으로는 종료 조건이 없다는 데 있습니다. 따라서 다음을 코드 레벨에서 강제하세요.

  • max_steps를 둔다
  • 각 step에서 done 여부를 구조화된 값으로 받는다
  • donetrue일 때만 종료한다
  • donefalse인데도 같은 행동을 반복하면 조기 중단한다

예시: 구조화된 step 결과 강제(JSON 스키마)

에이전트의 응답을 “텍스트”가 아니라 “계약”으로 받으면 루프 제어가 쉬워집니다.

{
  "type": "object",
  "properties": {
    "thought": {"type": "string"},
    "action": {"type": "string", "enum": ["tool", "final", "ask"]},
    "tool_name": {"type": "string"},
    "tool_input": {"type": "object"},
    "done": {"type": "boolean"},
    "final": {"type": "string"}
  },
  "required": ["action", "done"]
}

런타임에서는 다음처럼 처리합니다.

if step_result["done"] is True:
    return step_result.get("final", "")

if budget.steps >= budget.max_steps:
    return "Stopped: max_steps reached. Provide partial output and next actions."

중요한 포인트는 max_steps 도달 시에도 “그냥 실패”가 아니라 부분 산출물과 다음 액션을 강제 출력하게 만드는 것입니다. 그래야 사람이 이어서 판단할 수 있고, 자동 재시도 루프도 줄어듭니다.

3) 툴 게이트(Allowlist) + 위험도 기반 승인: 외부 호출을 통제

AutoGPT가 비용을 폭주시키는 가장 흔한 경로는 searchbrowse 같은 툴을 연속 호출하는 패턴입니다. 다음을 적용하세요.

  • 툴 Allowlist: 필요한 툴만 열어두기
  • 툴별 쿼터: 예를 들어 search는 5회, browse는 10회
  • 위험도 기반 승인: 결제, 삭제, 대량 메일 발송, DB 쓰기 등은 사람 승인
  • 입력 크기 제한: URL 수, 검색 질의 길이, 본문 길이 제한
  • 결과 캐싱: 같은 질의는 재호출하지 않기

예시: 툴 호출 정책(간단 정책 엔진)

TOOL_POLICY = {
    "search": {"max_calls": 5, "risk": "low"},
    "browse": {"max_calls": 10, "risk": "medium"},
    "db_write": {"max_calls": 1, "risk": "high"},
}

def authorize_tool(tool_name: str, budget, require_approval: bool = True):
    if tool_name not in TOOL_POLICY:
        raise PermissionError("tool not allowed")

    policy = TOOL_POLICY[tool_name]
    if budget.tool_calls >= budget.max_tool_calls:
        raise BudgetExceeded("tool budget exceeded")

    if policy["risk"] == "high" and require_approval:
        raise PermissionError("human approval required")

    return True

운영 환경에서는 툴 호출을 네트워크 경계에서도 막아야 합니다. 예를 들어 쿠버네티스에서 egress를 열어두면 브라우징 툴이 무한히 외부를 두드릴 수 있으니, 네임스페이스 단위로 네트워크 폴리시를 좁히고, 필요한 도메인만 허용하는 방식이 효과적입니다.

4) 반복 감지(Loop Detection) + 상태 체크포인트: 같은 실패를 재현하지 않게

무한루프는 대개 “관측이 불충분해서 같은 결론을 다시 내림” 또는 “실패 원인을 교정하지 못함”에서 발생합니다. 이를 막으려면 반복을 탐지하고, 상태를 저장해 되돌림 혹은 중단해야 합니다.

실전에서 잘 먹히는 방법들:

  • 최근 N개의 (action, tool_name, tool_input_hash)를 저장하고 동일 패턴 반복 시 중단
  • tool 결과가 동일한데도 같은 action을 반복하면 중단
  • 실패 코드나 예외 메시지가 동일하게 반복되면 중단
  • 체크포인트에 “결정 근거”를 저장하고, 다음 step에서 이를 반드시 참조하게 강제

예시: 간단 반복 탐지

import hashlib
from collections import deque

class LoopDetector:
    def __init__(self, window=8, max_repeat=3):
        self.window = window
        self.max_repeat = max_repeat
        self.events = deque(maxlen=window)

    def _sig(self, action, tool_name, tool_input):
        raw = f"{action}|{tool_name}|{tool_input}".encode("utf-8")
        return hashlib.sha256(raw).hexdigest()

    def push_and_check(self, action, tool_name, tool_input):
        sig = self._sig(action, tool_name, tool_input)
        self.events.append(sig)
        repeats = sum(1 for e in self.events if e == sig)
        if repeats >= self.max_repeat:
            return False  # loop suspected
        return True

# 사용 예
ld = LoopDetector(window=10, max_repeat=3)

ok = ld.push_and_check("tool", "search", {"q": "same query"})
if not ok:
    raise RuntimeError("loop detected: repeated tool call")

이 가드레일은 “에이전트가 멍청한 답을 반복한다”를 잡는 데 특히 유효합니다. 또한 관측 실패가 반복되는 경우(예: 네트워크 타임아웃)도 루프가 되기 쉬운데, 이는 인프라 레벨 타임아웃과 재시도 정책이 잘못 설계된 경우가 많습니다. gRPC나 내부 호출에서 타임아웃 폭증을 다뤘던 EKS에서 gRPC DEADLINE_EXCEEDED 폭증 해결 같은 패턴도 에이전트 툴체인에 그대로 적용됩니다.

5) 관측성(Observability)과 강제 중단 스위치: “왜 돈이 새는지” 즉시 보이게

가드레일을 넣어도 운영에서는 예외가 생깁니다. 결국 비용폭주를 막는 마지막 장치는 관측성과 킬 스위치입니다.

필수로 남겨야 할 로그/메트릭:

  • step 번호, 누적 토큰, 누적 비용(추정치)
  • 툴 호출명, 입력 크기, 응답 크기, 소요 시간
  • 에러 타입별 카운트(타임아웃, 4xx, 5xx, 스키마 에러)
  • 루프 탐지 이벤트와 중단 사유

그리고 운영에서 중요한 것은 “자동 차단”입니다.

  • 비용 임계치 초과 시 해당 워크스페이스 키 차단
  • 특정 툴 에러율 급증 시 툴 비활성화
  • 특정 사용자/태스크의 동시 실행 수 제한

예시: Prometheus 스타일 메트릭(의사 코드)

metrics.counter("agent_steps_total").inc()
metrics.counter("agent_tool_calls_total", labels={"tool": tool_name}).inc()
metrics.histogram("agent_tool_latency_ms", labels={"tool": tool_name}).observe(latency_ms)
metrics.gauge("agent_cost_usd_estimate").set(cost_usd)

if cost_usd > 2.0:
    raise BudgetExceeded("cost budget exceeded")

에이전트 시스템은 일반 웹 서비스보다 “비정상 패턴”이 훨씬 다양합니다. 따라서 대시보드에서 비용과 호출 패턴을 한 화면에 붙여두고, 이상 징후가 보이면 즉시 차단할 수 있어야 합니다. 이때 인증/권한/키 로테이션이 엮이면 장애가 복잡해지는데, 쿠버네티스에서 크리덴셜 문제로 재시도 폭주가 생기는 케이스도 흔합니다. 관련해서는 EKS에서 AWS SDK NoCredentialProviders 해결 가이드처럼 “인증 실패로 인한 무한 재시도”를 먼저 제거하는 게 비용 방어에 도움이 됩니다.

가드레일 5가지 체크리스트(운영 적용 순서)

  1. 예산 하드 리밋: 시간, step, 툴 호출, 토큰/비용
  2. 종료 조건 구조화: done 플래그와 max_steps 강제
  3. 툴 게이트: allowlist, 툴별 쿼터, 위험도 승인, 캐싱
  4. 반복 감지: 동일 행동/동일 실패 반복 시 중단, 체크포인트
  5. 관측성과 킬 스위치: 비용 대시보드, 자동 차단, 키/툴 비활성화

마무리: “스마트”보다 “안전하게 멈춤”이 먼저

AutoGPT는 자율성이 강점이지만, 운영 관점에서는 그 자율성이 곧 비용 리스크입니다. 위 5가지 가드레일은 모델 성능과 무관하게 효과가 나며, 특히 예산 하드 리밋과 툴 게이트만 제대로 해도 무한루프와 비용폭주의 대부분을 초기에 차단할 수 있습니다.

다음 단계로는 태스크 유형별로 정책을 분리해보세요. 예를 들어 리서치 태스크는 browse 쿼터를 늘리되 시간 예산을 줄이고, 코드 수정 태스크는 db_write를 기본 차단하는 식입니다. 이렇게 정책을 “태스크 프로파일”로 운영하면, AutoGPT를 서비스에 붙여도 통제 가능한 비용 곡선을 만들 수 있습니다.