LangChain과 Qdrant를 활용한 S3 데이터 인제스트

LangChain과 Qdrant를 활용한 S3 데이터 인제스트 (tutorials-build-essentials-data-ingestion-beginners)

벡터 저장소로의 데이터 인제스트(ingestion) 는 효과적인 검색·검색 알고리즘을 구축하는 데 필수적이에요. 특히 데이터의 거의 80%가 비정형이라 정해진 형식이 없기 때문이에요.

이 튜토리얼에서는 AWS S3에서 직접 데이터를 가져와 Qdrant로 넣는 간결한 데이터 인제스트 파이프라인을 만들어 볼 거예요. 벡터 임베딩을 살펴보면서 비정형 데이터를 문서를 의미적으로 검색할 수 있는 형식으로 변환해 볼 거예요. 비정형 데이터 속에 숨은 통찰을 발견할 새로운 방법을 찾아볼 준비를 해 봐요!

시간: 30분 난이도: 초급

인제스트 워크플로우 아키텍처

이 워크플로우에서는 클라우드 스토리지, 자연어 처리(NLP) 도구, 임베딩 기술을 사용해 강력한 문서 인제스트·분석 파이프라인을 구축할 거예요. S3 버킷의 원시 데이터로 시작해 LangChain으로 전처리하고, 텍스트와 이미지 모두에 임베딩 API를 적용한 뒤 결과를 유사성 검색에 최적화된 벡터 데이터베이스인 Qdrant에 저장할 거예요.

그림 1: 데이터 인제스트 워크플로우 아키텍처

data-ingestion-beginners-5

이 워크플로우의 각 구성 요소를 살펴볼게요.

  • S3 버킷: 우리의 출발점이에요. PDF, 이미지, 텍스트 등 다양한 파일 형식을 위한 중앙 집중식 확장 가능 스토리지 솔루션이에요.
  • LangChain: 파이프라인의 오케스트레이터 역할을 해요. 추출, 전처리를 처리하고 임베딩 생성을 위한 데이터 흐름을 관리해요. PDF 처리를 단순화해서 여기서 OCR(광학 문자 인식)을 적용할 걱정을 할 필요가 없어요.
  • Qdrant: 벡터 데이터베이스로서 Qdrant는 임베딩과 그 payload를 저장해 모든 콘텐츠 유형에서 효율적인 유사성 검색과 검색을 가능하게 해요.

사전 준비 (Prerequisites)

data-ingestion-beginners-11

이 섹션에서는 S3 버킷에서 데이터를 인제스트하는 단계별 안내를 볼 거예요. 하지만 그 전에 모든 사전 준비 사항을 갖췄는지 확인해 볼게요.

샘플 데이터 텍스트 형식의 제품 리뷰와 해당 이미지를 담은 폴더로 구성된 샘플 데이터셋을 사용해요.
AWS 계정 S3 서비스에 접근할 수 있는 활성 AWS 계정
Qdrant Cloud 컬렉션 관리와 쿼리 실행을 위해 WebUI에 접근할 수 있는 Qdrant Cloud 계정
LangChain 모든 것을 연결하는 인기 프레임워크

지원되는 문서 유형

인제스트에 사용되는 문서는 PDF, 텍스트 파일, 이미지 등 다양한 유형이 될 수 있어요. 테스트와 실험을 위해 지원되는 문서 유형의 폴더로 구성된 구조화된 S3 버킷을 만들 거예요.

Python 환경

Python 환경(Python 3.9 이상)에 다음 라이브러리가 설치되어 있어야 해요.

boto3
langchain-community
langchain
python-dotenv
unstructured
unstructured[pdf]
qdrant_client
fastembed

액세스 키: AWS 액세스 키, S3 시크릿 키, Qdrant API 키를 .env 파일에 저장해 쉽게 접근할 수 있게 해요. 샘플 .env 파일은 다음과 같아요.

