응답 품질 개선
벡터 DB에 지식을 쌓아 두고 유사도 검색만으로 LLM에 문서를 넘기면, 겉보기에는 RAG가 동작하는 것처럼 보여도 답변 품질이 기대에 못 미치는 경우가 많다. 질문과 의미는 비슷하지만 핵심 키워드가 빠진 청크가 상위에 오르거나, 반대로 키워드는 맞는데 문맥이 어긋난 문서가 끼어들기도 한다.
그래서 검색 단계를 한 번의 유사도 조회에서 끝내지 않고, 하이브리드 검색 → 유사도 게이트 → 리랭킹 → Top-N 선발로 나누어 정제하도록 파이프라인을 개편하였다. 이와 맞추어 청킹 설정도 잘게 쪼개 후보를 넓히는 쪽으로 조정했다.
의미 검색과 BM25를 섞는 하이브리드 검색, 크로스 인코더 리랭킹과 이중 필터링, 그리고 그에 맞춘 청킹·임계값 전략을 정리한다.
하이브리드 검색
하이브리드 검색은 벡터 DB의 의미 기반 유사도 검색(Dense Retrieval) 에 키워드 검색(BM25) 을 결합한 방식이다. 문서의 의미론적 유사도와 키워드 일치도를 함께 고려해, 한쪽만 쓸 때보다 검색 결과의 품질을 끌어올리는 것이 목적이다.
의미 검색만 하면 "비슷한 이야기"를 잘 찾지만, 질문자가 명시한 용어·제품명·고유 명사가 빠질 수 있다. 키워드 검색만 하면 단어는 맞지만 문맥이 어긋난 문서가 올라올 수 있다. 둘을 섞으면 순서가 바뀌기도 한다. 예를 들어 의미 유사도만 보면 ① 0.7 → ② 0.6 → ③ 0.5 순이던 결과가, 키워드를 반영하면 ② → ③ → ① 처럼 재정렬될 수 있다.
LangChain에서는 이 두 검색기를 묶는 EnsembleRetriever 를 제공한다. 의미 가중치와 키워드 가중치를 실수로 두고, 합이 1이 되도록 설정한다. 값이 높을수록 해당 방식이 최종 순위에 더 크게 반영된다.
| 항목 | 설정 |
|---|---|
| 의미 검색 | FAISS 코사인 유사도 기반 리트리버 |
| 키워드 검색 | BM25Retriever |
| 결합 | EnsembleRetriever |
| 가중치 | semantic_weight + (1 - semantic_weight) = 1 |
프로젝트에서는 RAG 설정의 semantic_weight 기본값을 0.7 로 두어, 의미 검색을 조금 더 우선하되 키워드도 반영하도록 했다.
필터링 및 리랭킹
질문 하나로 관련 문서를 찾아 LLM에 넣을 지식까지 고르는 과정은 대략 다음과 같다.
flowchart LR Q[질문] --> S[유사도 검색] S --> K[키워드 검색] K --> R[리랭킹] R --> T[Top-N 추출] T --> L[LLM] S -.-> F1[유사도 필터링] T -.-> F2[상위 N개 필터링]
이 워크플로에서 필터링은 두 번 일어난다.
- 유사도 필터링: 유사도 임계값 아래 문서를 걸러, 질문이 RAG 지식과 최소한의 연관이 있는지부터 판별한다.
- 상위 N개 필터링: 리랭크 후 재정렬된 순위에서 Top-N만 남긴다.
전략의 핵심은 첫 단계에서 후보를 충분히 확보하고(search_k 기준 약 20개), 두 번째 단계에서 LLM이 소화하기 좋은 수준(약 3~5개)으로 좁히는 것이다.
유사도 게이트
하이브리드 검색과 리랭킹은 비용이 크다. 그래서 본격적인 앙상블·리랭크에 들어가기 전, 의미 검색으로 최대 유사도만 먼저 확인한다. 임계값에 못 미치면 추가 검색을 생략하고 "관련 문서 없음"으로 처리한다.
기존에는 유사도 임계값을 대략 0.7 근처로 두는 경우가 많았다. 개편 후에는 질문이 RAG 데이터와 최소한의 연관성이 있는지 를 보는 게이트로 재정의하고, 0.4 ~ 0.5 부근으로 낮추었다. bge-m3 계열은 점수 분포가 상대적으로 낮게 나오는 경향이 있어, 임계값을 너무 높이면 관련 문서까지 잘리는 문제가 있었다.
리랭킹
하이브리드 검색만으로는 질문 의도·문맥과 문서의 관계를 깊게 보기 어렵다. 의미·키워드 점수가 높아도 질문과 동떨어진 문서가 위에 오르거나, 중요한 문서가 아래로 밀릴 수 있다.
리랭킹은 검색된 상위 문서들을 질문과 한 쌍으로 다시 읽어, 우선순위를 재정렬하는 단계이다. 여기서는 Full Self-Attention 기반의 크로스 인코더(Cross Encoder) 를 사용한다. 예로 bge-reranker-v2-m3 를 썼다.
크로스 인코더는 질문과 문서를 [SEP] 으로 이은 하나의 시퀀스로 넣는다. 시퀀스 안 모든 토큰이 서로 Attention을 수행하므로, 질문 의도, 문서 문맥, 질문-문서 연관성을 한꺼번에 보고 점수를 매긴다. 임베딩을 따로 만들어 거리만 비교하는 방식보다 느리지만, 후보가 이미 20개 안팎으로 줄어든 뒤에는 품질 이득이 크다.
LangChain의 CrossEncoderReranker 와 ContextualCompressionRetriever 로 앙상블 리트리버 위에 리랭커를 얹었다.
챗봇 응답 생성 시에는 RAG 설정값을 그대로 넘긴다.
flowchart TB A[전체 청크 풀] --> B[하이브리드 검색] B --> C["후보 약 20개 (search_k)"] C --> D[크로스 인코더 리랭킹] D --> E["최종 3~5개 (rerank_top_n)"] E --> F[LLM 참고 문서]
리랭크 후에는 단순 의미 검색에서 높았던 문서가 뒤로 밀리고, 낮았던 문서가 올라오는 일이 흔하다. 그만큼 "점수 높은 상위 k개"를 그대로 LLM에 넣던 방식의 한계를 보완한다.
| RAG 설정 | 기본값 | 역할 |
|---|---|---|
search_k | 20 | 하이브리드 검색 후보 수 |
score_threshold | 0.5 | 유사도 게이트 임계값 |
semantic_weight | 0.7 | 의미 검색 비중 (키워드는 0.3) |
rerank_top_n | 5 | 리랭크 후 LLM에 넘길 문서 수 |
청킹
검색·리랭크 메커니즘을 바꿨다면, 문서 분할(청킹)도 그에 맞게 다시 잡는 편이 좋다.
개편된 파이프라인의 핵심은 후보를 넓게 모은 뒤 리랭크로 최고만 고르는 것 이다. 따라서 청크를 크게 유지하기보다, 짧게 쪼개 지식을 고르게 분산시키는 쪽이 유리하다. 작은 청크가 많으면 하이브리드 검색이 다양한 조각을 후보로 올릴 수 있고, 리랭커가 그중에서 질문에 맞는 조각을 고를 여지가 커진다.
연구 과정에서 조정한 방향은 다음과 같다.
| 항목 | 이전 | 개편 후 |
|---|---|---|
| 청크 길이 | 1024 | 512 |
| 청크 오버랩 | 약 25% | 약 10~20% |
| 유사도 임계값 | 약 0.7 | 0.4 ~ 0.5 |
청킹 구현은 LangChain의 RecursiveCharacterTextSplitter 를 사용한다. 문단·문장·구두점 순으로 자르도록 separator를 두었고, 오버랩은 chunk_size * overlap_ratio 로 계산한다.
현재 제품별 RAG 설정의 기본값은 chunk_size=512, chunk_overlap_ratio=0.20 이다. 청크 크기나 오버랩을 바꾸면 기존 인덱스의 분할 단위와 어긋나므로, 관리 화면에서 해당 값을 변경하면 지식 소스를 다시 청킹·임베딩하도록 되어 있다.
유사도 게이트
질문이 지식과 최소 연관이 있는지 확인하고, 미달이면 검색을 중단한다.하이브리드 검색
의미 검색과 BM25를 가중치로 결합해 약 20개 후보를 모은다.리랭킹
크로스 인코더로 질문-문서 쌍을 다시 채점해 순서를 바꾼다.Top-N 전달
상위 3~5개만 컨텍스트로 넣어 LLM이 답변을 생성한다.
응답 품질은 LLM 모델만의 문제가 아니다. 어떤 청크를 만들고, 어떤 기준으로 후보를 거르고, 무엇을 최종 문맥으로 남기느냐가 답변을 좌우한다. 하이브리드 검색과 리랭킹, 이중 필터링, 작은 청크 전략은 그 전처리를 의도적으로 설계한 결과이다.