canwefly-log
PI Lab 8주 과정 · 4/6

#4지표는 올랐는데 체감은 나빠졌다 — RAG 고도화 두 달, 다섯 가지 역설

Vision 환각, 임베딩 분포, 올바른 거부 0점, 검색 ≠ 답변, 비결정성. RAG에서 진짜 어려운 건 측정이다.

by canwefly··36분 읽기·#pi-lab #rag #eval #llm #ai

들어가기 전에

2편은 인프라 레이어의 함정들이었다. 이번 편은 한 층 위, AI 모델과 평가 체계의 반직관적인 지점들을 모았다. 다섯 개의 에피소드가 공통으로 말하는 건 한 가지다.

"RAG에서 품질을 올리는 일은, 모델을 바꾸는 일이 아니라 측정을 똑바로 하는 일이다."

지표는 올라갔는데 사용자는 더 불편해하거나, 분명 올바른 응답인데 평가 함수가 0점을 주거나, 같은 설정으로 두 번 돌렸는데 0.80 vs 0.40이 나오거나. 이런 순간들이 8주 내내 반복되면서 얻은 감각에 대한 기록이다.


에피소드 1 — Vision 해상도를 올렸더니 환각이 늘었다

증상

RAG 챗봇에 sample-report.pdf를 올리고 그래프 관련 질문을 던진 사용자와의 대화 로그다.

사용자: 국가별 미국 평균관세율 그래프에 수치가 있지 않나요? 어시스턴트: 해당 문서에는 수치가 명시되어 있지 않습니다.

사용자: 그래프 안에 텍스트로 있어요. 어시스턴트: 문서에 따르면 해당 수치는 약 2.5%입니다.

사용자: 그건 다른 그래프 아닌가요? 어시스턴트: 죄송합니다. 확인해보니 4.1%입니다.

사용자: ... 왜 계속 바뀌죠?

같은 문서, 같은 질문, 매번 다른 숫자. 이 문제를 해결하겠다고 일주일을 태웠다.

1차 시도 — Vision 파이프라인 강화

직관적인 접근부터 했다. "Vision 모델에 더 선명하게 보여주면 되겠지."

  • 차트 영역 크롭: 파서(opendataloader-pdf)의 figure 좌표와 pdfplumber page.images를 조합해 차트로 추정되는 영역의 bbox를 잡고, 그 영역만 잘라 300dpi로 Vision에 전달
  • detail: "high" + 300dpi: GPT-4o Vision 고해상도 모드
  • has_data_labels 판별: "이 그래프에 숫자 라벨이 직접 표기돼 있는가?"를 boolean으로 답하게 하고, false면 수치 추출 자체를 포기

구조상 완벽했고, 데이터 라벨 없는 그래프에서 수치 환각이 사라졌다. 근데 세 방향에서 문제가 터졌다.

문제 1 — 비용·시간

업로드 한 번에 Vision API 50건 호출, 총 142초 이상. 사용자가 PDF 업로드 후 2분 넘게 기다리게 두긴 어렵다. 비용도 부담.

문제 2 — 수치는 막았는데 추세가 환각의 통로가 됐다

데이터 라벨이 없는 그래프에서 Vision이 생성한 trend_description:

"명목임금 상승률은 전반적으로 완만하게 증가하는 추세입니다."

실제 그래프는 급격한 등락이 있는 차트였다. Vision이 "대충 우상향이니 완만하게 증가"로 요약한 것. 수치를 막으니 추세가 환각의 새 통로가 됐다.

문제 3 — LLM이 프롬프트를 무시했다

Vision이 만든 청크에는 명시적으로 이렇게 적혀 있었다:

"이 그래프에는 데이터 라벨이 표기되어 있지 않아 정확한 수치를 제공할 수 없습니다."

그런데 LLM의 최종 응답:

"2025년의 전년대비 증감률은 2.5%입니다[3]"

출처 [3]으로 인용한 청크에는 그 숫자가 없었다. LLM이 문맥에서 "이 정도 수치가 자연스럽겠다"는 느낌으로 숫자를 생성한 것이다. 프롬프트에 "절대 숫자를 만들어내지 마세요"를 강하게 박아도, LLM은 가끔 사전 지식을 끌어와 답변을 완성했다.

