안녕하세요, 여러분! 오늘은 Milvus 벡터 데이터베이스와 하이브리드 임베딩을 사용하여 고급 벡터 검색 시스템을 구현하는 방법에 대해 알아보겠습니다.
 
이 기술은 대규모 텍스트 데이터에서 효율적인 유사성 검색을 가능하게 하며, 특히 자연어 처리 및 정보 검색 분야에서 매우 유용합니다.


1. 소개

최근 자연어 처리 기술의 발전으로 텍스트 데이터를 벡터로 변환하는 임베딩 기술이 크게 주목받고 있습니다.
이러한 벡터 표현을 효율적으로 저장하고 검색하기 위해 벡터 데이터베이스가 필수적입니다.
오늘 살펴볼 Milvus는 대규모 벡터 데이터를 위한 오픈소스 벡터 데이터베이스 솔루션입니다.
또한, 우리는 BGE-M3(BAAI/bge-m3) 모델을 사용한 하이브리드 임베딩 접근 방식을 살펴볼 것입니다.
이 방식은 밀집(dense) 벡터와 희소(sparse) 벡터를 결합하여 더 정확하고 효율적인 검색을 가능하게 합니다.

2. 필요한 라이브러리 설치 및 설정

먼저, 필요한 라이브러리들을 설치합니다.

pip install --upgrade pymilvus
pip install "pymilvus[model]"
pip install langchain-milvus

 
필요한 라이브러리들을 임포트합니다:

import warnings
warnings.filterwarnings('ignore', category=FutureWarning)
from langchain_milvus import Milvus
from pymilvus import (
    CollectionSchema, DataType, FieldSchema, Collection, connections, utility, model
)

이 코드는 Milvus 사용에 필요한 핵심 컴포넌트들을 임포트합니다.
warnings.filterwarnings()는 불필요한 경고 메시지를 숨깁니다.
 

3. 하이브리드 임베딩 래퍼 클래스 구현

BGE-M3 모델을 사용하기 위한 래퍼 클래스를 만듭니다:

class BGEM3EmbeddingWrapper:
    def __init__(self, embedding_function):
        self.embedding_function = embedding_function

    def embed_query(self, query):
        if not query:
            pass
        else:
            embeddings = self.embedding_function.encode_queries([query])
            dense_embeddings = embeddings["dense"]
            sparse_embeddings = embeddings["sparse"]
            return dense_embeddings, sparse_embeddings
    
    def embed_documents(self, texts: list[str]):
        if not texts:
            pass
        else: 
            embeddings = self.embedding_function.encode_documents(texts)
            return embeddings

bge_m3 = model.hybrid.BGEM3EmbeddingFunction(
    model_name = 'BAAI/bge-m3', 
    device = 'cuda:0', 
    use_fp16 = False
)

이 래퍼 클래스는 쿼리와 문서에 대한 임베딩을 생성합니다.
BGE-M3 모델은 밀집 벡터와 희소 벡터를 모두 생성하는 하이브리드 접근 방식을 사용합니다.
 

4. Milvus 매니저 클래스 구현

Milvus 데이터베이스와의 상호작용을 관리하는 클래스를 만듭니다:

class MilvusManager:
    def __init__(self):
        self.dbname = "default"
        self.connection_args = {
            "uri": "주소",
            "db_name": self.dbname
        }
        self.collection_name = "test_sparse"
        self.milvus = self.get_milvus()

    def connect(self):
        # 연결 로직...

    def create_collection(self, collection_name):
        # 컬렉션 생성 로직...

    def get_milvus(self):
        load_embedding = BGEM3EmbeddingWrapper(bge_m3)

        milvus = Milvus(
            connection_args=self.connection_args,
            collection_name=self.collection_name,
            embedding_function=load_embedding,
            vector_field='text_vector'
        )

        return milvus

    def insert_data(self, texts, text_vectors, sparse_vectors):
        # 데이터 삽입 로직...

이 클래스는 Milvus 데이터베이스와의 연결 설정, 컬렉션 생성, 데이터 삽입 등 주요 기능을 담당합니다.
 

5. Milvus 연결로직

