MultiRetriever

MultiRetriever

여러 텍스트 Retriever를 병렬로 실행하고, reciprocal rank fusion이나 중복 제거로 결과를 합치는 컴포넌트예요.

출처: 문서

본문

Experimental: MultiRetriever는 실험 단계 컴포넌트예요. 향후 릴리스에서 사전 공지 없이 바뀌거나 제거될 수 있어요. 이 컴포넌트를 초기화할 때 ExperimentalWarning이 출력돼요.

MultiRetriever는 임의 개수의 텍스트 Retriever를 하나의 컴포넌트로 묶어요. 모든 Retriever가 스레드 풀로 병렬 조회되고, 결과는 반환되기 전에 병합돼요.

이 컴포넌트는:

  • 모든 Retriever를 동시에 조회해 성능을 높여요.
  • 설정된 join_mode로 Retriever 간 결과를 병합해요.
  • active_retrievers로 실행 시점에 일부 Retriever만 선택적으로 켤 수 있어요.

MultiRetriever에 넘기는 모든 Retriever는 TextRetriever 프로토콜을 구현해야 해요. 즉 run 메서드가 텍스트 query, filters, top_k를 받아야 하죠. 임베딩 기반 Retriever를 이 컴포넌트에서 쓰려면 TextEmbeddingRetriever로 감싸세요.

조인 모드

join_mode 파라미터는 여러 Retriever의 결과를 어떻게 병합할지 정해요.

  • reciprocal_rank_fusion(기본값): Reciprocal Rank Fusion 알고리즘으로 각 문서가 여러 검색 목록에서 차지한 순위를 바탕으로 점수를 매겨요. 여러 목록에서 높은 순위에 오른 문서일수록 점수가 높아지죠. 결과는 중복 제거 후 점수 내림차순으로 반환돼요. BM25와 임베딩 Retriever처럼 점수를 비교하기 어려운 Retriever를 합칠 때 권장하는 모드예요.
  • concatenate: 모든 결과를 단일 목록으로 합치고 중복을 제거해요.

더 알아보기 (Learn more)

단독으로 쓰기

이 예제는 BM25 Retriever와 임베딩 기반 Retriever(TextEmbeddingRetriever로 감쌈)를 합친 MultiRetriever를 구성해요. 둘 다 병렬로 조회하고 결과는 reciprocal rank fusion으로 병합돼요.

이 페이지의 예제는 sentence-transformers-haystack 패키지의 Sentence Transformers 임베더를 사용해요. 예제를 실행하려면 설치하세요.

pip install sentence-transformers-haystack
from haystack import Document
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.document_stores.types import DuplicatePolicy
from haystack_integrations.components.embedders.sentence_transformers import (
    SentenceTransformersDocumentEmbedder,
    SentenceTransformersTextEmbedder,
)
from haystack.components.retrievers import (
    InMemoryBM25Retriever,
    InMemoryEmbeddingRetriever,
)
from haystack.components.retrievers import MultiRetriever, TextEmbeddingRetriever
from haystack.components.writers import DocumentWriter


documents = [
    Document(
        content="Renewable energy is energy that is collected from renewable resources.",
    ),
    Document(
        content="Solar energy is a type of green energy that is harnessed from the sun.",
    ),
    Document(
        content="Wind energy is another type of green energy that is generated by wind turbines.",
    ),
]

doc_store = InMemoryDocumentStore()
doc_embedder = SentenceTransformersDocumentEmbedder(
    model="sentence-transformers/all-MiniLM-L6-v2",
)
doc_writer = DocumentWriter(document_store=doc_store, policy=DuplicatePolicy.SKIP)
doc_writer.run(documents=doc_embedder.run(documents)["documents"])

retriever = MultiRetriever(
    retrievers={
        "bm25": InMemoryBM25Retriever(document_store=doc_store),
        "embedding": TextEmbeddingRetriever(
            retriever=InMemoryEmbeddingRetriever(document_store=doc_store),
            text_embedder=SentenceTransformersTextEmbedder(
                model="sentence-transformers/all-MiniLM-L6-v2",
            ),
        ),
    },
    top_k=3,
)

result = retriever.run(query="green energy sources")
for doc in result["documents"]:
    print(doc.content)

실행 시점에 Retriever 선택하기

active_retrievers 파라미터로 일부 Retriever만 실행할 수 있어요. 이름은 retrievers 딕셔너리의 키와 일치해야 해요. 위 예제를 이어서:

# Run only the BM25 retriever
result = retriever.run(query="green energy sources", active_retrievers=["bm25"])
for doc in result["documents"]:
    print(doc.content)

RAG 파이프라인에서 쓰기

이 RAG 파이프라인은 LLM으로 답변을 생성하기 전에 MultiRetriever로 BM25 검색과 임베딩 검색을 합쳐요.

from haystack import Document, Pipeline
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.document_stores.types import DuplicatePolicy
from haystack.components.builders import ChatPromptBuilder
from haystack_integrations.components.embedders.sentence_transformers import (
    SentenceTransformersDocumentEmbedder,
    SentenceTransformersTextEmbedder,
)
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.components.retrievers import (
    InMemoryBM25Retriever,
    InMemoryEmbeddingRetriever,
)
from haystack.components.retrievers import MultiRetriever, TextEmbeddingRetriever
from haystack.components.writers import DocumentWriter
from haystack.dataclasses import ChatMessage


documents = [
    Document(
        content="Renewable energy is energy that is collected from renewable resources.",
    ),
    Document(
        content="Solar energy is a type of green energy that is harnessed from the sun.",
    ),
    Document(
        content="Wind energy is another type of green energy that is generated by wind turbines.",
    ),
]

doc_store = InMemoryDocumentStore()
doc_embedder = SentenceTransformersDocumentEmbedder(
    model="sentence-transformers/all-MiniLM-L6-v2",
)
doc_writer = DocumentWriter(document_store=doc_store, policy=DuplicatePolicy.SKIP)
doc_writer.run(documents=doc_embedder.run(documents)["documents"])

prompt_template = [
    ChatMessage.from_system(
        "You are a helpful assistant that answers questions based on the provided documents.",
    ),
    ChatMessage.from_user(
        "Given these documents, answer the question.\nDocuments:\n"
        "{% for doc in documents %}{{ doc.content }}\n{% endfor %}\n"
        "Question: {{ question }}",
    ),
]

pipeline = Pipeline()
pipeline.add_component(
    "retriever",
    MultiRetriever(
        retrievers={
            "bm25": InMemoryBM25Retriever(document_store=doc_store),
            "embedding": TextEmbeddingRetriever(
                retriever=InMemoryEmbeddingRetriever(document_store=doc_store),
                text_embedder=SentenceTransformersTextEmbedder(
                    model="sentence-transformers/all-MiniLM-L6-v2",
                ),
            ),
        },
        top_k=3,
    ),
)
pipeline.add_component(
    "prompt_builder",
    ChatPromptBuilder(
        template=prompt_template,
        required_variables=["documents", "question"],
    ),
)
pipeline.add_component("llm", OpenAIChatGenerator())

pipeline.connect("retriever.documents", "prompt_builder.documents")
pipeline.connect("prompt_builder.prompt", "llm.messages")

result = pipeline.run(
    {
        "retriever": {"query": "green energy sources"},
        "prompt_builder": {"question": "What types of green energy exist?"},
    },
)
print(result["llm"]["replies"][0].text)