깨달음 — Vision 강화로는 못 넘는 벽

일주일을 태우고 한 걸음 물러서서 보니 답이 이미 있었다.

  • 데이터 라벨 있는 그래프: 어차피 본문 텍스트에도 같은 수치가 있다. Vision 없이도 RAG로 답할 수 있다.
  • 데이터 라벨 없는 그래프: Vision도 정확히 못 읽는다. 추세 설명은 환각의 통로가 된다.

결국 Vision은 가치를 추가하지 못하면서 비용·시간·환각 위험을 올리고 있었다.

2차 설계 — Vision을 빼고, 출처 정직성으로

목표를 재정의했다.

🎯 "정확한 답변"이 아니라, "없으면 없다고, 다른 곳에서 가져왔으면 다른 곳에서 가져왔다고 솔직하게 말하는 답변".

출처 정직성 규칙 (RAG system prompt 발췌)
 
1. 컨텍스트에 없는 수치를 절대 만들어내지 마세요.
2. 그래프 질문이지만 본문에서 답을 가져왔다면 출처를 밝히세요:
   "그래프에서 직접 확인하기 어렵지만, 본문에 따르면 ..."
3. 컨텍스트에 아예 없으면 "없다"고 단정하지 말고
   "시스템 한계로 읽지 못했을 수 있다"고 구분하세요.

파이프라인이 더 단순해졌고, 더 빨라졌고, 더 정직해졌다.

교훈

  1. "더 좋은 모델"로 풀 수 없는 문제가 있다. 근본적으로 못 읽는 건 못 읽는다. 엔지니어링의 무게 중심은 "더 잘 읽게 만들기"가 아니라 "못 읽는 것을 어떻게 다룰 것인가" 다.
  2. 복잡도는 환각의 연료다. 더 많은 컴포넌트를 붙이는 것이 거의 항상 "더 정확해짐"과 동의어는 아니다. 때로는 빼는 것이 품질을 올린다.
  3. 사용자는 틀린 답보다 "모른다"는 답을 선호한다. 신뢰 문제다.

에피소드 2 — 임베딩 모델마다 유사도 분포가 이렇게 다를 줄이야

맥락

RAG에서 threshold는 흔히 "유사도 몇 점 이상만 검색 결과로 쓸까"를 정하는 파라미터다. 튜토리얼에서는 보통 threshold=0.5로 시작한다. 나도 그랬다.

그런데 모델을 바꿔가며 실험해보니 이 숫자가 모델마다 꽤 다른 의미를 갖는다는 걸 알게 됐다.

실험 — 같은 질문·같은 문서, 임베딩 모델만 교체

한국어 멀티모달 영상 데이터셋에 동일한 질문셋을 투입해서, 세 모델의 유사도 분포를 측정했다. 아래 수치는 이 실험의 데이터(한국어 뉴스·인터뷰 영상 전사 텍스트)에서 관측된 값이고, 모델의 일반 특성으로 일반화하는 게 아니라는 점을 먼저 밝혀둔다.

모델차원이 데이터에서 관측된 유사도 범위threshold=0.5 통과율관찰
nomic-embed-text7680.65 ~ 0.87거의 전부높은 범위에 압축 → 관련/비관련 구분 흐림
bge-m310240.39 ~ 0.64대부분적절한 분포 → threshold 필터링 유의미
text-embedding-3-small15360.22 ~ 0.52거의 없음낮은 범위 → threshold=0.5면 거의 0건 검색

실제로 text-embedding-3-smallthreshold=0.5를 적용했더니 9개 질문 중 8개가 0건 검색됐다. GPT-4o를 답변 LLM으로 쓰는데도, 임베딩이 이 데이터에서 유사도를 0.22~0.52 범위에 압축해서 필터링을 전부 잘라버린 것. (이 모델은 영어·다국어 태스크에서는 훨씬 더 넓은 분포를 보인다. 우리 데이터 특성과 한국어 전사 텍스트의 스타일이 겹쳐 생긴 결과로 본다.)

반대로 nomic-embed-text는 모든 한국어 텍스트를 0.65~0.87의 좁은 범위에 몰아넣었다. "이 식당의 주소는?" 같은 완전히 무관한 질문도 유사도 0.88로 나온다. 이 데이터에서는 threshold 필터링이 거의 효과가 없었다.

