데이터 로딩
데이터 로딩 (인제스천)
선택한 LLM이 여러분의 데이터를 다루기 전에, 먼저 데이터를 처리하고 로드해야 해요. 이는 ML 세계의 데이터 정제/피처 엔지니어링 파이프라인, 또는 전통적 데이터 환경의 ETL 파이프라인과 비슷해요.
이 인제스천 파이프라인은 보통 세 가지 주요 단계로 구성돼요:
- 데이터 로드
- 데이터 변환
- 데이터 인덱싱 및 저장
인덱싱/저장은 이후 섹션에서 다뤄요. 이 가이드에서는 주로 로더와 변환에 대해 이야기할게요.
로더 (Loaders)
선택한 LLM이 데이터를 다루기 전에 로드가 필요해요. LlamaIndex는 데이터 커넥터(또는 Reader)로 이를 처리해요. 데이터 커넥터는 서로 다른 데이터 소스의 데이터를 흡수해 Document 객체로 형식화해요. Document는 데이터(현재는 텍스트, 향후에는 이미지와 오디오)와 그 데이터에 대한 메타데이터의 모음이에요.
SimpleDirectoryReader로 로드하기
쓰기 가장 쉬운 리더는 SimpleDirectoryReader예요. 주어진 디렉토리의 모든 파일에서 문서를 만들어요. LlamaIndex에 내장되어 있고 Markdown, PDF, Word 문서, PowerPoint 덱, 이미지, 오디오, 비디오 등 다양한 형식을 읽을 수 있어요.
from llama_index.core import SimpleDirectoryReader
documents = SimpleDirectoryReader("./data").load_data()
LlamaHub의 리더 사용하기
데이터를 가져올 수 있는 곳이 너무 많아서 전부 내장되지는 않아요. 대신 데이터 커넥터 레지스트리인 LlamaHub에서 다운로드해요.
이 예제에서 LlamaIndex는 DatabaseReader라는 커넥터를 다운로드해 설치해요. 이 커넥터는 SQL 데이터베이스에 대해 쿼리를 실행하고 결과의 모든 행을 Document로 반환해요:
from llama_index.core import download_loader
from llama_index.readers.database import DatabaseReader
reader = DatabaseReader(
scheme=os.getenv("DB_SCHEME"),
host=os.getenv("DB_HOST"),
port=os.getenv("DB_PORT"),
user=os.getenv("DB_USER"),
password=os.getenv("DB_PASS"),
dbname=os.getenv("DB_NAME"),
)
query = "SELECT * FROM users"
documents = reader.load_data(query=query)
LlamaHub에 사용할 수 있는 커넥터가 수백 개 있어요!
Document 직접 만들기
로더를 쓰는 대신 Document를 직접 사용할 수도 있어요.
from llama_index.core import Document
doc = Document(text="text")
변환 (Transformations)
데이터를 로드한 뒤에는 저장 시스템에 넣기 전에 데이터를 처리하고 변환해야 해요. 이 변환에는 청킹, 메타데이터 추출, 각 청크 임베딩이 포함돼요. 데이터가 검색되고 LLM이 최적으로 사용할 수 있게 만들기 위해 필요한 과정이에요.
변환의 입력/출력은 Node 객체예요(Document는 Node의 서브클래스죠). 변환은 쌓거나 재정렬할 수도 있어요.
문서를 변환하는 고수준 API와 저수준 API를 모두 제공해요.
고수준 변환 API
인덱스에는 .from_documents() 메서드가 있고, Document 객체 배열을 받아 올바르게 파싱하고 청킹해요. 하지만 가끔은 문서를 어떻게 나눌지 더 세밀하게 제어하고 싶을 때가 있어요.
from llama_index.core import VectorStoreIndex
vector_index = VectorStoreIndex.from_documents(documents)
vector_index.as_query_engine()
내부적으로 이 과정은 Document를 Node 객체로 나눠요. Node는 Document와 비슷하지만(텍스트와 메타데이터를 담고 있지만) 부모 Document와의 관계를 갖고 있어요.
텍스트 스플리터 같은 핵심 컴포넌트를 이 추상화를 통해 커스터마이징하고 싶다면 커스텀 transformations 목록을 넘기거나 전역 Settings에 적용할 수 있어요:
from llama_index.core.node_parser import SentenceSplitter
text_splitter = SentenceSplitter(chunk_size=512, chunk_overlap=10)
# global
from llama_index.core import Settings
Settings.text_splitter = text_splitter
# per-index
index = VectorStoreIndex.from_documents(
documents, transformations=[text_splitter]
)
저수준 변환 API
이 단계들을 명시적으로 정의할 수도 있어요.
이는 변환 모듈(텍스트 스플리터, 메타데이터 추출기 등)을 독립 컴포넌트로 사용하거나, 선언적인 Transformation Pipeline 인터페이스에서 조합해서 할 수 있어요.
단계를 하나씩 살펴볼게요.
문서를 노드로 나누기
문서를 처리하는 핵심 단계는 문서를 '청크'/Node 객체로 나누는 것이에요. 핵심 아이디어는 데이터를 검색하고 LLM에 공급할 수 있는 작은 조각으로 처리하는 것이에요.
LlamaIndex는 단락/문장/토큰 기반 스플리터부터 HTML, JSON 같은 파일 기반 스플리터까지, 광범위한 텍스트 스플리터를 지원해요.
이들은 단독으로 또는 인제스천 파이프라인의 일부로 사용할 수 있어요.
from llama_index.core import SimpleDirectoryReader
from llama_index.core.ingestion import IngestionPipeline
from llama_index.core.node_parser import TokenTextSplitter
documents = SimpleDirectoryReader("./data").load_data()
pipeline = IngestionPipeline(transformations=[TokenTextSplitter(), ...])
nodes = pipeline.run(documents=documents)
메타데이터 추가
문서와 노드에 메타데이터를 추가할 수도 있어요. 수동으로 하거나 자동 메타데이터 추출기로 할 수 있어요.
다음은 1) Document 커스터마이징, 2) Node 커스터마이징 가이드예요.
document = Document(
text="text",
metadata={"filename": "<doc_file_name>", "category": "<category>"},
)
임베딩 추가
노드를 벡터 인덱스에 넣으려면 임베딩이 있어야 해요. 자세한 내용은 인제스천 파이프라인 또는 임베딩 가이드를 참고해요.
Node 직접 생성·전달
원한다면 노드를 직접 만들어 인덱서에 Node 목록을 직접 넘길 수 있어요:
from llama_index.core.schema import TextNode
node1 = TextNode(text="<text_chunk>", id_="<node_id>")
node2 = TextNode(text="<text_chunk>", id_="<node_id>")
index = VectorStoreIndex([node1, node2])