어둠 속 노드들이 가는 선으로 연결되고 하나만 오렌지색으로 빛나는 성좌 이미지

RAG + Eval — 평가 게이트가 있는 검색증강 파이프라인

의존성 0개 순수 Python RAG. 골든셋 40문항 평가 하네스가 CI 게이트로 붙어 있어 검색 품질이 조용히 후퇴할 수 없다.

2026설계·구현·평가셋 전부 (1인)

  • Python (stdlib only)
  • BM25 + Dense (RRF)
  • Ollama
  • CI eval gate
90.6%실측hit@3 — 골든셋 32문항 (CI 오프라인 모드)
100%실측범위 밖 질문 거절 재현율 (refusal recall)
40문항실측골든 평가셋 (in-scope 32 + out-of-scope 8, EN+KO)

문제

RAG 데모는 흔하다. 문제는 대부분 평가가 없다는 것이다. 검색 품질이 후퇴해도 아무도 모르고, “그럴듯한 답”과 “근거 있는 답”이 구분되지 않는다. 채용 시장에서도, 실무에서도, 2026년 RAG/에이전트 작업의 가장 큰 차별점은 모델이 아니라 평가 규율이다.

접근

작지만 끝까지 측정되는 파이프라인을 만들었다. 서드파티 의존성 0개(순수 Python stdlib), 로컬 LLM으로 생성 비용 $0.

  1. 하이브리드 검색. BM25(희소) + 코사인(밀집)을 Reciprocal Rank Fusion으로 융합 후 재순위. 임베더는 플러그블 — ollama(실제 시맨틱) 또는 hash(결정론적, 오프라인) 두 모드라서 CI가 GPU·네트워크 없이 돈다.
  2. 골든셋 40문항. 답이 코퍼스에 있는 32문항 + 없는 8문항(EN+KO). 답이 없을 때 “모른다”고 말하는지(refusal)까지 채점한다.
  3. CI 게이트. hit@k, answer keyword recall, refusal precision/recall, citation accuracy 네 지표에 임계값을 걸고, 하나라도 미달이면 빌드가 실패한다(exit ≠ 0). 인용 정확도는 LLM 심판이 아니라 결정론적 문자열 대조로 잰다 — 평가 자체가 재현 가능해야 하므로.

결과 (실측 — 리포 fixture 기준, 명령어로 재현 가능)

모드 임베더 hit@3 answer recall refusal P / R citation acc
CI (오프라인) hash 90.6% 80% / 100% 33.3%
로컬 (풀) nomic-embed-text 84.4% 75.0% 66.7% / 100% 48.4%

두 모드 모두 CI 게이트 임계값을 통과한다. 수치가 완벽해 보이지 않는 것은 의도다 — 오프라인 프록시는 해시 임베더 + BM25 신호가 정직하게 잴 수 있는 만큼만 재도록 보정했다.

한계와 실패 모드

  • 코퍼스 10청크 규모의 데모다. 수만 청크 규모의 인덱싱·캐싱·비용 문제는 이 리포가 증명하지 않는다.
  • citation accuracy는 top-k 구조상 낮게 나오는 프록시(오프라인 33.3%)로, README에 계산 방식과 한계를 그대로 적었다.
  • refusal 정밀도(66.7~80%)는 개선 여지가 있다 — 거절해야 할 것을 거절하는 능력(재현율 100%)을 우선했고, 과잉 거절이 남아 있다.

배운 것

“평가 없는 RAG”는 출하되지 않은 기능과 같다. 평가를 CI에 넣는 순간, 검색 품질은 감(感)이 아니라 회귀 테스트가 있는 코드가 된다. 다음 단계는 pgvector 기반 라이브 데모로 같은 골든셋을 옮겨 클라우드 환경에서도 동일 게이트를 유지하는 것.