Milvus 결렉션과 연결을 위해 코드를 추가합니다.

def connect(self):
        try:
            print("Milvus와 연결 중...")
            connections.connect(
                alias=self.dbname,
                **self.connection_args
            )

            if connections.has_connection(self.dbname):
                print("Milvus와 연결되었습니다.")
                return True
            else:
                print("Milvus 연결 실패: 연결이 확인되지 않았습니다.")
                return False
        except Exception as e:
            print(f"Milvus 연결 실패: {e}")
            return False

6. Milvus 컬렉션 생성

Milvus 컬렉션을 생성하기 위한 코드를 작성합니다.

def create_collection(self, collection_name):
        try:
            # milvus 연결
            if not self.connect():
                return
            
            # 컬렉션이 이미 존재하는지 확인
            if utility.has_collection(collection_name, using=self.dbname):
                print(f"Collection '{collection_name}'이 이미 존재합니다.")
                return
            
            fields = [
                FieldSchema(name="pk", dtype=DataType.INT64, is_primary=True, auto_id=True),
                FieldSchema(name="text", dtype=DataType.VARCHAR, max_length=65535),
                FieldSchema(name="text_vector", dtype=DataType.FLOAT_VECTOR, dim=1024),
                FieldSchema(name="sparse_vector", dtype=DataType.SPARSE_FLOAT_VECTOR),  # 희소 벡터 필드 추가
            ]

            collection_schema = CollectionSchema(
                fields=fields,
                enable_dynamic_fields=True,
                description=f"{collection_name}의 컬렉션"
            )

            # 컬렉션 생성
            collection = Collection(name=collection_name, schema=collection_schema, using=self.dbname)
            print(f"Collection: '{collection_name}'생성되었습니다.")

            index_params = {
                "metric_type": "COSINE",
                "index_type": "HNSW",
                "params": {"M": 512, "efConstruction": 1024}
            }

            # text_vector 필드에 대한 인덱스 생성
            collection.create_index(field_name="text_vector", index_params=index_params)
            print(f"'text_vector' 인덱스 생성 완료")

            # sparse_vector 필드에 대한 인덱스 생성
            index_params_sparse = {
                "index_type": "SPARSE_INVERTED_INDEX",
                "metric_type": "IP",
                "params": {"drop_ratio_build": 0.2}
            }
            collection.create_index(field_name="sparse_vector", index_params=index_params_sparse)
            print(f"'sparse_vector' 인덱스 생성 완료")

        except Exception as e:
            print(f"생성 실패'{collection_name}': {e}")

 

6-2. 컬렉션 스키마 설정

Milvus 컬렉션을 위한 스키마를 정의합니다:

fields = [
    FieldSchema(name="pk", dtype=DataType.INT64, is_primary=True, auto_id=True),
    FieldSchema(name="text", dtype=DataType.VARCHAR, max_length=65535),
    FieldSchema(name="text_vector", dtype=DataType.FLOAT_VECTOR, dim=1024),
    FieldSchema(name="sparse_vector", dtype=DataType.SPARSE_FLOAT_VECTOR),
]

이 스키마는 텍스트, 밀집 벡터, 희소 벡터를 저장할 필드를 정의합니다.
 

6-3. 인덱스 생성

효율적인 검색을 위해 인덱스를 생성합니다:

index_params = {
    "metric_type": "COSINE",
    "index_type": "HNSW",
    "params": {"M": 512, "efConstruction": 1024}
}
collection.create_index(field_name="text_vector", index_params=index_params)

index_params_sparse = {
    "index_type": "SPARSE_INVERTED_INDEX",
    "metric_type": "IP",
    "params": {"drop_ratio_build": 0.2}
}
collection.create_index(field_name="sparse_vector", index_params=index_params_sparse)

밀집 벡터와 희소 벡터에 대해 각각 다른 유형의 인덱스를 생성합니다.
HNSW(Hierarchical Navigable Small World) 인덱스는 밀집 벡터에, 역인덱스는 희소 벡터에 사용됩니다.
 

6-4. 희소 벡터(Sparse Vector)와 SPARSE_FLOAT_VECTOR 설명

