본문 바로가기

인공지능/LLM 거대모델

RAG 시스템 사용을 위해

반응형

실제 GPT 서비스들을 이용해 보면서 LLM에 관심이 생겼고, 실제 운영환경에서는 다양한 기법들을 접목해서 사용하는 것을 알았다. 

그 중 하나가 RAG라는 벡터 임베딩을 활용한 유사도 검색을 통해서 LLM의 환각을 막는 것인데, 이 RAG를 직접 LLM 시스템에 붙여서 사용해보고자 한다.

 

Vector DB

벡터 데이터베이스를 GPT에게 물어보니 다양한 것들이 있었고, VectorDB가 내장되어 사용가능한 라이브러리도 여러가지가 있었다. 그 중에서 상업적으로도 무료로 이용가능하고, 로컬 환경에서 벡터 디비를 활용해 안전한 Qdrant를 사용해보고자 한다.

 

Qdrant

Qdrant는 설치할 필요도 없이 도커를 통해 손쉽게 로컬 환경에서 구동해줄 수 있다. mysql 설치처럼 간단하게 사용가능할 것 같다.

 

docker pull qdrant/qdrant
docker run -p 6333:6333 qdrant/qdrant

 

이후에는 파이썬 코드를 통해 서버와 상호작용한다.

 

pip install qdrant-client

 

Qdrant 서버에 접속하기

도커로 실행되고 있는 qdrant서버에 접속하기 위해 qdrant-client 패키지를 사용합니다. 다음 코드를 이용해 qdrant서버에 접속해줍니다.

from qdrant_client import QdrantClient
from qdrant_client.http.models import Distance, VectorParams, PointStruct, PointInsertOperations
qdrant = QdrantClient(host="localhost", port=6333)

 

 

Qdrant Collection 만들기

qdrant.create_collection(
    collection_name="klue_mrc_contexts",
    vectors_config=VectorParams(size=384, distance=Distance.COSINE)
)

콜렉션 이름과, 백터에 대한 환경변수를 지정해 콜렉션을 만들어 줄 수 있다. 

위에서는 간단하게 테스트용도로 벡터 크기를 768, 벡터간 비교 방법을 코사인 유사도를 사용한다. 

그외 다른 설정에 대해서는 아래 공식 문서를 참고한다.

 

Create a collection | Qdrant | API Reference

 

api.qdrant.tech

 

데이터 셋과 토큰 임베딩을 위한 토크나이저

from datasets import load_dataset

dataset = load_dataset('klue', 'mrc', split='train')
print(dataset[0])

from sentence_transformers import SentenceTransformer

model = SentenceTransformer('sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2')

# 문맥(context)만 추출
contexts = list(set(example['context'] for example in dataset))  # 중복 제거
context_embeddings = model.encode(contexts, show_progress_bar=True)

테스트를 위해 klue 데이터셋을 불러오고, 해당 데이터셋을 토큰화하기 위해 버트 모델을 사용한다.

 

 

qdrant에 저장하기

qdrant.upsert(
    collection_name="klue_mrc_contexts",
    points=[
        {
            "id": idx,
            "vector": vec.tolist(),
            "payload": {"context": contexts[idx]}
        }
        for idx, vec in enumerate(context_embeddings)
    ]
)


from tqdm import tqdm

batch_size = 100
for i in tqdm(range(0, len(context_embeddings), batch_size)):
    batch_vectors = context_embeddings[i:i+batch_size]
    batch_payloads = contexts[i:i+batch_size]
    client.upsert(
        collection_name="klue_mrc_contexts",
        points=[
            {
                "id": i + j,
                "vector": vec.tolist(),
                "payload": {"context": batch_payloads[j]}
            }
            for j, vec in enumerate(batch_vectors)
        ]
    )

오류 발생 시 qdrant 연결 설정에서 timeout시간을 늘려줍니다. 그래도 시간이 부족하다면 업로드 상 배치 사이즈를 정해 데이터를 나눠 업로드 하는 방법을 고려합니다.

 

qdrant 검색 

query = "KT는 무슨 회사야?"
query_vec = model.encode(query)

results = qdrant.query_points(
    collection_name="klue_mrc_contexts",
    limit=5,
    with_payload=True,
    query=query_vec.tolist(),
)

for result in results:
    for i, re in enumerate(result[1], 1):
        print(f"🔹 [결과 {i}]")
        print(f"🆔 ID: {re.id}")
        print(f"📊 점수: {re.score:.4f}")
        print(f"내용: {re.payload['context'][:100]}")
        print()

쿼리에 대한 문맥을 반환합니다. 이 문맥을 LLM 프롬프트에 결합시켜서 사용하면 환각을 막을 수 있습니다. 

유사도 점수도 같이 나와서, 특정 점수 이상의 문맥만 포함시키는게 가능.

 

 

통합방법 ?

1. 사용자가 prompt로 질문을 한다. 

2. 벡터 DB에서 사용자 prompt에 대한 맥락을 찾아 LLM 모델에 들어갈 prompt에 추가한다. (환각 방지)

3. 사용자의 질문을 새로 벡터화 해 DB에 저장한다. ? 

 

 

반응형