밀버스 및 라마인덱스와 함께 하이브리드 검색을 사용하는 RAG
하이브리드 검색은 시맨틱 검색과 키워드 매칭의 강점을 모두 활용하여 보다 정확하고 문맥에 맞는 결과를 제공합니다. 시맨틱 검색과 키워드 매칭의 장점을 결합한 하이브리드 검색은 복잡한 정보 검색 작업에 특히 효과적입니다.
이 노트북에서는 LlamaIndex RAG 파이프라인에서 하이브리드 검색을 위해 Milvus를 사용하는 방법을 보여드립니다. 권장되는 기본 하이브리드 검색(시맨틱 + BM25)으로 시작한 다음, 다른 대체 스파스 임베딩 방법과 하이브리드 재랭커의 사용자 정의에 대해 살펴봅니다.
전제 조건
설치 종속성
시작하기 전에 다음 종속성이 설치되어 있는지 확인하세요:
$ pip install llama-index-vector-stores-milvus
$ pip install llama-index-embeddings-openai
$ pip install llama-index-llms-openai
Google Colab을 사용하는 경우 런타임을 다시 시작해야 할 수 있습니다(인터페이스 상단의 '런타임' 메뉴로 이동한 후 드롭다운 메뉴에서 '세션 다시 시작'을 선택합니다).
계정 설정하기
이 튜토리얼에서는 텍스트 임베딩 및 답변 생성을 위해 OpenAI를 사용합니다. OpenAI API 키를 준비해야 합니다.
import openai
openai.api_key = "sk-"
Milvus 벡터 스토어를 사용하려면 Milvus 서버 URI (또는 선택적으로 TOKEN)를 지정합니다. 밀버스 서버를 시작하려면 밀버스 설치 가이드에 따라 밀버스 서버를 설정하거나 질리즈 클라우드를 무료로 체험해 보세요.
전체 텍스트 검색은 현재 Milvus Standalone, Milvus Distributed 및 Zilliz Cloud에서 지원되지만 Milvus Lite(향후 구현 예정)에서는 아직 지원되지 않습니다. 자세한 내용은 support@zilliz.com 으로 문의하세요.
URI = "http://localhost:19530"
# TOKEN = ""
예제 데이터 로드
다음 명령을 실행하여 샘플 문서를 "data/paul_graham" 디렉터리에 다운로드합니다:
$ mkdir -p 'data/paul_graham/'
$ wget 'https://raw.githubusercontent.com/run-llama/llama_index/main/docs/docs/examples/data/paul_graham/paul_graham_essay.txt' -O 'data/paul_graham/paul_graham_essay.txt'
그런 다음 SimpleDirectoryReaderLoad 을 사용하여 폴 그레이엄의 "내가 작업한 것"이라는 에세이를 로드합니다:
from llama_index.core import SimpleDirectoryReader
documents = SimpleDirectoryReader("./data/paul_graham/").load_data()
# Let's take a look at the first document
print("Example document:\n", documents[0])
Example document:
Doc ID: f9cece8c-9022-46d8-9d0e-f29d70e1dbbe
Text: What I Worked On February 2021 Before college the two main
things I worked on, outside of school, were writing and programming. I
didn't write essays. I wrote what beginning writers were supposed to
write then, and probably still are: short stories. My stories were
awful. They had hardly any plot, just characters with strong feelings,
which I ...
BM25를 사용한 하이브리드 검색
이 섹션에서는 BM25를 사용해 하이브리드 검색을 수행하는 방법을 보여줍니다. 시작하기 위해 MilvusVectorStore 을 초기화하고 예제 문서에 대한 색인을 생성합니다. 기본 구성은 다음을 사용합니다:
- 기본 임베딩 모델의 고밀도 임베딩(OpenAI의
text-embedding-ada-002) - enable_sparse가 True인 경우 전체 텍스트 검색을 위한 BM25
- 하이브리드 검색이 활성화된 경우 결과 결합을 위한 k=60의 RRFRanker
# Create an index over the documnts
from llama_index.vector_stores.milvus import MilvusVectorStore
from llama_index.core import StorageContext, VectorStoreIndex
vector_store = MilvusVectorStore(
uri=URI,
# token=TOKEN,
dim=1536, # vector dimension depends on the embedding model
enable_sparse=True, # enable the default full-text search using BM25
overwrite=True, # drop the collection if it already exists
)
storage_context = StorageContext.from_defaults(vector_store=vector_store)
index = VectorStoreIndex.from_documents(documents, storage_context=storage_context)
2025-04-17 03:38:16,645 [DEBUG][_create_connection]: Created new connection using: cf0f4df74b18418bb89ec512063c1244 (async_milvus_client.py:547)
Sparse embedding function is not provided, using default.
Default sparse embedding function: BM25BuiltInFunction(input_field_names='text', output_field_names='sparse_embedding').
다음은 밀도 및 스파스 필드 구성 인자에 대한 자세한 정보입니다( MilvusVectorStore:
조밀 필드
enable_dense (bool): 고밀도 임베딩을 활성화 또는 비활성화하는 부울 플래그입니다. 기본값은 True입니다.dim (int, optional): 컬렉션에 대한 임베딩 벡터의 차원입니다.embedding_field (str, optional): 컬렉션에 대한 고밀도 임베딩 필드의 이름(기본값은 DEFAULT_EMBEDDING_KEY)입니다.index_config (dict, optional): 고밀도 임베딩 인덱스를 구축하는 데 사용되는 구성입니다. 기본값은 없음입니다.search_config (dict, optional): 밀버스 고밀도 인덱스 검색에 사용되는 구성입니다.index_config에서 지정한 인덱스 유형과 호환되어야 합니다. 기본값은 없음입니다.similarity_metric (str, optional): 밀도 임베딩에 사용할 유사성 메트릭으로, 현재 IP, COSINE 및 L2를 지원합니다.
스파스 필드
enable_sparse (bool): 스파스 임베딩을 활성화 또는 비활성화하는 부울 플래그입니다. 기본값은 False입니다.sparse_embedding_field (str): 스파스 임베딩 필드의 이름, 기본값은 DEFAULT_SPARSE_EMBEDDING_KEY입니다.sparse_embedding_function (Union[BaseSparseEmbeddingFunction, BaseMilvusBuiltInFunction], optional): enable_sparse가 True인 경우, 텍스트를 스파스 임베딩으로 변환하려면 이 객체를 제공해야 합니다. None이면 기본 스파스 임베딩 함수(BM25BuiltInFunction)가 사용되거나, 내장 함수가 없는 기존 컬렉션의 경우 BGEM3SparseEmbedding을 사용합니다.sparse_index_config (dict, optional): 스파스 임베딩 인덱스를 구축하는 데 사용되는 구성입니다. 기본값은 없음입니다.
쿼리 단계에서 하이브리드 검색을 사용하려면 vector_store_query_mode 을 "hybrid"로 설정합니다. 그러면 시맨틱 검색과 전체 텍스트 검색 모두에서 검색 결과가 결합되고 순위가 재조정됩니다. 샘플 쿼리로 테스트해 보겠습니다: "작성자는 Viaweb에서 무엇을 배웠나요?":
import textwrap
query_engine = index.as_query_engine(
vector_store_query_mode="hybrid", similarity_top_k=5
)
response = query_engine.query("What did the author learn at Viaweb?")
print(textwrap.fill(str(response), 100))
The author learned about retail, the importance of user feedback, and the significance of growth
rate as the ultimate test of a startup at Viaweb.
텍스트 분석기 사용자 지정
분석기는 문장을 토큰으로 나누고 어간 및 중단어 제거와 같은 어휘 처리를 수행함으로써 전체 텍스트 검색에서 중요한 역할을 합니다. 일반적으로 언어에 따라 다릅니다. 자세한 내용은 Milvus 분석기 가이드를 참조하세요.
Milvus는 두 가지 유형의 분석기를 지원합니다: 기본 제공 분석 기와 사용자 정의 분석기입니다. 기본적으로 enable_sparse 이 True로 설정된 경우 MilvusVectorStore 은 구두점을 기준으로 텍스트를 토큰화하는 표준 기본 제공 분석기를 사용하는 기본 구성의 BM25BuiltInFunction 을 사용합니다.
다른 분석기를 사용하거나 기존 분석기를 사용자 정의하려면 BM25BuiltInFunction 을 작성할 때 analyzer_params 인수에 값을 제공한 다음 MilvusVectorStore 에서 이 함수를 sparse_embedding_function 으로 설정하면 됩니다.
from llama_index.vector_stores.milvus.utils import BM25BuiltInFunction
bm25_function = BM25BuiltInFunction(
analyzer_params={
"tokenizer": "standard",
"filter": [
"lowercase", # Built-in filter
{"type": "length", "max": 40}, # Custom cap size of a single token
{"type": "stop", "stop_words": ["of", "to"]}, # Custom stopwords
],
},
enable_match=True,
)
vector_store = MilvusVectorStore(
uri=URI,
# token=TOKEN,
dim=1536,
enable_sparse=True,
sparse_embedding_function=bm25_function, # BM25 with custom analyzer
overwrite=True,
)
2025-04-17 03:38:48,085 [DEBUG][_create_connection]: Created new connection using: 61afd81600cb46ee89f887f16bcbfe55 (async_milvus_client.py:547)
다른 스파스 임베딩과 하이브리드 검색
밀버스는 시맨틱 검색과 BM25의 결합 외에도 BGE-M3와 같은 스파스 임베딩 기능을 사용한 하이브리드 검색도 지원합니다. 다음 예는 내장된 BGEM3SparseEmbeddingFunction 을 사용하여 스파스 임베딩을 생성하는 예제입니다.
먼저 FlagEmbedding 패키지를 설치해야 합니다:
$ pip install -q FlagEmbedding
그런 다음 고밀도 임베딩을 위한 기본 OpenAI 모델과 희소 임베딩을 위한 기본 제공 BGE-M3를 사용하여 벡터 저장소와 인덱스를 구축해 보겠습니다:
from llama_index.vector_stores.milvus.utils import BGEM3SparseEmbeddingFunction
vector_store = MilvusVectorStore(
uri=URI,
# token=TOKEN,
dim=1536,
enable_sparse=True,
sparse_embedding_function=BGEM3SparseEmbeddingFunction(),
overwrite=True,
)
storage_context = StorageContext.from_defaults(vector_store=vector_store)
index = VectorStoreIndex.from_documents(documents, storage_context=storage_context)
Fetching 30 files: 100%|██████████| 30/30 [00:00<00:00, 68871.99it/s]
2025-04-17 03:39:02,074 [DEBUG][_create_connection]: Created new connection using: ff4886e2f8da44e08304b748d9ac9b51 (async_milvus_client.py:547)
Chunks: 100%|██████████| 1/1 [00:00<00:00, 1.07it/s]
이제 샘플 질문으로 하이브리드 검색 쿼리를 수행해 보겠습니다:
query_engine = index.as_query_engine(
vector_store_query_mode="hybrid", similarity_top_k=5
)
response = query_engine.query("What did the author learn at Viaweb??")
print(textwrap.fill(str(response), 100))
Chunks: 100%|██████████| 1/1 [00:00<00:00, 17.29it/s]
The author learned about retail, the importance of user feedback, the value of growth rate in a
startup, the significance of pricing strategy, the benefits of working on things that weren't
prestigious, and the challenges and rewards of running a startup.
스파스 임베딩 기능 사용자 지정
다음 방법을 포함하여 BaseSparseEmbeddingFunction 에서 상속하는 한 스파스 임베딩 함수를 사용자 정의할 수도 있습니다:
encode_queries: 이 메서드는 텍스트를 쿼리에 대한 스파스 임베딩 목록으로 변환합니다.encode_documents: 이 메서드는 텍스트를 문서에 대한 스파스 임베딩 목록으로 변환합니다.
각 메서드의 출력은 사전 목록인 스파스 임베딩의 형식을 따라야 합니다. 각 사전에는 차원을 나타내는 키(정수)와 해당 차원에서의 임베딩의 크기를 나타내는 해당 값(실수)이 있어야 합니다(예: {1: 0.5, 2: 0.3}).
예를 들어, 다음은 BGE-M3을 사용한 사용자 정의 스파스 임베딩 함수 구현입니다:
from FlagEmbedding import BGEM3FlagModel
from typing import List
from llama_index.vector_stores.milvus.utils import BaseSparseEmbeddingFunction
class ExampleEmbeddingFunction(BaseSparseEmbeddingFunction):
def __init__(self):
self.model = BGEM3FlagModel("BAAI/bge-m3", use_fp16=False)
def encode_queries(self, queries: List[str]):
outputs = self.model.encode(
queries,
return_dense=False,
return_sparse=True,
return_colbert_vecs=False,
)["lexical_weights"]
return [self._to_standard_dict(output) for output in outputs]
def encode_documents(self, documents: List[str]):
outputs = self.model.encode(
documents,
return_dense=False,
return_sparse=True,
return_colbert_vecs=False,
)["lexical_weights"]
return [self._to_standard_dict(output) for output in outputs]
def _to_standard_dict(self, raw_output):
result = {}
for k in raw_output:
result[int(k)] = raw_output[k]
return result
하이브리드 리랭커 커스터마이징
Milvus는 두 가지 유형의 리랭크 전략을 지원합니다: 상호 순위 융합(RRF)과 가중치 점수. MilvusVectorStore 하이브리드 검색의 기본 랭커는 k=60의 RRF입니다. 하이브리드 랭커를 사용자 지정하려면 다음 매개변수를 수정하세요:
hybrid_ranker (str): 하이브리드 검색 쿼리에 사용되는 랭킹러의 유형을 지정합니다. 현재 ["RRFRanker", "WeightedRanker"]만 지원합니다. 기본값은 "RRFRanker"입니다.hybrid_ranker_params (dict, optional): 하이브리드 랭커의 구성 매개변수입니다. 이 사전의 구조는 사용 중인 특정 랭커에 따라 다릅니다:- "RRFRanker"의 경우 다음을 포함해야 합니다:
- "k" (int): 상호 순위 융합(RRF)에 사용되는 매개변수입니다. 이 값은 검색 관련성을 높이기 위해 여러 순위 전략을 단일 점수로 결합하는 RRF 알고리즘의 일부로 순위 점수를 계산하는 데 사용됩니다. 지정하지 않으면 기본값은 60입니다.
- "가중치랭커"의 경우, 예상되는 값은 다음과 같습니다:
- "가중치"(플로트 목록): 정확히 두 개의 가중치 목록입니다:
- 밀집 임베딩 컴포넌트에 대한 가중치.
- 희소 임베딩 구성 요소에 대한 가중치. 이러한 가중치는 하이브리드 검색 프로세스에서 임베딩의 고밀도 및 희소 구성 요소의 중요도 균형을 맞추는 데 사용됩니다. 지정하지 않으면 기본 가중치는 [1.0, 1.0]입니다.
- "가중치"(플로트 목록): 정확히 두 개의 가중치 목록입니다:
- "RRFRanker"의 경우 다음을 포함해야 합니다:
vector_store = MilvusVectorStore(
uri=URI,
# token=TOKEN,
dim=1536,
overwrite=False, # Use the existing collection created in the previous example
enable_sparse=True,
hybrid_ranker="WeightedRanker",
hybrid_ranker_params={"weights": [1.0, 0.5]},
)
index = VectorStoreIndex.from_vector_store(vector_store)
query_engine = index.as_query_engine(
vector_store_query_mode="hybrid", similarity_top_k=5
)
response = query_engine.query("What did the author learn at Viaweb?")
print(textwrap.fill(str(response), 100))
2025-04-17 03:44:00,419 [DEBUG][_create_connection]: Created new connection using: 09c051fb18c04f97a80f07958856587b (async_milvus_client.py:547)
Sparse embedding function is not provided, using default.
No built-in function detected, using BGEM3SparseEmbeddingFunction().
Fetching 30 files: 100%|██████████| 30/30 [00:00<00:00, 136622.28it/s]
Chunks: 100%|██████████| 1/1 [00:00<00:00, 1.07it/s]
The author learned several valuable lessons at Viaweb, including the importance of understanding
growth rate as the ultimate test of a startup, the significance of user feedback in shaping the
software, and the realization that web applications were the future of software development.
Additionally, the experience at Viaweb taught the author about the challenges and rewards of running
a startup, the value of simplicity in software design, and the impact of pricing strategies on
attracting customers.