1) 희소 벡터(Sparse Vector)란 무엇인가?

희소 벡터(Sparse Vector)는 대부분의 값이 0인 벡터를 의미합니다. 텍스트 데이터의 경우, 단어의 수가 매우 많아질 수 있지만, 개별 문서에서 실제로 나타나는 단어는 그 중 일부에 불과합니다. 이런 경우, 전체 단어 목록에 대한 벡터 표현에서 대부분의 요소가 0이 되는 희소 벡터가 생성됩니다.
예를 들어, 수천 또는 수만 개의 단어를 특징으로 하는 벡터 공간에서 하나의 문서는 그 단어들 중 몇 개만 포함하고 있을 것입니다. 이 경우, 이 문서를 나타내는 벡터는 대부분의 위치에 0을 가지게 되며, 일부 단어들에 대해서만 값이 있는 형태를 가지게 됩니다. 이러한 희소 벡터는 밀집 벡터보다 메모리 효율성이 높으며, 특정 종류의 검색 작업에 유리합니다.

2) SPARSE_FLOAT_VECTOR 데이터 타입

Milvus에서 SPARSE_FLOAT_VECTOR는 희소 벡터를 효율적으로 저장하고 검색하기 위한 데이터 타입입니다. 이 데이터 타입은 희소 벡터의 메모리 사용을 최적화하고, 희소 벡터에 특화된 인덱스와 검색 방법을 사용할 수 있도록 합니다.

  • 메모리 최적화: 희소 벡터는 대부분의 값이 0이기 때문에, 모든 요소를 저장하는 것은 비효율적입니다. SPARSE_FLOAT_VECTOR는 비제로(Non-Zero) 값을 가진 요소들만 저장하여 메모리 사용을 최적화합니다.
  • 검색 효율성: 희소 벡터의 경우, 벡터 간의 유사성 계산이 밀집 벡터와 다르게 처리될 수 있습니다. 예를 들어, 역인덱스(Inverted Index) 구조를 사용하여 희소 벡터의 빠른 검색이 가능해집니다.

3) 왜 하이브리드 임베딩에서 희소 벡터가 중요한가?

하이브리드 임베딩은 밀집 벡터와 희소 벡터를 결합하여 텍스트의 의미를 더 정확하게 표현할 수 있도록 합니다. 밀집 벡터는 텍스트의 전반적인 의미를 캡처하는 데 강점이 있지만, 드물거나 특정 도메인에 중요한 용어의 표현에 한계가 있을 수 있습니다. 이때 희소 벡터가 이러한 드문 단어나 특정 키워드를 효과적으로 캡처하여 전체적인 검색의 정확도를 높이는 데 기여합니다.
예를 들어, 밀집 벡터는 "컴퓨터"와 "노트북" 사이의 높은 유사성을 캡처할 수 있지만, 희소 벡터는 특정 문서에만 포함된 고유한 기술 용어를 강조하여 검색 결과를 더 정확하게 만듭니다.
 

6-5. SPARSE_INVERTED_INDEX와 인덱스 파라미터

SPARSE_INVERTED_INDEX는 Milvus에서 희소 벡터의 효율적인 검색을 위해 사용되는 인덱스 유형입니다.
이 인덱스는 각 차원(dimension)이 0이 아닌 값을 가지는 벡터들을 리스트로 관리합니다.
검색 시, Milvus는 쿼리 벡터의 각 차원을 순회하며 해당 차원에서 0이 아닌 값을 가진 벡터들에 대해 점수를 계산합니다.
이를 통해 희소 벡터에서의 검색이 빠르고 정확하게 이루어질 수 있습니다.

인덱스 생성 파라미터

  • drop_ratio_build:
    • 설명: 인덱스 생성 과정에서 무시할 작은 벡터 값의 비율을 설정합니다. 이 파라미터는 효율성과 정확성 사이에서 균형을 맞추는 역할을 하며, 작은 값을 무시함으로써 인덱스 생성 과정에서 성능을 높일 수 있습니다.
    • 범위: [0, 1]

