Mistral AI와 Pinecone으로 RAG 구축하기
Mistral AI와 Pinecone으로 RAG 구축하기
Mistral의 임베딩 모델 mistral-embed와 벡터 데이터베이스 Pinecone을 사용해 RAG 파이프라인을 만드는 쿡북이에요. ArXiv 논문 데이터를 임베딩해 Pinecone에 저장하고, 검색한 뒤 Mistral Large로 답변을 생성합니다.
출처: 문서
본문
데이터 준비 (Data Preparation)
먼저 필요한 라이브러리를 설치합니다.
!pip3 install -qU datasets mistralai pinecone
인코딩해서 저장할 데이터셋을 내려받습니다. jamescalam/ai-arxiv2-semantic-chunks 데이터셋은 LLM과 GenAI 중심의 인기 ArXiv 논문에서 스크랩한 데이터를 담고 있어요.
from datasets import load_dataset
data = load_dataset(
"jamescalam/ai-arxiv2-semantic-chunks",
split="train[:10000]"
)
data
약 20만 개의 청크가 있으며, 각 청크는 대략 1~2문단 길이입니다.
data[0]
필요한 형식으로 데이터를 정리합니다. id, 임베딩할 text, 그리고 메타데이터를 담습니다.
data = data.map(lambda x: {
"id": x["id"],
"metadata": {
"title": x["title"],
"content": x["content"],
}
})
# drop unneeded columns
data = data.remove_columns([
"title", "content", "prechunk_id",
"postchunk_id", "arxiv_id", "references"
])
data
검색을 위한 벡터를 만들 임베딩 모델로 Mistral AI의 mistral-embed를 사용합니다. 이 모델은 비용이 들 수 있으니 유의하세요 (이 노트북 실행 비용은 <$1).
import os
from mistralai.client import Mistral
import getpass # console.mistral.ai/api-keys/
# get API key from left navbar in Mistral console
mistral_api_key = os.getenv("MISTRAL_API_KEY") or getpass.getpass("Enter your Mistral API key: ")
# initialize client
mistral = Mistral(api_key=mistral_api_key)
임베딩을 생성합니다.
embed_model = "mistral-embed"
embeds = mistral.embeddings.create(
model=embed_model, inputs=["this is a test"]
)
반환된 임베딩의 차원을 확인합니다. 벡터 인덱스를 초기화할 때 이 값이 필요해요.
dims = len(embeds.data[0].embedding)
dims
벡터 DB인 Pinecone을 생성합니다. 이를 위해 무료 Pinecone API key가 필요해요. API key는 Pinecone 대시보드 왼쪽 내비게이션의 "API Keys" 버튼에서 찾을 수 있습니다.
from pinecone import Pinecone
# initialize connection to pinecone (get API key at app.pinecone.io)
api_key = os.getenv("PINECONE_API_KEY") or getpass.getpass("Enter your Pinecone API key: ")
# configure client
pc = Pinecone(api_key=api_key)
인덱스 사양을 설정해 클라우드 제공자와 지역을 정합니다. 사용 가능한 모든 제공자·지역 목록은 여기에서 확인할 수 있어요.
from pinecone import ServerlessSpec
spec = ServerlessSpec(
cloud="aws", region="us-east-1"
)
인덱스를 생성합니다. mistral-embed의 차원(1024)과 호환되는 metric(cosine 또는 dotproduct)을 사용합니다.
import time
index_name = "mistral-rag"
existing_indexes = [
index_info["name"] for index_info in pc.list_indexes()
]
# check if index already exists (it shouldn't if this is first time)
if index_name not in existing_indexes:
# if does not exist, create index
pc.create_index(
index_name,
dimension=dims, # dimensionality of mistral-embed
metric='dotproduct',
spec=spec
)
# wait for index to be initialized
while not pc.describe_index(index_name).status['ready']:
time.sleep(1)
# connect to index
index = pc.Index(index_name)
time.sleep(1)
# view index stats
index.describe_index_stats()
한 번의 임베딩 배치에 너무 많은 토큰을 넣지 않도록 하는 임베딩 함수를 정의합니다 (2024년 5월 21일 기준 한도는 16384 토큰).
def embed(metadata: list[dict]):
batch_size = len(metadata)
while batch_size >= 1: # Allow batch_size to go down to 1
try:
embeds = []
for j in range(0, len(metadata), batch_size):
j_end = min(len(metadata), j + batch_size)
input_texts = [x["title"] + "\n" + x["content"] for x in metadata[j:j_end]]
embed_response = mistral.embeddings.create(
inputs=input_texts,
model=embed_model
)
embeds.extend([x.embedding for x in embed_response.data])
return embeds
except Exception as e:
batch_size = int(batch_size / 2)
print(f"Hit an exception: {e}, attempting {batch_size=}")
raise Exception("Failed to embed metadata after multiple attempts.")
현재 인덱스는 total_vector_count가 0인 빈 상태예요. 여기에 mistral-embed로 만든 임베딩을 채워 넣습니다.
⚠️ 경고: 2024년 1월 3일 기준 전체 데이터셋의 임베딩 비용은 약 $5.70입니다.
from tqdm.auto import tqdm
batch_size = 32 # how many embeddings we create and insert at once
for i in tqdm(range(0, len(data), batch_size)):
# find end of batch
i_end = min(len(data), i+batch_size)
# create batch
batch = data[i:i_end]
# create embeddings
embeds = embed(batch["metadata"])
assert len(embeds) == (i_end-i)
to_upsert = list(zip(batch["id"], embeds, batch["metadata"]))
# upsert to Pinecone
index.upsert(vectors=to_upsert)
검색 테스트하기
쿼리를 임베딩해 Pinecone 인덱스에서 검색합니다.
def get_docs(query: str, top_k: int) -> list[str]:
# encode query
xq = mistral.embeddings.create(
inputs=[query],
model=embed_model
).data[0].embedding
# search pinecone index
res = index.query(vector=xq, top_k=top_k, include_metadata=True)
# get doc text
docs = [x["metadata"]['content'] for x in res["matches"]]
return docs
query = "can you tell me about mistral LLM?"
docs = get_docs(query, top_k=5)
print("\n---\n".join(docs))
검색 컴포넌트가 동작합니다. 이제 이것을 Mistral Large LLM에 넣어 답변을 생성해 봅니다.
def generate(query: str, docs: list[str]):
system_message = (
"You are a helpful assistant that answers questions about AI using the "
"context provided below.\n\n"
"CONTEXT:\n"
"\n---\n".join(docs)
)
messages = [
{
"role":"system", "content":system_message
},
{
"role":"user", "content":query
}
]
# generate response
chat_response = mistral.chat.complete(
model="mistral-large-latest",
messages=messages
)
return chat_response.choices[0].message.content
out = generate(query=query, docs=docs)
print(out)
작업이 끝나면 리소스를 아끼기 위해 인덱스를 삭제하는 걸 잊지 마세요!
pc.delete_index(index_name)
더 알아보기 (Learn more)
- Pinecone 공식 문서 — 벡터 데이터베이스 가이드
mistral.embeddings.create()— Mistral 임베딩 생성 API (mistral-embed, 1024차원)mistral-large-latest— 답변 생성에 사용한 LLM 모델ServerlessSpec— Pinecone 서버리스 인덱스 사양 (여기서는aws/us-east-1)