프롬프트 회귀(Prompt Regression)를 위한 Datadog 대시보드: 실제로 유지하고 있는 패널들
요약
LLM 프롬프트 변경 시 발생하는 성능 회귀를 감지하기 위해 Datadog을 활용한 모니터링 체계를 구축한 사례를 소개합니다. 통합 통과율 대신 기준별(per-criterion) 통과율을 개별 메트릭으로 관리하여 미세한 성능 저하를 정확히 포착하는 방법을 다룹니다.
핵심 포인트
- 통합 통과율은 특정 기준의 급격한 성능 저하를 은폐할 수 있음
- 기준별(per-criterion) 통과율을 개별 메트릭으로 분리하여 모니터링 권장
- Datadog을 활용해 백엔드 상태와 LLM 평가 지표를 통합 관리
- GitHub Actions와 Python을 이용한 자동화된 평가 메트릭 전송
약 4개월에 걸쳐 우리의 LLM 평가(eval) 스위트를 Datadog에 연결했습니다. 우리가 만든 패널 대부분은 삭제되었습니다. 여기 살아남은 5개의 패널과 그에 데이터를 공급하는 메트릭(metrics)들이 있습니다.
요약(TL;DR): 우리는 프롬프트를 수정하는 모든 PR(Pull Request)에 대해 LLM-as-judge 평가 스위트를 실행하며, 그 결과를 커스텀 메트릭(custom metrics)으로 Datadog에 전송합니다. 대시보드는 14개의 패널로 시작했지만, 5개만 남겼습니다. 실제 회귀(regressions)를 가장 많이 잡아내는 것은 단일 통합 통과율(rolled-up pass-rate) 수치가 아니라, 판사 기준(judge criterion)별로 분리된 기준별 통과율(per-criterion pass-rate)입니다. 91%라는 통합 수치는 하나의 기준이 0.95에서 0.62로 떨어졌다는 사실을 숨겼기 때문입니다. 아래에는 우리가 전송하는 메트릭, 이를 제출하는 Python 코드, 알림을 설정한 모니터(monitor) 구성, 그리고 시도했다가 폐기한 패널들이 나열되어 있습니다.
이후 내용을 이해하기 위한 설정 배경입니다. 우리는 Series-C 단계의 개발 도구(dev-tool) 스타트업입니다. 실제 작업(분류(classification), 추출(extraction), 에이전트 루프 내의 요약(summarization) 단계)을 수행하는 몇 가지 프롬프트가 프로덕션 환경에 있습니다. 각 프롬프트는 태그가 지정된 80개에서 400개 사이의 예시로 구성된 평가 세트(eval set)를 가지고 있습니다. 판사(judge)는 루브릭(rubric)에 따라 각 출력을 점수화하는 별도의 모델 호출입니다. 우리는 GitHub Actions에서 이 스위트를 실행합니다. 평가 작업은 매 실행이 끝날 때마다 Datadog으로 메트릭을 전송합니다. 백엔드 서비스 상태(health)는 이미 Datadog에 있었으므로, 평가 데이터를 그 옆에 두는 것은 장애 발생 시 두 곳이 아닌 한 곳만 확인하면 된다는 것을 의미했습니다.
1. 단순히 통합된 수치가 아닌, 기준별 통과율(per-criterion pass-rate)을 전송하세요
이 패널은 그 가치를 충분히 증명합니다. 우리의 판사는 여러 기준에 따라 각 출력을 점수화합니다. 추출(extraction) 프롬프트의 경우 기준은 네 가지입니다: 정확한 필드(correct fields), 환각 필드 없음(no hallucinated fields), 유효한 형식(format valid), 거부 없음(no refusal). 초기에는 모든 기준을 통과한 예시의 비율인 prompt_eval.pass_rate라는 단 하나의 숫자만 전송했습니다. 그 수치는 스모크 테스트(smoke test)에는 괜찮지만, 디버깅에는 무용지물입니다.
문제는 깔끔해 보였던 프롬프트 변경 사항에서 나타났습니다. 전체 통과율(pass-rate)은 0.93에서 0.91로 떨어졌습니다. 단 2% 포인트 차이였습니다. 2% 포인트 차이 때문에 PR(Pull Request)을 막을 사람은 아무도 없을 것입니다. 하지만 그 이면을 들여다보니, "환각 필드 없음(no hallucinated fields)" 기준은 0.96에서 0.71로 급락했고, "형식 유효성(format valid)"은 평균값에서 이를 가릴 만큼 충분히 상승해 있었습니다. 우리는 정확성을 포맷팅과 맞바꾸고 있었고, 합산된 수치는 모든 것이 기본적으로 괜찮다고 말하고 있었습니다.
그래서 이제는 모든 기준(criterion)이 태그(tag)가 지정된 개별 메트릭(metric)을 갖게 되었습니다. 메트릭 이름은 prompt_eval.pass_rate로 유지하고, 기준이 태그로 따라붙습니다. 이렇게 하면 메트릭 개수를 합리적으로 유지하면서 하나의 패널에 모든 기준을 그래프로 그릴 수 있습니다.
# eval_metrics.py
# 실행이 완료된 후 Datadog에 평가 결과를 제출합니다.
from datadog import initialize, api
...
처음에 제가 실수했던 두 가지가 있습니다. 기준을 태그로 넣는 대신 메트릭 이름에 포함시켰습니다(prompt_eval.pass_rate.no_hallucinated_fields). 이 방식은 프롬프트당 기준별로 새로운 커스텀 메트릭을 생성하여 카디널리티(cardinality)를 높였고, 각 항목을 일일이 나열하지 않고는 함께 그래프를 그릴 수 없게 만들었습니다. 태그를 사용하면 이 두 가지 문제가 모두 해결됩니다. 또 다른 실수는 40자 전체의 git SHA를 태그로 지정한 것이었습니다. 이는 카디널리티가 매우 높은 태그 값이며, 그 정도 길이는 유용하지 않습니다. 12자로 자르는 것만으로도 커밋을 찾는 데 충분하며 태그가 폭발하는 것을 방지할 수 있습니다.
2. 인간과 비교하여 판정기(judge)를 추적하십시오. 그렇지 않으면 노이즈를 그래프로 그리게 됩니다.
저의 확고한 의견을 솔직하게 말씀드리자면, LLM-as-judge(판정기로서의 LLM)는 유일하게 확장 가능한 평가 방식이지만, 대부분의 팀은 판정기 자체를 검증하지 않기 때문에 이를 잘못 사용하고 있습니다. 판정기가 자기 자신과 동의하는 것만을 측정하고 있다면, 아름답게 보이는 통과율 패널은 가치가 없습니다. 우리는 몇 주 동안 약 30%의 거짓 양성(false-positive) 비율로 작동하던 환각 탐지 판정기(hallucination-detection judge)를 통해 이 교훈을 느리게 배웠습니다. 대시보드는 초록색이었지만, 고객들은 그렇지 않았습니다.
따라서 prompt_eval.judge_kappa는 이제 일급 시민(first-class) 지표가 되었습니다. 우리는 프롬프트당 소규모의 사람이 라벨링한 홀드아웃(holdout) 데이터셋을 유지합니다 (우리 중 두 명이 라벨링하고, 의견 불일치는 제3자가 해결하는 200개의 예시). 모든 평가(eval) 실행 시 해당 홀드아웃 데이터도 점수를 매기며, 판사(judge)와 사람의 라벨 사이의 코헨 카파(Cohen's kappa) 계수를 계산합니다. 이 수치는 통과율(pass-rate)과 함께 Datadog으로 전송됩니다.
이를 위한 패널은 0.6에 마커 라인이 표시된 단일 시계열(timeseries) 그래프입니다. 카파(kappa) 값이 라인 아래로 드리프트(drift)하면, 그 위의 통과율 수치는 아무런 의미가 없게 되며, 우리는 어떠한 회귀(regression) 신호를 신뢰하기 전에 판사 프롬프트(judge prompt)를 다시 살펴봐야 한다는 것을 알게 됩니다. 우리의 설정에서 프롬프트 상태가 양호할 때 카파 값은 약 0.66에서 0.72 사이에 위치합니다. 한 번은 판사 루브릭(judge rubric)을 잘못 다시 작성했을 때, 단 한 번의 실행만으로 0.41까지 떨어졌으며, 그 하락을 통해 모델이 아니라 루브릭 변경이 문제였다는 것을 알 수 있었습니다.
from sklearn.metrics import cohen_kappa_score
def compute_judge_kappa(human_labels, judge_labels):
...
홀드아웃 데이터셋은 클 필요가 없습니다. 실제 사람이 라벨링해야 하며, 프롬프트의 역할이 변경될 때 갱신되어야 합니다. 우리는 한 달에 한 번 정도, 또는 프롬프트의 범위(scope)가 이동할 때마다 다시 라벨링을 합니다.
3. 대시보드를 신뢰하기 전에 모니터를 연결하세요
아무도 쳐다보지 않는 대시보드는 새벽 2시에 아무것도 잡아내지 못합니다. 패널은 무언가 변했다는 것을 이미 알고 난 후 디버깅(debugging)하기 위한 용도입니다. 모니터는 무언가 변했다는 것을 알려주는 역할을 합니다. 우리는 두 가지 종류를 실행합니다. 첫 번째는 기준별(per-criterion) 통과율에 대한 절대적 하한선(absolute floor)입니다. 두 번째는 전체 통과율에 대한 변화 기반(change-based) 모니터로, 이를 통해 단일 실행이 하한선을 건드리지 않더라도 주간 단위(week-over-week)의 느린 하락을 포착할 수 있습니다.
다음은 기준별 하한선을 Terraform의 datadog_monitor 리소스로 구현한 예시입니다. 이렇게 하면 누군가의 브라우저 탭이 아니라 버전 관리(version control) 시스템 내에 존재하게 됩니다.
resource "datadog_monitor" "extraction_no_hallucinated_fields" {
name = "[prompt-eval] extraction: no_hallucinated_fields below floor"
type = "metric alert"
...
min(last_3)에 관한 참고 사항: 우리는 단 한 번의 실행 결과에 대해서는 알람을 보내지 않습니다. 평가 세트(Eval sets)에는 샘플링 노이즈(sampling noise)가 존재하며, 운이 나쁜 한 번의 실행으로 인해 기준(criterion)이 하한선(floor) 아래로 떨어졌다가 다음 실행에서 다시 회복될 수 있기 때문입니다. 기준선 아래로 세 번 연속 실행되는 경우를 요구함으로써 잘못된 페이지 호출(false pages)을 크게 줄일 수 있었습니다. CI 체크 자체는 첫 번째 실행에서 이미 빨간색(실패)으로 표시되므로, PR(Pull Request)은 이미 차단된 상태입니다. 페이지 알람은 느린 드리프트(slow drift)를 위한 것이고, 빨간색 체크는 명백한 파손(obvious break)을 위한 것입니다. notify_no_data: true는 보기보다 훨씬 중요합니다. 가장 흔한 실패 사례는 성능 회귀(regression)가 아니었습니다. 평가 작업(eval job)이 조용히 실행되지 않아 대시보드가 아무 변화 없이 평탄하게 유지되는 것이 문제였습니다.
4. 우리가 유지한 5개의 패널과 삭제한 9개의 패널
우리가 정착한 테스트 기준: 만약 어떤 패널이 지난 한 달 동안 누군가가 수행한 작업에 변화를 주지 못했다면, 삭제합니다.
| 패널 | 지표 (Metric) | 유지 또는 삭제 |
|---|---|---|
| 기준별 통과율 (criterion당 한 줄) | prompt_eval.pass_rate by criterion | 유지. 가장 많이 사용되는 패널. |
| ... |
삭제된 항목들의 패턴: 이미 더 나은 패널이 있는 숫자를 다른 관점에서 보여주는 것이거나, 장애 상황(incident) 도중 대시보드를 실제로 살펴보는 10초 내에 읽기에는 너무 밀도가 높은 것들이었습니다. 우리는 일반적인 서비스 대시보드 레이아웃을 복사하는 것으로 시작했는데, 그것은 실수였습니다. 서비스 대시보드는 요청(request)이 연속적으로 흐른다고 가정합니다. 반면 평가 실행(Eval runs)은 PR 상에서 발생하는 이산적인 이벤트(discrete events)입니다.
5. 모든 것에 프롬프트와 SHA 태그를 붙여 대시보드가 "어떤 변경 사항인지" 답하게 하라
회귀(regression) 상황에서 핵심은 단 하나의 질문에 빠르게 답하는 것입니다: "어떤 프롬프트 변경이 이 지표를 변화시켰는가?" 우리가 전송하는 모든 지표에는 prompt, git_sha (truncated), env가 포함됩니다. 통과율(pass-rate)에는 criterion도 포함됩니다. 이러한 태그가 있으면 "어떤 커밋(which commit)" 테이블은 git_sha에 대한 단순한 group-by 연산이 됩니다. 특정 기준(criterion)이 떨어지면, 테이블을 읽고 SHA를 찾아 1분 이내에 차이점(diff)을 확인할 수 있습니다. 또한 각 평가 실행 시작 시 Datadog 이벤트(event)를 오버레이(overlay)로 게시하여, 그래프의 하락 지점이 커밋과 시각적으로 일치하도록 합니다.
FAQ
kappa(카파)를 위해 정말로 사람이 라벨링한 홀드아웃(holdout) 데이터셋이 필요할까요? 프롬프트당 한 번은 필요하며, 가끔씩 갱신해 주어야 합니다. 두 명의 사람이 200개의 예시를 라벨링하는 데는 오후 한나절이면 충분합니다. 이것이 없다면, 당신은 아무런 검증 없이 판사(judge)를 신뢰하는 셈이 됩니다.
왜 평가(eval) 도구 자체의 대시보드 대신 Datadog을 사용하나요? 우리는 이미 서비스 상태(service health) 확인을 위해 Datadog을 사용하고 있었습니다. 만약 당신의 팀이 그렇지 않다면, 이것이 Datadog을 도입해야 할 이유는 아닐 것입니다. 지표(metrics)는 그것이 렌더링되는 표면(surface)보다 더 중요합니다.
어떤 임계값(thresholds)으로 시작해야 하나요? 제 것을 그대로 복사하지 마세요. 일주일 동안 메인(main) 브랜치에서 스위트(suite)를 실행하며 각 기준(criterion)이 어디에 위치하는지 관찰한 뒤, 정상 범위보다 약간 낮은 수준으로 하한선(floor)을 설정하세요.
이것이 Promptfoo나 로컬 평가 프레임워크(eval framework)를 실행하는 것을 대체하나요? 아니요. 프레임워크는 여전히 평가(evals)를 실행하며, 개별 예시(per-example)에 대한 세부 정보를 읽는 곳입니다. Datadog은 그 위에 구축된 롤업(rollup) 및 알림(alerting) 계층입니다.
왜 count나 rate가 아닌 gauge를 사용하나요? 통과율(pass-rate)은 특정 시점의 스냅샷 값(snapshot value)이므로 gauge가 적합합니다. 잘못된 유형을 사용하는 것은 제가 초기에 저지른 실수 중 하나였습니다.
여전히 고민 중인 부분들
프롬프트의 역할이 변하면 kappa 홀드아웃 데이터가 노후화(stale)되는데, 재라벨링(re-labeling) 외에는 이것이 언제 노후화되었는지 알 수 있는 깔끔한 신호가 없습니다. min(last_3) 윈도우(window) 방식은 탐지 속도를 희생하는 대신 잘못된 페이지 호출(false pages)을 줄여주지만, 평가 세트당 '3'이라는 숫자가 적절한지는 확신할 수 없습니다. 그리고 더 어려운 문제는 이것이 이미 평가 세트가 있는 프롬프트의 회귀(regressions)만을 잡아낸다는 점입니다. 판사(judge)는 루브릭(rubric)이 묻는 것에 대해서만 점수를 매길 수 있습니다. 모든 것이 통과되었음에도 고객은 여전히 틀린 결과가 나오는 버그 유형은 기준(criteria) 사이의 간극에 존재하며, 저는 제가 측정하는 것을 잊어버린 항목에 대한 패널을 가지고 있지 않습니다.
만약 당신이 기준별(per-criterion) 평가 알림(eval alerting)을 연결해 두었거나, 3회 실행보다 더 나은 윈도우를 찾았거나, 재라벨링 없이 판사 홀드아웃이 언제 노후화되었는지 알 수 있는 방법을 찾았다면, 꼭 알려주시기 바랍니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기