RAG 평가의 0점과 측정 불가를 구분하기
평가 화면에 근거 검색 점수가 100%로 나왔다고 하자. 그런데 근거를 요구한 질문 중 실제로 측정한 것은 일부뿐이었다면, 그 숫자를 전체 검색 품질로 읽어도 될까?
Deep Connector의 평가 집계에서는 평균과 함께 근거를 요구한 행 수, 측정 가능한 근거가 있는 행 수를 구분한다. 이 설계를 검토하면서 작은 데이터를 만들어 집계 함수를 실행해 봤다. 같은 입력도 null과 0을 어떻게 처리하느냐에 따라 평균이 30%, 50%, 75%로 달라졌다.
아래 질문과 점수는 집계 규칙을 확인하려고 만든 설명용 데이터다. 고객 문서나 실제 평가 성적을 사용하지 않았다. 실험은 저장소의 특정 구현 스냅샷에서 가져온 순수 함수만 실행했으며, 검색 엔진이나 LLM을 호출한 성능 실험은 아니다.
점수가 없는 이유부터 분리
근거 평가에는 질문과 기대 답변만으로 부족할 때가 있다. 어떤 문서의 어느 내용이 답변을 뒷받침하는지도 정답 데이터에 있어야 한다. 검색 결과에 그 근거가 포함됐는지 비교하려면, 기대 근거 자체를 읽고 해석할 수 있어야 한다.
이때 0은 비교를 수행했지만 찾은 근거나 덮은 내용이 없다는 측정값이다. null은 해당 지표의 측정값이 없다는 뜻이다. 근거를 지정했어도 참조를 해석하지 못하면 측정값을 만들지 못할 수 있다.
근거를 아예 지정하지 않은 질문도 별도로 봐야 한다. 문서 이름만으로 검색 순위를 평가하는 질문을 근거 본문 평가에 실패한 질문으로 세면, 서로 다른 평가 대상을 섞게 된다.
근거를 요구하지 않음 -> 이 근거 지표의 평가 대상 아님
근거를 요구했으나 측정 불가 -> 측정값 없음
근거를 측정했으나 못 찾음 -> 측정값 0
모두 화면에서 빈칸처럼 보일 수 있지만, 고쳐야 하는 대상은 다르다. 검색 실패를 조사할 때와 정답 근거의 참조 문제를 조사할 때 같은 작업을 시작해서는 안 된다.
같은 점수 배열로 평균을 바꿔 보기
설명용 입력에 측정값 1, 0.5, 0을 넣었다. 이어서 근거를 요구했으나 측정하지 못한 행과 근거를 요구하지 않은 행을 하나씩 추가했다. 이 지표만 배열로 꺼내면 다음과 같다.
const values = [1, 0.5, 0, null, null];
평균을 낼 때 null을 전부 0으로 바꾸면 합계는 그대로지만 분모가 늘어난다. 반대로 filter(Boolean)으로 빈 값을 제거하면 측정값 0까지 사라진다. JavaScript에서 0과 null은 둘 다 falsy이기 때문이다.
아래 코드는 이 차이를 보여주는 독립 실행 예시다. 제품 소스 전체를 옮긴 코드가 아니며, 입력은 유한한 0~1 값과 null로 제한했다.
const values = [1, 0.5, 0, null, null];
const mean = xs => xs.length
? xs.reduce((sum, value) => sum + value, 0) / xs.length
: null;
console.log(mean(values.map(value => value ?? 0)));
console.log(mean(values.filter(value => typeof value === "number")));
console.log(mean(values.filter(Boolean)));
실행 결과는 다음 순서였다.
0.3
0.5
0.75
첫 번째 값은 측정하지 못했거나 대상이 아닌 행까지 실패로 센 결과다. 마지막 값은 측정된 실패를 분모에서 지운 결과다. 가운데 값만 이 예시의 측정값 1, 0.5, 0을 모두 보존했다.
검색 결과는 하나도 바꾸지 않았다. 바꾼 것은 평균을 낼 때 포함한 행뿐이다. 다만 typeof value === "number"는 일반적인 입력 검증기가 아니다. NaN이나 무한대도 number 타입이므로, 외부 입력을 받는 코드라면 유한성과 허용 범위를 별도로 확인해야 한다.
평균 옆에 측정 행 수를 붙인 이유
숫자만 골라 평균을 내더라도 문제가 남는다. 근거를 요구한 질문 중 얼마나 측정했는지 모르면, 높은 평균이 얼마나 좁은 범위를 설명하는지 알 수 없다.
검토한 집계 코드에는 다음 카운터가 있다.
evidence_rows는 근거 목록에 항목이 하나 이상 있거나 공백이 아닌 기대 근거 본문이 있는 행 수다.evidence_measured는 그중 측정 가능한 근거가 있는 행 수다.
근거 목록은 항목 중 하나라도 measurable이면 측정 가능한 행으로 센다. 따라서 이 카운터는 질문 안의 모든 근거가 준비됐다는 보증이 아니다. 단수 근거 본문을 쓰는 경로에서는 본문이 있고 coverage 값이 null이나 undefined가 아니면 측정 가능한 행으로 분류한다. 여기서 coverage 0은 제외하면 안 된다.
설명용 입력을 실제 집계 함수에 넣었을 때, 전체 행은 5개이고 근거를 요구한 행은 4개였다. 그중 측정 가능한 행은 3개였고 평균 coverage는 0.5였다. 표시 함수에도 같은 요약을 넘겼다. 예시에서는 recall과 coverage에 같은 값을 넣어 표시 결과를 비교했다.
근거 50% 찾음 · 50% 덮음 (4행 중 3행만 측정)
이 문구는 “전체 질문에서 근거를 절반 찾았다”와 다르다. 측정된 값의 평균이 50%이고, 근거를 요구한 행 중 측정하지 못한 행이 남아 있다는 뜻이다.
100%여도 측정은 끝나지 않을 수 있다
별도 입력에서는 근거를 요구하는 행을 세 개 만들었다. 그중 한 행에만 측정값 1을 주고 나머지는 null로 뒀다. 실제 집계 함수와 표시 함수의 결과는 이랬다.
근거 100% 찾음 · 100% 덮음 (3행 중 1행만 측정)
평균 100% 자체가 산술적으로 틀린 것은 아니다. 다만 측정되지 않은 행의 검색 성능까지 설명하지는 못한다. 측정하기 쉬운 항목만 남았다면 평균이 전체 데이터셋을 대표하지 못할 수도 있다.
한 행도 측정하지 못한 경우도 따로 실행했다. 근거를 요구한 행이 하나라면 평균은 null이고 표시 문자열은 근거 1행 중 0행만 측정이었다. 근거를 아예 요구하지 않은 행만 있으면 평균과 근거 요약 문자열이 모두 null이었다. 측정값 0만 있는 경우에는 근거 0% 찾음 · 0% 덮음이 유지됐다.
평균을 표시하지 않는다고 측정 불가 상태까지 숨겨서는 안 된다. 값이 없는 이유를 보여줘야 정답 데이터 준비 문제를 검색 실패로 오해하지 않는다.
recall과 coverage도 같은 평균이 아니다
근거 recall은 측정 가능한 기대 근거 중 얼마나 찾았는지를 본다. coverage는 검색 결과가 기대 근거 내용을 얼마나 덮는지를 본다. 이 글의 실험에서 두 지표에 같은 숫자를 넣은 것은 집계와 표시를 비교하기 위한 설정이지, 두 지표가 같다는 뜻이 아니다.
검토한 집계 구현도 recall과 coverage에 숫자 값이 있는 행을 각각 세어 평균을 낸다. 두 지표의 평균 분모가 항상 같다고 가정할 수 없고, evidence_measured를 어느 평균의 분모로든 그대로 대입해서도 안 된다.
목록에 측정 가능한 근거가 있는지 세는 조건과 개별 지표 값이 숫자인지 확인하는 조건은 다른 검사다. 카운터와 지표가 일관된지는 값을 만드는 앞단에서도 확인해야 한다.
이 구분을 모든 평가 지표에 일괄 적용해서도 안 된다. 예를 들어 검색 순위의 MRR은 못 찾은 항목을 0으로 계산하는 규칙을 쓸 수 있다. 근거 지표에서 측정값이 없는 행을 제외한다는 이유로, 검색 실패 행을 모든 지표의 분모에서 빼면 안 된다.
이번에 확인한 범위
저장소의 집계 함수와 표시 함수를 가져와 TypeScript 5.6.3으로 ES2022 대상으로 변환한 뒤 Node.js v22.22.3에서 실행했다. 혼합 입력, 일부만 측정한 입력, 전부 측정 불가인 입력, 근거를 요구하지 않은 입력, 측정값이 0인 입력을 각각 확인했다. 앞의 독립 실행 예시도 별도로 실행했다.
확인한 것은 준비된 입력이 어떤 평균, 카운터, 문자열로 바뀌는지다. 실제 정답 데이터의 정확성, 근거 참조의 해석, 검색 품질, LLM 답변 품질, 브라우저 화면 동작은 이 실험으로 검증하지 않았다. 설명용 입력을 직접 넣었으므로 전체 API 경로의 검증 결과로 볼 수도 없다.
평가 결과를 읽는 순서
평가 평균을 비교하기 전에 근거를 요구한 행과 측정 가능한 행부터 확인하려 한다. 측정 범위가 달라졌다면 같은 질문을 넣었어도 평균이 설명하는 대상은 달라질 수 있다. 측정 불가 항목을 조사하고, 비교 대상과 조건을 맞춘 뒤 검색 설정의 효과를 판단하는 편이 낫다.
낮은 점수를 고치는 일과 점수를 낼 수 없는 상태를 고치는 일은 다르다. 평가 코드가 그 차이를 보존해야 개발자도 어디부터 고칠지 결정할 수 있다.
References
- MDN: Falsy:
0과null의 Boolean 변환 - MDN: Array.prototype.filter(): 필터 조건에 따른 요소 선택
- Introduction to Information Retrieval: Evaluation of unranked retrieval sets: 일반적인 precision과 recall의 정의
