
단 하나의 토큰을 생성하기 전 던져야 할 68가지 질문: 평가 우선 RAG (eval-first RAG)
요약
RAG 파이프라인의 신뢰성을 높이기 위해 LLM 생성 단계 이전에 검색(Retrieval) 단계를 우선적으로 평가하는 'eval-first RAG' 방법론을 소개합니다. 68개의 정교한 질문 세트를 활용하여 검색 결과의 정확성을 단위 테스트 방식으로 검증하는 전략을 다룹니다.
핵심 포인트
- LLM 생성 전 검색 단계의 정확성을 먼저 검증하는 것이 중요함
- 버전 관리되는 질문 세트를 회귀 테스트 수트로 활용
- 동의어 처리(expect_contains_any) 및 어간 추출의 필요성
- 범위 외 질문(out-of-scope)에 대한 검색 결과 검증
시리즈의 파트 2입니다. 파트 1: 왜 나는 원예를 위해 일반적인 LLM을 신뢰하는 것을 그만두었는가.
파트 1에서 저는 모든 것을 바꾼 결정에 대해 약속했습니다. 여기 그 내용이 있습니다:
나는 고정된 도메인 질문 세트가 검색 (retrieval)을 통과할 때까지 LLM을 건드리지 않는다.
"데모가 좋아 보였다"가 아닙니다. "다섯 가지를 물어봤고 답변을 했다"도 아닙니다. 단위 테스트 (unit tests)를 다루는 방식과 동일하게, 예상되는 근거와 함께 버전 관리되는 질문 파일이 회귀 테스트 세트 (regression suite)로서 실행되어야 합니다. 오늘날 그 세트는 68개의 질문으로 구성되어 있습니다: 사과 45개, 배 8개, 자두 10개, 그리고 파이프라인이 과수원에만 하드코딩되어 있지 않음을 증명하기 위한 HR 정책 샌드박스용 질문 5개입니다.
답변이 아닌 검색 (retrieval)인 이유
RAG 파이프라인은 두 곳에서 실패합니다: 적절한 구절 (passage)이 프롬프트 (prompt)에 도달하지 못하거나, 도달한 구절을 모델이 망가뜨리는 경우입니다. 첫 번째 실패는 탐지 비용이 저렴하고 테스트 비용이 들지 않습니다. API 키도, 토큰도, 루프 내의 불안정한 LLM도 필요하지 않습니다. 따라서 기본 평가 (eval) 모드는 검색 (retrieval) 단계에서 멈춥니다:
{
"crop_id": "apple",
"question": "What are signs of scab?",
...
러너 (runner)는 질문을 검색 서비스 (POST/rag/context)로 보내고, 모든 expect_contains 하위 문자열이 결합된 검색 컨텍스트 (retrieved context)에 나타나는지 확인합니다. 제가 예상했던 것보다 더 중요하게 작용했던 몇 가지 세부 사항은 다음과 같습니다:
- 동의어를 위한
expect_contains_any. 문헌에는 _Marssonina_라고 기록되어 있지만, 사용자는 구어체 질병명을 입력할 수 있습니다. 둘 중 어느 것이든 증거로 간주합니다. - 가벼운 어간 추출 (Light stemming).
rootstock은rootstocks와 일치해야 합니다. 이것이 없으면 예상 문자열을 특정 기사의 문구에 과적합(overfit)시키거나 잘못된 실패(false failures)를 겪게 됩니다. expect_out_of_scope: true질문들. 예를 들어 자동차 정비에 관한 질문은 약하거나 없는 컨텍스트 (context)를 반환해야 합니다. 이는 벡터 검색 (vector search)이 어떤 문자열에 대해서든 기쁘게
| 플래그 (Flag) | 효과 (Effect) |
|---|---|
--in-process | 검색 (retrieval) 모듈을 직접 임포트하며, HTTP를 건너뜁니다 |
| ... |
약 20초 정도 소요되는 --in-process --fast 조합은 제가 반사적으로 실행하는 방식입니다. 리랭킹 (reranking)을 포함한 전체 실행은 릴리스 전이나 재인덱싱 (reindexing) 후에 수행합니다. CI (지속적 통합) 환경에서는 모든 PR (Pull Request)마다 단위 테스트 (unit tests)가 실행되며, 전체 평가 (complete eval)는 수동 GitHub Actions 워크플로우로 진행합니다. 모델 다운로드가 너무 무거워 매 푸시 (push)마다 실행하기에는 무리가 있기 때문입니다.
결과는 eval/results/<timestamp>_<suite>.json에 저장되므로, "화요일의 청킹 (chunking) 변경이 pear 관련 질문에 악영향을 주었는가?"라는 질문은 논쟁이 아닌 차이 (diff)의 문제입니다.
평가 스위트 (suite)가 잡아낸 것들 (샘플)
- 실험 테이블을 헤더 (header)와 분리해버린 청킹 (chunking) 변경 — apple의
pass_rate가 7포인트 하락했으며, 다른 지표는 변동이 없었습니다. 몇 분 만에 원복되었습니다. - 코퍼스 (corpus) 업데이트 후 BM25 인덱스가 재구축되지 않음 — 벡터 검색 (vector search)이 일반적인 질문들에 대해서는 이를 가려주었지만, 정확한 코드 질문 (rootstock "SK-4")은 즉시 실패했습니다.
- 용어 사전 (glossary) 항목이 용어를 너무 공격적으로 확장하여, 두 가지 질문에 대해 올바른 문서를 Top-5에서 밀어냄:
pass_rate는 변하지 않았지만 MRR (Mean Reciprocal Rank) 하락으로 나타났습니다.
이 중 그 어떤 것도 "봇과 잠시 대화해보기"로는 잡아낼 수 없었을 것입니다. 이 모든 문제들이 그대로 배포되었을 것입니다.
여러분이 가져갔으면 하는 것들 (Steal)
- 검색 (retrieval)을 튜닝하기 전에 평가 (eval) 파일을 작성하세요. 단 20개의 질문만으로도 작업 방식이 달라집니다.
- 생성 (generation)과 분리하여 검색 (retrieval)을 별도로 테스트하세요. 그것이 비용이 적게 드는 80%의 핵심입니다.
- 범위 외 (out-of-scope) 질문을 조기에 추가하세요. 적절하게 거절하는 것도 하나의 기능 (feature)입니다.
- 빠른 경로 (fast path)를 30초 미만으로 만드세요. 그렇지 않으면 실행을 멈추게 될 것입니다.
Part 3는 그 보상에 대한 내용입니다. 그 68개의 질문을 통과시키기 위해 실제로 무엇이 필요했는지 — 하이브리드 검색 (hybrid search), RRF (Reciprocal Rank Fusion), 그리고 왜 "그저 더 나은 임베딩 모델 (embedding model)을 사용하라"는 것이 정답이 아니었는지에 대해 다룹니다.
면책 조항 (Disclaimer): 어시스턴트 (assistant)의 출력은 정보 제공을 목적으로 합니다. 현장의 결정에는 현지 전문가와 규정을 준수하는 제품 라벨이 필요합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기