검색 파라미터

  • drop_ratio_search:
    • 설명: 검색 과정에서 무시할 작은 벡터 값의 비율을 설정합니다. 이 파라미터는 쿼리 벡터의 가장 작은 값들을 무시하여 검색 속도를 향상시키며, 성능과 정밀도 사이의 균형을 맞추는 데 사용됩니다. 설정된 값이 작을수록 작은 값들이 최종 점수에 덜 영향을 미치게 되며, 성능은 향상되지만 정확도에는 최소한의 영향을 미칩니다.
    • 범위: [0, 1]

이 두 파라미터는 SPARSE_INVERTED_INDEX를 사용할 때 인덱스 생성 및 검색 과정에서 효율성과 정확성 간의 균형을 조정하는 중요한 역할을 합니다. 이를 통해 사용자는 애플리케이션의 요구 사항에 맞게 인덱스와 검색 프로세스를 최적화할 수 있습니다.
 
Milvus_sparse 벡터
https://milvus.io/docs/sparse_vector.md

Sparse Vector | Milvus Documentation

Learn how to use sparse vectors in Milvus. | v2.4.x

milvus.io

 
지원되는 SPARSE의 메트릭 타입은 오직 "IP"만 사용가능합니다.
milvus_sparse 메트릭 타입
https://milvus.io/docs/index.md?tab=sparse

In-memory Index | Milvus Documentation

Index mechanism in Milvus. | v2.4.x

milvus.io

 

7. 데이터 삽입

def insert_data(self, texts, text_vectors, sparse_vectors):
        if not self.connect():
            print("Milvus와 연결이 안되어있습니다.")
            return
        
        try:
            collection = Collection(name=self.collection_name, using=self.dbname)

            entities = [
                texts,
                text_vectors,
                sparse_vectors
            ]

            collection.insert(entities)

            # 데이터 삽입 후 flush 호출
            collection.flush() 

            # 메모리 로드
            collection.load()  
            print(f"'{self.collection_name}'이 메모리에 로드되었습니다.")

        except Exception as e:
            print(f"문서가 {self.collection_name}에 삽입 실패하였습니다. \n{e}")
 

이 메서드는 텍스트, 밀집 벡터, 희소 벡터를 Milvus 컬렉션에 삽입합니다.
 


자 준비가 완료되었습니다.

이제 직접 인덱스를 생성 후 문서청킹 + 임베딩 + 삽입하는것까지 해보겠습니다!

 

1. 필요한 모듈 임포트

import os
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import TextLoader
from langchain_community.llms.ollama import Ollama
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnableLambda, RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
from pymilvus import (
    RRFRanker, WeightedRanker, AnnSearchRequest
)
from new_db_sparse import MilvusManager

 

2. 데이터베이스 생성!

MilvusManager().create_collection('test_sparse')
생성된 모습

이렇게 정상적으로 데이터베이스 생성하는걸 보실 수 있습니다.


코드 설명

txt 파일로드 후 dense, sparse 임베딩 생성하는 코드를 작성해보겠습니다.
 
1. MilvusManager 초기화

milvus_manager = MilvusManager()
milvus = milvus_manager.get_milvus()

먼저, MilvusManager 클래스의 인스턴스를 초기화하여 Milvus 데이터베이스와의 연결을 설정합니다. milvus_manager.get_milvus()를 통해 Milvus 인스턴스를 가져옵니다.
 

2. 파일 경로 설정

file_path = ""

벡터화할 텍스트 파일들이 저장된 디렉토리의 경로를 설정합니다.
 

3. CSR(Compressed Sparse Row) 벡터를 Sparse Dictionary로 변환

def csr_to_sparse_dict(csr_vec):
	"""CSR 배열을 dict 형식으로 변환"""
	coo = csr_vec.tocoo()  # CSR -> COO 형식으로 변환
	sparse_dict = {int(idx): float(val) for idx, val in zip(coo.col, coo.data)}
	return sparse_dict