ACCESS_KEY = ""
SECRET_ACCESS_KEY = ""
QDRANT_KEY = ""

코드가 PDF 처리를 지원하지만, 샘플 데이터에는 현재 PDF 파일이 없어요.

Step 1: S3에서 데이터 인제스트

data-ingestion-beginners-9.png

LangChain 프레임워크는 PDF, 이미지, 텍스트 파일 같은 형식의 문서를 로드하는 내장 지원 덕분에 AWS S3 같은 스토리지 서비스에서 데이터를 쉽게 인제스트하게 해줘요.

LangChain을 S3에 연결하려면 S3DirectoryLoader를 사용해요. 이 로더는 S3 버킷에서 파일을 LangChain 파이프라인으로 직접 로드할 수 있게 해줘요.

예시: LangChain이 S3에서 파일을 로드하도록 구성

S3 버킷에서 데이터를 인제스트하도록 LangChain을 설정하는 방법은 다음과 같아요.

from langchain_community.document_loaders import S3DirectoryLoader

# S3 문서 로더 초기화
loader = S3DirectoryLoader(
   "product-dataset",  # S3 버킷 이름
   "p_1", #S3 폴더 이름 (첫 번째 제품 데이터 포함)
   aws_access_key_id=aws_access_key_id,  # AWS 액세스 키
   aws_secret_access_key=aws_secret_access_key  # AWS 시크릿 액세스 키
)

# 지정된 S3 버킷에서 문서 로드
docs = loader.load()

Step 2: 문서를 임베딩으로 변환

임베딩은 여기서 핵심 비결이에요. 쉽게 비교할 수 있는 형태로 "의미"를 포착하는 데이터(텍스트, 이미지, 오디오)의 수치 표현이에요. 텍스트와 이미지를 임베딩으로 변환하면 빠르고 효율적으로 유사성 검색을 수행할 수 있어요. 임베딩을 Qdrant에서 데이터의 의미 있는 통찰을 저장하고 검색하는 다리로 생각해 보세요.

임베딩 생성에 사용할 모델

시작하기 위해 두 가지 강력한 모델을 사용할 거예요.

  1. 텍스트 데이터 변환을 위한 sentence-transformers/all-MiniLM-L6-v2 임베딩
  2. 이미지 데이터를 위한 CLIP(Contrastive Language-Image Pretraining)

문서 처리 함수

data-ingestion-beginners-8.png

다음으로 문서 파이프라인의 각기 다른 파일 유형을 처리할 process_textprocess_image 두 함수를 정의할 거예요. process_text 함수는 텍스트 문서에서 원시 콘텐츠를 추출해 반환하고, process_image는 S3 소스에서 이미지를 검색해 메모리로 로드해요.

from PIL import Image

def process_text(doc):
    source = doc.metadata['source']  # 문서 소스 추출 (예: S3 URL)

    text = doc.page_content  # 텍스트 파일에서 콘텐츠 추출
    print(f"Processing text from {source}")
    return source, text

def process_image(doc):
    source = doc.metadata['source']  # 문서 소스 추출 (예: S3 URL)
    print(f"Processing image from {source}")

    bucket_name, object_key = parse_s3_url(source)  # S3 URL 파싱
    response = s3.get_object(Bucket=bucket_name, Key=object_key)  # S3에서 이미지 가져오기
    img_bytes = response['Body'].read()

    img = Image.open(io.BytesIO(img_bytes))
    return source, img

문서 처리를 위한 헬퍼 함수

S3에서 이미지를 검색하려면, 헬퍼 함수 parse_s3_url이 S3 URL을 버킷과 주요 구성 요소로 분해해요. 이는 S3 스토리지에서 이미지를 가져오는 데 필수적이에요.

def parse_s3_url(s3_url):
    parts = s3_url.replace("s3://", "").split("/", 1)
    bucket_name = parts[0]
    object_key = parts[1]
    return bucket_name, object_key

Step 3: 임베딩을 Qdrant에 로드

