Published on

AutoGPT 무한루프 막기

Authors
Binance registration banner

AutoGPT 계열 에이전트를 운영하다 보면 가장 먼저 부딪히는 문제가 바로 무한루프입니다. 목표는 분명한데 에이전트가 같은 생각을 반복하거나, 비슷한 도구를 계속 호출하거나, 실패한 액션을 재시도하다가 토큰과 비용만 소모하는 상황이 자주 발생합니다. 단순히 max_steps 값을 낮추는 것만으로는 해결되지 않고, 루프가 생기는 구조 자체를 이해한 뒤 종료 조건, 상태 저장, 툴 설계, 관측 가능성을 함께 다뤄야 안정적으로 운영할 수 있습니다.

왜 AutoGPT는 무한루프에 빠질까

에이전트는 보통 생각 -> 행동 -> 관찰 -> 재계획의 순환 구조로 동작합니다. 이 구조는 자율성을 주는 대신, 한 번 잘못된 방향으로 흐르면 스스로를 계속 강화하는 피드백 루프가 되기 쉽습니다.

대표적인 원인은 다음과 같습니다.

  • 목표가 너무 추상적이라 완료 기준이 모호한 경우
  • 툴 결과가 불완전하거나 실패했는데도 재시도만 하는 경우
  • 메모리나 상태가 갱신되지 않아 같은 판단을 반복하는 경우
  • 프롬프트에 종료 조건이 약하거나, 성공 판정 규칙이 없는 경우
  • 외부 API 오류, rate limit, 타임아웃이 반복되어 복구 루프가 생기는 경우

특히 에이전트는 “더 시도하면 해결될 것 같다”는 방향으로 쉽게 수렴합니다. 이때 중요한 것은 에이전트의 추론 능력을 높이는 것이 아니라, 반복을 멈추게 하는 제어 장치를 넣는 것입니다. 이 관점은 OpenAI Responses API 429·rate_limit 해결 10가지에서 다룬 API 재시도 문제와도 연결됩니다. 외부 실패를 내부 무한루프로 번역하지 않도록 분리해야 합니다.

무한루프를 막는 핵심 원칙

1. 명확한 종료 조건을 프롬프트에 넣기

에이전트에게 “끝났는지 판단하는 기준”을 주지 않으면, 결과가 애매할 때 계속 탐색합니다. 따라서 목표와 종료 조건을 분리해서 정의해야 합니다.

예를 들어 아래처럼 작성할 수 있습니다.

목표: 특정 주제에 대한 초안을 작성한다.
종료 조건:
1. 초안이 3개 섹션 이상이면 종료
2. 더 이상 새로운 정보가 없고 중복이 2회 이상이면 종료
3. 외부 도구 실패가 3회 연속이면 중단하고 원인 보고

이 방식의 핵심은 “성공”뿐 아니라 “중단”도 명시하는 것입니다. 에이전트는 종종 성공 조건만 보고 실패를 무시하려고 하므로, 실패 종료가 반드시 필요합니다.

2. 상태를 구조화해서 저장하기

무한루프는 대부분 “내가 무엇을 이미 했는지”를 잊어버릴 때 발생합니다. 단순 텍스트 메모리보다 구조화된 상태가 훨씬 효과적입니다.

예시 상태는 다음처럼 관리할 수 있습니다.

{
  "goal": "write_blog_draft",
  "completed_steps": ["research", "outline"],
  "failed_tools": ["search_web"],
  "retry_count": 2,
  "last_observation_hash": "a81f...",
  "termination_reason": null
}

여기서 중요한 포인트는 last_observation_hash처럼 관찰 결과의 중복 여부를 판단할 수 있는 지표를 두는 것입니다. 같은 결과를 계속 받는다면, 에이전트는 새 정보가 없다는 사실을 인지하고 다른 경로를 택하거나 종료해야 합니다.

이런 패턴은 Rust Iterator·Option·Result로 FP 파이프라인에서처럼 단계별 결과를 명시적으로 흘려보내는 설계와도 닮아 있습니다. 흐름을 숨기지 말고, 상태와 실패를 값으로 다루면 루프 제어가 쉬워집니다.

실전에서 가장 효과적인 차단 장치

1. max_stepsmax_tool_calls를 분리하기