인사이트 — threshold는 모델에 종속적

이 실험에서 얻은 감각 세 가지.

  1. 유사도 절대값은 모델 간 비교 불가. nomic의 0.7과 bge-m3의 0.5는 같은 의미가 아니다. 오히려 정반대에 가깝다.
  2. 우리 데이터에서는 다국어 특화 모델(bge-m3)이 한국어 구분력이 가장 선명했다. nomicemb-3-small은 분포가 한쪽으로 몰려 threshold 기준을 잡기 어려웠다. 모델의 "공식적 성능"과 실제 이 데이터에서의 유사도 분포 특성은 별개 차원이다.
  3. 모델 교체 시 threshold 재조정은 필수. "모델만 바꿨을 뿐인데 동작이 이상하다"는 십중팔구 이 문제다.

왜 이 문제가 자주 묻히는가

대부분의 RAG 튜토리얼은 영어 데이터로 작성된다. 영어에서는 이 세 모델의 유사도 분포가 훨씬 더 일관되게 나온다. 한국어에서 이런 차이가 두드러지게 나타난다.

튜토리얼에서 threshold=0.5로 잘 되던 게, 한국어 프로덕션에서는 안 된다. 이걸 안 겪어보면 "왜 검색이 안 되지?" 하고 며칠 날린다.

그래서 동적 threshold가 필요하다

고정 threshold의 한계를 피하려고, Top-K + 최소 보장 패턴을 도입했다.

MIN_RESULTS = 2
all_results = supabase.rpc(MATCH_RPC, {...}).execute()
filtered = [r for r in all_results if r["similarity"] >= threshold]
if len(filtered) < MIN_RESULTS:
    filtered = all_results[:MIN_RESULTS]  # fallback
return filtered
  • Threshold 이상: 최소 품질 보장
  • 최소 2건 미달 시: 유사도 무관 상위 2건 강제 포함

"정답 세그먼트 유사도가 threshold 아래에 있어서 0건 검색되는" 케이스를 구조적으로 방어한다. 물론 C유형 질문(답이 없는 질문)에서 무리한 답변을 생성할 위험도 함께 올라가니, 프롬프트 레이어의 환각 방지와 함께 가야 한다.

교훈

  1. threshold=0.5 같은 "업계 표준 숫자"는 없다. 모델·언어·데이터별로 다 다르다.
  2. 모델 교체는 threshold 재실험을 동반해야 한다. 이게 귀찮다고 건너뛰면, 모델 바꾼 효과를 절반도 못 본다.
  3. 고정 threshold는 필연적으로 실패한다. 동적 threshold 혹은 최소 보장 패턴이 필수.

에피소드 3 — "올바른 거부"가 0점을 받고, 환각이 높은 점수를 받는 평가 함수

맥락

8주 동안 가장 정신을 많이 쓴 주제가 "LLM-as-Judge 기반 평가 시스템" 이다. 자동 평가가 없으면 RAG 고도화는 미신이고, 있는데 틀리면 미신보다 더 나쁘다.

증상 — 정답이 0점을 받는 역설

평가 파이프라인이 이렇게 생겼다. 질문셋을 A/B/C 세 유형으로 분류한다.

  • A유형: 문서에 답이 있는 질문
  • B유형: 문서에 부분 정보만 있는 모호한 질문
  • C유형: 문서에 답이 전혀 없는 질문 (환각 유도용)

각 응답에 대해 calculate_answer_relevance(question, answer)를 호출해서 LLM에게 "이 답변이 질문에 얼마나 잘 답하는가?"를 0~1로 평가시킨다. 그런데 C유형에서 이상한 일이 벌어졌다.

질문 (C유형): 쿠팡의 2025년 연간 매출액은? 답변 A: "해당 영상에서는 쿠팡의 연간 매출액이 언급되지 않습니다." answer_relevance 점수: 0.0 ❌ (정직한 거부인데 0점)

답변 B: "쿠팡의 2025년 연간 매출액은 약 25조 원입니다." answer_relevance 점수: 0.8 ✅ (완전한 환각인데 높은 점수)

완전히 거꾸로였다. 올바른 행동이 벌점을 받고, 환각이 상을 받는다.

원인 — 평가 함수의 설계 한계