data-ingestion-beginners-10

이제 문서가 처리되고 임베딩으로 변환됐으니, 다음 단계는 이 임베딩을 Qdrant에 로드하는 거예요.

Qdrant에서 컬렉션 생성

Qdrant에서 데이터는 컬렉션으로 구성돼요. 각 컬렉션은 임베딩(또는 포인트) 집합과 연관된 메타데이터(payload)를 나타내요. 앞서 생성한 임베딩을 저장하려면 먼저 컬렉션을 만들어야 해요.

텍스트와 이미지 임베딩을 모두 저장하는 Qdrant 컬렉션을 만드는 방법은 다음과 같아요.

def create_collection(collection_name):
    qdrant_client.create_collection(
        collection_name,
        vectors_config={
            "text_embedding": models.VectorParams(
                size=384,  # 텍스트 임베딩의 차원
                distance=models.Distance.COSINE,  # 비교에 cosine 유사도 사용
            ),
            "image_embedding": models.VectorParams(
                size=512,  # 이미지 임베딩의 차원
                distance=models.Distance.COSINE,  # 비교에 cosine 유사도 사용
            ),
        },
    )

create_collection("products-data")

이 함수는 텍스트(384차원)와 이미지(512차원) 임베딩을 저장하는 컬렉션을 만들고, 컬렉션 내에서 임베딩을 비교하는 데 cosine 유사도를 사용해요.

컬렉션이 준비되면 임베딩을 Qdrant에 로드할 수 있어요. 여기에는 임베딩과 연관 메타데이터(payload)를 지정된 컬렉션에 삽입(또는 업데이트)하는 작업이 포함돼요.

임베딩을 Qdrant에 로드하는 코드는 다음과 같아요.

def ingest_data(points):
    operation_info = qdrant_client.upsert(
        collection_name="products-data",  # 데이터가 삽입되는 컬렉션
        points=points
    )
    return operation_info

인제스트 설명

  1. 데이터 포인트 Upsert: qdrant_client의 upsert 메서드는 각 PointStruct를 지정된 컬렉션에 삽입해요. 같은 ID의 포인트가 이미 있다면 새 값으로 업데이트돼요.
  2. 작업 정보: 함수는 성공 상태나 잠재적 오류 같은 upsert 작업의 세부 정보를 담은 operation_info를 반환해요.

인제스트 코드 실행

함수를 호출하고 데이터를 인제스트하는 방법은 다음과 같아요.

from qdrant_client import models

if __name__ == "__main__":
    collection_name = "products-data"
    create_collection(collection_name)
    for i in range(1,6): # 다섯 개의 문서
        folder = f"p_{i}"
        loader = S3DirectoryLoader(
            "product-dataset",
            folder,
            aws_access_key_id=aws_access_key_id,
            aws_secret_access_key=aws_secret_access_key
        )
        docs = loader.load()
        points, text_review, product_image = [], "", ""
        for idx, doc in enumerate(docs):
            source = doc.metadata['source']
            if source.endswith(".txt") or source.endswith(".pdf"):
                _text_review_source, text_review = process_text(doc)
            elif source.endswith(".png"):
                product_image_source, product_image = process_image(doc)
        if text_review:
            point = models.PointStruct(
                id=idx,  # 각 포인트의 고유 식별자
                vector={
                    "text_embedding": models.Document(
                        text=text_review, model="sentence-transformers/all-MiniLM-L6-v2"
                    ),
                    "image_embedding": models.Image(
                        image=product_image, model="Qdrant/clip-ViT-B-32-vision"
                    ),
                },
                payload={"review": text_review, "product_image": product_image_source},
            )
            points.append(point)
    operation_info = ingest_data(points)
    print(operation_info)

