코딩 에이전트(Coding Agent)를 조종하는 가장 간과된 방법: 도구가 반환하는 메시지
요약
코딩 에이전트의 성능을 높이기 위해 프롬프트 수정보다 도구 반환 메시지(Tool results)를 최적화하는 전략을 제시합니다. 예외 대신 구조화된 결과를 반환하고, 실패 시 구체적인 정보를 포함하며, 성공 시에도 주석을 달아 모델의 가이드를 강화해야 합니다.
핵심 포인트
- 예외 대신 모델이 읽을 수 있는 구조화된 결과 반환
- 거절 메시지에 '아쉬운 실패(near-miss)' 상황과 유사도 정보 포함
- 성공적인 작업 시에도 린터 경고 등 상세 주석 제공
- 도구 결과 최적화가 프롬프트 엔지니어링보다 효과적일 수 있음
도구 결과(Tool results)는 LLM(Large Language Model)을 가이드하는 데 있어 가장 간과되는 소스입니다. 프롬프트 엔지니어링(Prompt engineering)이 주목을 받지만, 프롬프트는 작업을 단 한 번 기술할 뿐입니다. 반면 도구 결과는 호출될 때마다 도착하며, 각 결과는 모델이 작업하는 동안 모델을 조종할 수 있는 기회가 됩니다. 만약 여러분이 하네스(Harness)를 구축하고 있다면, 도구가 반환하는 내용을 네 가지 변경하는 것이 대부분의 프롬프트 수정보다 더 큰 효과를 낼 것입니다.
- 절대 예외를 발생시키지 마세요(Never raise) — 모델이 읽을 수 있는 무언가를 반환하세요
- 거절(Refusal) 메시지 안에 아쉬운 실패(Near-miss) 상황을 포함하세요
- 실패뿐만 아니라 성공도 주석을 다세요(Annotate)
- 작은 실패들을 카운트하고 그 패턴에 따라 행동하세요
이 각각의 방법은 이전 방법이 남기는 문제를 해결하기 위해 존재하므로, 이 순서대로 구축할 가치가 있습니다. 우리의 하네스는 전체 과정에서 실전 예제로 사용됩니다. 설명된 모든 메커니즘은 실제 운영 환경(Production)에서 작동하고 있습니다.
절대 예외를 발생시키지 마세요 — 모델이 읽을 수 있는 무언가를 반환하세요
기본적인 문제는 쓰레기 데이터(Garbage)를 기반으로 구축되는 에이전트입니다. 조용히 실패한 도구 결과는 신뢰를 얻게 되고, 오류는 그 원인으로부터 멀리 떨어진 곳에서 나타납니다. 더 노골적인 버전은 처리되지 않은 예외(Unhandled exception)로, 도구가 충돌하고 루프가 종료되며 모델이 전혀 반응할 수 없게 되는 상황입니다. 두 실패 모두 근본적인 원인은 동일합니다. 모델에게 읽을 수 있는 정보가 전혀 전달되지 않았다는 점입니다.
따라서 모든 도구가 예외가 아닌 구조화된 결과(Structured result)를 반환하도록 계약(Contract)을 만드세요. 우리의 경우, 완료(Completion) 경로와 오류(Error) 경로 모두 성공 플래그(Success flag)와 실패 시 오류 자체를 포함하는 결과 객체(Result object)를 생성합니다. 실패한 호출은 모델이 다음 턴에 읽을 수 있는 텍스트가 되며, 어떤 상황에서도 실행은 안정성을 유지합니다. 이것이 바로 강력한 거절(Hard no) — 크고, 명시적이며, 생존 가능한 방식입니다.
거절 메시지 안에 아쉬운 실패 상황을 포함하세요
단순한 강력한 거절만으로는 여전히 비용이 발생합니다.
수정(Edits)이 구체적인 사례입니다. 저희는 파일 내의 앵커 텍스트 블록(anchor block of text)을 매칭하여 교체하는 방식으로 이를 적용합니다. 이는 매칭에 실패하더라도 '거의 일치할 뻔한(near-miss)' 정보가 함께 제공됨을 의미합니다. 즉, 결과값에 실제로 발견된 가장 유사한 텍스트와 얼마나 근접했는지에 대한 신뢰도 점수(confidence score)가 포함됩니다. 모델의 두 번째 시도는 단순히 주사위를 새로 던지는 것이 아니라, 보통 오래된 라인(stale line)이나 어긋난 공백(drifted whitespace)과 같은 실제 불일치 지점을 겨냥하게 됩니다. 설명이 포함된 거절은 낭비되는 재시도를 목표가 분명한 시도로 바꿔줍니다.
실패뿐만 아니라 성공에도 주석을 다세요 (Annotate success, not just failure)
실패 상황이 처리되고 나면, 성공 상황이야말로 조용히 피해가 누적되는 지점이 됩니다. 깔끔하게 반영된 쓰기(write) 작업이라 할지라도 컴파일러가 불평하지 않는 문제를 남길 수 있습니다. 예를 들어 린터(linter)의 새로운 경고, 스타일 오류나 의심스러운 코드를 표시하는 정적 분석기(static checker)의 지적, 짝을 잃은 중괄호, 혹은 아무도 요청하지 않은 TODO 주석 등이 이에 해당합니다. 이러한 것들은 단순히 "ok"라는 결과에서는 보이지 않으므로, 성공 역시 실패만큼이나 주석(annotating)이 필요합니다.
성공 결과 자체에 보고서를 첨부하세요. 저희의 하네스(harness)에서 성공적인 쓰기 작업은 해당 쓰기가 파일 내 린터의 문제 개수를 어떻게 변화시켰는지, 모든 괄호가 여전히 균형을 이루는지, 어떤 TODO 주석이 나타났는지, 그리고 파일의 새로운 길이는 얼마인지를 명시하여 반환합니다. 이를 통해 모델은 파일을 다시 읽는 데 턴(turn)을 소비하지 않고도, 쓰기 성공을 확인하는 동일한 결과 내에서 자신의 쓰기가 무엇을 남겼는지 학습할 수 있습니다. 읽기(Reads) 작업도 동일한 방식으로 작동하며, 린터의 현재 발견 사항과 파일의 함수 및 클래스 개요(outline)를 함께 전달합니다. 따라서 코드의 상태가 모델이 확인하기 위해 기억해야 할 대상이 아니라, 모델의 눈앞에 지속적으로 머물게 됩니다.
피드백이 밀어붙일 수 있는 기준을 제공하세요
주석만으로는 사소한 정보에 불과합니다. 린트 경고(lint warning)는 누군가에게 깨끗한 코드를 제공해야 할 의무가 있는 모델에게만 방향을 제시할 수 있습니다. 피드백(feedback)과 표준(standards)은 하나의 메커니즘을 구성하는 두 절반이며, 어느 하나라도 없이 다른 하나만 전달하는 것은 효과가 거의 없습니다.
표준(standard)을 에이전트의 정체성, 즉 시스템 프롬프트(system prompt)에 포함시키십시오. 저희의 코드 에이전트(code agent)에는 "커밋(commit)이 허용되기 전에 실행되는 린트(lint) 및 테스트 훅(test hooks)인 프리커밋 체크(pre-commit checks)를 건너뛰지 마십시오"라거나 "테스트를 통과시키기 위해 테스트 코드를 수정하지 마십시오"와 같은 취지의 문구들이 포함되어 있습니다. 이러한 정체성에 비추어 볼 때, 새로운 린트 경고를 보고하는 도구(tool)의 결과는 단순한 정보가 아닙니다. 그것은 에이전트가 이미 동의한, 충족되지 않은 의무입니다.
작은 실패들을 집계하고 패턴에 따라 행동하십시오
여전히 하나의 간극이 남아 있는데, 그것은 개별 사건(event)과 패턴(pattern) 사이의 차이입니다. 모델은 린트 경고를 인지하고도 이를 수정하지 못한 채 다음 단계로 넘어갈 수 있습니다. 각 결과를 개별적으로 판단한다면, 작은 실패들이 서서히 누적되어 전체 세션을 좌초시키는 것을 막을 방법이 없습니다.
따라서 '안 된다(no)'는 신호들이 축적되게 만드십시오. 저희의 모든 에이전트는 회로 차단기(circuit breakers)를 탑재하고 있습니다. 이는 전체 실행 과정 동안 유지되는 비치명적 이슈(non-fatal issues)의 누적 횟수이며, 이 횟수가 충분히 높아지면 차단기가 작동합니다. 예를 들어 세 번째 린트 실패, 마찬가지로 타입 체크(type-check) 실패, 혹은 다섯 번의 패턴 위반 등이 이에 해당합니다. 차단기가 작동한다고 해서 대화가 중단되는 것은 아닙니다. 다만 에이전트가 그 이후에 진행하는 방식이 바뀔 뿐입니다. 재시도 예산(retry budgets)은 반대편에서 루프(loop)를 제한하여, 계속해서 실패하는 단계는 무한히 재시도하는 대신 실패로 표시되도록 합니다. 단 한 번의 '안 된다'는 하나의 사건이지만, 차단기는 '안 된다'는 패턴을 진단으로 바꾸어 주는 역할을 합니다.
도구 피드백(tool feedback)이 할 수 없는 것
린트는 판단(judgment)이 아닌 형태(shape)를 측정합니다. 설계상의 실수는 린트 상으로는 깨끗하게 나타날 수 있으며, 형식이 잘 갖춰져 있고 타입 안정성(type-safe)이 확보되었더라도 잘못된 함수를 그 어떤 검증 보고서도 잡아내지 못할 것입니다. 이러한 종류의 문제는 저희의 프레임워크(harness)나 그 누구의 도구 피드백으로도 해결되지 않습니다. 이것이 바로 리뷰(review)가 별도의 단계로서 존재하며, 자체적인 게이트(gates)를 가진 별도의 에이전트들에 의해 수행되는 이유입니다.
어떤 프레임워크에서든 도구 결과(tool result)는 프롬프트 표면(prompt surface)입니다. 만약 피드백이 오직 "ok"와 예외(exception)로만 들어온다면, 모델은 호출당 단 1비트의 정보로 조종되고 있는 것입니다. 그 두 극단 사이에 존재하는 모든 것들—의도된 재시도, 조건부 승인(yes-with-caveats), 축적된 패턴—은 당신이 이미 비용을 지불한 대역폭(bandwidth)입니다.
당신의 하네스(harness)가 위치한 곳
세 가지 질문이 이 지도 위에서 모든 하네스(harness)의 위치를 찾아냅니다. 도구가 실패했을 때, 모델이 실제로 읽을 수 있는 무언가를 전달받습니까? 편집(edit)이 실패했을 때, 거절 메시지가 얼마나 근접했었는지를 알려줍니까? 쓰기(write)가 성공했을 때, 모델에게 무엇을 남겼는지 알려주는 것이 있습니까? 모든 '아니오(no)'는 하네스(harness)가 재시도(retries)와 턴(turns)을 소모하는 지점이며, 등급이 매겨진 피드백(graded feedback)이 돌려주었을 정보들입니다. 그리고 대부분의 하네스(harness)가 완전히 건너뛰는 '조용한 아니오(quiet no)'가 가장 큰 비용을 치르게 합니다.
Favur는 우리의 멀티 에이전트(multi-agent) 소프트웨어 팀이자, 이러한 메커니즘이 탄생한 하네스(harness)입니다. 이는 폐쇄 소스(closed source)이며 초대 전용(invite-only)이지만, 그것이 생성하는 리포지토리(repositories)는 공개되어 있습니다. https://favur.dev/go/devto/tool-feedback에서 실제 실행의 리플레이(replay)를 구동해 보거나, https://evals.favur.dev/go/devto/tool-feedback에서 여러 모델에 걸쳐 동일한 하네스(harness)가 어떻게 점수를 받는지 확인할 수 있습니다. 저는 이 프로젝트를 작업하고 있으므로, 프레임워크(framing)를 그에 따라 고려하여 판단하시기 바랍니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기