이 함수는 희소 벡터를 CSR 형식에서 Dictionary 형식으로 변환합니다.
CSR 형식의 벡터는 효율적이지만, Milvus에 저장하기 위해선 Dictionary로 변환하는 것이 필요합니다.
tocoo() 메소드를 사용하여 CSR 벡터를 COO(좌표 리스트) 형식으로 변환한 후, 비제로 값들을 딕셔너리로 저장합니다.
 

4. 디렉토리에서 파일 목록 가져오기

def list_files(directory):
    try:
        file_names = os.listdir(directory)
        file_names = [f for f in file_names if os.path.isfile(os.path.join(directory, f))]
        return file_names
    except FileNotFoundError:
        return "Directory not found"
    except Exception as e:
        return str(e)

이 함수는 지정된 디렉토리 내의 파일 목록을 반환합니다.
폴더가 존재하지 않거나, 다른 예외가 발생하면 에러 메시지를 반환합니다.
 

5. 파일 처리

file_names = list_files(file_path)
uploaded_file_names = []

for file in file_names:
    file_full_path = os.path.join(file_path, file)

    with open(file_full_path, "rb") as f:
        content = f.read()
        uploaded_file_names.append(file)

디렉토리에서 가져온 파일 이름들을 순회하며 파일을 열고, 내용을 읽습니다.
읽은 파일들은 uploaded_file_names 리스트에 추가됩니다.
 

6. 텍스트 파일 로드 및 처리

loader = None

# txt 확장자
if file.endswith(".txt"):
    loader = TextLoader(file_full_path, encoding='utf-8')
        
# 문서를 로드 후 텍스트를 분활.
if loader is not None:
    documents = loader.load()

    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=512,
        chunk_overlap=128,
        separators=["\n\n"]
    )

    processed_texts = []
    for document in documents:
        split_texts = text_splitter.split_text(document.page_content)
        processed_texts.extend(split_texts)

.txt 확장자를 가진 텍스트 파일을 로드하고, 텍스트 데이터를 RecursiveCharacterTextSplitter를 사용하여 적절한 크기로 분할합니다.
이 도구는 텍스트를 일정한 크기(chunk)로 나누고, 각 조각 간의 중첩을 설정하여 컨텍스트를 보존할 수 있습니다.
 

7. 벡터화 및 데이터 삽입

processed_vector = milvus_manager.milvus.embedding_func.embed_documents(processed_texts)
        
if not processed_vector:
    print("처리된 텍스트가 없습니다.")
    continue

try:
    texts, text_vectors, sparse_vectors = [], [], []
    for idx in range(len(processed_texts)):
        text = processed_texts[idx]
        text_vector = processed_vector["dense"][idx]
        sparse_vector = processed_vector["sparse"]

        # sparse_vector는 coo_matrix 객체에서 각 행을 추출
        sparse_row = sparse_vector.getrow(idx)  # 인덱싱 대신 행을 추출
        sparse_dict = csr_to_sparse_dict(sparse_row)  # 행을 dict로 변환

        texts.append(text)
        text_vectors.append(text_vector)
        sparse_vectors.append(sparse_dict)

    milvus_manager.insert_data(texts, text_vectors, sparse_vectors)
except Exception as e:
    print(f"데이터 처리 중 오류 발생: {e}")
    continue

분할된 텍스트 조각들을 벡터화하고, 각 조각에 대해 밀집 벡터와 희소 벡터를 생성합니다.
희소 벡터는 이전에 정의한 csr_to_sparse_dict 함수를 사용해 딕셔너리 형식으로 변환합니다.
그런 다음, MilvusManager의 insert_data 메서드를 호출하여 Milvus 컬렉션에 벡터화된 데이터를 삽입합니다.
 

8. 지원되지 않는 파일 형식 처리

else:
    print("지원되지 않는 파일 형식")

만약 파일이 지원되지 않는 형식일 경우, 그에 대한 메시지를 출력합니다.
 
이번에는 gpt에게 정보를 추출(?)한  "로스트아크", "리그오브레전드" 파일과 "개인정보 보호법"을 넣어보겠습니다.
내부에는 캐릭터 혹은 챔피언별 장점과 단점으로 구성되어 있으면 개인정보보호법은 진짜법률 데이터 입니다.

 

 