PointStruct는 다음 핵심 매개변수로 초기화돼요.

  • id: 각 임베딩의 고유 식별자로, 일반적으로 증가하는 인덱스예요.
  • vector: 임베딩할 텍스트·이미지 입력을 담는 딕셔너리예요. qdrant-client는 내부적으로 FastEmbed를 사용해 이 입력들로부터 벡터 표현을 로컬에서 자동 생성해요.
  • payload: 제품 리뷰와 이미지 참조 같은 추가 메타데이터를 저장하는 딕셔너리로, 검색 중 검색과 맥락에 매우 유용해요.

이 코드는 S3 버킷에서 폴더를 동적으로 로드하고, 텍스트와 이미지 파일을 따로 처리한 뒤 전용 리스트에 임베딩과 연관 데이터를 저장해요. 그런 다음 각 데이터 항목에 대해 PointStruct를 만들고 인제스트 함수를 호출해 Qdrant에 로드해요.

Qdrant WebUI 대시보드 살펴보기

임베딩이 Qdrant에 로드되면 WebUI 대시보드로 컬렉션을 시각화하고 관리할 수 있어요. 대시보드는 컬렉션과 그 데이터를 보는 명확하고 구조화된 인터페이스를 제공해요. 다음 섹션에서 자세히 살펴볼게요.

Step 4: Qdrant WebUI에서 데이터 시각화

Qdrant WebUI에서 데이터 시각화를 시작하려면 Overview 섹션으로 이동해 Access the database를 선택해요.

그림 2: Qdrant UI에서 데이터베이스 접근 data-ingestion-beginners-2.png

프롬프트가 나타나면 API 키를 입력해요. 접속하면 컬렉션과 해당 데이터 포인트를 볼 수 있어요. 컬렉션이 다음과 같이 표시될 거예요.

그림 3: Qdrant의 product-data 컬렉션 data-ingestion-beginners-4.png

Qdrant에 인제스트된 가장 최근 포인트를 살펴볼게요.

그림 4: product-data 컬렉션에 추가된 최신 포인트 data-ingestion-beginners-6.png

Qdrant WebUI의 검색 기능은 컬렉션 전반에 걸쳐 벡터 검색을 수행할 수 있게 해줘요. 필터와 매개변수를 적용할 수 있는 옵션 덕분에 관련 임베딩을 검색하고 데이터 내 관계를 탐색하는 것이 쉬워져요. 시작하려면 왼쪽 패널의 Console로 이동해 쿼리를 만들 수 있어요.

그림 5: Qdrant Console 개요 data-ingestion-beginners-1.png

첫 번째 쿼리는 모든 컬렉션을 검색하고, 두 번째는 product-data 컬렉션에서 포인트를 가져오며, 세 번째는 샘플 쿼리를 수행해요. 이는 Qdrant UI에서 데이터와 상호작용하는 것이 얼마나 간단한지 보여줘요.

이제 쿼리로 데이터베이스에서 일부 문서를 검색해 볼게요.

그림 6: 관련 문서를 검색하기 위한 Qdrant 클라이언트 쿼리 data-ingestion-beginners-3.png

이 예시에서는 디자인이 개선된 Phones(폰) 를 쿼리했어요. 그런 다음 OpenAI로 텍스트를 벡터로 변환하고 디자인 개선을 강조하는 관련 폰 리뷰를 검색했어요.

결론 (Conclusion)

이 가이드에서 S3 버킷을 설정하고, 다양한 데이터 유형을 인제스트하고, 임베딩을 Qdrant에 저장했어요. LangChain을 사용해 텍스트·이미지 파일을 동적으로 처리해 각 파일 유형을 작업하기 쉽게 만들었어요.

이제 여러분 차례예요. 비디오 같은 다양한 데이터 유형으로 실험해 보고, 애플리케이션을 강화하기 위해 Qdrant의 고급 기능을 탐색해 보세요. 시작하려면 가입해 주세요.

data-ingestion-beginners-12

출처: Qdrant 공식문서 - S3 Ingestion with LangChain and Qdrant

더 알아보기 (Learn more)