자율 코딩 에이전트(Autonomous Coding Agents)의 도구 사용 루프(Tool-Use Loops)를 해결하는 방법
요약
자율 코딩 에이전트가 동일한 작업을 반복하며 비용을 소진하는 '도구 사용 루프(Tool-use loops)' 문제의 원인과 해결책을 다룹니다. 에이전트가 상태를 인지하지 못하는 문제를 해결하기 위해 명시적 작업 이력 추적과 루프 탐지기 도입의 필요성을 강조합니다.
핵심 포인트
- 도구 사용 루프는 에이전트 설계에서 가장 비용이 많이 드는 실패 모드임
- 근본 원인은 작업 상태를 인지하지 못하는 '상태 없는 의사결정'에 있음
- 명시적인 구조화된 로그를 통해 모델에게 시도한 작업 이력을 제공해야 함
- 모델 스스로 진전 여부를 묻는 성찰 단계와 루프 탐지기 도입이 필요함
지난달 저는 한 친구의 자율 코딩 에이전트(autonomous coding agent) 디버깅을 도와주고 있었습니다. 그 에이전트는 47분 동안 작업을 "수행"하며 약 12달러의 API 비용을 소진했고, 결국 어떻게 된 일인지 처음 시작했던 지점으로 되돌아와 있었습니다. 로그를 확인해보니 동일한 5개의 파일에 대해 read_file을 23번이나 호출한 상태였습니다.
만약 여러분이 AI 코딩 에이전트를 구축하거나 실험해 본 적이 있다면, 아마 이런 상황을 본 적이 있을 것입니다. 이는 디버깅하기 즐거운 버그가 아닙니다. 에이전트가 충돌(crashing)하거나 에러(erroring)를 내는 것도 아니고, 그저... 결코 끝나지 않을 뿐입니다.
문제점: 왜 에이전트는 영원히 루프를 도는가
도구 사용 루프(Tool-use loops)는 에이전트 설계에서 가장 비용이 많이 드는 실패 모드(failure mode)입니다. 겉으로 보기에 에이전트는 바빠 보입니다. 파일을 읽고, 도구를 호출하고, 생각을 생성하며, 출력을 만들어냅니다. 하지만 목표를 향한 진전은 전혀 없습니다.
그 양상은 거의 항상 동일합니다:
- 에이전트가 파일 A를 읽음
- 에이전트가 파일 B의 컨텍스트(context)가 필요함을 깨달음
- 파일 B를 읽지만, 예상치 못한 무언가로 인해 혼란을 느낌
- "재확인"을 위해 다시 파일 A로 돌아감
- 파일 A에 필요한 내용이 없기 때문에 다시 파일 B를 읽음
- 지갑이 울 때까지 반복
저는 두 개의 사이드 프로젝트와 한 번의 클라이언트 업무를 통해 서로 다른 세 가지 에이전트 설정에서 이 현상을 목격했습니다. 증상은 매번 동일했습니다.
근본 원인: 상태가 없는 의사결정 (Stateless Decision-Making)
근본적인 문제는 에이전트의 작업 상태(working state)가 N단계와 N+5단계에서 거의 동일해 보인다는 점입니다. 시스템 프롬프트(system prompt)의 작업 설명도 같고, 암묵적으로 사용 가능한 파일도 같으며, 대화의 전반적인 느낌도 같습니다. 따라서 모델은 본질적으로 동일한 입력을 받게 되고, 본질적으로 동일한 결정을 내리게 됩니다.
구분할 가치가 있는 세 가지 구체적인 원인이 있습니다:
- 명시적인 작업 이력(Action history)의 부재. 에이전트가
read_file("config.yaml")을 네 번 호출했지만, 매 턴 모델은 자신이 이미 무엇을 시도했는지에 대한 패턴이 아니라 주로 가장 최근의 도구 결과(tool result)만을 "봅"니다. - 성찰 단계(Reflection step)의 부재. 루프 내에서 "내가 실제로 진전을 보이고 있는가?"라고 묻는 과정이 전혀 없습니다.
- 오류가 요약되어 사라짐. 도구 호출 실패(tool failure)가 "이전 호출에 문제가 있었습니다"와 같은 모호한 내용으로 압축되어, 모델은 동일한 잘못된 입력값으로 재시도하게 됩니다.
각 문제를 해결하는 과정을 살펴보겠습니다.
1단계: 도구 호출(Tool Calls)을 명시적으로 추적하기
무엇을 시도했는지 인코딩하기 위해 대화 이력(conversation history)에만 의존하지 마세요. 모델이 실제로 추론할 수 있는 구조화된 로그(structured log)를 구축해야 합니다.
from collections import Counter
from dataclasses import dataclass, field
from typing import Any
...
그런 다음 매 턴 log.summary_for_model()을 시스템 프롬프트(system prompt)에 주입합니다. 갑자기 모델은 자신이 read_file("config.yaml")을 다섯 번째로 호출하려 한다는 사실을 인지할 수 있게 되며, 대부분의 최신 모델들은 스스로 경로를 수정(course-correct)할 것입니다.
2단계: 루프 탐지기(Loop Detector) 추가하기
모델이 항상 이를 알아차릴 것이라고 믿지 마세요. 회로 차단기(circuit breaker)를 추가하십시오:
MAX_IDENTICAL_CALLS = 3
MAX_TOTAL_STEPS = 40
...
이 조건이 트리거되면, 다음 모델 호출 전에 반환된 문자열을 사용자 메시지(user message)로 주입합니다. 제가 테스트한 워크플로우에서는 이 단 한 번의 변경만으로 낭비되는 토큰(tokens)을 거의 절반 가까이 줄일 수 있었습니다. 결과는 환경마다 다를 수 있지만, 방향성은 일관됩니다.
3단계: 정기적인 성찰(Reflection) 강제하기
루프가 감지되지 않더라도, 모델은 긴 작업(long tasks) 중에 경로를 이탈합니다. 주기적으로 강제적인 성찰을 수행하는 것이 도움이 됩니다. 제가 정착한 주기는 도구 호출 8~10회마다 한 번씩입니다:
REFLECTION_INTERVAL = 8
def maybe_reflect(step: int, task: str) -> str | None:
...
이는 인간의 페어 프로그래밍(pair programming)에서 빌려온 개념입니다. 가끔씩 "자, 우리가 지금 어디쯤 와 있지?"라고 묻는 것은 건강한 방식입니다.
4단계: 오류를 명확하게 전달하기 (Make Errors Loud)
마지막 해결책은 가장 지루하지만 아마도 가장 중요할 것입니다. 도구 호출이 실패했을 때, 오류 메시지를 완화하지 마세요:
def format_tool_error(name: str, args: dict, exc: Exception) -> str:
# 무엇이 실패했는지 구체적으로 명시하세요. 일반적인 오류는 재시도를 유발합니다.
return (
...
"동일한 인자로 재시도하지 마세요 (Do NOT retry with identical arguments)"라는 문구는 바보같이 들릴 수도 있지만, 실제로 큰 차이를 만들어냅니다. 동일한 작업에 대해 이 문구가 있을 때와 없을 때를 세 번 테스트해 보았습니다. 이 문구가 없었을 때는 에이전트가 실패한 호출을 약 60%의 확률로 재시도했습니다. 이 문구가 있을 때는 10%에 가까웠습니다. 표본 크기는 작지만, 효과는 분명했습니다.
예방: 도움이 되는 설계 선택지
에이전트를 구축할 때 제가 기본적으로 사용하는 몇 가지 패턴은 다음과 같습니다:
- 도구별 컨텍스트 제한 (Cap context per tool). 파일 전체를 쏟아붓는 대신
read_file결과를 관련 섹션으로 잘라내세요(Truncate). 노이즈는 줄이고 신호(Signal)는 늘립니다. - 스크래치패드 파일(Scratchpad files) 사용. 에이전트가 쓸 수 있는
notes.md를 제공하세요. 외부화된 메모리(Externalized memory)는 채팅 기록에서 상태를 다시 유도하는 것보다 비용이 저렴합니다. - 계획(Planning)과 실행(Execution)의 분리. 5단계 계획을 생성하는 작은 "플래너(Planner)" 호출을 먼저 수행한 뒤, 이를 따르는 실행기(Executor)를 배치하세요. 두 가지를 모두 수행하는 단일 에이전트보다 루프가 훨씬 적게 발생합니다.
- 개발 중 모든 것을 기록(Log)하세요. 볼 수 없는 것은 디버깅할 수 없습니다. 새로운 에이전트를 구축하는 초기 몇 주 동안은 전체 도구 이력(Tool histories)을 디스크에 저장하세요.
이 중 새로운 것은 없습니다. 더 넓은 에이전트 연구 커뮤니티는 이미 한동안 성찰(Reflection), 계획(Planning), 메모리(Memory)에 대해 글을 써왔습니다. 하지만 프로토타입을 급하게 만들다 보면 이러한 것들을 건너뛰기 쉽고, "모델이 알아서 해결할 것"이라고 가정하기 쉽습니다. 하지만 모델은 해결하지 못할 것입니다. 안정적으로 말이죠.
마무리
도구 사용 루프(Tool-use loops)는 모델의 문제라기보다 하네스(Harness, 제어 환경)의 문제입니다. 모델은 매 턴 동일한 입력이 주어졌을 때 예상되는 동작을 정확히 수행하고 있는 것입니다. 모델 주변의 루프를 구축하는 사람으로서 당신의 역할은 입력이 동일하지 않도록 만드는 것입니다. 즉, 에이전트가 자신의 이력을 볼 수 있게 하고, 막혔을 때 넛지(Nudge, 유도)를 받을 수 있으며, 자신의 오류의 무게를 느낄 수 있도록 해야 합니다.
이 네 가지 문제를 해결하면 통제 불능인 에이전트 비용의 대부분이 사라집니다. 나머지는 그저 튜닝의 영역입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기