OpenSearchBM25Retriever

OpenSearchBM25Retriever

OpenSearch Document Store에서 검색어와 일치하는 문서를 가져오는 키워드 기반 Retriever예요.

출처: 문서

본문

OpenSearchBM25Retriever는 OpenSearchDocumentStore에서 검색어와 일치하는 문서를 가져오는 키워드 기반 Retriever예요. 문서와 검색어의 유사도를 BM25 알고리즘으로 결정하는데, 두 문자열 사이의 가중 단어 겹침(weighted word overlap)을 계산하죠.

OpenSearchBM25Retriever는 단어 겹침으로 문자열을 매칭하기 때문에, 사람 이름·제품 이름·ID·명확한 오류 메시지의 정확한 일치를 찾는 데 자주 쓰여요. BM25 알고리즘은 매우 가볍고 단순해요. 그럼에도 도메인 밖(out-of-domain) 데이터에서는 더 복잡한 임베딩 기반 접근을 이기기 어려운 경우가 많죠.

query 외에도 OpenSearchBM25Retriever는 top_k(가져올 최대 문서 수)와 검색 공간을 좁히는 filters 같은 선택 파라미터를 받아요. fuzziness 파라미터로 비정확한 퍼지 매칭이 수행되는 방식을 조정할 수 있어요. 또한 all_terms_must_match 파라미터로 검색어의 모든 용어가 일치해야 하는지 지정할 수 있는데, 기본값은 False예요.

검색어를 문서에 더 유연하게 매칭하고 싶다면, LLM이 만든 벡터로 관련 정보를 검색하는 OpenSearchEmbeddingRetriever를 쓸 수 있어요.

설정과 설치

OpenSearch 인스턴스를 설치하고 실행해요.

Docker가 설정되어 있다면 Docker 이미지를 받아 실행하는 것을 권장해요.

docker pull opensearchproject/opensearch:3.5.0

docker run -p 9200:9200 -p 9600:9600 -e "discovery.type=single-node" -e "ES_JAVA_OPTS=-Xms1024m -Xmx1024m" -e "OPENSEARCH_INITIAL_ADMIN_PASSWORD=<custom-admin-password>" opensearchproject/opensearch:3.5.0

대안으로 OpenSearch integration GitHub에 가서 제공된 docker-compose.yml로 OpenSearch를 실행하는 Docker 컨테이너를 시작할 수 있어요.

docker compose up

실행 중인 OpenSearch 인스턴스가 준비되면 opensearch-haystack 통합을 설치해요.

pip install opensearch-haystack

더 알아보기 (Learn more)

단독으로 쓰기

이 Retriever는 OpensearchDocumentStore와 인덱싱된 문서가 필요해요. 단독으로는 쓸 수 없어요.

RAG 파이프라인에서 쓰기

OPENAI_API_KEY를 환경 변수로 설정한 뒤 다음 코드를 실행해요.

from haystack_integrations.components.retrievers.opensearch import (
    OpenSearchBM25Retriever,
)
from haystack_integrations.document_stores.opensearch import OpenSearchDocumentStore

from haystack import Document
from haystack import Pipeline
from haystack.components.builders.answer_builder import AnswerBuilder
from haystack.components.builders import ChatPromptBuilder
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.dataclasses import ChatMessage
from haystack.document_stores.types import DuplicatePolicy


# OpenAIChatGenerator reads the OPENAI_API_KEY environment variable by default.

# Create a RAG query pipeline
prompt_template = [
    ChatMessage.from_user(
        """
    Given these documents, answer the question.\nDocuments:
    {% for doc in documents %}
        {{ doc.content }}
    {% endfor %}

    \nQuestion: {{question}}
    \nAnswer:
    """,
    ),
]

document_store = OpenSearchDocumentStore(
    hosts="http://localhost:9200",
    use_ssl=True,
    verify_certs=False,
    http_auth=("admin", "<custom-admin-password>"),
)

# Add Documents
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.",
    ),
]

# DuplicatePolicy.SKIP param is optional, but useful to run the script multiple times without throwing errors
document_store.write_documents(documents=documents, policy=DuplicatePolicy.SKIP)

retriever = OpenSearchBM25Retriever(document_store=document_store)
rag_pipeline = Pipeline()
rag_pipeline.add_component(name="retriever", instance=retriever)
rag_pipeline.add_component(
    instance=ChatPromptBuilder(template=prompt_template, required_variables="*"),
    name="prompt_builder",
)
rag_pipeline.add_component(instance=OpenAIChatGenerator(), name="llm")
rag_pipeline.add_component(instance=AnswerBuilder(), name="answer_builder")
rag_pipeline.connect("retriever", "prompt_builder.documents")
rag_pipeline.connect("prompt_builder.prompt", "llm.messages")
rag_pipeline.connect("llm.replies", "answer_builder.replies")
rag_pipeline.connect("retriever", "answer_builder.documents")

question = "How many languages are spoken around the world today?"
result = rag_pipeline.run(
    {
        "retriever": {"query": question},
        "prompt_builder": {"question": question},
        "answer_builder": {"query": question},
    },
)

print(result["answer_builder"]["answers"][0])

다음은 예시 출력이에요.

# GeneratedAnswer(
#   data='Over 7,000 languages are spoken around the world today.',
#   query='How many languages are spoken around the world today?',
#   documents=[
#     Document(id=cfe93bc1c274908801e6670440bf2bbba54fad792770d57421f85ffa2a4fcc94, content: 'There are over 7,000 languages spoken around the world today.', meta: {'source_index': 1}, score: 3.263233),
#     Document(id=7f225626ad1019b273326fbaf11308edfca6d663308a4a3533ec7787367d59a2, content: 'In certain parts of the world, like the Maldives, Puerto Rico, and San Diego, you can witness the ph...', meta: {'source_index': 2}, score: 0.51940084)],
#   meta={'model': 'gpt-5-mini-2025-08-07', 'index': 0, 'finish_reason': 'stop',
#     'usage': {'completion_tokens': 86, 'prompt_tokens': 85, 'total_tokens': 171,
#       'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0,
#         'reasoning_tokens': 64, 'rejected_prediction_tokens': 0},
#       'prompt_tokens_details': {'audio_tokens': 0, 'cache_write_tokens': None, 'cached_tokens': 0}},
#     'all_messages': [ChatMessage(_role=<ChatRole.ASSISTANT: 'assistant'>, ...)]})

추가 참고 자료

🧑‍🍳 Cookbook: PDF-Based Question Answering with Amazon Bedrock and Haystack