많은 구현이 단순히 전체 스텝 수만 제한합니다. 하지만 실제로는 생각만 반복하는 경우와 도구만 반복 호출하는 경우를 구분해야 합니다.

권장 방식은 다음과 같습니다.

  • max_steps: 전체 추론 루프 상한
  • max_tool_calls: 외부 도구 호출 상한
  • max_same_tool_calls: 동일 툴 연속 호출 상한
  • max_consecutive_failures: 연속 실패 상한

예를 들면 아래와 같습니다.

MAX_STEPS = 20
MAX_TOOL_CALLS = 8
MAX_SAME_TOOL_CALLS = 3
MAX_CONSECUTIVE_FAILURES = 3

state = {
    "steps": 0,
    "tool_calls": 0,
    "same_tool_calls": 0,
    "last_tool": None,
    "consecutive_failures": 0,
}

def should_stop(state):
    return (
        state["steps"] >= MAX_STEPS or
        state["tool_calls"] >= MAX_TOOL_CALLS or
        state["same_tool_calls"] >= MAX_SAME_TOOL_CALLS or
        state["consecutive_failures"] >= MAX_CONSECUTIVE_FAILURES
    )

이렇게 하면 “아직 시도할 수 있다”는 착각 때문에 무한히 돌지 않습니다. 특히 동일 툴 반복은 비용 대비 효과가 낮으므로 강하게 제한하는 편이 좋습니다.

2. 동일 관찰 반복 시 즉시 중단하기

에이전트가 같은 검색 결과, 같은 에러 메시지, 같은 응답 구조를 반복해서 받는다면 이미 루프에 들어간 것입니다. 이때는 재시도보다 중단이 더 현명합니다.

def is_repeated_observation(current, previous, threshold=2):
    return current == previous

if is_repeated_observation(current_obs, previous_obs):
    repeated_count += 1
else:
    repeated_count = 0

if repeated_count >= 2:
    terminate("Repeated observation detected")

이 원리는 인프라 문제에서도 중요합니다. 예를 들어 Kubernetes gRPC UNAVAILABLE·RST_STREAM 원인과 Envoy·NGINX 대응처럼 네트워크 계층 오류가 반복될 때, 애플리케이션이 계속 재시도만 하면 장애가 증폭됩니다. 에이전트도 마찬가지로, 반복 관찰은 곧 중단 신호로 해석해야 합니다.

3. 툴 호출에 비용과 페널티를 부여하기

에이전트가 도구를 “공짜”로 생각하면 쉽게 남발합니다. 각 툴 호출에 비용을 부여하고, 실패나 중복 호출에는 페널티를 주면 탐색 품질이 좋아집니다.

예시 정책:

  • 검색 툴 1회 호출: 비용 1점
  • 동일 쿼리 재호출: 추가 비용 3점
  • 실패 응답: 추가 비용 2점
  • 비용 총합이 임계치 초과 시 종료

이 방식은 단순하지만 매우 강력합니다. 에이전트는 비용을 인식할 때만 “지금 이 행동이 정말 필요한가?”를 다시 생각하게 됩니다.

프롬프트 설계에서 놓치기 쉬운 부분

에이전트가 무한루프에 빠지는 이유는 모델의 성능 부족보다 프롬프트 설계 미흡인 경우가 많습니다. 다음 요소를 반드시 넣어야 합니다.

해야 할 것

  • 완료 조건을 숫자로 표현하기
  • 실패 시 대체 행동을 명시하기
  • 동일 행동 반복 금지 규칙 넣기
  • 최종 출력 형식을 강제하기
  • 중간 결과를 요약하고 다음 행동을 선택하게 하기

피해야 할 것

  • “충분히 알아서 해줘” 같은 모호한 지시
  • 성공과 실패의 경계를 흐리는 표현
  • 무조건 재시도하라는 암묵적 유도
  • 도구 실패를 무시하고 계속 진행하라는 구조

예시 프롬프트는 아래와 같습니다.

당신은 작업 관리자 에이전트입니다.
목표를 달성하되, 다음 조건을 반드시 지키세요.
1. 같은 도구를 연속 3회 이상 호출하지 마세요.
2. 같은 관찰이 2회 반복되면 종료하세요.
3. 실패가 3회 연속이면 원인과 함께 종료하세요.
4. 완료 조건을 만족하면 즉시 최종 요약을 출력하세요.
5. 더 이상 새로운 정보가 없으면 탐색을 멈추세요.

