밀버스 및 헤이스택을 사용한 전체 텍스트 검색
전체 텍스트 검색은 텍스트의 특정 키워드나 구문을 일치시켜 문서를 검색하는 전통적인 방법입니다. 용어 빈도 등의 요소로 계산된 관련성 점수를 기반으로 결과의 순위를 매깁니다. 시맨틱 검색은 의미와 문맥을 이해하는 데 더 효과적이지만, 전체 텍스트 검색은 정확한 키워드 매칭에 탁월하므로 시맨틱 검색을 보완하는 데 유용합니다. BM25 알고리즘은 전체 텍스트 검색에서 순위를 매기는 데 널리 사용되며 검색 증강 세대(RAG)에서 핵심적인 역할을 합니다.
Milvus 2.5에는 BM25를 사용한 기본 전체 텍스트 검색 기능이 도입되었습니다. 이 접근 방식은 텍스트를 BM25 점수를 나타내는 스파스 벡터로 변환합니다. 원시 텍스트를 입력하기만 하면 수동으로 스파스 임베딩을 생성할 필요 없이 Milvus가 자동으로 스파스 벡터를 생성하고 저장합니다.
Haystack은 이제 이 Milvus 기능을 지원하므로 RAG 애플리케이션에 전체 텍스트 검색을 쉽게 추가할 수 있습니다. 전체 텍스트 검색과 고밀도 벡터 시맨틱 검색을 결합하여 시맨틱 이해와 키워드 매칭 정확도 모두에서 이점을 얻을 수 있는 하이브리드 접근 방식을 사용할 수 있습니다. 이 조합은 검색 정확도를 향상시키고 사용자에게 더 나은 결과를 제공합니다.
이 튜토리얼에서는 Haystack과 Milvus를 사용해 애플리케이션에서 전체 텍스트 및 하이브리드 검색을 구현하는 방법을 보여드립니다.
Milvus 벡터 저장소를 사용하려면 Milvus 서버 URI (또는 선택적으로 TOKEN)를 지정합니다. 밀버스 서버를 시작하려면 밀버스 설치 가이드를 따라 밀버스 서버를 설정하거나 Zilliz Cloud(완전 관리형 밀버스)를 무료로 사용해 보세요.
- 전체 텍스트 검색은 현재 Milvus 독립형, Milvus 분산형 및 Zilliz Cloud에서 사용할 수 있지만, Milvus Lite(향후 이 기능이 구현될 예정)에서는 아직 지원되지 않습니다. 자세한 내용은 support@zilliz.com 으로 문의하세요.
- 이 튜토리얼을 진행하기 전에 전체 텍스트 검색에 대한 기본적인 이해와 Haystack Milvus 통합의 기본 사용법을 숙지하고 있는지 확인하세요.
전제 조건
이 노트북을 실행하기 전에 다음 종속성이 설치되어 있는지 확인하세요:
$ pip install --upgrade --quiet pymilvus milvus-haystack
Google Colab을 사용하는 경우 방금 설치한 종속성을 사용하려면 런타임을 다시 시작해야 할 수 있습니다(화면 상단의 '런타임' 메뉴를 클릭하고 드롭다운 메뉴에서 '세션 다시 시작'을 선택).
OpenAI의 모델을 사용합니다. 환경 변수로 OPENAI_API_KEY API 키를 준비해야 합니다.
import os
os.environ["OPENAI_API_KEY"] = "sk-***********"
데이터 준비
이 노트북에서 필요한 패키지를 가져옵니다. 그런 다음 몇 가지 샘플 문서를 준비합니다.
from haystack import Pipeline
from haystack.components.embedders import OpenAIDocumentEmbedder, OpenAITextEmbedder
from haystack.components.writers import DocumentWriter
from haystack.utils import Secret
from milvus_haystack import MilvusDocumentStore, MilvusSparseEmbeddingRetriever
from haystack.document_stores.types import DuplicatePolicy
from milvus_haystack.function import BM25BuiltInFunction
from milvus_haystack import MilvusDocumentStore
from milvus_haystack.milvus_embedding_retriever import MilvusHybridRetriever
from haystack.utils import Secret
from haystack.components.builders import PromptBuilder
from haystack.components.generators import OpenAIGenerator
from haystack import Document
documents = [
Document(content="Alice likes this apple", meta={"category": "fruit"}),
Document(content="Bob likes swimming", meta={"category": "sport"}),
Document(content="Charlie likes white dogs", meta={"category": "pets"}),
]
RAG 시스템에 전체 텍스트 검색을 통합하면 시맨틱 검색과 정확하고 예측 가능한 키워드 기반 검색의 균형을 맞출 수 있습니다. 전체 텍스트 검색만 사용하도록 선택할 수도 있지만, 더 나은 검색 결과를 위해 전체 텍스트 검색과 시맨틱 검색을 결합하는 것이 좋습니다. 여기서는 데모 목적으로 전체 텍스트 검색만 사용하는 경우와 하이브리드 검색을 사용하는 경우를 보여드리겠습니다.
임베딩 없이 BM25 검색
인덱싱 파이프라인 만들기
전체 텍스트 검색을 위해 Milvus MilvusDocumentStore는 builtin_function 매개변수를 허용합니다. 이 매개변수를 통해 Milvus 서버 측에서 BM25 알고리즘을 구현하는 BM25BuiltInFunction 의 인스턴스를 전달할 수 있습니다. 지정된 builtin_function 을 BM25 함수 인스턴스로 설정합니다. 예를 들어
connection_args = {"uri": "http://localhost:19530"}
# connection_args = {"uri": YOUR_ZILLIZ_CLOUD_URI, "token": Secret.from_env_var("ZILLIZ_CLOUD_API_KEY")}
document_store = MilvusDocumentStore(
connection_args=connection_args,
sparse_vector_field="sparse_vector", # The sparse vector field.
text_field="text",
builtin_function=[
BM25BuiltInFunction( # The BM25 function converts the text into a sparse vector.
input_field_names="text",
output_field_names="sparse_vector",
)
],
consistency_level="Bounded", # Supported values are (`"Strong"`, `"Session"`, `"Bounded"`, `"Eventually"`).
drop_old=True, # Drop the old collection if it exists and recreate it.
)
connection_args의 경우:
- 도커 또는 쿠버네티스에서 더 성능이 우수한 Milvus 서버를 설정할 수 있습니다. 이 설정에서는 서버 주소(예:
http://localhost:19530)를uri으로 사용하세요. - 밀버스의 완전 관리형 클라우드 서비스인 질리즈 클라우드를 사용하려면, 질리즈 클라우드의 퍼블릭 엔드포인트와 API 키에 해당하는
uri와token을 조정하세요.
Milvus 문서 저장소에 문서를 쓰기 위한 인덱싱 파이프라인을 구축합니다.
writer = DocumentWriter(document_store=document_store, policy=DuplicatePolicy.NONE)
indexing_pipeline = Pipeline()
indexing_pipeline.add_component("writer", writer)
indexing_pipeline.run({"writer": {"documents": documents}})
{'writer': {'documents_written': 3}}
검색 파이프라인 생성
document_store 을 감싸는 래퍼인 MilvusSparseEmbeddingRetriever 을 사용하여 Milvus 문서 저장소에서 문서를 검색하는 검색 파이프라인을 생성합니다.
retrieval_pipeline = Pipeline()
retrieval_pipeline.add_component(
"retriever", MilvusSparseEmbeddingRetriever(document_store=document_store)
)
question = "Who likes swimming?"
retrieval_results = retrieval_pipeline.run({"retriever": {"query_text": question}})
retrieval_results["retriever"]["documents"][0]
Document(id=bd334348dd2087c785e99b5a0009f33d9b8b8198736f6415df5d92602d81fd3e, content: 'Bob likes swimming', meta: {'category': 'sport'}, score: 1.2039074897766113)
시맨틱 검색과 전체 텍스트 검색을 사용한 하이브리드 검색
색인 파이프라인 생성
하이브리드 검색에서는 BM25 함수를 사용하여 전체 텍스트 검색을 수행하고, 밀도 벡터 필드 vector 를 지정하여 시맨틱 검색을 수행합니다.
document_store = MilvusDocumentStore(
connection_args=connection_args,
vector_field="vector", # The dense vector field.
sparse_vector_field="sparse_vector", # The sparse vector field.
text_field="text",
builtin_function=[
BM25BuiltInFunction( # The BM25 function converts the text into a sparse vector.
input_field_names="text",
output_field_names="sparse_vector",
)
],
consistency_level="Bounded", # Supported values are (`"Strong"`, `"Session"`, `"Bounded"`, `"Eventually"`).
drop_old=True, # Drop the old collection and recreate it.
)
문서를 임베딩으로 변환하는 색인 파이프라인을 만듭니다. 그런 다음 문서가 Milvus 문서 저장소에 기록됩니다.
writer = DocumentWriter(document_store=document_store, policy=DuplicatePolicy.NONE)
indexing_pipeline = Pipeline()
indexing_pipeline.add_component("dense_doc_embedder", OpenAIDocumentEmbedder())
indexing_pipeline.add_component("writer", writer)
indexing_pipeline.connect("dense_doc_embedder", "writer")
indexing_pipeline.run({"dense_doc_embedder": {"documents": documents}})
print("Number of documents:", document_store.count_documents())
Calculating embeddings: 100%|██████████| 1/1 [00:01<00:00, 1.15s/it]
Number of documents: 3
검색 파이프라인 만들기
document_store 을 포함하고 하이브리드 검색에 대한 매개변수를 수신하는 MilvusHybridRetriever 을 사용하여 Milvus 문서 저장소에서 문서를 검색하는 검색 파이프라인을 만듭니다.
# from pymilvus import WeightedRanker
retrieval_pipeline = Pipeline()
retrieval_pipeline.add_component("dense_text_embedder", OpenAITextEmbedder())
retrieval_pipeline.add_component(
"retriever",
MilvusHybridRetriever(
document_store=document_store,
# top_k=3,
# reranker=WeightedRanker(0.5, 0.5), # Default is RRFRanker()
),
)
retrieval_pipeline.connect("dense_text_embedder.embedding", "retriever.query_embedding")
<haystack.core.pipeline.pipeline.Pipeline object at 0x3383ad990>
🚅 Components
- dense_text_embedder: OpenAITextEmbedder
- retriever: MilvusHybridRetriever
🛤️ Connections
- dense_text_embedder.embedding -> retriever.query_embedding (List[float])
MilvusHybridRetriever 를 사용하여 하이브리드 검색을 수행할 때 선택적으로 topK 및 재랭커 매개변수를 설정할 수 있습니다. 그러면 벡터 임베딩과 내장 함수가 자동으로 처리되고 마지막으로 재랭커를 사용하여 결과를 구체화합니다. 검색 프로세스의 기본 구현 세부 사항은 사용자에게 숨겨져 있습니다.
하이브리드 검색에 대한 자세한 내용은 하이브리드 검색 소개를 참조하세요.
question = "Who likes swimming?"
retrieval_results = retrieval_pipeline.run(
{
"dense_text_embedder": {"text": question},
"retriever": {"query_text": question},
}
)
retrieval_results["retriever"]["documents"][0]
Document(id=bd334348dd2087c785e99b5a0009f33d9b8b8198736f6415df5d92602d81fd3e, content: 'Bob likes swimming', meta: {'category': 'sport'}, score: 0.032786883413791656, embedding: vector of size 1536)
분석기 사용자 지정
분석기는 문장을 토큰으로 나누고 어간 및 중지 단어 제거와 같은 어휘 분석을 수행하여 전체 텍스트 검색에 필수적입니다. 분석기는 일반적으로 언어별로 다릅니다. 이 가이드를 참조하여 Milvus의 분석기에 대해 자세히 알아보세요.
Milvus는 두 가지 유형의 분석기를 지원합니다: 기본 제공 분석 기와 사용자 지정 분석기입니다. 기본적으로 BM25BuiltInFunction 에서는 구두점으로 텍스트를 토큰화하는 가장 기본적인 분석기인 표준 내장 분석기를 사용합니다.
다른 분석기를 사용하거나 분석기를 사용자 정의하려면 BM25BuiltInFunction 초기화에서 analyzer_params 매개 변수를 전달하면 됩니다.
analyzer_params_custom = {
"tokenizer": "standard",
"filter": [
"lowercase", # Built-in filter
{"type": "length", "max": 40}, # Custom filter
{"type": "stop", "stop_words": ["of", "to"]}, # Custom filter
],
}
document_store = MilvusDocumentStore(
connection_args=connection_args,
vector_field="vector",
sparse_vector_field="sparse_vector",
text_field="text",
builtin_function=[
BM25BuiltInFunction(
input_field_names="text",
output_field_names="sparse_vector",
analyzer_params=analyzer_params_custom, # Custom analyzer parameters.
enable_match=True, # Whether to enable match.
)
],
consistency_level="Bounded",
drop_old=True,
)
# write documents to the document store
writer = DocumentWriter(document_store=document_store, policy=DuplicatePolicy.NONE)
indexing_pipeline = Pipeline()
indexing_pipeline.add_component("dense_doc_embedder", OpenAIDocumentEmbedder())
indexing_pipeline.add_component("writer", writer)
indexing_pipeline.connect("dense_doc_embedder", "writer")
indexing_pipeline.run({"dense_doc_embedder": {"documents": documents}})
Calculating embeddings: 100%|██████████| 1/1 [00:00<00:00, 1.39it/s]
{'dense_doc_embedder': {'meta': {'model': 'text-embedding-ada-002-v2',
'usage': {'prompt_tokens': 11, 'total_tokens': 11}}},
'writer': {'documents_written': 3}}
Milvus 컬렉션의 스키마를 살펴보고 사용자 정의된 분석기가 올바르게 설정되었는지 확인할 수 있습니다.
document_store.col.schema
{'auto_id': False, 'description': '', 'fields': [{'name': 'text', 'description': '', 'type': <DataType.VARCHAR: 21>, 'params': {'max_length': 65535, 'enable_match': True, 'enable_analyzer': True, 'analyzer_params': {'tokenizer': 'standard', 'filter': ['lowercase', {'type': 'length', 'max': 40}, {'type': 'stop', 'stop_words': ['of', 'to']}]}}}, {'name': 'id', 'description': '', 'type': <DataType.VARCHAR: 21>, 'params': {'max_length': 65535}, 'is_primary': True, 'auto_id': False}, {'name': 'vector', 'description': '', 'type': <DataType.FLOAT_VECTOR: 101>, 'params': {'dim': 1536}}, {'name': 'sparse_vector', 'description': '', 'type': <DataType.SPARSE_FLOAT_VECTOR: 104>, 'is_function_output': True}], 'enable_dynamic_field': True, 'functions': [{'name': 'bm25_function_7b6e15a4', 'description': '', 'type': <FunctionType.BM25: 1>, 'input_field_names': ['text'], 'output_field_names': ['sparse_vector'], 'params': {}}]}
더 자세한 개념 설명은 analyzer, tokenizer, filter, enable_match, analyzer_params, 분석기 설명서를 참조하세요.
RAG 파이프라인에서 하이브리드 검색 사용
헤이스택과 밀버스에서 기본 BM25 빌트인 기능을 사용하는 방법을 배웠고, 로드된 document_store 을 준비했습니다. 이제 하이브리드 검색을 통해 최적화된 RAG 구현을 소개해 보겠습니다.
이 다이어그램은 키워드 매칭을 위한 BM25와 시맨틱 검색을 위한 고밀도 벡터 검색을 결합한 하이브리드 검색 및 재랭크 프로세스를 보여줍니다. 두 방법의 결과가 병합되고 순위가 재조정된 후 LLM으로 전달되어 최종 답변을 생성합니다.
하이브리드 검색은 정확도와 의미론적 이해의 균형을 유지하여 다양한 쿼리에 대한 정확도와 견고성을 향상시킵니다. BM25 전체 텍스트 검색과 벡터 검색으로 후보를 검색하여 의미론적, 문맥 인식, 정확한 검색을 모두 보장합니다.
하이브리드 검색을 통해 최적화된 RAG 구현을 시도해 보겠습니다.
prompt_template = """Answer the following query based on the provided context. If the context does
not include an answer, reply with 'I don't know'.\n
Query: {{query}}
Documents:
{% for doc in documents %}
{{ doc.content }}
{% endfor %}
Answer:
"""
rag_pipeline = Pipeline()
rag_pipeline.add_component("text_embedder", OpenAITextEmbedder())
rag_pipeline.add_component(
"retriever", MilvusHybridRetriever(document_store=document_store, top_k=1)
)
rag_pipeline.add_component("prompt_builder", PromptBuilder(template=prompt_template))
rag_pipeline.add_component(
"generator",
OpenAIGenerator(
api_key=Secret.from_token(os.getenv("OPENAI_API_KEY")),
generation_kwargs={"temperature": 0},
),
)
rag_pipeline.connect("text_embedder.embedding", "retriever.query_embedding")
rag_pipeline.connect("retriever.documents", "prompt_builder.documents")
rag_pipeline.connect("prompt_builder", "generator")
results = rag_pipeline.run(
{
"text_embedder": {"text": question},
"retriever": {"query_text": question},
"prompt_builder": {"query": question},
}
)
print("RAG answer:", results["generator"]["replies"][0])
RAG answer: Bob likes swimming.
밀버스-헤이스택 사용 방법에 대한 자세한 내용은 밀버스-헤이스택 공식 리포지토리를 참조하세요.