네, 데이터가 잘 들어갔군요.


하이브리드 검색 시스템 구축

이제 구축한 벡터 데이터베이스를 이용해 하이브리드 검색을 수행하는 방법을 알아보겠습니다.
 

1.1 하이브리드 검색 함수

RAG의 핵심은 밀집 벡터와 희소 벡터를 결합하여 검색하는 것입니다.
hybrid_search 함수는 이러한 검색을 수행하며, Milvus 데이터베이스에서 관련 문서를 검색합니다.

def hybrid_search(query):
    dense_vector, sparse_vector = milvus.embedding_func.embed_query(query)

    dense_req = AnnSearchRequest(
        anns_field="text_vector",
        data=dense_vector,
        param={"metric_type": "COSINE", "params": {"ef": 3072}},
        limit=10
    )

    sparse_req = AnnSearchRequest(
        anns_field="sparse_vector",
        data=sparse_vector,
        param={"metric_type": "IP"},
        limit=10,
    )

    result = milvus.col.hybrid_search(
        [sparse_req, dense_req],
        rerank=RRFRanker(),
        limit=3,
        output_fields=["text"]
    )

    return result

여기서 rerank부분에 RRFRanker()함수를 사용했습니다.
다른 재정렬 방법은 WeightedRanker() 함수를 사용하시면 됩니다.
 
Milvus-reranking
https://milvus.io/docs/reranking.md

Reranking | Milvus Documentation

This topic covers the reranking process, explaining its significance and implementation of two reranking methods. | v2.4.x

milvus.io

 
 
1.2 검색된 문서 출력 포맷팅
검색된 문서 결과는 정리된 형태로 출력됩니다. 이 단계에서 사용자는 문서를 쉽게 이해할 수 있습니다.

def format_docs(docs):
    formatted_docs = []
    for hit in docs:
        for res in hit:
        	print(f"""\n가져온 문서: Text: {res.entity.get('text')}]""")
            formatted_docs.append(f"{res.entity.get('text')}")
    return "\n\n".join(formatted_docs)

 
1.3 질의응답 프롬프트 구성

prompt = PromptTemplate.from_template("""
너는 도움을 주는 AI 언어모델이야.
모든 답변은 한글(Korean)로 대답하고, 주어진 context에 기반해서 대답해.
잘 모르는 정답이면 "잘 모르겠습니다."라고 출력해.

#Question: 
{question} 
#Context: 
{context} 

#Answer:"""
)

 
1.4 질의응답 체인 구성
마지막으로, 검색된 문서와 사용자의 질문을 바탕으로 AI 모델이 적절한 답변을 생성하도록 질의응답 체인을 구성합니다.

chain = (
    {
        "context": hybrid_search | RunnableLambda(format_docs),
        "question": RunnablePassthrough()
    } 
    | prompt 
    | llm 
    | StrOutputParser()
)

 
1.4 체인 실행
이제 "워로드의 장점과 단점에 댜해 설명하세요"라는 질문을 던져, 시스템이 관련된 문서를 검색하고 답변을 생성하게 해봅시다.

question = "워로드의 장점과 단점에 대해 설명하세요"
res = chain.invoke(question)
print(res)

 
 

가져온문서

아래의 [더보기]를 클릭하시면 보실 수 있습니다.
 

더보기

