10차시. 벡터저장소 이해
3. 실습 ① — 인덱스 만들고 저장하기
출처: 원본 PDF 10차시_벡터저장소이해.pdf 7쪽 · 쪽 그림 7쪽
7차시의 prepare_chunks를 그대로 이어받아 시작합니다.
📝 src/build_index.py
from dotenv import load_dotenv
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import FAISS
from prepare import prepare_chunks
load_dotenv()
INDEX_PATH = "faiss_index"
EMBED_MODEL = "text-embedding-3-small"
emb = OpenAIEmbeddings(model=EMBED_MODEL)
# ① 조각 준비
chunks = prepare_chunks("data/manual.pdf")
# ② 인덱스 생성 — 여기서 임베딩 비용 발생
print("임베딩 중...")
store = FAISS.from_documents(chunks, emb)
# ③ 디스크에 저장 (핵심!)
store.save_local(INDEX_PATH)
print(f"✓ 인덱스를 {INDEX_PATH}/ 에 저장했습니다")실행 결과 예시 (코드를 읽고 만든 예상 화면입니다. 실제 실행 결과, 특히 모델의 답은 다를 수 있습니다)
✓ 3쪽 로딩 완료 (총 1529자)
✓ 조각 21개 생성 (평균 77자, 최소 47자, 최대 99자)
⚠ 50자 미만 조각이 있습니다. chunk_size 확인 권장
임베딩 중...
✓ 인덱스를 faiss_index/ 에 저장했습니다실습 코드 · 10/build_index.py
실습 코드: 실습/src/10/build_index.py · 위의 원본 PDF 10차시 7쪽 「3️⃣ 실습 ① — 인덱스 만들고 저장하기」과 이어집니다.
# ============================================================
# 10차시 | PPT: 10차시_벡터저장소이해.pptx
# 역할: chunk를 embedding으로 바꿔 FAISS 인덱스 파일로 저장
# 입력: ../../data/manual.pdf
# 출력: 로딩/조각 수, 저장 완료, faiss_index 안의 파일 목록
# 흐름:
# manual.pdf -> prepare_chuncks (chunk)
# -> OpenAIEmbeddings (embedding, vector)
# -> FAISS.from_documents -> save_local -> 파일 목록 print
# ============================================================
# chunk: 잘라 낸 문서 조각
# embedding: 문장을 숫자 목록으로 바꾼 값. vector는 그 목록입니다.
# FAISS: 벡터를 파일로 저장하고 나중에 비슷한 벡터를 찾는 저장소
from langchain_openai import OpenAIEmbeddings
from dotenv import load_dotenv
import os
import sys
from langchain_community.vectorstores import FAISS
load_dotenv()
# 저장할 때 쓴 모델을 불러올 때도 똑같이 써야 합니다. (9차시: 모델마다 좌표계가 다릅니다)
emb = OpenAIEmbeddings(model="text-embedding-3-small")
# 7차시 폴더의 prepare.py를 가져오기 위한 준비입니다.
CURRENT_DIR = os.path.dirname(__file__)
PREPARE_DIR = os.path.join(CURRENT_DIR, "..", "07")
sys.path.insert(0, PREPARE_DIR)
from prepare import prepare_chuncks
# 경로와 모델 이름을 상수로 모아 두면 다른 파일과 맞추기 쉽습니다.
DOC_PATH = "../../data/manual.pdf"
INDEX_PATH = "faiss_index"
EMBED_MODEL = "text-embedding-3-small"
# import 시 한 번, 여기서 한 번 더 PDF를 읽습니다. 조각 수도 출력됩니다.
chunks = prepare_chuncks(DOC_PATH)
print(f"총 {len(chunks)}개의 청크를 준비했습니다.")
print("임베딩 중...")
# 각 chunk의 벡터를 만들어 FAISS에 넣습니다. 네트워크 호출입니다.
store = FAISS.from_documents(chunks, emb)
# 메모리에 있던 인덱스를 faiss_index 폴더에 파일로 씁니다. 오늘의 핵심 한 줄입니다.
store.save_local(INDEX_PATH)
print("인덱스를 저장했습니다.")
# 저장한 파일의 목록 확인
# index.faiss(벡터)와 index.pkl(원문과 metadata) 두 개가 한 쌍으로 보여야 합니다.
saved_files = os.listdir(INDEX_PATH)
print(f"\n'{INDEX_PATH}' 디렉토리에 저장된 파일 수: {len(saved_files)}")
print("저장된 파일 목록:")
for file in saved_files:
print(file)실행 결과 샘플 (교안 기록. API 키가 필요한 코드는 키 없이 맞춘 예시이고, 모델의 답은 실행할 때마다 달라집니다)
===== 실행 결과 샘플 =====
실행: cd 실습/src/10
python build_index.py
예시 출력 (API 키 필요, 실행하지 않음)
3쪽 로딩 완료 (총 1529 자)
3쪽 로딩 완료 (총 1529 자)
11 개
총 11개의 청크를 준비했습니다.
임베딩 중...
인덱스를 저장했습니다.
'faiss_index' 디렉토리에 저장된 파일 수: 2
저장된 파일 목록:
index.faiss
index.pkl
(11개는 API 없이 확인한 prepare_chuncks 결과입니다.
파일 2개는 이미 있는 src/10/faiss_index 폴더의 index.faiss, index.pkl입니다.
유사도 점수는 이 파일이 출력하지 않습니다.)코드 해설
chunk를 embedding으로 바꿔 FAISS 인덱스 파일로 저장입니다. 입력은 ../../data/manual.pdf이고, 출력은 로딩/조각 수, 저장 완료, faiss_index 안의 파일 목록입니다.
주석에 적힌 흐름입니다.
manual.pdf -> prepare_chuncks (chunk)
-> OpenAIEmbeddings (embedding, vector)
-> FAISS.from_documents -> save_local -> 파일 목록 print코드 앞에서 고정해 두는 말입니다.
- chunk: 잘라 낸 문서 조각
- embedding: 문장을 숫자 목록으로 바꾼 값. vector는 그 목록입니다.
- FAISS: 벡터를 파일로 저장하고 나중에 비슷한 벡터를 찾는 저장소
함수로 나누지 않고 위에서 아래로 실행됩니다.
chunks = prepare_chuncks(DOC_PATH)— import 시 한 번, 여기서 한 번 더 PDF를 읽습니다. 조각 수도 출력됩니다.store = FAISS.from_documents(chunks, emb)— 각 chunk의 벡터를 만들어 FAISS에 넣습니다. 네트워크 호출입니다.saved_files = os.listdir(INDEX_PATH)— 저장한 파일의 목록 확인
파일 맨 아래 실행 결과 샘플 주석은 실행되지 않습니다. 화면 출력은 다음 절에 다시 적어 두었습니다.
3-1) 저장된 파일 확인
$ ls -la faiss_index/
index.faiss ← 벡터들이 담긴 파일
index.pkl ← 원문 텍스트와 metadata👉 두 파일이 한 쌍입니다. 같이 관리하세요.
💡 왜 파일이 두 개일까?
9차시에서 배웠듯이 벡터로는 원문을 복원할 수 없습니다.
· index.faiss — 검색용 좌표
· index.pkl — 실제 텍스트와 출처 정보
검색은 .faiss로 하고,
찾은 결과의 내용은 .pkl에서 꺼내옵니다.
그래서 하나만 있으면 쓸 수 없습니다.