Skip to main content

검색 (Retrieval)

검색은 RAG 파이프라인의 두 번째 단계로, 사용자 질문과 관련된 문서 청크를 벡터 데이터베이스에서 찾아오는 과정입니다. 검색 품질이 곧 답변 품질을 결정합니다.

검색 파이프라인

벡터 검색 (Dense Retrieval)

벡터 검색은 쿼리와 문서를 동일한 임베딩 공간에 매핑한 뒤, 벡터 간 거리로 관련성을 판단합니다.
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma

embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma(
    collection_name="documents",
    embedding_function=embeddings,
    persist_directory="./chroma_db",
)

# 기본 유사도 검색
results = vectorstore.similarity_search(
    query="RAG의 검색 단계에서 가장 중요한 것은?",
    k=4,
)

for doc in results:
    print(f"[{doc.metadata.get('source', 'unknown')}] {doc.page_content[:100]}...")

유사도 메트릭

벡터 간 유사도를 측정하는 세 가지 주요 메트릭입니다.
두 벡터의 방향이 얼마나 유사한지를 측정합니다. 크기(길이)에 영향을 받지 않아 텍스트 검색에 가장 널리 사용됩니다.cosine(A,B)=ABAB\text{cosine}(A, B) = \frac{A \cdot B}{\|A\| \|B\|}
  • 범위: -1 ~ 1 (1에 가까울수록 유사)
  • 적합한 경우: 문서 길이가 다양할 때, 정규화된 임베딩
# Chroma 기본값
vectorstore = Chroma(
    collection_name="docs",
    embedding_function=embeddings,
    collection_metadata={"hnsw:space": "cosine"},
)

메트릭 비교

메트릭방향 고려크기 고려계산 속도권장 사용
CosineOX보통기본 선택 (대부분의 경우)
Euclidean (L2)OO보통클러스터링, 이상치 탐지
Dot ProductOO빠름정규화된 임베딩, 대규모 검색
대부분의 경우 Cosine Similarity를 기본으로 사용하세요. OpenAI, Cohere 등 대부분의 임베딩 모델이 정규화된 벡터를 반환하므로, Cosine과 Dot Product 결과가 동일합니다.

검색 파라미터

Top-K

검색 결과에서 상위 K개의 문서를 반환합니다.
# top-k 설정
retriever = vectorstore.as_retriever(
    search_kwargs={"k": 4},  # 상위 4개 반환
)
K 값장점단점
K=2~3노이즈 최소, 빠른 응답관련 정보 누락 가능
K=4~6균형 잡힌 선택-
K=8~10포괄적 정보 수집노이즈 증가, 토큰 소비
K=10+최대 커버리지비용 증가, 성능 저하

Score Threshold

유사도 점수가 임계값 이상인 문서만 반환합니다.
retriever = vectorstore.as_retriever(
    search_type="similarity_score_threshold",
    search_kwargs={
        "score_threshold": 0.7,  # 유사도 0.7 이상만
        "k": 10,                 # 최대 10개
    },
)

MMR (Maximal Marginal Relevance)

관련성과 다양성을 동시에 고려합니다. 중복된 내용의 문서를 줄이고 다양한 관점을 포함합니다.
retriever = vectorstore.as_retriever(
    search_type="mmr",
    search_kwargs={
        "k": 4,
        "fetch_k": 20,      # 후보 20개에서
        "lambda_mult": 0.5,  # 관련성 vs 다양성 균형 (0=다양성, 1=관련성)
    },
)

검색 결과 확인

from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma

embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma(
    collection_name="documents",
    embedding_function=embeddings,
    persist_directory="./chroma_db",
)

# 유사도 점수와 함께 검색
results = vectorstore.similarity_search_with_score(
    query="RAG에서 검색 품질을 높이는 방법은?",
    k=4,
)

for doc, score in results:
    print(f"[Score: {score:.4f}] {doc.page_content[:80]}...")
    print(f"  Source: {doc.metadata.get('source', 'N/A')}")
    print()

메타데이터 필터링

메타데이터를 활용하면 검색 범위를 좁혀 정확도를 높일 수 있습니다.
# 특정 소스의 문서만 검색
results = vectorstore.similarity_search(
    query="인덱싱 방법",
    k=4,
    filter={"source_type": "pdf"},
)

# 여러 조건 결합 (Chroma)
results = vectorstore.similarity_search(
    query="인덱싱 방법",
    k=4,
    filter={
        "$and": [
            {"source_type": {"$eq": "pdf"}},
            {"word_count": {"$gte": 100}},
        ]
    },
)

검색 후처리 기초

검색된 문서를 LLM에 전달하기 전에 간단한 후처리를 수행할 수 있습니다.
def post_process_results(results, min_score=0.5, max_docs=4):
    """검색 결과 후처리"""
    # 1. 낮은 유사도 필터링
    filtered = [(doc, score) for doc, score in results if score >= min_score]

    # 2. 중복 내용 제거
    seen_content = set()
    unique = []
    for doc, score in filtered:
        content_hash = hash(doc.page_content[:200])
        if content_hash not in seen_content:
            seen_content.add(content_hash)
            unique.append((doc, score))

    # 3. 최대 문서 수 제한
    return unique[:max_docs]
검색 전략의 상세 비교 (Dense, Sparse, Hybrid 등)는 검색 전략 페이지에서, 재순위화 기법은 재순위화 페이지에서 다룹니다.