Vibecoding Evals를 해보니 (생각보다 효과가 좋았습니다)
요약
코딩 에이전트를 활용한 AI 앱 개발 시 발생할 수 있는 논리적 오류를 해결하기 위해 DeepEval을 도입한 사례를 소개합니다. 단순한 기능 작동을 넘어, 프롬프트 변경 시에도 의도한 정책대로 동작하는지 검증하는 평가(evals) 프로세스의 중요성을 강조합니다.
핵심 포인트
- 단순한 기능 작동(유효한 JSON 반환)이 올바른 비즈니스 로직을 보장하지 않음
- 프롬프트 변경 시 기존 기능의 퇴보를 막기 위해 재실행 가능한 평가 케이스 필요
- DeepEval을 활용하여 골든 데이터셋(JSON) 기반의 체크리스트 구축
- 예상 값과 실제 출력값을 비교하는 지표를 통해 시스템의 신뢰성 확보
저는 한동안 코딩 에이전트(coding agents)를 사용하여 AI 앱을 구축해 왔습니다.
최근에는 평가(evals) 실험도 병행하고 있습니다.
이 예시에 등장하는 앱은 대부분 제대로 작동했습니다. 바로 그것이 문제였습니다.
버그 (The bug)
저는 가상의 화물 추적 회사를 위한 작은 고객 지원 분류(support-triage) 앱을 만들었습니다.
고객이 지원 티켓(support ticket)을 보내면, 앱은 그것이 무엇에 관한 것인지, 얼마나 긴급한지, 그리고 사람이 응답해야 하는지를 결정합니다.
실제 서비스 중단(outage) 상황은 에스컬레이션(escalated)되어야 합니다.
하지만 이 티켓은 달랐습니다:
“긴급: 지금 즉시 키 로테이션(key rotation)이 필요합니다”
고객은 보안 검토 전에 자신의 API 키를 어떻게 로테이션하는지 묻고 있었습니다.
앱은 이를 보안 사고로 분류하고 사람에게 에스컬레이션했습니다.
그것은 잘못된 판단이었습니다. 정책에 따르면 일반적인 키 로테이션은 셀프 서비스 방식의 방법 문의(how-to request)였습니다.
시스템이 충돌한 것은 아니었습니다. 앱은 유효한 JSON을 반환했습니다. 모든 필드에는 허용된 값이 포함되어 있었습니다.
하지만 동작 자체는 여전히 틀렸습니다.
클릭만으로는 충분하지 않았던 이유
몇 개의 티켓을 수동으로 테스트하여 앱이 잘 작동한다고 스스로 확신할 수는 있었습니다.
하지만 프롬프트(prompt)를 변경한 후에는 실제로 무엇을 알 수 있을까요?
서비스 중단 케이스는 여전히 에스컬레이션될까요?
일반적인 방법 문의 질문들은 일반 대기열에 그대로 머물까요?
다른 API 키 관련 질문은 다르게 동작할까요?
저는 프롬프트를 변경하고 단순히 운이 좋기를 바라며 기도하고 싶지 않았습니다.
저는 다시 실행할 수 있는 일련의 케이스(cases)를 원했습니다.
Cursor에 DeepEval 추가하기
저는 DeepEval 에이전트 스킬을 설치했습니다:
npx skills add confident-ai/deepeval --skill "deepeval"
그 다음 Cursor에게 앱에 평가(evals)를 추가해달라고 요청했습니다:
이 앱은 가끔 일반적인 지원 질문을 비상 상황처럼 취급하여
사람에게 전달합니다.
리포지토리(repo)에 이미 있는 티켓과 정책을 사용하여 이를 테스트할 수 있도록
DeepEval을 추가해 주세요.
...
Cursor는 이미 앱, 티켓, 정책을 가지고 있었기에 즉시 베이스라인(baseline)을 생성하기 시작했습니다.
골든(Goldens)은 체크리스트입니다
첫 번째로 유용한 결과물은 JSON 데이터셋이었습니다.
각 골든(golden) 데이터에는 다음이 포함되었습니다:
- 고객 메시지
- 예상 카테고리 (expected category)
- 예상 우선순위 (expected priority)
- 사람의 개입 필요 여부
예를 들면 다음과 같습니다:
{
"input": "URGENT need key rotation now",
"expected": {
...
앱은 이러한 예시들로부터 학습하지 않습니다.
이 데이터셋은 체크리스트입니다. 프롬프트 업데이트를 배포하기 전에 어떤 동작들이 계속 정상적으로 작동해야 하는지를 저에게 알려줍니다.
그 부분이 바로 저에게 평가 (evals)의 개념이 명확히 이해된 지점이었습니다.
지표 (The metrics)
Cursor는 내장된 DeepEval 지표를 사용하여 두 가지 서로 다른 사항을 확인했습니다.
첫 번째는 앱의 라우팅 필드 (routing fields)를 골든 데이터셋 (golden)의 예상 값과 비교하는 것이었습니다:
- 카테고리 (category)
- 우선순위 (priority)
- 사람의 개입 필요 여부 (human escalation)
두 번째는 결정 사항이 정책 (policy) 내의 에스컬레이션 규칙 (escalation rules)을 따랐는지 확인하는 것이었습니다.
따라서 저는 두 가지 별개의 질문을 던질 수 있었습니다:
- 앱이 예상된 결정을 반환했는가?
- 그 결정이 정책 하에서 타당한가?
기준점 실패 (The baseline failure)
첫 번째 실행에서 키 로테이션 (key-rotation) 케이스가 포착되었습니다.
앱은 다음과 같이 반환했습니다:
category: security
priority: P1
needs_human: true
...
예상 결과는 다음과 같았습니다:
category: how-to
priority: P3
needs_human: false
...
출력 형식은 유효했습니다. 하지만 동작은 유효하지 않았습니다.
정책 확인 (policy check) 과정은 또한 Cursor에게 왜 해당 결정이 지원 규칙에 부합하지 않는지에 대한 이유를 설명해 주었습니다.
그것은 프롬프트를 수정하고, 동일한 티켓을 다시 클릭하며, 다른 무언가를 망가뜨리지 않았기를 기도하는 것보다 이미 훨씬 더 유용했습니다.
프롬프트 수정 (Fixing the prompt)
실패 원인은 분류 (triage) 프롬프트 내의 지나치게 광범위한 지침으로 거슬러 올라갔습니다.
프롬프트는 API 키와 관련된 모든 것을 보안 사고 (security incident)로 취급하고 있었습니다.
그것은 너무 공격적이었습니다.
해결책은 실제 침해 (compromise), 의심스러운 접근 (suspicious access), 또는 서비스 중단 (outage)의 경우에만 에스컬레이션을 예약하는 것이었습니다.
그 후 Cursor는 동일한 테스트 케이스를 다시 실행했습니다.
키 로테이션 케이스는 통과했습니다.
실제 서비스 중단 케이스는 여전히 에스컬레이션되었습니다.
이 두 번째 부분이 중요합니다. 하나의 케이스를 고치는 것만으로는 충분하지 않습니다. 저는 또한 이미 잘 작동하고 있던 케이스를 망가뜨리지 않았다는 사실도 알고 싶습니다.
루프 (The loop)
이것이 제가 사용해 온 워크플로우입니다:
앱 구축 (Build the app)
테스트 케이스 생성 (Create test cases)
앱 실행 (Run the app)
...
평가 (evals)가 없다면, 저는 프롬프트를 수정하면서 세 개의 새로운 버그를 만들지 않았기만을 바랄 수밖에 없었을 것입니다.
평가 (evals)가 있다면, 모든 프롬프트 변경 사항 뒤에는 체크리스트가 존재하게 됩니다.
저는 전체 워크플로우를 여기에 기록했습니다:
비디오 시청: https://x.com/juampitech/status/2084330588593385637?s=20
DeepEval은 오픈 소스 (open source)이며 로컬에서 실행됩니다. 이 워크플로우를 위해 Confident AI 계정이 필요하지는 않습니다. LLM-as-a-judge 지표를 사용하려면 모델 API 키가 필요합니다.
DeepEval Vibe Coder 퀵스타트 (quickstart):
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기