Contextual Compression Retriever로 노이즈 줄이기

📌 3줄 요약
· 벡터 검색으로 가져온 문서에는 질문과 상관없는 문단이 섞여 있는 경우가 많아, LLM에게 그대로 넘기면 노이즈가 됩니다.
· Contextual Compression Retriever는 검색으로 찾은 문서를 질문에 맞게 압축·선별해, 꼭 필요한 부분만 남겨 LLM에 전달합니다.
· 불필요한 내용을 걷어 내면 답변 품질이 올라가고 토큰 낭비도 줄어, RAG 파이프라인이 한층 깔끔해집니다.
❓ 질문 → 🔍 기본 검색 → 🗜️ 압축·선별 → ✅ 핵심만 전달

앞선 글들에서 Retriever로 문서를 검색하고, MultiQuery나 Self-Query 같은 기법으로 검색의 폭과 정확도를 넓히는 방법을 살펴봤습니다. 그런데 문서를 "잘 찾는 것"과 그 문서를 "잘 다듬어 쓰는 것"은 또 다른 문제입니다. 검색으로 데려온 문서 안에는 질문과 관계없는 문장이 적지 않게 섞여 있기 때문입니다. 오늘은 이렇게 검색 결과에서 필요한 부분만 추려 LLM에 넘기는 Contextual Compression Retriever를 정리해 보겠습니다.


🧭 검색 결과에 섞이는 노이즈 문제

벡터 검색은 질문과 의미가 비슷한 문서를 통째로 데려옵니다. 문제는 그 문서 하나가 여러 주제를 담은 긴 글일 때입니다. 예를 들어 "환불 정책이 어떻게 되나요?"라고 물었는데, 가져온 문서가 배송·결제·환불·회원가입을 모두 다루는 긴 안내문이라면, 그중 정작 질문과 관련된 부분은 몇 문장뿐입니다. 나머지는 답변에 도움이 되지 않는 노이즈입니다.

이 상태로 문서 전체를 LLM에 넘기면 두 가지 손해가 생깁니다. 하나는 토큰 낭비로, 쓸모없는 내용까지 모델에 밀어 넣으니 비용과 처리 시간이 늘어납니다. 다른 하나는 답변 품질 저하인데, 관계없는 문장이 많을수록 모델이 핵심을 놓치거나 엉뚱한 부분에 집중할 위험이 커집니다.

"문서를 많이 가져오는 것"이 곧 "좋은 답변"은 아닙니다. 질문과 무관한 내용이 많으면 오히려 방해가 됩니다. Contextual Compression은 가져온 뒤 걸러 내는 단계를 더합니다.

💡 Contextual Compression의 아이디어

Contextual Compression Retriever의 핵심은 이름 그대로입니다. Contextual(맥락에 맞게) + Compression(압축), 즉 "지금 질문이라는 맥락에 비추어 검색 결과를 압축한다"는 뜻입니다. 여기서 압축은 파일을 줄이는 압축이 아니라, 질문과 관련 없는 내용을 덜어 내 분량을 줄이는 것을 말합니다.

동작 구조도 단순합니다. 먼저 평소 쓰던 기본 Retriever로 문서를 가져옵니다. 그 다음 압축기(Document Compressor)가 각 문서를 질문과 대조하며, 관련된 부분만 남기거나 관련 없는 문서를 아예 버립니다. 그래서 Contextual Compression Retriever는 기존 Retriever를 감싸는(wrapping) 구조로 만들어집니다. 기본 검색은 그대로 두고, 그 위에 걸러 내는 한 겹을 더 얹는 셈입니다.


🗂️ 압축기(Compressor)의 종류

압축을 담당하는 부분을 압축기(Compressor)라고 부릅니다. 어떤 방식으로 걸러 내느냐에 따라 크게 아래처럼 나눠 볼 수 있습니다. 상황에 맞게 고르거나 여러 개를 이어 붙여 쓸 수도 있습니다.

LLM으로 추출하기
LLM이 문서를 읽고 질문과 관련된 문장만 뽑아 새 문서로 만듭니다. 정교하지만 매 문서마다 모델을 호출합니다.
LLM으로 필터링하기
LLM이 각 문서가 질문과 관련 있는지 없는지만 판단해, 관련 없는 문서를 통째로 제외합니다.
임베딩으로 필터링하기
문서와 질문의 임베딩 유사도를 계산해 기준치에 못 미치는 문서를 거릅니다. LLM 없이 빠르고 저렴합니다.
파이프라인으로 묶기
여러 압축기를 순서대로 연결해, 가볍게 거른 뒤 정교하게 다듬는 식으로 단계를 조합합니다.

정리하면 LLM 기반은 정확하지만 비용이 들고, 임베딩 기반은 빠르고 저렴한 대신 판단이 덜 섬세합니다. 먼저 임베딩으로 크게 걸러 내고 LLM으로 다듬는 조합이 실무에서 자주 쓰이는 이유가 여기에 있습니다.


🛠️ 기본 사용법 코드로 보기

