LLM-as-Judge 패턴: AI 출력물 자동 평가 시스템 구현하기
Content Repurposer에서 9.8/10 평균 품질을 달성한 방법은 더 좋은 프롬프트가 아니었다.
같은 모델로 자신의 출력물을 평가하게 만드는 것이었다.
LLM-as-Judge 패턴이다. 구현 방법을 코드와 함께 공개한다.
문제: AI 출력물의 품질을 어떻게 측정하는가
AI로 콘텐츠를 생성하면 항상 같은 딜레마가 생긴다.
- 출력물이 좋은지 나쁜지 어떻게 아는가?
- 하드코딩된 규칙으로는 "좋은 LinkedIn 포스트"를 정의하기 어렵다
- 사람이 모든 출력물을 검토하면 자동화의 의미가 없다
전통적인 NLP 메트릭(BLEU, ROUGE)은 텍스트 유사도를 측정하지만, "좋은 콘텐츠"는 유사도가 아니라 적합성의 문제다.
해결책: LLM한테 물어보면 된다.
LLM-as-Judge란
LLM이 다른 LLM(또는 자기 자신)의 출력물을 평가하는 패턴이다.
Google, Anthropic, OpenAI의 내부 평가 시스템이 이 방식을 사용한다. LMSYS Chatbot Arena도 LLM 간 상호 평가로 리더보드를 만든다.
핵심 인사이트: LLM은 좋은 텍스트가 무엇인지 알고 있다. 생성과 평가는 다른 태스크다.
Content Repurposer의 구현
1단계: 평가 차원 정의
추상적인 "품질"을 측정 가능한 차원으로 분해했다.
const QUALITY_DIMENSIONS = {
platform_adaptation: {
weight: 0.25,
description: '플랫폼별 톤, 길이, 형식 적합성'
},
content_preservation: {
weight: 0.20,
description: '핵심 포인트와 논거 보존'
},
engagement_potential: {
weight: 0.25,
description: '훅, 구조, CTA의 매력도'
},
tone_consistency: {
weight: 0.15,
description: '원본 저자 목소리와의 일관성'
},
format_compliance: {
weight: 0.15,
description: '플랫폼별 제약 준수 (길이, 형식)'
}
};
가중치는 실험을 통해 조정했다. Platform adaptation과 engagement potential이 가장 중요하다고 판단했다.
2단계: 평가 프롬프트 작성
평가 프롬프트는 생성 프롬프트만큼 중요하다.
function buildValidationPrompt(format, originalContent, generatedContent) {
const formatGuidelines = {
twitter: `
- 각 트윗은 280자 이내
- 첫 트윗에 강한 훅이 있어야 함
- 대화체, 직접적인 어조
- 스레드 구조가 자연스럽게 이어져야 함
`,
linkedin: `
- 전문적 톤, 사고 리더십 느낌
- 1,100-1,300자 권장
- 줄바꿈으로 스캔 가능하게
- 소프트 CTA로 마무리
`,
// ... 다른 플랫폼
};
return `
당신은 소셜 미디어 콘텐츠 전문 편집자입니다.
아래 ${format} 포스트를 5가지 기준으로 각 1-10점으로 평가하세요.
=== 원본 콘텐츠 ===
${originalContent.substring(0, 800)}...
=== 생성된 ${format} 포스트 ===
${generatedContent}
=== ${format} 품질 기준 ===
${formatGuidelines[format]}
평가 기준:
1. platform_adaptation: 이 플랫폼에 맞는 톤과 형식인가?
2. content_preservation: 원본의 핵심 내용이 살아있는가?
3. engagement_potential: 실제 사용자가 참여할 만한가?
4. tone_consistency: 원본 저자의 목소리와 일치하는가?
5. format_compliance: 플랫폼 규칙을 지켰는가?
반드시 다음 JSON 형식으로만 응답하세요:
{
"scores": {
"platform_adaptation": <1-10>,
"content_preservation": <1-10>,
"engagement_potential": <1-10>,
"tone_consistency": <1-10>,
"format_compliance": <1-10>
},
"weighted_average": <계산된 평균>,
"feedback": "<개선이 필요한 경우 구체적인 피드백>",
"pass": <true/false (기준: 7.0 이상)>
}
`;
}
3단계: 자동 재시도 루프
품질 기준을 통과하지 못하면 피드백을 반영해 재생성한다.
async function generateWithQuality(blogContent, format, options = {}) {
const {
minQuality = 7.0,
maxRetries = 3,
verbose = false
} = options;
let attempt = 0;
let lastFeedback = null;
while (attempt < maxRetries) {
attempt++;
// 생성 (이전 피드백을 프롬프트에 포함)
const generated = await generateContent(blogContent, format, {
improvementHints: lastFeedback
});
// 평가
const validation = await validateQuality(
format,
blogContent,
generated
);
if (verbose) {
console.log(`Attempt ${attempt}: ${validation.weighted_average}/10`);
}
if (validation.pass) {
return {
content: generated,
quality: validation.weighted_average,
attempts: attempt,
scores: validation.scores
};
}
// 다음 시도를 위해 피드백 저장
lastFeedback = validation.feedback;
}
// maxRetries 초과 시 최선의 결과 반환
throw new Error(
`Quality threshold not met after ${maxRetries} attempts`
);
}
4단계: 병렬 실행
5개 포맷을 순차 실행하면 250초가 걸린다. Promise.all()로 병렬화하면 54초다.
async function repurposeAll(blogContent, formats) {
const results = await Promise.all(
formats.map(format =>
generateWithQuality(blogContent, format, {
minQuality: 7.0,
maxRetries: 3
}).catch(err => ({
format,
error: err.message,
quality: 0
}))
)
);
return results;
}
실제 결과
18개 테스트 케이스 결과:
| 케이스 | 입력 길이 | 평균 품질 | 재시도 필요 |
|---|---|---|---|
| 단문 팁 (293자) | 짧음 | 9.8/10 | 0회 |
| 기술 튜토리얼 (764자) | 중간 | 9.6/10 | 0회 |
| 리스트 포스트 (2,084자) | 긺 | 9.9/10 | 0회 |
| 코드 위주 (1,247자) | 중간 | 9.7/10 | 0회 |
| 의견 포스트 (1,847자) | 중간 | 10/10 | 0회 |
전체 18케이스: 100% 통과율, 평균 9.8/10, 재시도 0회
재시도 로직은 한 번도 실행되지 않았다. 첫 번째 시도가 항상 기준을 통과했다는 의미다. 품질 프롬프트가 생성 프롬프트를 더 신중하게 만들었기 때문이다.
LLM-as-Judge의 한계
자기참조 편향
같은 모델이 생성하고 평가하면 편향이 생길 수 있다. 모델은 자신의 출력 패턴을 "좋다"고 평가하는 경향이 있다.
완화 방법:
- 평가 프롬프트를 생성 프롬프트와 완전히 다르게 작성
- 평가 시 원본 콘텐츠와의 비교를 강제
- 가능하면 다른 모델로 평가 (생성: Gemini Flash, 평가: GPT-4o)
평가 자체의 비용
출력물 생성 1회 + 평가 1회 = API 비용 2배. 재시도하면 더 늘어난다.
Content Repurposer에서의 해결:
- 재시도 기준을 7.0으로 낮게 설정 (실제로 거의 발생 안 함)
- 평가 프롬프트를 간결하게 (토큰 사용 최소화)
- Gemini Flash 사용 (비용 대비 성능 최적)
주관적 판단의 한계
"좋은 Twitter 스레드"는 맥락에 따라 다르다. B2B SaaS 창업자 타겟과 10대 타겟의 기준이 다르다.
현재 구현은 일반적인 기준을 사용한다. 향후 개선 방향:
- 사용자 설정 가능한 톤/스타일 가이드
- 브랜드 보이스 설정
- 타겟 오디언스 명시
다른 사용 사례
LLM-as-Judge 패턴은 Content Repurposer 외에도 적용 가능하다:
코드 리뷰 자동화
생성: 코드 작성 LLM
평가: "이 코드의 보안 취약점, 성능 이슈, 코드 스타일 위반을 찾아라"
이메일 초안 품질 검사
생성: 영업 이메일 초안
평가: "스팸 필터 우회 가능성, 클릭률 예측, CTA 명확성 평가"
SQL 쿼리 검증
생성: 자연어 → SQL 변환
평가: "이 쿼리가 의도한 결과를 반환하는지, 성능 이슈가 있는지 분석"
구현 시작하기
Content Repurposer의 품질 시스템 코드는 오픈소스다.
# 설치
npm install -g content-repurposer
# 실행 (품질 점수 포함)
content-repurposer my-post.md --formats all --verbose
# 최소 품질 기준 설정
content-repurposer my-post.md --formats all --min-quality 8.5
소스 코드: github.com/p4r4d0xb0x/content-repurposer
핵심 요약
- LLM은 생성과 평가를 동시에 잘한다 — 두 태스크를 분리하라
- 평가 차원을 명시적으로 정의하라 — "좋다/나쁘다"는 측정 불가
- 피드백 루프를 만들어라 — 평가 결과가 다음 생성에 영향을 줘야 한다
- 병렬화는 필수다 — 순차 실행은 너무 느리다
- 재시도 기준을 현실적으로 설정하라 — 너무 높으면 무한 루프
Content Repurposer: github.com/p4r4d0xb0x/content-repurposer
호떡 🥞 — AI 엔지니어링 + Micro-SaaS 빌드 기록 중