answer_relevance는 구조적으로 "질문에 답하는 정도"를 측정한다. "해당 정보는 없습니다"라는 답변은 질문이 요구하는 정보를 제공하지 않는 응답으로 분류되어 낮은 점수를 받는다.

RAGAS 같은 외부 평가 프레임워크로 교체해봤지만, 같은 문제가 더 크게 드러났다.

RAGAS로 넘어갔더니 더 심해졌다

RAGAS의 AnswerRelevancy는 이렇게 동작한다.

답변: "해당 내용은 이 영상에서 확인되지 않습니다."
  ↓ LLM이 답변으로부터 역질문 생성 (3회)
역질문: "어떤 정보가 확인되지 않았나요?"
  ↓ 원래 질문 "쿠팡의 연간 매출액은?"과 코사인 유사도 비교
  → 유사도 ≈ 0  (완전히 다른 질문)

거부 답변에는 "어떤 질문에 대한 거부인지" 정보가 들어 있지 않아서, 역생성 방식으로는 원래 질문을 복원할 수 없다. RAGAS의 버그가 아니라 설계 범위의 한계다. RAGAS는 "답변이 있는 상황"을 전제로 만들어졌다.

해결 시도 1 — context 파라미터 추가

calculate_answer_relevance(question, answer, **context**)로 시그니처를 바꾸고, 채점 기준을 다시 썼다.

1.00: 컨텍스트 근거를 들어 구체적으로 답변
0.85: 적절히 답변하나 근거 인용 부족
0.70: 컨텍스트에 실제로 없는 정보를 올바르게 거부 (올바른 거부)
0.40: 컨텍스트에 정보가 있는데도 못 찾고 거부 (잘못된 거부)
0.00: 전혀 관련 없거나 없는 내용을 지어냄 (환각)

이제 올바른 거부는 0.70점, 환각은 0점으로 나온다. 순서가 맞아졌다.

해결 시도 2 — 유형별 평가 도구 분담

더 근본적인 해결은 "모든 질문 유형을 하나의 지표로 평가하겠다는 욕심을 버리는 것" 이었다.

유형평가 도구이유
A (근거 기반)RAGAS Faithfulness, AnswerRelevancy답변이 있는 상황 → RAGAS 본래 설계에 적합
B (모호)LLM-as-Judge (context 포함 answer_relevance)올바른 거부도 0.7점으로 평가 필요
C (답변 불가)rejection_rate (패턴 매칭)거부 성공/실패 이진 판정 → 패턴 매칭이 가장 정확

A유형은 RAGAS로 정밀하게, B유형은 커스텀 LLM Judge로 올바른 거부 구분, C유형은 아예 규칙 기반 패턴 매칭. 각 유형에 맞는 도구가 다르다.

C유형을 규칙 기반으로 한 이유가 재밌다. "LLM에게 거부 여부를 판정시키는 것보다, 단순 문자열 패턴 매칭이 더 정확"했기 때문. "확인되지 않습니다", "언급되지 않았습니다" 같은 키워드가 있으면 거부 성공. 끝. LLM이 오히려 잘못된 판정을 하는 경우가 더 많다.

또 하나의 함정 — 평가 LLM의 "0.7 천장"

로컬 llama3.1(8B)를 평가 LLM으로 쓸 때, 또 다른 이상한 현상이 나타났다.

모든 좋은 답변에 0.7점이 찍힌다. 타임스탬프를 정확히 인용하고, 근거를 들고, 질문에 정밀하게 답변한 답도 0.7. 조금 애매한 답도 0.7. 4단계 채점 루브릭(1.0/0.7/0.4/0.0)에서 소형 모델은 중간값에 몰리는 경향이 있다.

개선 효과를 수치로 측정하려 해도, 평가 LLM이 모든 실험을 0.7점으로 평평하게 만들어버린다. 이게 평가 LLM과 답변 LLM이 독립적이어야 한다는 원칙의 근거다. 우리는 최종적으로 평가 LLM을 GPT-4o로 고정했다. 실험 설정(답변 LLM, 프롬프트, threshold)을 아무리 바꿔도 평가 기준 자체가 일관되도록.