이런 명세는 Python 데코레이터 중첩 시 인자 깨짐 7가지 원인에서 말하는 것처럼, 보이지 않는 계층에서 인자가 꼬이는 문제를 줄이는 데도 도움이 됩니다. 에이전트 프롬프트도 결국 인터페이스이기 때문입니다.

코드 레벨에서 구현하는 안전장치

아래는 간단한 루프 제어 예시입니다.

def agent_loop(goal, llm, tools):
    state = {
        "steps": 0,
        "tool_calls": 0,
        "last_tool": None,
        "same_tool_calls": 0,
        "consecutive_failures": 0,
        "last_observation": None,
    }

    while True:
        if state["steps"] >= 20:
            return {"status": "stopped", "reason": "max_steps"}

        if state["consecutive_failures"] >= 3:
            return {"status": "stopped", "reason": "too_many_failures"}

        action = llm.plan(goal=goal, state=state)
        state["steps"] += 1

        if action["type"] == "finish":
            return {"status": "done", "result": action["result"]}

        tool_name = action["tool"]
        if tool_name == state["last_tool"]:
            state["same_tool_calls"] += 1
        else:
            state["same_tool_calls"] = 0

        if state["same_tool_calls"] >= 3:
            return {"status": "stopped", "reason": "same_tool_loop"}

        state["last_tool"] = tool_name
        state["tool_calls"] += 1

        try:
            obs = tools[tool_name](**action["args"])
            if obs == state["last_observation"]:
                state["consecutive_failures"] += 1
            else:
                state["consecutive_failures"] = 0
            state["last_observation"] = obs
        except Exception as e:
            state["consecutive_failures"] += 1
            obs = {"error": str(e)}

        if state["tool_calls"] >= 8:
            return {"status": "stopped", "reason": "max_tool_calls"}

이 코드는 완벽하지 않지만, 무한루프 방지의 핵심을 잘 보여줍니다. 핵심은 에이전트의 판단을 믿되, 반복 패턴을 시스템이 감지하고 강제로 종료하는 것입니다.

운영 관점에서 꼭 필요한 관측 지표

실서비스에서는 코드보다 모니터링이 더 중요할 때가 많습니다. 아래 지표를 꼭 수집하세요.

  • 평균 step 수
  • 작업당 평균 tool call 수
  • 동일 툴 연속 호출 비율
  • 연속 실패 횟수 분포
  • 종료 사유별 비율
  • 동일 관찰 반복 횟수

이 지표가 쌓이면 어떤 프롬프트, 어떤 툴, 어떤 작업 유형에서 루프가 자주 발생하는지 보입니다. 이후에는 문제를 감으로 고치는 대신, 데이터로 줄일 수 있습니다.

에이전트 설계는 재귀가 아니라 유한 상태 머신에 가깝다

AutoGPT를 만들 때 흔히 “스스로 생각하는 존재”처럼 다루지만, 실제 운영에서는 유한 상태 머신에 더 가깝게 설계하는 편이 안전합니다. 상태를 정의하고, 전이를 제한하고, 종료 상태를 명확히 두면 루프는 급격히 줄어듭니다.

이 관점은 C++23 std - -expected로 예외 없이 안전한 리턴처럼 실패를 예외로 던지지 않고 값으로 다루는 철학과도 맞닿아 있습니다. 에이전트도 실패를 숨기지 말고, 상태와 결과로 노출해야 제어할 수 있습니다.

마무리

AutoGPT 에이전트의 무한루프는 모델이 똑똑하지 않아서 생기는 문제가 아니라, 반복을 멈추게 하는 설계가 부족해서 생기는 문제입니다. 따라서 해결책도 단순 재시도 제한이 아니라 다음 네 가지를 함께 적용하는 데 있습니다.

  • 종료 조건을 명확히 정의하기
  • 상태를 구조화해서 기억하기
  • 동일 행동과 동일 관찰을 감지하기
  • 툴 호출에 상한과 비용을 부여하기

이 네 가지만 제대로 넣어도 에이전트의 안정성은 크게 올라갑니다. 자율성은 무제한 반복이 아니라, 필요할 때 멈출 수 있는 능력에서 완성됩니다.