스스로 개선되는 AI: 자율적 프롬프트 반복 루프 (Autonomous Prompt Iteration Loop)
요약
프롬프트 엔지니어링의 품질을 직관이 아닌 데이터로 측정하기 위해 AI를 활용한 자율적 평가 루프 구축 방법을 소개합니다. Claude를 사용하여 생성된 결과물의 길이, 점수 분산, 폴백 비율 등을 자동 측정하여 프롬프트를 최적화하는 과정을 다룹니다.
핵심 포인트
- 직관 대신 측정 가능한 지표(길이, 분산 등)를 통한 프롬프트 최적화
- Claude CLI를 활용한 Go 기반의 독립 실행형 평가 하네스 구축
- 통계적 유의성을 확보하기 위한 대량의 테스트 케이스 실행 및 자동화
각 로스트(Roast)는 업로드당 50초가 소요되었습니다. 품질은 알 수 없었습니다. 데이터가 아닌 느낌뿐이었죠. 프롬프트는 "직관에 의해" 작성되었으며 진지하게 평가된 적이 없었습니다. 질문은 간단했습니다. 프롬프트가 좋은지 어떻게 알 수 있으며, 하루 종일 수동으로 로스트를 읽으며 시간을 허비하지 않고 어떻게 개선할 수 있을까요?
정답은 AI 자체를 사용하여 루프(Loop) 내에서 평가 작업을 자동화하는 것이었습니다. 30장의 사진을 Claude에게 보내고, 품질 지표(Quality metrics)를 측정하며, 보고서를 생성하는 도구를 만듭니다. 프롬프트를 수정하고, 다시 실행하고, 비교합니다. 5번의 반복(Iteration) 끝에 우리가 배운 것은 다음과 같습니다.
문맥: RateMyFace
RateMyFace는 사진 기반의 AI 로스트(Roast) 사이트입니다. 사용자가 사진을 업로드하면, Claude가 이를 분석하여 점수 및 "티어 라벨(Tier label)"(예: "다리가 달린 WiFi 신호")과 함께 풍자적인 텍스트를 생성합니다. 결과물은 수집 가능한 트레이딩 카드 형태로 렌더링됩니다.
기술 스택: Go 모놀리스(Monolith), SQLite, 서브프로세스로 호출되는 Claude CLI (claude --print). 프롬프트는 Claude에게 5가지 로스트 스타일(표준, 랩, 셰익스피어, 수동적 공격성을 띠는 엄마, Gordon Ramsay) + 점수 + 라벨을 모두 JSON 형식으로 생성하도록 요청했습니다.
두 가지 구체적인 문제: 로스트 생성에 약 50초가 소요되었고(상호작용하기에는 너무 느림), 품질이 불투명했습니다. 우리는 무언가를 생성하고 있다는 것은 알았지만, 그것이 좋은지는 알지 못했습니다.
아이디어: 최적화하기 전에 측정하라
프롬프트 엔지니어링(Prompt engineering)에서 흔히 나타나는 반사적인 행동은 수동으로 반복하는 것입니다. 즉, 수정하고, 2~3개의 예시로 테스트하고, 더 나아졌는지 추정하는 방식입니다. 문제는 당신이 선택한 예시들에 대해서만 최적화할 뿐, 실제 분포(Real distribution)에 대해서는 최적화하지 못한다는 점입니다. 그리고 "더 나아 보인다"는 것은 지표(Metric)가 아닙니다.
대안적인 접근 방식: "좋다"는 것이 무엇인지 측정 가능한 방식으로 정의하고, 안정적인 통계치를 가질 수 있을 만큼 충분한 예시를 생성하며, 평가를 자동화하는 것입니다. 선택된 지표는 다음과 같습니다:
- 평균 길이 (Average length) — 목표 < 150자. 바이럴되는 로스트(roast)는 짧습니다.
- 점수 분산 (Score variance) — 목표 > 2.0. 모든 점수가 5~6점에 몰린다면 그 점수는 무용지물입니다.
- 폴백 비율 (Fallback rate) — Claude가 실패하여 기본 텍스트를 반환하는 빈도입니다.
- 점수 분포 (Score distribution) — 편향을 시각화하기 위한 1~10점 히스토그램입니다.
도구: Go 언어로 작성된 평가 하네스 (Evaluation harness)
cmd/prompttest/main.go에 위치한 독립 실행형 바이너리입니다. HTTP 서버 없이 Claude CLI를 직접 호출합니다. 30개의 고정된 테스트 케이스(randomuser.me에서 가져온 남성 및 여성 사진, FR/EN)를 실행 시간 측정과 함께 순차적으로 실행합니다.
func callClaude(ctx context.Context, photoURL, lang string) (*RoastResult, string, time.Duration, error) {
prompt := buildPrompt(lang)
fullPrompt := fmt.Sprintf("First, read the image file at %s and look at it carefully. Then:\n\n%s", photoURL, prompt)
...
--effort low 플래그는 첫 번째 속도 최적화 단계입니다. 응답 시간을 약 50초에서 약 27초로 단축합니다. 공식적으로 문서화되지는 않았지만 동작은 안정적입니다.
실행 종료 보고서:
╔══════════════════════════════════════════════╗
║ PROMPTTEST v4 — 30 tests
╠══════════════════════════════════════════════╣
...
다섯 가지 버전, 다섯 가지 교훈
| 버전 | 평균 글자 수 | 점수 분산 | 폴백 (Fallbacks) | 주요 문제 |
|---|---|---|---|---|
| v1 | 216 | 0.44 | 0 | "T'as [X] de quelqu'un qui" — 30개 중 17개의 로스트 구조가 동일함 |
| v2 | 110 | 0.58 | 0 | "[item] dit/says [X]" — 새로운 지배적 클리셰 발생 |
| v3 | 95 | 0.67 | 0 | "C'est la photo LinkedIn de..." — 세 번째 클리셰 발생 |
| v4 | 128 | 1.09 | 0 | 최상의 버전 — 구체적이고 다양한 로스트 |
| v5 | 146 | 0.90 | 1 | 8점인 점수가 3회 나타났으나, 전반적인 분산은 감소함 |
교훈 1 — 모든 긍정적인 예시는 클리셰를 만든다
v1에서는 프롬프트에 좋은 로스트의 예시를 제공했습니다. Claude는 30개 사례 중 17개에서 즉시 해당 예시들의 구조를 모방했습니다. 우리는 그 패턴을 금지하고 새로운 예시를 제공했지만, Claude는 그 새로운 예시를 다시 새로운 클리셰로 사용했습니다. 이 과정이 세 번 연속으로 반복되었습니다.
해결책 (v4): 긍정적인 구조 예시를 완전히 제거합니다. 대신, 감정적 목표("모르는 사람이 스크린샷을 찍어 단톡방에 전달할 만한 디스")를 설명하고, 부정적인 예시(명시적으로 금지된 패턴)만 축적합니다.
금지된 시작 문구 (이 패턴들은 너무 많이 사용된 쓰레기 같은 표현들입니다):
- "[item] dit/crie/says [X]" → 금지
- "T'as [X] de quelqu'un qui..." → 금지
...
레슨 2 — 점수 분산(Score variance)에는 자연적인 한계가 있습니다
점수 산정 지침을 어떻게 표현하든, randomuser.me 사진을 사용할 때 분산은 1.1 부근에서 정체되었습니다. 이 사진들은 의도적으로 "평범"하게 만들어졌으며, 일반적인 프로필 사진 역할을 합니다. 자연스럽게 4에서 7 사이로 압축된 분포에서 2.0의 분산을 추출해낼 수는 없습니다.
이것은 프롬프트의 문제가 아닙니다. 입력 데이터의 물리적 제약입니다. (진정으로 못생기거나 아름다운 사람들이 포함된) 실제 사용자 사진을 사용하면 분산은 자연스럽게 더 높아질 것입니다. v4 프롬프트는 이 테스트 세트에서 얻을 수 있는 최적의 결과물입니다.
레슨 3 — Claude는 낮은 점수를 주는 데 보수적입니다
"객관적으로 보기 힘든" 사람들에게 2~3점의 점수를 달라고 명시적으로 요청해도, Claude는 저항합니다. Anthropic의 안전 메커니즘 (Safety mechanisms)은 실제 인물이 못생겼다고 말하는 것을 피하도록 유도합니다. 반복적인 지시에도 불구하고 4.0 미만의 점수를 받는 경우는 거의 없었습니다.
우리의 유스케이스(동의를 구한 유머러스한 디스)와 같은 경우, 이는 약간 답답할 수 있지만 이해할 수 있는 부분입니다. 진짜 질문은 이것입니다: 사진을 업로드하는 사용자가 2/10점을 기대할까요? 설령 그것이 "더 솔직할지라도" 아마 아닐 것입니다.
레슨 4 — 텍스트 품질이 극적으로 향상됩니다
이것이 반복(Iteration)을 통해 얻은 진짜 이득입니다. v1과 v4 사이의 디스(Roast) 품질은 비교할 수 없을 정도입니다:
v1: "T'as la tête de quelqu'un qui a mis 'passionné par les synergies' dans son bio LinkedIn — le bâtiment derrière toi est plus intéressant que toi."
v4: "Le front avance plus vite que ta carrière et le regard est resté coincé à la page de chargement."
동일한 주제, 동일한 인물입니다. v4는 길이가 절반으로 짧아졌고, 구체성은 두 배가 되었으며, 재미는 세 배가 되었습니다. 측정 가능한 지표(길이, 폴백 (fallbacks))를 바탕으로 반복(Iteration)하는 과정은 프롬프트의 변경을 강제했고, 이는 주관적인 품질에 간접적인 영향을 미쳤습니다.
이 접근 방식의 한계
자율적 반복 루프 (Autonomous iteration loop)에는 반드시 염두에 두어야 할 중요한 한계가 있습니다.
정답 (Ground truth)의 부재. 지표(길이, 분산)는 텍스트의 속성을 측정할 뿐, 품질을 측정하는 것이 아닙니다. 90자 길이의 독설 (roast)이 180자 길이의 것보다 반드시 더 재미있다는 보장은 없습니다. 당신은 실제 목표가 아닌 대리 지표 (proxies)를 최적화하고 있는 것입니다.
테스트 세트가 실제 사용자를 대변하지 못함. randomuser.me는 일반적이고, 중립적이며, 조명이 밝은 프로필 사진을 제공합니다. 실제 사용자는 파티 사진, 흐릿한 셀카, 코스튬을 입은 사람들의 사진을 업로드합니다. 실제 데이터 분포는 이와 다릅니다.
매 실행마다 약 15분이 소요됨. 30회의 호출 × 약 30초 = 반복당 15분의 대기 시간이 발생합니다. 우리는 5회의 반복을 수행하여 총 75분의 실행 시간과 분석 시간을 소요했습니다. 이것은 실시간 최적화가 아닙니다.
점수 분산이 정체기에 도달하며, 이는 괜찮은 현상임. 우리는 큰 성공 없이 분산을 개선하기 위해 3회의 반복을 시도했습니다. 정체기 (plateau)를 인식하고 멈추는 것 또한 하나의 기술입니다.
루프가 실제로 가능하게 하는 것
주된 가치는
우리는 직관에 의해 작성된 프롬프트, 평균 216자의 Roast (비난/조롱), 그리고 사례의 56%를 차지하는 지배적인 클리셰(cliché)로 시작했습니다. 우리는 평균 128자의 프롬프트, 폴백 (fallback) 0건, 구체적이고 다양한 Roast — 그리고 무엇보다 중요한 점은, 각 버전이 이전 버전보다 왜 더 나았는지 혹은 더 나빴는지에 대한 명확한 이해를 얻으며 끝을 맺었습니다.
나를 놀라게 했던 점은 다음과 같습니다: 가장 효과적인 반복 (iteration, v4)은 AI에게 가장 많은 지침을 준 것이 아니었습니다. 오히려 가장 적은 지침을 준 것이었습니다 — 감정적 목표를 설명하고, 실패한 패턴을 금지하며, 모델이 다른 것을 찾도록 신뢰하는 방식이었습니다. 긍정적 제약 (positive constraints)은 줄이고, 부정적 제약 (negative constraints) 내에서의 창의적 자유는 더 높였습니다.
품질의 정체기 (quality plateau)는 존재합니다. 어느 시점에 도달하면 반복 (iteration)이 측정 가능한 어떤 것도 더 이상 개선하지 못하게 됩니다. 그것이 바로 멈춰야 한다는 신호입니다 — 프롬프트가 완벽해서가 아니라, 한계 효용 (marginal gains)이 더 이상 투입된 시간만큼의 가치를 제공하지 않기 때문입니다. 언제 멈춰야 하는지를 아는 것은 어떻게 반복해야 하는지를 아는 것만큼이나 중요합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기