교훈

  1. "평가 지표가 모든 유형에서 올바르게 작동한다"는 가정을 의심하자. 대부분의 지표는 특정 유형을 전제로 설계됐다.
  2. 모든 유형을 하나의 지표로 평가하려는 욕심을 버린다. A는 RAGAS, B는 커스텀 Judge, C는 패턴 매칭. 도구 분담이 정직한 방향.
  3. 평가 LLM과 답변 LLM은 독립적으로 선택한다. 평가 LLM은 항상 답변 LLM 이상의 성능이어야 한다. 소형 모델은 평가에서 "천장"이 생긴다.
  4. 때로는 단순한 패턴 매칭이 LLM보다 정확하다. 거부 여부 판정 같은 이진 결정은 특히 그렇다.

에피소드 4 — 검색은 맞았는데, 답변은 틀렸다

맥락

RAG 파이프라인을 개선하는 흔한 직관은 이거다.

"검색 품질을 올리면 답변 품질도 올라간다."

틀린 말은 아닌데, 충분한 말도 아니다. 멀티모달 RAG에서 이 가정이 깨지는 케이스를 반복해서 만났다.

실험 — 테이블 병합 효과 측정

PDF의 표가 페이지 경계를 넘으면 청킹 과정에서 쪼개지는 문제가 있다. 이걸 해결하려고 페이지 분할 테이블 자동 병합 로직을 넣었다.

  • 케이스 1: 동일 헤더가 반복되는 경우 → 헤더 제외하고 행만 이어 붙임
  • 케이스 2: 헤더 없이 데이터만 있는 경우 → 컬럼 수 동일 + 숫자 포함이면 전체 행 추가

병합 후 동일 질문을 돌려봤다. 검색(Retrieval)에서 정답 청크가 포함되는 비율이 확연히 올랐다. 분할돼 있을 때는 헤더나 일부만 검색되던 것이, 병합 후 전체가 한 청크로 들어왔다.

근데 correctness(답변 정확도)는 기대만큼 안 올랐다. 어떤 질문에서는 오히려 떨어졌다.

원인 — LLM의 표·수치 추론 한계

정답이 포함된 청크를 LLM이 정확히 받아놓고도 틀리는 케이스가 있었다. 한 예시.

질문: 청킹 F1 점수가 가장 높은 모델은?
 
컨텍스트(정답 포함):
| 모델         | Semantic | Heading | Contextual |
|-------------|:--------:|:-------:|:----------:|
| BERT        |  86.8    |  82.3   |   79.1     |
| bge-m3      |  85.4    |  84.7   |   81.2     |
| text-small  |  78.9    |  76.2   |   72.8     |
 
LLM 답변: "bge-m3가 가장 높습니다. Contextual 값이 81.2로 최고입니다."

정답은 BERT의 Semantic 86.8. LLM이 행과 열을 헷갈리거나, 한 열만 보고 최대값을 판단하는 일이 반복해서 일어났다. **"모든 행·열을 비교하라"**는 지시가 없으면, LLM은 표를 보고도 부분적인 비교만 한다.

인사이트 — "검색 품질"과 "답변 품질"은 독립적이다

이 경험에서 얻은 감각이 하나 있다.

"RAG 파이프라인은 두 개의 독립적인 품질 축을 갖는다: 검색 품질 (Recall·Precision) 과 답변 품질 (Groundedness·Accuracy). 둘 중 어느 하나가 망가지면 전체가 망가진다."

이건 평가에도 반영해야 했다. "검색에서 정답 청크가 포함됐는가""LLM이 그 청크로 올바른 답을 만들었는가"분리해서 측정하지 않으면, 어디서 문제가 터졌는지 파악할 수 없다.

더 큰 실험 — 변인별 효과 분리

멀티모달 RAG에서 5회 실험을 진행하면서 변인별 효과를 격리해봤다. 주요 발견:

변인주요 효과부작용
rerank 적용faithfulness +0.106table_retrieval -0.222, 답변 불가 4건 (과잉 필터링)
strict 프롬프트correctness +0.10rejection_accuracy -0.20
top_k 증가 (3→5)텍스트+이미지 교차 검증 성공무관 청크 유입으로 C유형 환각 증가
테이블 병합분할 테이블 복원 성공청크 경계 재구성의 연쇄 효과 — 다른 질문 회귀

