ElasticsearchHybridRetriever
ElasticsearchHybridRetriever
단일 컴포넌트로 하이브리드 리트리버를 구현한 SuperComponent예요. 백엔드 Document Store로 Elasticsearch를 사용해요.
하이브리드 리트리버는 기존의 키워드 기반 검색(BM25)과 임베딩 기반 검색을 모두 사용해 문서를 검색하며, 두 접근법의 장점을 결합해요. 그런 다음 리트리버가 두 방법의 결과를 병합하고 다시 순위를 매겨요.
출처: 문서
본문
- 파이프라인에서의 일반적인 위치: 1. RAG 파이프라인에서 TextEmbedder 뒤, PromptBuilder 앞. 2. 하이브리드 검색 파이프라인의 마지막 컴포넌트. 3. 추출형 QA 파이프라인에서 TextEmbedder 뒤, TransformersExtractiveReader 앞.
- 필수 초기화 변수:
document_store(ElasticsearchDocumentStore인스턴스),embedder(TextEmbedder프로토콜을 구현하는 임베더) - 필수 실행 변수:
query(쿼리 문자열) - 출력 변수:
documents(쿼리와 일치하는 문서 리스트)
개요 (Overview)
ElasticsearchHybridRetriever는 두 가지 검색 방법을 결합해요.
- BM25 검색: BM25 알고리즘으로 용어 빈도와 역문서 빈도를 바탕으로 문서를 찾는 키워드 기반 검색이에요.
ElasticsearchBM25Retriever컴포넌트를 기반으로 하며, 이름, ID, 명확히 정의된 용어의 정확한 일치를 찾는 데 적합해요. - 임베딩 기반 검색: 벡터 유사도로 쿼리와 의미적으로 유사한 문서를 찾는 시맨틱 검색이에요.
ElasticsearchEmbeddingRetriever컴포넌트를 기반으로 하며, 의미적 검색에 적합해요.
이 컴포넌트는 다음을 자동으로 처리해요.
- 제공된 임베더를 사용해 쿼리를 임베딩으로 변환하고,
- 두 검색 방법을 병렬로 실행하고,
- 지정된 결합 모드(기본값: Reciprocal Rank Fusion)로 결과를 병합하고 다시 순위를 매겨요.
설치 (Installation)
Elasticsearch를 설치한 뒤 인스턴스를 시작하세요. Haystack은 Elasticsearch 8을 지원해요.
Docker가 설정되어 있다면 Docker 이미지를 받아 실행하는 것을 권장해요.
docker pull docker.elastic.co/elasticsearch/elasticsearch:8.19.7
docker run -p 9200:9200 -e "discovery.type=single-node" -e "ES_JAVA_OPTS=-Xms1024m -Xmx1024m" -e "xpack.security.enabled=false" elasticsearch:8.19.7
대안으로 Elasticsearch 통합 GitHub에서 제공하는 docker-compose.yml을 사용해 Elasticsearch를 실행하는 Docker 컨테이너를 시작할 수 있어요.
docker compose up
실행 중인 Elasticsearch 인스턴스가 있으면 elasticsearch-haystack 통합을 설치하세요.
pip install elasticsearch-haystack
선택적 파라미터 (Optional Parameters)
이 리트리버는 다양한 선택적 파라미터를 받아요. 가장 최신 파라미터 목록은 API Reference에서 확인할 수 있어요.
top_k_bm25, fuzziness, filters_bm25, scale_score, filter_policy_bm25, top_k_embedding, filters_embedding, num_candidates, filter_policy_embedding 파라미터로 내부의 BM25 및 임베딩 리트리버 컴포넌트에 추가 파라미터를 전달할 수 있어요.
DocumentJoiner 파라미터(join_mode, weights, top_k, sort_by_score)는 모두 ElasticsearchHybridRetriever 클래스에 직접 노출되어 있어요.
사용법 (Usage)
단독 사용 (On its own)
이 리트리버는 실행하려면 문서(임베딩 포함)가 채워진 ElasticsearchDocumentStore가 필요해요.
from haystack import Document
from haystack_integrations.components.embedders.sentence_transformers import (
SentenceTransformersTextEmbedder,
SentenceTransformersDocumentEmbedder,
)
from haystack_integrations.components.retrievers.elasticsearch import (
ElasticsearchHybridRetriever,
)
from haystack_integrations.document_stores.elasticsearch import (
ElasticsearchDocumentStore,
)
document_store = ElasticsearchDocumentStore(hosts="http://localhost:9200/")
model = "sentence-transformers/all-MiniLM-L6-v2"
documents = [
Document(content="There are over 7,000 languages spoken around the world today."),
Document(
content="Elephants have been observed to behave in a way that indicates a high level of self-awareness, such as recognizing themselves in mirrors.",
),
Document(
content="In certain parts of the world, like the Maldives, Puerto Rico, and San Diego, you can witness the phenomenon of bioluminescent waves.",
),
]
doc_embedder = SentenceTransformersDocumentEmbedder(model=model)
docs_with_embeddings = doc_embedder.run(documents)
document_store.write_documents(docs_with_embeddings["documents"])
embedder = SentenceTransformersTextEmbedder(model=model)
retriever = ElasticsearchHybridRetriever(
document_store=document_store,
embedder=embedder,
)
results = retriever.run(query="How many languages are spoken around the world today?")
print(results["documents"])
파이프라인에서 사용 (In a pipeline)
문서를 임베딩과 함께 저장하는 인덱싱 파이프라인과, ElasticsearchHybridRetriever로 하이브리드 검색을 수행하는 쿼리 파이프라인을 보여주는 전체 예시예요.
OPENAI_API_KEY를 환경 변수로 설정한 뒤 다음 코드를 실행하세요.
from haystack import Document, Pipeline
from haystack.components.builders import ChatPromptBuilder
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack_integrations.components.embedders.sentence_transformers import (
SentenceTransformersDocumentEmbedder,
SentenceTransformersTextEmbedder,
)
from haystack.components.writers import DocumentWriter
from haystack.dataclasses import ChatMessage
from haystack.document_stores.types import DuplicatePolicy
from haystack_integrations.components.retrievers.elasticsearch import (
ElasticsearchHybridRetriever,
)
from haystack_integrations.document_stores.elasticsearch import (
ElasticsearchDocumentStore,
)
document_store = ElasticsearchDocumentStore(hosts="http://localhost:9200/")
model = "sentence-transformers/all-MiniLM-L6-v2"
documents = [
Document(content="There are over 7,000 languages spoken around the world today."),
Document(
content="Elephants have been observed to behave in a way that indicates a high level of self-awareness, such as recognizing themselves in mirrors.",
),
Document(
content="In certain parts of the world, like the Maldives, Puerto Rico, and San Diego, you can witness the phenomenon of bioluminescent waves.",
),
]
# Indexing Pipeline
indexing_pipeline = Pipeline()
indexing_pipeline.add_component(
"doc_embedder",
SentenceTransformersDocumentEmbedder(model=model),
)
indexing_pipeline.add_component(
"doc_writer",
DocumentWriter(document_store=document_store, policy=DuplicatePolicy.SKIP),
)
indexing_pipeline.connect("doc_embedder", "doc_writer")
indexing_pipeline.run({"doc_embedder": {"documents": documents}})
# Query Pipeline
prompt_template = [
ChatMessage.from_user(
"""
Given these documents, answer the question.\nDocuments:
{% for doc in documents %}
{{ doc.content }}
{% endfor %}
\nQuestion: {{question}}
\nAnswer:
""",
),
]
embedder = SentenceTransformersTextEmbedder(model=model)
retriever = ElasticsearchHybridRetriever(
document_store=document_store,
embedder=embedder,
top_k_bm25=3,
top_k_embedding=3,
join_mode="reciprocal_rank_fusion",
)
query_pipeline = Pipeline()
query_pipeline.add_component("retriever", retriever)
query_pipeline.add_component(
"prompt_builder",
ChatPromptBuilder(template=prompt_template, required_variables="*"),
)
query_pipeline.add_component("llm", OpenAIChatGenerator())
query_pipeline.connect("retriever.documents", "prompt_builder.documents")
query_pipeline.connect("prompt_builder.prompt", "llm.messages")
question = "How many languages are spoken around the world today?"
result = query_pipeline.run(
{
"retriever": {"query": question},
"prompt_builder": {"question": question},
},
)
print(result["llm"]["replies"][0].text)