LangChain과 Pinecone을 활용한 문서 벡터화 및 검색 실습
이 노트북에서는 OpenAI 임베딩 모델을 이용해 텍스트 문서를 벡터로 변환하고, Pinecone 벡터 데이터베이스에 인덱싱하여 저장한 후, 유사도 검색을 통해 입력 질의와 관련성이 높은 문서를 찾아내는 과정을 단계별로 시연합니다. 각 단계마다 코드 셀 위에 해당 단계의 목적과 작동 원리를 상세히 설명하여, 초급자도 흐름을 이해할 수 있도록 구성하였습니다.
1. 필수 패키지 설치하기
이 단계에서는 프로젝트에 필요한 파이썬 라이브러리들을 설치합니다. Jupyter 노트북에서 %pip install 매직 명령을 사용하여, 아래 나열된 여러 패키지들을 한 번에 설치합니다:
- python-dotenv: .env 파일에서 환경 변수를 읽어오는 라이브러리입니다.
- langchain: LLM(대규모 언어 모델) 기반 응용을 쉽게 개발할 수 있게 도와주는 프레임워크입니다.
- langchain-openai: LangChain에서 OpenAI의 API(예: 챗GPT, 임베딩 모델 등)를 손쉽게 사용할 수 있도록 해주는 모듈입니다.
- langchain-pinecone: LangChain에서 Pinecone 벡터 데이터베이스를 연동하는 모듈입니다.
- pinecone: Pinecone 벡터 DB(데이터베이스)에 접속하고 조작하기 위한 Python 클라이언트 라이브러리입니다.
- pandas: 데이터 분석용 라이브러리로, 여기서는 CSV 파일로부터 문서 데이터를 읽어오는 데 사용됩니다.
해당 셀을 실행하면 각 패키지가 다운로드 및 설치되는 과정이 출력으로 표시됩니다. 설치가 완료되면 "kernel을 재시작해야 할 수도 있다"는 안내 메시지도 나타나는데, 이는 새로운 패키지를 설치한 후에 필요한 경우 Jupyter 커널을 재시작해야 함을 알려주는 것입니다.
%pip install python-dotenv langchain langchain-openai langchain-pinecone pinecone pandas
출력 결과:
Collecting python-dotenv
Using cached python_dotenv-1.1.0-py3-none-any.whl.metadata (24 kB)
Collecting langchain
Using cached langchain-0.3.25-py3-none-any.whl.metadata (7.8 kB)
Collecting langchain-openai
Using cached langchain_openai-0.3.19-py3-none-any.whl.metadata (2.3 kB)
Collecting langchain-pinecone
Downloading langchain_pinecone-0.2.8-py3-none-any.whl.metadata (5.3 kB)
Collecting pinecone
Using cached pinecone-7.0.2-py3-none-any.whl.metadata (9.5 kB)
...
Successfully installed PyYAML-6.0.2 SQLAlchemy-2.0.41 aiohappyeyeballs-2.6.1 aiohttp-3.12.9 aiohttp-retry-2.9.1 aiosignal-1.3.2 annotated-types-0.7.0 anyio-4.9.0 attrs-25.3.0 certifi-2025.4.26 cffi-1.17.1 charset-normalizer-3.4.2 distro-1.9.0 frozenlist-1.6.2 greenlet-3.2.3 h11-0.16.0 httpcore-1.0.9 httpx-0.28.1 idna-3.10 iniconfig-2.1.0 jiter-0.10.0 jsonpatch-1.33 jsonpointer-3.0.0 langchain-0.3.25 langchain-core-0.3.64 langchain-openai-0.3.19 langchain-pinecone-0.2.8 langchain-tests-0.3.20 langchain-text-splitters-0.3.8 langsmith-0.3.45 markdown-it-py-3.0.0 mdurl-0.1.2 multidict-6.4.4 numpy-2.2.6 openai-1.84.0 orjson-3.10.18 packaging-24.2 pandas-2.3.0 pinecone-7.0.2 pinecone-plugin-assistant-1.6.1 pinecone-plugin-interface-0.0.7 pluggy-1.6.0 propcache-0.3.1 py-cpuinfo-9.0.0 pycparser-2.22 pydantic-2.11.5 pydantic-core-2.33.2 pytest-8.4.0 pytest-asyncio-0.26.0 pytest-benchmark-5.1.0 pytest-codspeed-3.2.0 pytest-recording-0.13.4 pytest-socket-0.7.0 python-dotenv-1.1.0 pytz-2025.2 regex-2024.11.6 requests-2.32.3 requests-toolbelt-1.0.0 rich-14.0.0 sniffio-1.3.1 syrupy-4.9.1 tenacity-9.1.2 tiktoken-0.9.0 tqdm-4.67.1 typing-inspection-0.4.1 tzdata-2025.2 urllib3-2.4.0 vcrpy-7.0.0 wrapt-1.17.2 yarl-1.20.0 zstandard-0.23.0
Note: you may need to restart the kernel to use updated packages.
2. 환경 변수 불러오기
이 단계에서는 OpenAI와 Pinecone 서비스에 필요한 API 키와 설정 값들을 .env 파일에서 불러옵니다. 먼저 python-dotenv 패키지의 load_dotenv() 함수를 호출하여 현재 작업 디렉터리에 위치한 .env 파일을 읽어 환경 변수로 로드합니다.
그 후 os.getenv() 함수를 사용하여 불러온 환경 변수들로부터 각 설정 값을 가져와 해당 변수에 저장합니다. 여기에는 OpenAI API 키(OPENAI_API_KEY), 사용할 LLM 및 임베딩 모델명(OPENAI_LLM_MODEL, OPENAI_EMBEDDING_MODEL), Pinecone API 키(PINECONE_API_KEY), 그리고 Pinecone 인덱스의 설정 값들(인덱스 이름, 리전 지역, 클라우드 환경, 유사도 지표(metric), 벡터 차원 등)이 포함됩니다. 특히 PINECONE_INDEX_DIMENSION은 숫자 값을 나타내므로 문자열 상태로 불러온 뒤 int() 함수를 통해 정수형으로 변환합니다.
마지막으로 print("환경 변수 로딩 완료")를 호출하여 환경 변수 설정이 모두 완료되었음을 콘솔에 출력합니다. 이 메시지를 통해 .env 파일에서 필요한 값들을 성공적으로 읽어왔음을 알 수 있습니다.
import os
from dotenv import load_dotenv
# .env 파일 로드
load_dotenv()
# 환경 변수 설정
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
OPENAI_LLM_MODEL = os.getenv("OPENAI_LLM_MODEL")
OPENAI_EMBEDDING_MODEL = os.getenv("OPENAI_EMBEDDING_MODEL")
PINECONE_API_KEY = os.getenv("PINECONE_API_KEY")
PINECONE_INDEX_REGION = os.getenv("PINECONE_INDEX_REGION")
PINECONE_INDEX_CLOUD = os.getenv("PINECONE_INDEX_CLOUD")
PINECONE_INDEX_NAME = os.getenv("PINECONE_INDEX_NAME")
PINECONE_INDEX_METRIC = os.getenv("PINECONE_INDEX_METRIC")
PINECONE_INDEX_DIMENSION = int(os.getenv("PINECONE_INDEX_DIMENSION"))
print("환경 변수 로딩 완료")
출력 결과:
환경 변수 로딩 완료
3. Pinecone 인덱스 생성 및 초기화하기
이 단계에서는 Pinecone 벡터 데이터베이스에 연결하여 벡터를 저장할 **인덱스(index)**를 준비합니다. 먼저 pinecone 패키지의 Pinecone 클래스를 사용하여 API 키를 전달함으로써 Pinecone 클라이언트를 초기화하고 pc 객체를 생성합니다. 이렇게 생성된 pc 객체를 통해 Pinecone 서비스에 접근하고 명령을 실행할 수 있습니다.
다음으로, pc.list_indexes().names()를 호출하여 현재 Pinecone 프로젝트에 존재하는 모든 인덱스 이름들을 가져온 뒤, 그 목록에 우리가 사용하려는 인덱스 이름(PINECONE_INDEX_NAME)이 포함되어 있는지 확인합니다. 인덱스란 Pinecone 상에서 벡터 데이터를 저장하는 공간으로, 데이터베이스의 테이블과 비슷한 개념입니다. 만약 해당 이름의 인덱스가 존재하지 않는다면 pc.create_index() 함수를 사용하여 새로운 인덱스를 생성합니다. 이때 인덱스의 이름, 벡터의 차원 크기(dimension), 유사도 계산 방식(metric) 등을 지정하고, ServerlessSpec을 통해 인덱스를 어느 리전(region)과 어느 클라우드 환경에 만들지 설정합니다. 인덱스가 이미 존재한다면 생성을 생략하고 다음 단계로 넘어갑니다.
인덱스를 생성하거나 확인한 후에는 pc.Index(PINECONE_INDEX_NAME)를 호출하여 해당 이름의 Pinecone 인덱스 객체를 가져와 index 변수에 저장합니다. 이제 이 index 객체를 사용하여 벡터 데이터를 추가하거나 검색할 수 있습니다. 마지막으로 인덱스 생성 여부에 따라 "인덱스 '예시인덱스' 생성 완료" 또는 "인덱스 '예시인덱스'가 이미 존재합니다." 와 같은 메시지를 출력합니다. 이어서 print("Pinecone 인덱스 연결 완료")를 호출하여 Pinecone 인덱스에 성공적으로 연결되었음을 확인시켜줍니다.
from pinecone import Pinecone, ServerlessSpec
# Pinecone 클라이언트 초기화
pc = Pinecone(api_key=PINECONE_API_KEY)
# 인덱스 생성 여부 확인 및 생성
if PINECONE_INDEX_NAME not in pc.list_indexes().names():
pc.create_index(
name=PINECONE_INDEX_NAME,
dimension=PINECONE_INDEX_DIMENSION,
metric=PINECONE_INDEX_METRIC,
spec=ServerlessSpec(
region=PINECONE_INDEX_REGION,
cloud=PINECONE_INDEX_CLOUD
)
)
print(f"인덱스 '{PINECONE_INDEX_NAME}' 생성 완료")
else:
print(f"인덱스 '{PINECONE_INDEX_NAME}'가 이미 존재합니다.")
# 인덱스 객체 가져오기
index = pc.Index(PINECONE_INDEX_NAME)
print("Pinecone 인덱스 연결 완료")
출력 결과:
인덱스 'ir' 생성 완료
Pinecone 인덱스 연결 완료
4. OpenAI 임베딩 모델 설정하기
이 단계에서는 텍스트 임베딩(embedding)을 수행할 OpenAI 임베딩 모델을 초기화합니다. LangChain의 OpenAIEmbeddings 클래스를 사용하여, 미리 지정된 모델 이름(OPENAI_EMBEDDING_MODEL)과 OpenAI API 키를 인자로 제공함으로써 embedding_model 객체를 생성합니다. 이렇게 하면 해당 객체를 통해 OpenAI의 임베딩 API를 호출해 텍스트를 벡터로 변환할 준비가 된 것입니다.
생성된 embedding_model은 이후에 문서의 내용이나 검색 질의를 벡터 표현으로 변환하는 데 사용됩니다. 마지막 줄의 print("임베딩 모델 로딩 완료")는 임베딩 모델이 성공적으로 설정되었음을 알리는 확인용 출력입니다.
from langchain_openai import OpenAIEmbeddings
# OpenAI 임베딩 모델 초기화
embedding_model = OpenAIEmbeddings(
model=OPENAI_EMBEDDING_MODEL,
openai_api_key=OPENAI_API_KEY
)
print("임베딩 모델 로딩 완료")
출력 결과:
임베딩 모델 로딩 완료
5. Pinecone 벡터 스토어 연결하기
이 단계에서는 Pinecone 벡터 스토어와 앞서 설정한 임베딩 모델을 연동합니다. LangChain에서 제공하는 PineconeVectorStore 클래스를 사용하여 vector_store 객체를 생성하는데, 인자로 Pinecone 인덱스의 이름(index_name)과 임베딩 객체(embedding)를 전달합니다. 이를 통해 vector_store는 주어진 Pinecone 인덱스에 새 문서를 추가하거나 질의에 대한 유사도 검색을 할 때 지정된 OpenAI 임베딩 모델을 활용하도록 구성됩니다.
이제 vector_store 객체를 사용하면 문서를 벡터화하여 Pinecone에 저장하거나, 질의 벡터와 Pinecone 내의 벡터들을 비교하는 작업을 손쉽게 수행할 수 있습니다. print("벡터 스토어 연결 완료") 출력으로 이러한 벡터 스토어 설정이 완료되었음을 알 수 있습니다.
from langchain_pinecone import PineconeVectorStore
# Pinecone 벡터 스토어 연결
vector_store = PineconeVectorStore(
index_name=PINECONE_INDEX_NAME,
embedding=embedding_model
)
print("벡터 스토어 연결 완료")
출력 결과:
벡터 스토어 연결 완료
6. 문서 데이터 로드 및 준비하기
이 단계에서는 인덱싱할 문서 데이터를 불러옵니다. pandas 라이브러리의 read_csv() 함수를 이용하여 documents.csv 파일을 읽어 documents_df라는 데이터프레임으로 저장합니다. 이 CSV 파일에는 여러 문서의 ID와 본문 내용이 기록되어 있다고 가정합니다.
그 다음, 읽어온 문서들을 Pinecone에 올리기 위해 docs_to_index라는 리스트를 생성하고, documents_df.iterrows()를 사용하여 데이터프레임의 각 행(row)을 하나씩 반복 처리합니다. 각 행마다 doc_id와 content(본문 텍스트)를 추출하여 (doc_id, content) 형태의 튜플을 만들어 docs_to_index 리스트에 추가합니다. 반복이 끝나면 이 리스트에는 모든 문서의 ID와 내용이 담기게 되며, print() 함수를 통해 준비된 문서의 개수를 출력합니다. (예를 들어 출력된 메시지 "총 30개의 문서를 인덱싱 준비"는 30개의 문서가 목록에 추가되었음을 의미합니다.)
import pandas as pd
# 문서 데이터 로드 (documents.csv 파일 경로를 지정하세요)
documents_df = pd.read_csv("documents.csv")
# 인덱싱용 리스트 생성: (id, 텍스트)
docs_to_index = []
for idx, row in documents_df.iterrows():
doc_id = row['doc_id']
content = row['content']
docs_to_index.append((doc_id, content))
print(f"총 {len(docs_to_index)}개의 문서를 인덱싱 준비")
출력 결과:
총 30개의 문서를 인덱싱 준비
7. 문서를 Pinecone에 업서트하기
이 단계에서는 앞서 준비한 문서들을 Pinecone 벡터 스토어에 **업서트(upsert)**합니다. 여기서 업서트란 새로운 레코드를 추가하거나, 동일한 키의 레코드가 이미 있으면 해당 내용을 갱신(업데이트)하는 동작을 의미합니다.
코드에서는 langchain.schema 모듈의 Document 클래스를 사용하여 각 문서를 Pinecone에 추가하기 전에 적절한 객체로 변환하고 있습니다. for 루프로 docs_to_index 리스트에 들어있는 (doc_id, content)를 하나씩 꺼내어, Document(page_content=content, metadata={"doc_id": doc_id})와 같이 문서 객체를 생성합니다. 이 객체에는 문서의 본문 내용은 page_content 속성에, 문서의 ID는 metadata 속성에 담깁니다. 그런 다음 vector_store.add_documents([doc])를 호출하여 해당 문서 객체를 Pinecone 인덱스에 추가합니다. LangChain의 add_documents 메서드는 내부적으로 문서의 내용을 임베딩 벡터로 변환한 뒤 Pinecone에 저장하는 작업을 수행합니다.
모든 문서에 대한 추가 작업이 완료되면 print("모든 문서 업서트 완료")를 실행하여 모든 문서의 업서트가 끝났음을 출력합니다. 이로써 우리가 가진 문서들이 모두 벡터 형태로 Pinecone DB에 저장되었습니다.
# Pinecone에 문서 업서트
from langchain.schema import Document
# 문서를 벡터로 변환하여 업서트
for doc_id, content in docs_to_index:
# LangChain Document 생성
doc = Document(page_content=content, metadata={"doc_id": doc_id})
# 벡터 저장
vector_store.add_documents([doc])
print("모든 문서 업서트 완료")
출력 결과:
모든 문서 업서트 완료
8. 유사도 검색으로 문서 찾기
마지막 단계에서는 구축한 벡터 스토어의 검색 기능을 테스트합니다. 예시 질의(query)로 "제주도 관광 명소"라는 문자열을 설정하고, vector_store.similarity_search(query, k=5)를 호출하여 Pinecone에 저장된 문서들 중 이 질의와 가장 유사한 상위 5개 문서를 검색합니다. 이 함수는 지정된 embedding_model을 사용해 질의를 벡터로 변환한 다음 Pinecone 인덱스에서 유사도가 높은 벡터들을 찾아줍니다.
검색 결과는 results 리스트로 반환되며, 각 원소는 우리가 앞서 Pinecone에 저장한 문서에 대응하는 LangChain Document 객체입니다. 이 객체에는 문서의 내용(page_content)과 함께 우리가 메타데이터로 넣어두었던 문서 ID (doc.metadata['doc_id'])가 들어 있습니다. 코드에서는 검색 결과 목록을 순회하면서 각 문서의 ID를 순위와 함께 출력하고 있습니다. 예시 출력에서 볼 수 있듯이, 질의에 대해 가장 관련성이 높은 문서 5개의 ID(D1, D12, D2, D13, D8)가 순서대로 표시됩니다. 이를 통해 이전 단계에서 구축한 벡터 스토어를 활용하여 사용자의 질의와 가장 유사한 내용을 가진 문서를 성공적으로 찾아낼 수 있음을 확인할 수 있습니다.
# 테스트 검색 예시
query = "제주도 관광 명소"
results = vector_store.similarity_search(query, k=5)
print("질의:", query)
for rank, doc in enumerate(results, start=1):
print(f"{rank}. 문서 ID: {doc.metadata['doc_id']}")
출력 결과:
질의: 제주도 관광 명소
1. 문서 ID: D1
2. 문서 ID: D12
3. 문서 ID: D2
4. 문서 ID: D13
5. 문서 ID: D8'HRDI_AI > [인공지능] RAG 검색 성능 최적화와 최신 Retrieval 전략' 카테고리의 다른 글
| 6. contextual_compression (1) | 2025.06.07 |
|---|---|
| 5. cohere_rerank_experiment (0) | 2025.06.07 |
| 4. hyde_experiment (1) | 2025.06.07 |
| 3. rrf_comparision (0) | 2025.06.07 |
| 2. bm25_dense_comparision (0) | 2025.06.07 |