Gel 벡터 저장소
Gel 벡터 저장소 (Gel Vector Store)
이번엔 Gel을 LlamaIndex의 벡터 저장소 백엔드로 활용하는 방법을 배워볼게요. Gel은 개발에서 프로덕션까지 빠른 사이클을 목표로 하는 오픈소스 PostgreSQL 데이터 레이어로, 엄격한 타입의 그래프형 데이터 모델과 계층적 쿼리 언어를 제공한답니다. 스키마만 만들어 주면 다른 벡터 저장소처럼 그대로 쓸 수 있어요.
출처: 문서
본문
Gel 은 개발에서 프로덕션까지 빠른 사이클을 위해 최적화된 오픈소스 PostgreSQL 데이터 레이어입니다. 엄격한 타입의 그래프형 고수준 데이터 모델, 조합 가능한 계층적 쿼리 언어, 완전한 SQL 지원, 마이그레이션, Auth 및 AI 모듈이 함께 제공됩니다.
콜랩 노트북에서 열고 있다면 LlamaIndex 설치가 필요할 거예요 🦙.
! pip install gel llama-index-vector-stores-gel
! pip install llama-index
# import logging
# import sys
# Uncomment to see debug logs
# logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
# logging.getLogger().addHandler(logging.StreamHandler(stream=sys.stdout))
from llama_index.core import SimpleDirectoryReader, StorageContext
from llama_index.core import VectorStoreIndex
from llama_index.vector_stores.gel import GelVectorStore
import textwrap
import openai
OpenAI 설정 (Setup OpenAI)
첫 단계로 openai 키를 구성합니다. 이 키는 인덱스에 로드된 문서에 대한 임베딩을 생성하는 데 사용됩니다.
import os
os.environ["OPENAI_API_KEY"] = "<your key>"
openai.api_key = os.environ["OPENAI_API_KEY"]
데이터 다운로드
!mkdir -p 'data/paul_graham/'
!wget 'https://raw.githubusercontent.com/run-llama/llama_index/main/docs/examples/data/paul_graham/paul_graham_essay.txt' -O 'data/paul_graham/paul_graham_essay.txt'
문서 로드 (Loading documents)
SimpleDirectoryReader 를 사용해 data/paul_graham/ 폴더에 저장된 문서를 로드합니다.
documents = SimpleDirectoryReader("./data/paul_graham").load_data()
print("Document ID:", documents[0].doc_id)
데이터베이스 생성 (Create the Database)
Gel을 벡터 저장소의 백엔드로 사용하려면 동작하는 Gel 인스턴스가 필요합니다. 다행히도 원하지 않는다면 Docker 컨테이너나 복잡한 과정을 거칠 필요가 없습니다.
로컬 인스턴스를 설정하려면 다음을 실행하세요.
! gel project init --non-interactive
Gel Cloud를 사용한다면(사용하는 걸 추천해요!) 해당 명령에 인자를 하나 더 추가하세요.
터미널 창
gel project init --server-instance <org-name>/<instance-name>
Gel을 실행하는 다양한 방법의 전체 목록은 참조 문서의 Running Gel 섹션을 참고하세요.
스키마 설정 (Set up the schema)
Gel 스키마 는 애플리케이션 데이터 모델에 대한 명시적인 고수준 설명입니다. 데이터가 어떻게 배치될지 정확히 정의할 수 있게 해 줄 뿐만 아니라, 링크(links), 접근 정책(access policies), 함수(functions), 트리거(triggers), 제약(constraints), 인덱스(indexes) 등 Gel의 많은 강력한 기능을 구동합니다.
LlamaIndex의 GelVectorStore 는 다음과 같은 스키마 레이아웃을 기대합니다.
schema_content = """
using extension pgvector;
module default {
scalar type EmbeddingVector extending ext::pgvector::vector<1536>;
type Record {
required collection: str;
text: str;
embedding: EmbeddingVector;
external_id: str {
constraint exclusive;
};
metadata: json;
index ext::pgvector::hnsw_cosine(m := 16, ef_construction := 128)
on (.embedding)
}
}
""".strip()
with open("dbschema/default.gel", "w") as f:
f.write(schema_content)
데이터베이스에 스키마 변경을 적용하려면 Gel의 migration tool 을 사용해 마이그레이션을 실행하세요.
! gel migration create --non-interactive
! gel migrate
이 시점부터 GelVectorStore 는 LlamaIndex에서 제공하는 다른 벡터 저장소의 대체품(drop-in replacement)으로 사용할 수 있습니다.
인덱스 생성 (Create the index)
vector_store = GelVectorStore()
storage_context = StorageContext.from_defaults(vector_store=vector_store)
index = VectorStoreIndex.from_documents(
documents, storage_context=storage_context, show_progress=True
)
query_engine = index.as_query_engine()
인덱스 쿼리 (Query the index)
이제 인덱스를 사용해 질문할 수 있습니다.
response = query_engine.query("What did the author do?")
print(textwrap.fill(str(response), 100))
response = query_engine.query("What happened in the mid 1980s?")
print(textwrap.fill(str(response), 100))
메타데이터 필터 (Metadata filters)
GelVectorStore 는 노드에 메타데이터를 저장하고, 검색 단계에서 해당 메타데이터를 기반으로 필터링하는 것을 지원합니다.
git 커밋 데이터셋 다운로드
!mkdir -p 'data/git_commits/'
!wget 'https://raw.githubusercontent.com/run-llama/llama_index/main/docs/examples/data/csv/commit_history.csv' -O 'data/git_commits/commit_history.csv'
import csv
with open("data/git_commits/commit_history.csv", "r") as f:
commits = list(csv.DictReader(f))
print(commits[0])
print(len(commits))
커스텀 메타데이터로 노드 추가하기
# Create TextNode for each of the first 100 commits
from llama_index.core.schema import TextNode
from datetime import datetime
import re
nodes = []
dates = set()
authors = set()
for commit in commits[:100]:
author_email = commit["author"].split("<")[1][:-1]
commit_date = datetime.strptime(
commit["date"], "%a %b %d %H:%M:%S %Y %z"
).strftime("%Y-%m-%d")
commit_text = commit["change summary"]
if commit["change details"]:
commit_text += "\n\n" + commit["change details"]
fixes = re.findall(r"#(\d+)", commit_text, re.IGNORECASE)
nodes.append(
TextNode(
text=commit_text,
metadata={
"commit_date": commit_date,
"author": author_email,
"fixes": fixes,
},
)
)
dates.add(commit_date)
authors.add(author_email)
print(nodes[0])
print(min(dates), "to", max(dates))
print(authors)
vector_store = GelVectorStore()
index = VectorStoreIndex.from_vector_store(vector_store=vector_store)
index.insert_nodes(nodes)
print(index.as_query_engine().query("How did Lakshmi fix the segfault?"))
메타데이터 필터 적용하기 (Apply metadata filters)
이제 노드를 검색할 때 커밋 작성자나 날짜로 필터링할 수 있습니다.
from llama_index.core.vector_stores.types import (
MetadataFilter,
MetadataFilters,
)
filters = MetadataFilters(
filters=[
MetadataFilter(key="author", value="[email protected]"),
MetadataFilter(key="author", value="[email protected]"),
],
condition="or",
)
retriever = index.as_retriever(
similarity_top_k=10,
filters=filters,
)
retrieved_nodes = retriever.retrieve("What is this software project about?")
for node in retrieved_nodes:
print(node.node.metadata)
filters = MetadataFilters(
filters=[
MetadataFilter(key="commit_date", value="2023-08-15", operator=">="),
MetadataFilter(key="commit_date", value="2023-08-25", operator="<="),
],
condition="and",
)
retriever = index.as_retriever(
similarity_top_k=10,
filters=filters,
)
retrieved_nodes = retriever.retrieve("What is this software project about?")
for node in retrieved_nodes:
print(node.node.metadata)
중첩 필터 적용하기 (Apply nested filters)
위 예제에서는 AND나 OR로 여러 필터를 결합했습니다. 여러 필터 세트를 결합할 수도 있습니다.
filters = MetadataFilters(
filters=[
MetadataFilters(
filters=[
MetadataFilter(
key="commit_date", value="2023-08-01", operator=">="
),
MetadataFilter(
key="commit_date", value="2023-08-15", operator="<="
),
],
condition="and",
),
MetadataFilters(
filters=[
MetadataFilter(key="author", value="[email protected]"),
MetadataFilter(key="author", value="[email protected]"),
],
condition="or",
),
],
condition="and",
)
retriever = index.as_retriever(
similarity_top_k=10,
filters=filters,
)
retrieved_nodes = retriever.retrieve("What is this software project about?")
for node in retrieved_nodes:
print(node.node.metadata)
위의 내용은 IN 연산자를 사용해 단순화할 수 있습니다. GelVectorStore 는 요소를 리스트와 비교하는 in, nin, contains 연산자를 지원합니다.
filters = MetadataFilters(
filters=[
MetadataFilter(key="commit_date", value="2023-08-01", operator=">="),
MetadataFilter(key="commit_date", value="2023-08-15", operator="<="),
MetadataFilter(
key="author",
value=["[email protected]", "[email protected]"],
operator="in",
),
],
condition="and",
)
retriever = index.as_retriever(
similarity_top_k=10,
filters=filters,
)
retrieved_nodes = retriever.retrieve("What is this software project about?")
for node in retrieved_nodes:
print(node.node.metadata)
# Same thing, with NOT IN
filters = MetadataFilters(
filters=[
MetadataFilter(key="commit_date", value="2023-08-01", operator=">="),
MetadataFilter(key="commit_date", value="2023-08-15", operator="<="),
MetadataFilter(
key="author",
value=["[email protected]", "[email protected]"],
operator="nin",
),
],
condition="and",
)
retriever = index.as_retriever(
similarity_top_k=10,
filters=filters,
)
retrieved_nodes = retriever.retrieve("What is this software project about?")
for node in retrieved_nodes:
print(node.node.metadata)
# CONTAINS
filters = MetadataFilters(
filters=[
MetadataFilter(key="fixes", value="5680", operator="contains"),
]
)
retriever = index.as_retriever(
similarity_top_k=10,
filters=filters,
)
retrieved_nodes = retriever.retrieve("How did these commits fix the issue?")
for node in retrieved_nodes:
print(node.node.metadata)