어떤 변인도 모든 지표를 동시에 올리지 못했다. 한쪽을 올리면 다른 쪽이 내려간다. 이게 실제 RAG 엔지니어링의 모습이다.

반직관적 관찰 — 리랭커를 붙였더니 Retrieval Precision이 하락했다

리랭커(LLM 기반 2-stage retrieval)를 붙이면 Retrieval Precision이 오히려 떨어지는 결과가 나왔다. 돌이켜보면 recall-precision 트레이드오프의 교과서적인 케이스지만, 실험 중에는 숫자의 의미를 한 박자 늦게 잡았다.

지표Baseline (OFF)Rerank (ON)Δ
Answer Relevance (A/B)0.9441.000+0.056
Groundedness0.6000.709+0.109
Retrieval Precision0.5270.400-0.127
RAGAS Faithfulness0.8410.861+0.020

Precision은 떨어졌는데, 답변 기준 지표(Relevance, Groundedness, Faithfulness)는 전부 올랐다.

해석하면 이렇다. 리랭커는 threshold=0.1로 넓게 30개 후보를 가져와 그중 5개를 선별한다. 개별 후보의 유사도는 낮아도 리랭커가 실제로 관련 있는 청크를 잘 골라낸다. 그래서 "검색된 청크의 평균 품질(Precision)"은 떨어져 보이지만, "답변 품질"은 오히려 올라간다.

📌 핵심: Retrieval Precision이라는 단일 지표로 RAG 품질을 판정할 수 없다. 파이프라인의 여러 지표를 함께 봐야 실제 상태가 보인다.

교훈

  1. 검색이 맞아도 답변이 틀릴 수 있다. LLM의 표·수치 추론에는 별도의 한계가 있다.
  2. 검색 품질과 답변 품질은 다른 축이다. 평가도 분리해서 측정해야 한다.
  3. 어떤 개선도 부작용을 동반한다. 공짜 점심은 없다. 개선안이 "어떤 지표를 올리고 어떤 지표를 내리는지"를 같이 기록해야 한다.
  4. 단일 지표로 품질을 판정하지 말자. 특히 Retrieval Precision 같은 지표는 다른 지표와 함께 봐야 의미가 생긴다.

에피소드 5 — 같은 설정으로 두 번 돌렸는데, 0.80 vs 0.40이 나왔다

증상

페어와 동일한 파라미터·동일한 문서·동일한 질문셋으로 각자 실험을 돌렸다. 결과가 이랬다.

지표팀원 A팀원 B (같은 설정)
correctness0.500.80
rejection_accuracy0.800.40

같은 설정인데 점수가 거의 반반씩 뒤집혔다. 누구 실험이 "진짜"인가?

A/B 비교 UI — 같은 질문을 두 가지 설정으로 나란히 실행한 결과 화면 A/B 비교를 한 화면에 띄워 놓고 보기 시작하자, 1회 실행의 결과가 얼마나 자주 뒤집히는지가 눈에 들어왔다.

원인 — LLM 비결정성 × 소규모 데이터셋

두 가지가 곱해진 결과였다.

  1. LLM은 결정적(deterministic)이지 않다. 같은 프롬프트로 여러 번 돌려도 답이 조금씩 다르다. temperature=0이어도 완전한 결정성은 보장되지 않는다.
  2. 데이터셋이 작다. 우리 실험의 C유형은 5개 질문이었다. 5개 중 1~2개만 결과가 바뀌어도 점수가 0.20~0.40씩 움직인다.

그러니까 "동일 설정 1회 실행"의 결과는 신뢰할 수 없다. 시그널보다 노이즈가 더 크다.

더 본질적인 문제 — correctness가 뒤집혀 있었다

더 꼼꼼히 살펴보니 더 이상한 일이 벌어지고 있었다.

  • 팀원 A의 correctness 0.50 중 일부 질문은, 환각 답변이었는데 LLM-as-Judge가 **"정확하다"**고 채점했다.
  • 팀원 B의 correctness 0.80은 정직한 답변이 많았는데, 오히려 낮은 점수를 받은 건도 있었다.

LLM-as-Judge가 환각을 "정확하다"고 False Positive로 채점하는 케이스가 꾸준히 나타난 것. 이건 평가 LLM을 바꿔도 100% 제거되지 않는다.

해결 — 반복 3회 + 수동 검증

