⚡ 중급2026-04-118분
프롬프트를 '감'이 아니라 평가셋으로 개선하기
프롬프트 A vs B를 눈으로 비교하지 마라. 20개 테스트 케이스와 채점 함수가 있어야 진짜 개선이다.
프롬프트eval
왜 감으로는 안 되나
프롬프트 바꾸면 3개 샘플 돌려보고 "좋아진 것 같다"로 배포한다. 그러다 실제로는 다른 케이스에서 회귀한다. 평가셋 없는 프롬프트 튜닝은 복권 긁기다.
최소 평가 셋
- 20~50개 실제 입력
- 각 입력에 기대 출력 또는 채점 기준
- 에지 케이스 5개 이상 (빈 입력, 너무 긴 입력, 욕설, 이상한 포맷)
[
{ "input": "환불 언제 돼요?", "expected_intent": "refund", "expected_urgency_max": 3 },
{ "input": "", "expected_intent": "spam" },
{ "input": "아니 씨 내일까지 안 오면 고소!", "expected_intent": "complaint", "expected_urgency_min": 4 }
]
간단한 러너
import fs from "node:fs"
import Anthropic from "@anthropic-ai/sdk"
const client = new Anthropic()
const cases = JSON.parse(fs.readFileSync("eval.json", "utf8"))
async function runPrompt(systemPrompt: string) {
let pass = 0
for (const c of cases) {
const msg = await client.messages.create({
model: "claude-haiku-4-5",
system: systemPrompt,
max_tokens: 200,
messages: [{ role: "user", content: c.input }],
})
const out = JSON.parse((msg.content[0] as any).text)
const ok =
(!c.expected_intent || out.intent === c.expected_intent) &&
(!c.expected_urgency_min || out.urgency >= c.expected_urgency_min) &&
(!c.expected_urgency_max || out.urgency <= c.expected_urgency_max)
if (ok) pass++
else console.log("FAIL:", c.input, "→", out)
}
console.log(`${pass}/${cases.length}`)
}
await runPrompt(process.argv[2] === "v2" ? PROMPT_V2 : PROMPT_V1)
A/B 비교 절차
- 기존 프롬프트로 평가셋 돌려 베이스라인 점수 기록
- 프롬프트 1곳 수정 (한 번에 한 변경만)
- 같은 평가셋 돌려 점수 비교
- 떨어진 항목 분석 — 이게 핵심. 어떤 케이스가 회귀했나
- 회귀 있으면 롤백하거나 그 케이스 해결하는 추가 지시 투입
LLM-as-judge
채점이 애매한 글쓰기 작업은 Opus 4.7을 심판으로 쓸 수 있다.
const judge = await client.messages.create({
model: "claude-opus-4-7",
max_tokens: 300,
messages: [{
role: "user",
content: `아래 두 답변 중 더 나은 쪽을 A/B/tie로만 답해. 이유는 한 줄.
[질문]
${question}
[A]
${answerA}
[B]
${answerB}`,
}],
})
주의: 같은 답을 A/B 순서 바꿔 돌리고 둘 다 같은 쪽이 이겨야 신뢰 가능.
체크리스트
- [ ] 평가셋 20개 이상
- [ ] 에지 케이스 포함
- [ ] 변경 전후 점수 기록
- [ ] 회귀 케이스 따로 문서화
- [ ] 평가는 CI에 붙여서 자동화