가져온 문서: Text: 1. 버서커 (Berserker)
장점:
높은 대미지와 강력한 한방 스킬로 적을 빠르게 제압 가능  
생존력도 높아 전투에서 안정적
단점:
스킬 쿨다운이 길어 순간 폭발력에 의존
이동 속도가 느려 기동성이 떨어짐
2. 디스트로이어 (Destroyer)
장점:
탱킹 능력이 뛰어나 파티에서 방어 역할 가능
강력한 CC(군중 제어) 스킬 보유
단점:
공격 속도가 느리고 연계가 어려움
데미지가 상대적으로 낮아 딜링 역할에서는 부족함
3. 워로드 (Warlord)
장점:
뛰어난 방어력과 생존력으로 파티 보호에 탁월
안정적인 탱커 역할 가능
단점:
딜링 능력이 부족하고 공격 속도가 느림
다양한 스킬 활용에 높은 숙련도 요구
4. 데빌헌터 (Devil Hunter)
장점:
다양한 무기 사용으로 다양한 전술 가능
높은 기동성과 빠른 공격 속도
단점:
방어력이 낮아 생존력이 떨어짐
스킬 사용에 많은 자원 소모
5. 블레이드 (Blade)
장점:
빠른 공격 속도와 높은 기동성으로 적을 끊임없이 압박 가능
뛰어난 연계 스킬로 강력한 딜링 가능
단점:
방어력이 낮고 컨트롤이 어려움
높은 숙련도가 요구됨
6. 아르카나 (Arcana)
장점:
다양한 디버프와 버프 스킬로 파티 지원 가능
랜덤 요소가 가미된 독특한 전투 스타일
단점:
운에 크게 좌우되는 플레이 스타일
방어력이 낮아 생존에 어려움이 있음
7. 서머너 (Summoner)
장점:
소환수를 이용해 안정적인 딜링과 파티 지원 가능
다양한 전략적 플레이 가능
단점:
소환수 의존도가 높아 직접 전투 능력은 낮음
소환수 관리가 어려울 수 있음
8. 바드 (Bard)
장점:
강력한 힐과 버프로 파티의 생존을 지원
다양한 디버프 스킬로 적을 약화 가능
단점:
낮은 딜링 능력
스킬 사용 타이밍이 중요하여 숙련도 요구]

가져온 문서: Text: 1. 가렌 (Garen) - 탑 (Top)
장점:
뛰어난 생존력과 지속적인 유지력
간단한 스킬 구성으로 쉽게 접근 가능
궁극기의 강력한 처치 능력
단점:
기동성이 낮고, CC(군중 제어) 스킬에 취약
후반으로 갈수록 상대적으로 약해짐
2. 다리우스 (Darius) - 탑 (Top)
장점:
강력한 체인 킬 가능성 (특히 5스택 이후)
라인전에서 우월한 존재감
회복 및 유지력 뛰어남
단점:
기동성이 부족하여 카이팅에 취약
팀 파이트에서 진입이 어려울 수 있음
3. 리 신 (Lee Sin) - 정글 (Jungle)
장점:
높은 기동성과 뛰어난 갱킹 능력
다양한 플레이메이킹 가능 (인섹 킥 등)
초반 강력한 존재감
단점:
후반으로 갈수록 효율이 떨어짐
높은 숙련도 요구
4. 자르반 4세 (Jarvan IV) - 정글 (Jungle)
장점:
강력한 진입 능력과 팀 파이트 개시 능력
탱커로서의 역할 수행 가능
다재다능한 스킬 구성
단점:
스킬 샷에 의존도가 높아 실수가 치명적일 수 있음
후반으로 갈수록 데미지 부족
5. 아리 (Ahri) - 미드 (Mid)
장점:
뛰어난 기동성과 도주 능력
다양한 상황에서 사용 가능한 스킬 구성
매혹 스킬로 적을 견제 가능
단점:
스킬 샷에 대한 의존도가 높음
콤보 실패 시 딜링 능력 부족
6. 카타리나 (Katarina) - 미드 (Mid)
장점:
뛰어난 폭발력과 리셋으로 적을 순식간에 제압 가능
높은 기동성과 순간적인 전투 능력
단점:
CC에 매우 취약하며, 스킬이 차단되면 무력화됨
높은 숙련도 요구
7. 이즈리얼 (Ezreal) - 원딜 (ADC)
장점:
높은 기동성과 긴 사거리로 안전하게 딜링 가능
스킬 샷 기반의 플레이로 유연한 전투 가능
뛰어난 포킹 능력
단점:
스킬 샷에 크게 의존해 실수 시 큰 손해
초반 약세로 인해 성장에 시간 필요
8. 미스 포츈 (Miss Fortune) - 원딜 (ADC)
장점:
강력한 AOE 궁극기로 팀 파이트에서 큰 영향력 발휘
라인전에서 강한 압박 가능
높은 데미지 출력
단점:
기동성이 낮아 생존에 어려움
CC 스킬이 없어 생존 능력이 떨어짐
9. 쓰레쉬 (Thresh) - 서포터 (Support)
장점:
뛰어난 CC와 포지셔닝 능력
다양한 유틸리티 스킬로 팀 지원 가능
높은 스킬 융통성
단점:
높은 숙련도 요구
스킬 샷 실패 시 팀에 부담
10. 레오나 (Leona) - 서포터 (Support)
장점:
강력한 CC로 적을 지속적으로 제어 가능
높은 탱킹 능력으로 팀 파이트 개시 가능
라인전에서 강력한 압박력
단점:
진입 후 탈출이 어려워 상황 판단 중요
상대적으로 낮은 데미지 출력]