아래는 기본 Retriever 위에 LLM 추출 압축기를 얹어 Contextual Compression Retriever를 만드는 예시입니다. base_retriever는 앞선 글에서처럼 미리 준비되어 있다고 가정합니다.

from langchain_openai import ChatOpenAI
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import LLMChainExtractor

# 1) 압축기 준비 (LLM이 관련 문장만 추출)
llm = ChatOpenAI(temperature=0)
compressor = LLMChainExtractor.from_llm(llm)

# 2) 기본 retriever를 압축기로 감싸기
compression_retriever = ContextualCompressionRetriever(
    base_compressor=compressor,
    base_retriever=base_retriever,
)

# 3) 질문하면 "압축된 문서"가 돌아옴
docs = compression_retriever.invoke("환불 정책이 어떻게 되나요?")

구조를 보면 base_retriever(기본 검색)와 base_compressor(압축기)를 한 쌍으로 묶는 것이 전부입니다. 검색 방식은 건드리지 않고, 그 결과를 걸러 내는 단계만 추가한 셈입니다. invoke로 돌아오는 문서는 원본 그대로가 아니라, 질문과 관련된 부분만 남은 압축본입니다.


🔎 문서가 걸러지는 과정

Contextual Compression Retriever가 질문을 받으면 대략 다음 단계를 거칩니다. 아래 표로 흐름을 정리했습니다.

단계 하는 일
1. 기본 검색base_retriever가 질문과 비슷한 문서를 평소대로 가져옵니다.
2. 대조압축기가 각 문서를 질문과 견주어 관련성을 따집니다.
3. 압축·선별관련된 문장만 남기거나, 관련 없는 문서를 제외합니다.
4. 전달다듬어진 문서만 다음 단계(LLM 답변 생성)로 넘깁니다.

여기서 2~3단계가 Contextual Compression의 진짜 핵심입니다. 같은 문서라도 어떤 질문이냐에 따라 남는 부분이 달라진다는 점이 중요합니다. "환불"을 물으면 환불 문단이, "배송"을 물으면 배송 문단이 남습니다. 즉 압축은 질문이라는 맥락에 따라 매번 다르게 이루어지며, 그래서 이름에 Contextual이 붙어 있는 것입니다.


🔧 실무 팁

  • 비용과 정확도 저울질 — LLM 압축기는 문서 수만큼 모델을 호출하므로, 문서가 많다면 임베딩 필터로 먼저 거른 뒤 LLM을 쓰는 편이 경제적입니다.
  • 파이프라인으로 조합 — 가벼운 필터 → 정교한 추출 순서로 압축기를 여러 단계 연결하면, 속도와 품질을 함께 챙길 수 있습니다.
  • 너무 세게 압축하지 않기 — 걸러 내는 기준이 과하면 정답이 담긴 문장까지 삭제될 수 있습니다. 기준치를 조정하며 적정선을 찾으세요.
  • temperature는 낮게 — 관련 문장을 추출·판별하는 작업은 일관성이 중요하므로, 모델 온도를 낮춰 매번 비슷하게 동작하도록 두는 편이 안전합니다.

❓ 자주 묻는 질문 (FAQ)

Q. 기존 Retriever를 바꿔야 하나요?
A. 아닙니다. Contextual Compression Retriever는 기존 Retriever를 감싸는 구조라, 쓰던 검색기는 그대로 두고 압축 한 겹만 덧붙이면 됩니다.

Q. 검색 결과 개수를 줄이는 것과 뭐가 다른가요?
A. 개수를 줄이는 것은 문서 단위로 덜어 내는 것이고, 압축은 한 문서 안에서도 관련 문장만 추려 냅니다. 긴 문서의 노이즈를 다루는 데 더 효과적입니다.

Q. 꼭 LLM이 있어야 하나요?
A. 아닙니다. 임베딩 유사도 필터를 쓰면 LLM 호출 없이도 관련 없는 문서를 거를 수 있습니다. 비용이 부담될 때 좋은 선택지입니다.


마무리

정리하면, Contextual Compression Retriever는 "문서를 잘 찾는 것"에서 한 걸음 더 나아가 "찾은 문서를 질문에 맞게 다듬는" 역할을 합니다. 기본 Retriever로 가져온 결과를 압축기가 질문과 대조해, 관련된 부분만 남기고 노이즈를 걷어 냅니다. 덕분에 LLM에 넘어가는 내용이 깔끔해져 답변 품질이 올라가고 토큰도 아낄 수 있습니다. 비용이 걱정된다면 임베딩 필터로 크게 거른 뒤 LLM으로 다듬는 조합을 떠올려 보세요. 검색은 "많이"보다 "정확하게"가 중요하다는 점을 기억해 두면 좋겠습니다. 다음 편에서는 검색 결과의 순서를 다시 매기는 또 다른 기법으로 이어가 보겠습니다.

댓글

이 블로그의 인기 게시물

LCEL — LangChain 표현식으로 체인 깔끔하게 잇기

Streaming — 답변을 실시간으로 흘려보내기

Callback와 로깅 — LangChain 내부 들여다보기