MultiQueryTextRetriever
MultiQueryTextRetriever
텍스트 기반 Retriever로 여러 개의 검색어를 병렬로 사용해 문서를 검색해 주는 컴포넌트예요.
출처: 문서
본문
MultiQueryTextRetriever는 여러 검색어를 병렬로 검색해 리트리벌 재현율(recall)을 높여요. 텍스트 기반 Retriever(예: InMemoryBM25Retriever)를 감싸고, 스레드 풀로 여러 검색어 문자열을 동시에 처리하죠.
이 컴포넌트는:
- 검색어를 병렬 처리해 성능을 높여요.
- 문서 내용을 기준으로 결과의 중복을 자동으로 제거해요.
- 최종 결과를 관련성 점수순으로 정렬해요.
이 Retriever는 단일 사용자 검색어에서 여러 검색어 변형을 만들어 내는 QueryExpander와 함께 쓸 때 특히 효과적이에요. 이 변형들로 검색하면 원래 검색어와 다른 키워드를 쓰는 문서도 찾을 수 있어요.
문서가 사용자 검색어와 다른 단어를 쓰거나, 키워드 기반 검색(BM25)에 검색어 확장을 쓰고 싶을 때 MultiQueryTextRetriever를 쓰세요. 여러 검색어를 돌리면 시간이 더 걸리지만 max_workers를 늘려 병렬 실행하면 빨라져요.
언제 MultiQueryEmbeddingRetriever를 쓸까?
정확한 키워드 일치보다 의미가 더 중요한 시맨틱 검색이 필요하다면 MultiQueryEmbeddingRetriever를 쓰세요. 임베딩 기반 Retriever와 동작하고 Text Embedder가 필요해요.
추가 Retriever 파라미터 넘기기
retriever_kwargs를 사용해 내부 Retriever에 추가 파라미터를 넘길 수 있어요.
result = multiquery_retriever.run(
queries=["renewable energy", "sustainable power"],
retriever_kwargs={"top_k": 5},
)
더 알아보기 (Learn more)
단독으로 쓰기
이 예제에서는 "renewable energy", "geothermal", "hydropower" 세 개의 검색어를 직접 Retriever에 넘겨요. Retriever는 각 검색어에 대해 BM25 검색(검색어당 최대 2개 문서)을 실행한 뒤, 모든 결과를 합치고 중복을 제거한 다음 점수순으로 정렬해요.
from haystack import Document
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.components.retrievers import (
InMemoryBM25Retriever,
MultiQueryTextRetriever,
)
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.",
),
Document(
content="Hydropower is a form of renewable energy using the flow of water to generate electricity.",
),
Document(
content="Geothermal energy is heat that comes from the sub-surface of the earth.",
),
]
document_store = InMemoryDocumentStore()
document_store.write_documents(documents)
retriever = MultiQueryTextRetriever(
retriever=InMemoryBM25Retriever(document_store=document_store, top_k=2),
)
results = retriever.run(queries=["renewable energy", "geothermal", "hydropower"])
for doc in results["documents"]:
print(f"Content: {doc.content}, Score: {doc.score:.4f}")
QueryExpander와 함께 파이프라인에서 쓰기
이 파이프라인은 "sustainable power" 하나의 검색어를 LLM으로 여러 변형(예: "renewable energy sources", "green electricity", "clean power")으로 확장해요. Retriever는 각 변형을 검색하고 결과를 합치죠. 이렇게 하면 "sustainable power"라는 단어가 없는 "solar energy"나 "hydropower" 문서도 찾을 수 있어요.
from haystack import Document, Pipeline
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.components.query import QueryExpander
from haystack.components.retrievers import (
InMemoryBM25Retriever,
MultiQueryTextRetriever,
)
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.",
),
Document(
content="Hydropower is a form of renewable energy using the flow of water to generate electricity.",
),
Document(
content="Geothermal energy is heat that comes from the sub-surface of the earth.",
),
]
document_store = InMemoryDocumentStore()
document_store.write_documents(documents)
pipeline = Pipeline()
pipeline.add_component("query_expander", QueryExpander(n_expansions=3))
pipeline.add_component(
"retriever",
MultiQueryTextRetriever(
retriever=InMemoryBM25Retriever(document_store=document_store, top_k=2),
),
)
pipeline.connect("query_expander.queries", "retriever.queries")
result = pipeline.run({"query_expander": {"query": "sustainable power"}})
for doc in result["retriever"]["documents"]:
print(f"Score: {doc.score:.3f} | {doc.content}")
components:
query_expander:
type: haystack.components.query.query_expander.QueryExpander
init_parameters:
n_expansions: 3
retriever:
type: haystack.components.retrievers.multi_query_text_retriever.MultiQueryTextRetriever
init_parameters:
retriever:
type: haystack.components.retrievers.in_memory.bm25_retriever.InMemoryBM25Retriever
init_parameters:
document_store:
type: haystack.document_stores.in_memory.document_store.InMemoryDocumentStore
init_parameters: {}
top_k: 2
connections:
- sender: query_expander.queries
receiver: retriever.queries
RAG 파이프라인에서 쓰기
이 RAG 파이프라인은 검색어 확장을 사용해 질문에 답해요. 사용자가 "What types of energy come from natural sources?"라고 물으면 파이프라인은:
- LLM으로 질문을 여러 검색 검색어로 확장해요.
- 각 검색어 변형에 대한 관련 문서를 검색해요.
- 검색된 문서와 원래 질문을 담은 프롬프트를 만들어요.
- LLM에 프롬프트를 보내 답변을 생성해요.
질문은 query_expander(검색 검색어 생성용)와 prompt_builder(LLM용 최종 프롬프트) 양쪽으로 보내져요.
from haystack import Document, Pipeline
from haystack.document_stores.in_memory import InMemoryDocumentStore
from haystack.components.builders import ChatPromptBuilder
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.components.query import QueryExpander
from haystack.components.retrievers import (
InMemoryBM25Retriever,
MultiQueryTextRetriever,
)
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.",
),
]
document_store = InMemoryDocumentStore()
document_store.write_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.\n"
"Documents:\n"
"{% for doc in documents %}"
"{{ doc.content }}\n"
"{% endfor %}\n"
"Question: {{ question }}",
),
]
# Note: This assumes OPENAI_API_KEY environment variable is set
rag_pipeline = Pipeline()
rag_pipeline.add_component("query_expander", QueryExpander(n_expansions=2))
rag_pipeline.add_component(
"retriever",
MultiQueryTextRetriever(
retriever=InMemoryBM25Retriever(document_store=document_store, top_k=2),
),
)
rag_pipeline.add_component(
"prompt_builder",
ChatPromptBuilder(
template=prompt_template,
required_variables=["documents", "question"],
),
)
rag_pipeline.add_component("llm", OpenAIChatGenerator())
rag_pipeline.connect("query_expander.queries", "retriever.queries")
rag_pipeline.connect("retriever.documents", "prompt_builder.documents")
rag_pipeline.connect("prompt_builder.prompt", "llm.messages")
question = "What types of energy come from natural sources?"
result = rag_pipeline.run(
{"query_expander": {"query": question}, "prompt_builder": {"question": question}},
)
print(result["llm"]["replies"][0].text)
components:
query_expander:
type: haystack.components.query.query_expander.QueryExpander
init_parameters:
n_expansions: 2
retriever:
type: haystack.components.retrievers.multi_query_text_retriever.MultiQueryTextRetriever
init_parameters:
retriever:
type: haystack.components.retrievers.in_memory.bm25_retriever.InMemoryBM25Retriever
init_parameters:
document_store:
type: haystack.document_stores.in_memory.document_store.InMemoryDocumentStore
init_parameters: {}
top_k: 2
prompt_builder:
type: haystack.components.builders.chat_prompt_builder.ChatPromptBuilder
init_parameters:
required_variables:
- documents
- question
llm:
type: haystack.components.generators.chat.openai.OpenAIChatGenerator
init_parameters: {}
connections:
- sender: query_expander.queries
receiver: retriever.queries
- sender: retriever.documents
receiver: prompt_builder.documents
- sender: prompt_builder.prompt
receiver: llm.messages