Developer Log
AI & RAG

RAG 리트리버 튜닝: BM25 전환부터 RRF 최적화까지

Vantage에서 구현한 RAG 검색은 처음에 Dense 벡터 검색과 PostgreSQL FTS를 함께 사용했습니다. 하지만 기술 기사를 다룰 때는 정답 문서가 검색되는지뿐 아니라 정답이 몇 번째에 나타나는지, 여러 기사를 함께 찾아야 하는 질문에서 필요한 정보를 모두 회수하는지도 중요합니다.

문제와 평가 환경

  • 코퍼스: 기사 2,150건으로 고정한 평가 스냅샷
  • 골든셋: 개발용 143문항
  • 비교 대상: Dense, PostgreSQL FTS, ParadeDB BM25, FTS 하이브리드, BM25 하이브리드
  • 기본 융합: Reciprocal Rank Fusion(RRF), 초기 가중치 5:5
  • 평가 지표: Recall@5, MRR@5, nDCG@5, complete@5

Dense 검색은 표현이 달라도 의미가 유사한 문서를 찾는 데 유리하지만, 제품명이나 고유명사의 일치를 항상 보장하지 못합니다. 반면 lexical 검색은 정확한 표현에 강하되 어휘가 달라지면 놓칠 수 있습니다. 그래서 검색 엔진 자체의 성능과 두 검색 결과를 합치는 방식을 나누어 측정했습니다.

MRR@5는 첫 정답이 앞에 있을수록 높고, nDCG@5는 정답의 순위 품질을 반영합니다. complete@5는 필요한 정답을 모두 상위 5개에서 회수한 문항 비율입니다. 여러 기사를 함께 묻는 질문에는 complete가 중요한 보완 지표가 됩니다.

1. FTS에서 BM25로 전환

기존 PostgreSQL FTS의 ts_rank를 BM25로 교체했습니다. 이 과정에서 기존 pg_vector만 사용중인 postgres를 paradeDB로 교체했습니다.

한국어 키워드 처리에는 Kiwi 형태소 분석을 사용했습니다. 아래는 RRF의 k=60, 하이브리드 가중치 5:5, 검색 arm별 fetch_k=50 기준입니다.

검색 구성Recall@5MRR@5nDCG@5complete@5
Dense 단독0.9160.8440.8570.909
FTS 단독0.7900.6530.6780.762
BM25 단독0.9370.9060.9130.937
FTS + Dense0.9480.8540.8740.937
BM25 + Dense0.9790.9030.9210.979

BM25 하이브리드의 Recall@5는 FTS 하이브리드 대비 0.948에서 0.979로 3.1%p 개선, MRR@5는 0.854에서 0.903으로 4.9%p 개선됐습니다. complete@5도 0.937에서 0.979로 상승했습니다.

더 중요한 발견은 BM25 단독의 MRR@5가 0.906으로 Dense 단독 0.844보다 높았다는 점입니다. lexical 검색이 약하다는 이전 가정이 바뀌었으므로, 융합 파라미터도 새 엔진 기준으로 다시 살펴봐야 했습니다.

2. RRF의 k를 60에서 10으로 조정

RRF는 각 검색 엔진의 문서 순위를 합치는 방법입니다. k가 클수록 순위 차이가 완만하게 반영되고, 작을수록 각 검색 엔진의 상위 순위를 더 강하게 반영합니다.

BM25와 Dense에 동일한 가중치를 둔 상태에서 k를 1부터 200까지 바꿔봤습니다. Recall@5는 대부분 0.979 근처였지만, MRR과 nDCG는 작은 k에서 개선되는 경향을 보였습니다.

지표기존 k=60반영 k=10
Recall@50.9790.979
Recall@100.9860.979
MRR@50.9030.914
nDCG@50.9210.931
complete@50.9790.979

상위 5개 내 회수 성능을 유지하면서 정답을 더 높은 순위로 올리는 효과가 나타났습니다. 다만 Recall@10은 0.007 하락했습니다. 챗봇이 주로 상위 5개를 사용한다는 점을 고려해 k=10을 채택했습니다.

