AI 에이전트가 스스로 프롬프트를 재작성하게 해보았습니다. 어려운 점은 성능이 저하되는 것을 막는 것이었습니다.
요약
AI 에이전트가 스스로 프롬프트를 재작성하며 진화할 때 발생하는 성능 저하(drift)와 퇴보 문제를 다룹니다. 단순한 루프 구축을 넘어, 품질을 검증하고 안전하게 롤백하는 '게이트(Gate)' 메커니즘의 중요성을 강조합니다.
핵심 포인트
- 자기 진화형 에이전트의 주요 실패 원인은 성능 표류(drift)와 퇴보(regression)임
- 비평가가 잘못된 신호에 보상할 경우 품질이 조용히 하락할 수 있음
- 성공적인 에이전트 구축을 위해서는 루프보다 검증을 위한 '게이트'가 핵심임
- 성능 저하 시 마지막 양호한 상태로 자동 롤백하는 메커니즘이 필수적임
AI 에이전트가 스스로 프롬프트를 재작성하게 해보았습니다. 어려운 점은 성능이 저하되는 것을 막는 것이었습니다.
대부분의 "자기 진화형 에이전트 (self-evolving agent)" 데모는 제품 출시를 고민하는 순간 막히게 됩니다. 아이디어가 나빠서가 아니라, 자신의 프롬프트를 재작성할 수 있는 에이전트는 자신을 더 나쁜 상태로 조용히 재작성할 수도 있기 때문입니다. 성능이 표류(drift)합니다. 비평가(critic)가 잘못된 것에 보상을 주기 시작합니다. 퇴보(regression)가 발생하지만, 출력물이 겉보기에는 여전히 괜찮아 보이기 때문에 일주일 동안 아무도 알아차리지 못합니다.
저는 정확히 이러한 실패 모드(failure mode)를 중심으로 TypeScript 프레임워크를 구축하는 데 3개월의 대부분을 보냈으며, 아무도 데모에서 보여주지 않는 부분, 즉 영리한 루프(loop)가 아니라 루프를 정직하게 유지하는 게이트(gate)에 대해 이야기해보고자 합니다.
여기서 "자기 진화"가 실제로 의미하는 것
아이디어는 간단합니다. 일반적인 에이전트는 정적인 프롬프트(static prompt)를 가집니다. 한 번 작성하면 스스로는 결코 더 나아지지 않습니다. 당신이 영원히 수동으로 최적화(optimizer)를 수행해야 합니다.
Darwin은 이를 뒤집습니다. 에이전트가 실행되면, 무언가가 그 실행이 얼마나 좋았는지 측정하고, 시간이 지남에 따라 시스템은 프롬프트의 약점을 학습하고 더 나은 버전을 제안합니다. 그리고 여기서 중요한 점은, 단순히 새로운 버전을 신뢰하지 않는다는 것입니다. 그것은 자격을 증명해야 합니다.
에이전트를 실행합니다
|
Darwin이 품질을 측정합니다 (비평가가 출력을 점수화함)
...
마지막 줄은 마케팅용 버전입니다. 솔직한 버전은 그 아래에 훨씬 더 많은 메커니즘이 작동하고 있습니다. 왜냐하면 "승자가 기본값이 된다"는 지점이 모든 것이 잘못될 수 있는 곳이기 때문입니다.
왜 대부분이 데모 수준에 머무는가
여기 5분짜리 영상에서는 볼 수 없는 실패 사례가 있습니다. 루프를 연결하고, LLM이 자신의 출력을 비평하며, 자신의 프롬프트를 재작성하게 만듭니다. 처음 10번의 실행 동안은 진정으로 개선되는 것처럼 보입니다. 그러다 다음과 같은 상황 중 하나가 발생합니다:
비평가가 잘못된 신호에 대해 최적화합니다. 답변이 길어지거나 더 자신감 있는 답변에 보상을 주기 시작하며, 점수는 올라가지만 품질은 조용히 떨어집니다.
에이전트가 의존하는 도구에 일시적인 문제가 발생합니다. 프롬프트와는 전혀 상관없는 이유로 출력 품질이 저하되면, 시스템은 이를 "현재 프롬프트가 나쁘다"라고 해석하여 실제로 괜찮았던 프롬프트로부터 멀어지는 방향으로 진화해 버립니다.
재작성 과정에서 제약 조건이 약화되기도 합니다. 기존 프롬프트에는 "절대 출처를 지어내지 마시오"라는 내용이 있었습니다. 하지만 점수가 더 높은 새로운 변이체는 문장이 더 유창해지는 대신, 약간 더 말을 지어내려는 경향을 보입니다. 점수는 올라갔지만, 안전성은 떨어졌습니다.
매 실행마다 검사하는 방식은 위양성 (False Positive)을 부풀려, 시스템이 그저 노이즈에 불과한 것을 승자로 선언하게 만듭니다.
이러한 문제들은 생소한 것이 아닙니다. 루프 (Loop)만 구축하고 게이트 (Gate)를 구축하지 않았을 때 나타나는 기본 결과입니다.
게이트가 실제 제품이다
루프는 전체 작업의 약 3분의 1에 불과할지도 모릅니다. 나머지는 변이 (Mutation)의 생존 여부를 결정하는 일련의 가드 (Guards)들입니다. 그중 다음 네 가지가 가장 중요합니다.
마지막으로 확인된 양호한 상태로의 회귀 롤백 (Regression rollback to last-known-good). 승격된 모든 프롬프트에는 기록된 베이스라인 (Baseline)이 있습니다. 새로 승격된 변이체가 임계값을 넘어 이전 버전보다 성능이 낮게 나오면 자동으로 롤백됩니다. 진화는 새로운 시도를 할 수는 있지만, 에이전트를 더 나쁘게 만든 것을 유지할 수는 없습니다.
진화를 일시 중단시키는 데이터 품질 가드 (Data-quality guards). 비평가 (Critic)에게 전달되는 신호가 도구의 타임아웃, 빈 응답, 에러 급증 등으로 인해 망가진 것처럼 보인다면, 진화는 쓰레기 데이터로부터 학습하는 대신 일시 중단됩니다. 서비스 장애 중에 에이전트가 잘못된 결론을 내리게 해서는 안 됩니다.
모든 변이에 대한 정렬 검사 (An alignment check on every mutation). 재작성된 프롬프트가 후보가 되기 전, 에이전트가 지켜야 할 제약 조건들을 준수하는지 검사합니다. 안전 규칙을 은밀하게 무시하면서 문장만 유창해진 프롬프트는 점수 경쟁에 참여할 수 없습니다. 아예 경기장에 들어오지도 못하기 때문입니다.
통계적으로 정직한 A/B 테스트 (Statistically honest A/B). 매 실행 후 결과를 미리 확인하고 싶은 유혹이 크기 때문에, 게이트는 항상 유효한 순차적 검정 (Always-valid sequential tests, mSPRT 및 Hoeffding 방식의 경계값)을 사용합니다. 이를 통해 지속적인 확인이 통계적 유의성을 조작하지 않도록 합니다. 변이체는 당신이 적절한 타이밍에 훔쳐보았을 때가 아니라, 실제로 승리했을 때 승리합니다.
만약 당신이 프롬프트 변경 이후 에이전트의 성능이 미묘하게 저하되는 것을 목격했음에도 이를 포착할 원칙적인 방법이 없었다면, 이 스택이 존재하는 이유가 바로 그것입니다.
코드를 보여드리겠습니다
에이전트를 실행하는 것은 단 한 줄의 명령어로 가능합니다:
npm install darwin-agents better-sqlite3
export ANTHROPIC_API_KEY=sk-ant-... # 또는 OPENAI_API_KEY, 혹은 Claude CLI 사용
...
진화 (evolution) 기능을 켜는 것은 에이전트별로 선택 사항 (opt-in)입니다. 당신이 허용하지 않는 한 아무것도 스스로 재작성하지 않습니다:
npx darwin evolve writer --enable
npx darwin status writer
자신만의 에이전트를 정의하는 데는 약 12줄 정도가 소요됩니다:
import { defineAgent } from 'darwin-agents';
export const writer = defineAgent({
...
상태 (State)는 백엔드(SQLite 또는 Postgres)당 단일 JSON 블롭 (blob)으로 저장되며, 이는 하위 호환성 (backward-compatibility)을 유지하는 데 매우 중요한 역할을 하는 것으로 밝혀졌습니다. 에이전트의 진화 상태에 새로운 선택적 필드를 추가하더라도 이전 행 (rows)이 깨지지 않습니다. 해당 행에는 단순히 키 (key)가 없을 뿐이며, 방어적으로 읽어오면 됩니다. 지루하게 들릴 수 있지만, 이는 업그레이드가 당신의 이력을 잡아먹지 않는다는 것을 의미합니다.
제가 가장 자랑스럽게 생각하는 부분 (간략히)
변이 (mutation) 자체는 일주일에 한 번 실행하는 오프라인 배치 작업 (offline batch job) 대신, 게이트 (gate) 내부에서 온라인으로 실행되는 GEPA 성찰적 최적화 도구 (reflective optimizer)에 의해 구동될 수 있습니다. 에이전트는 자신의 최근 궤적 (trajectories)을 성찰하고, 타겟팅된 재작성을 제안하며, 그 재작성된 내용은 배포되기 전에 반드시 위에 언급된 모든 가드 (guard)를 통과해야 합니다. 성찰 (Reflection)은 제안하고, 게이트 (gate)는 결정합니다. 이 분리가 핵심 비결입니다.
솔직히 현재 상황은 이렇습니다
숫자를 미화하지 않겠습니다. 솔직한 버전이 홍보용 버전보다 더 흥미롭기 때문입니다.
지난 3개월 동안 대부분의 시간 동안 이 프로젝트는 한 자릿수 별(star) 수에 머물러 있었습니다. 버전을 계속해서 허공에 던져 올리며, 과연 누군가가 이를 실행하고 있는지 궁금해하는 고요한 시기였습니다. 그러다 지난 2주 동안, 별도의 출시(launch) 없이도 무언가 변하기 시작했습니다. 0에서 1로 나아가는 수개월의 시간 끝에, 단 하루 만에 12개의 별이 추가되었습니다. 핵심 패키지는 하루 약 6회 설치에서 약 18회 설치로 늘어났습니다. 제가 5주 전에 출시한 LangGraph 어댑터는 미미한 수준에서 주당 수백 건의 다운로드로 증가했습니다.
절대적인 수치는 여전히 작습니다. 별 8개는 하나의 움직임(movement)이 아니며, 저는 그렇지 않은 척하지 않겠습니다. 하지만 숫자가 작은 것과, 숫자가 작으면서도 가속도가 붙고 있는 것 사이에는 실질적인 차이가 있으며, 곡선은 더 이상 평탄하지 않습니다.
참고로, 리포지토리(repo)가 작은 이유는 프로젝트가 새로 나왔기 때문이 아닙니다. 이 코드 중 일부는 이미 몇 달 동안 저희의 자체 에이전트 플릿(agent fleet)을 구동하는 데 사용되어 왔습니다. 규모가 작은 이유는 제가 최근에야 공유하기로 결정했기 때문이며, 제가 성장 전략(growth tactics)에 진심으로 서툴기 때문입니다. 코드는 실제이며, 사용되고 있고, 이슈(issue)에 대한 답변도 이루어지고 있습니다. 그것이 제가 제공하는 전부입니다.
팔마 데 마요르카(Palma de Mallorca)의 작은 스튜디오에서.
직접 살펴보길 원하신다면
이 프로젝트는 MIT 라이선스이며, TypeScript로 작성되었습니다. npm에서는 darwin-agents로 사용할 수 있으며, 이미 LangGraph를 사용 중이라면 darwin-langgraph 어댑터도 제공합니다. 소스 코드와 문서는 GitHub의 studiomeyer-io 아래에 있습니다.
이 커뮤니티로부터 진심으로 듣고 싶은 한 가지는 이것입니다: 스스로 개선되는 에이전트(self-improving agent)가 표류(drifting)하지 않도록 하기 위해 여러분은 무엇을 하시나요? 위에 언급한 게이트(gate)가 저의 답변이지만, 저 역시 이 부분을 여전히 배우는 중이며, 실패 모드(failure modes)는 보이는 것보다 훨씬 더 교묘합니다. 만약 여러분이 이 문제로 어려움을 겪은 적이 있다면, 어떤 경우였는지 알고 싶습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기