이 문제는 "더 좋은 측정 도구"로 풀 문제가 아니라, 실험 방법론으로 풀어야 했다.

원칙 1 — 변수 1개 격리 × 반복 3회

한 번에 한 변수만 바꾸고, 각 설정을 최소 3회 반복 실행해서 평균과 분산을 본다. 1회 실행의 결과는 기록하되, 확정 결론으로 쓰지 않는다.

원칙 2 — 수동 검증 5~10문항 병행

자동 지표가 아무리 올라도, 수동 검증에서 "어 이거 이상한데?"가 나오면 부분 성공으로 분류한다. 반대로 지표가 안 올라도 수동 검증에서 개선이 보이면 기록한다.

자동 지표와 수동 검증이 불일치할 때가 진짜 중요한 순간이다. 대부분 LLM-as-Judge의 False Positive나 평가 데이터셋의 맹점이 드러난다.

원칙 3 — 실험 결과에 불확실성 범위를 기록한다

실험 #17 — k=5, threshold=0.3, rerank=ON
 
- 3회 실행 평균:
  correctness = 0.72 ± 0.08
  rejection_accuracy = 0.80 ± 0.05
- 수동 검증 (5문항): 4/5 개선, 1건 환각
- 결론: "개선" 로 분류 (1회 최고 0.85는 노이즈로 판단)

단일 숫자가 아니라 평균 ± 표준편차로 적는 것만으로도 해석이 완전히 달라진다.

RAGAS 도입으로도 완전히 해결되지 않았다

RAGAS를 도입해서 LLM-as-Judge 의존도를 줄이려고 했다. 근데 RAGAS 활성/비활성만 바꿔도 평가 점수가 0.05 이상 변동하는 현상이 재확인됐다. 문제의 뿌리는 "평가 도구" 하나가 아니라, LLM 기반 평가 전체의 특성이었다.

교훈

  1. LLM은 결정적이지 않다. "한 번 돌렸을 때 좋았다"는 근거가 되지 못한다.
  2. 소규모 데이터셋에서의 점수 차이는 노이즈가 시그널보다 크다. 결론 내리기 전에 반복 실행이 필수.
  3. LLM-as-Judge는 환각을 정답으로 False Positive 채점할 수 있다. 수동 검증은 선택이 아니라 필수.
  4. 실험 결과는 "평균 ± 분산"으로 기록한다. 단일 숫자는 거의 항상 거짓말한다.

다섯 에피소드의 공통점

이 다섯 가지는 전부 "평가를 똑바로 한다는 것이 얼마나 어려운가" 에 대한 이야기다.

  • 모델을 바꿔도 유사도 분포가 달라 threshold 의미가 변한다 (2)
  • 평가 함수가 올바른 행동을 벌점 주고 환각을 칭찬한다 (3)
  • 검색 품질과 답변 품질은 다른 축이라 단일 지표로 판정 불가 (4)
  • LLM 비결정성 × 소규모 데이터 → 1회 실험은 노이즈 (5)

그리고 1번 Vision 환각 에피소드는 이 모든 것의 원천 같은 이야기다. "더 좋은 모델·더 높은 해상도가 더 좋은 결과를 준다"는 직관이, RAG에서 얼마나 자주 배신당하는지에 대한 기록.

결국 내가 8주 동안 가장 크게 바뀐 건 "어떻게 하면 잘 될까"에서 "어떻게 하면 잘 되고 있는지 알 수 있을까"로 질문이 옮겨간 것이었다.

RAG에서 품질을 올리는 일은 모델을 바꾸는 일이 아니라 측정을 똑바로 하는 일이다.

이 감각이 없으면 고도화의 모든 실험은 미신이 된다.


다음 편

평가 체계의 역설을 풀고 나면, 다음 층은 인프라 함정이다. 같은 코드인데 환경이 바뀌면 조용히 무너지는 순간들 — 다음 편은 그쪽 이야기.



프론트엔드 6년차 개발자가 PI Lab에서 AI 엔지니어링 8주 과정을 수료하며 정리한 기록입니다. 매일 Cursor·Claude로 바이브코딩하면서도 머신러닝 안쪽은 잘 몰랐던 개발자가, AI 시스템을 본격적으로 들여다본 회고입니다.