가져온 문서: Text: ② 정보주체는 개인정보처리자가 자동화된 결정을 한 경우에는 그 결정에 대하여 설명 등을 요구할 수 있다.

③ 개인정보처리자는 제1항 또는 제2항에 따라 정보주체가 자동화된 결정을 거부하거나 이에 대한 설명 등을 요구한 경우에는 정당한 사유가 없는 한 자동화된 결정을 적용하지 아니하거나 인적 개입에 의한 재처리ㆍ설명 등 필요
한 조치를 하여야 한다.

④ 개인정보처리자는 자동화된 결정의 기준과 절차, 개인정보가 처리되는 방식 등을 정보주체가 쉽게 확인할 수 있도록 공개하여야 한다.

⑤ 제1항부터 제4항까지에서 규정한 사항 외에 자동화된 결정의 거부ㆍ설명 등을 요구하는 절차 및 방법, 거부ㆍ설명 등의 요구에 따른 필요한 조치, 자동화된 결정의 기준ㆍ절차 및 개인정보가 처리되는 방식의 공개 등에 필요한
 사항은 대통령령으로 정한다.

[본조신설 2023. 3. 14.]]

 

AI답변

아래의 [더보기]를 클릭하시면 보실 수 있습니다.

더보기

 답변:
 워로드는 게임 내에서 뛰어난 방어력과 생존력을 자랑하는 탱커로서 파티 보호에 매우 탁월합니다. 안정적인 탱킹 역할을 하며 동료들의 생존 가능성을 높여줍니다.

단점으로는 공격력이 상대적으로 부족하며, 스킬 사용 시 정확도가 중요하기 때문에 실수에 취약한 점이 있습니다.

 

 
문서에 있는 워로드 장점 과 단점입니다.

3. 워로드 (Warlord)
장점:
뛰어난 방어력과 생존력으로 파티 보호에 탁월
안정적인 탱커 역할 가능
단점:
딜링 능력이 부족하고 공격 속도가 느림
다양한 스킬 활용에 높은 숙련도 요구

 
생각보다 답변이 잘 나온거 같네요~. 많은 청크된 문서들이 있는데 이런 정확도라니 신기하죠?
 
이번 포스트에서는 RAG 기반의 하이브리드 검색 시스템을 구축하는 과정을 다루었습니다.
Milvus 데이터베이스와 GPT 기반 언어 모델을 활용하여  관련 문서에서 정보를 효율적으로 검색하고, 이에 기반한 답변을 생성하는 방법을 살펴보았습니다.
이 시스템은 법률 문서, 기술 문서, 그리고 대규모의 다른 텍스트 데이터를 다루는 데 있어 매우 유용하며, 특히 정확한 정보 검색이 요구되는 분야에서 활용될 수 있습니다.
 
코드 구현 및 작성을 하면서 ["테디노트"]님의 랭체인 Langchian 튜토리얼을 많이 찾아보면서 구현했습니다.
테디노트님 감사합니다.
 
링크 : https://wikidocs.net/book/14314

<랭체인LangChain 노트> - LangChain 한국어 튜토리얼🇰🇷

**추천**은 공유할 수 있는 무료 전자책을 집필하는데 정말 큰 힘이 됩니다. **추천** 한 번씩만 부탁 드리겠습니다🙏🙏 ✅ **랭체인 한국어 튜토리얼 강의** …

wikidocs.net