k=20에서 Recall@5가 일시적으로 0.986까지 올랐지만 인접 값에서 재현되지 않아 작은 평가셋의 변동으로 봤습니다. 현재 저장소의 RRF 기본 상수 역시 10으로 확인했습니다.

3. BM25 비중을 높여봤지만 유지하지 않은 이유

융합 가중치도 여러 비율로 실험했습니다. 아래 비율은 BM25 : Dense 순서이며 k=10에서 측정했습니다.

가중치Recall@5MRR@5nDCG@5
5:5 (유지)0.9790.9140.931
5.7:4.30.9790.9250.939
6:40.9720.9200.933
6.7:3.30.9580.9150.925

5.7:4.3에서 MRR은 더 높았지만 그 주변에서 Recall이 빠르게 떨어졌습니다. 143문항에서 최고점을 보인 좁은 구간을 선택하면 평가셋에 과적합할 위험이 있습니다. 새로운 가중치를 도입할 만큼 안정적인 이득이라고 보지 않아 5:5를 유지했습니다.

4. MMR과 벡터 재정렬은 적용하지 않았습니다

처음에는 같은 사건을 다루는 중복 기사가 상위 검색 결과를 차지해 MMR(Maximal Marginal Relevance)로 다양성을 높이려 했습니다. 하지만 스토리 그룹핑을 적용해 대표 기사 중심으로 검색한 뒤에는 해당 문제가 크게 줄었습니다.

BM25 전환 후 개발셋의 열거형과 다중 기사 종합형 문항에서 complete@5가 모두 1.000이었습니다. 추가 MMR로 얻을 개선 근거가 없어 구현 자체를 보류했습니다.

키워드 검색으로 후보를 만든 뒤 벡터 점수로만 재정렬하는 대안도 실험했습니다. BM25 후보 79건을 사용한 결과 Recall@5 0.976, MRR@5 0.870이었는데, 하이브리드의 0.979, 0.914에 못 미쳤습니다. 특히 강한 BM25 순위 신호를 최종 벡터 정렬이 덮어쓰는 문제가 있었습니다.

5. 리트리버 구조 변경

기사들은 이미 클러스터링이 적용되어 반환하는데 항상 그룹의 모든 기사를 보여주기엔 토큰 사용량이 너무 컸습니다. 대표기사 1개만 보여주고 새로운 도구를 추가하여 그룹 전체를 읽을 수 있게도 해봤지만 에이전트가 대표기사만 읽고 답변을 하여 일부 정보를 놓치는 문제가 발생하였습니다.

새로운 리트리버는 검색시 제목과 메타데이터만 보여주며 이를 에이전트가 선택하여 그룹 기사를 모두 확인하게 변경하였습니다. 이로 인해 에이전트에게 보여주는 정보를 일부 강제하였고 그 과정에서 사용 토큰 절감 효과도 얻었습니다.

  • 컨텍스트 길이 중앙값: 17,754자 → 3,935자 (약 78% 감소)

남은 한계

complete@5=0.979는 143문항 중 140문항에서 필요한 정답을 모두 회수한 수준입니다. 이미 개발셋이 상당히 포화해 한 문항 차이가 약 0.7%p를 만들었습니다. 튜닝 당시 별도의 홀드아웃 테스트는 설정 선택에 사용하지 않았으며, 운영 전체 코퍼스에서도 같은 개선이 유지되는지는 별도로 검증해야 합니다.

한국어 음차와 영문 고유명사 사이의 불일치 같은 문제도 남았습니다. 예를 들어 한국어로 적은 제품 이름을 원문 영문 표기와 연결하는 과정은 BM25만으로 해결되지 않습니다.

이번 작업에서 배운 점은 새 기법을 무조건 추가하는 것보다 평가 결과를 통해 효과 없는 변경을 과감히 버리는 것이 중요하다는 것입니다. BM25 전환과 RRF k 조정은 반영했고, 가중치 조정과 MMR은 더 확실한 근거가 생길 때까지 보류했습니다.