1. Vector DB 구성
1) Vector DB 정의: 대규모 텍스트 데이터 및 임베딩 벡터를 저장, 검색용
2) Vector DB 과정
텍스트 추출: Loader
다양한 문서로부터 텍스트 추출하기
= >
텍스트 분할: Splitter
chunk 단위로 분할
Document 객체로 만들기
=>
텍스트 벡터화: Text Embedding
=>
Vector DB로 저장: Vector Store
2. 벡터 디비 구성과정
1) Loader
다양한 소스에서 문서를 불러오고 처리하는 과정을 담당
from langchain.document_loaders import TextLoader
# 텍스트 파일 경로 지정
file_path = "상록수.txt"
# TextLoader를 이용하여 문서 로드
loader = TextLoader(path+file_path)
documents = loader.load()
# 로드된 문서 출력
print(documents)
print(type(documents))
print(len(documents))
print(type(documents[0]))
- 내용만 불러오기
documents[0].page_content[:1000]
- Document: LangChain에서 텍스트 데이터를 구조적으로 표현하는 기본 단위
Document의 기본속성은 metadata, page_content
metadata: 문서의 출처, 태그, 카테고리 등의 부가정보
page_content: 문서의 실제 텍스트 내용
2) Splitter
긴 문서를 작은 단위인 청크로 나누는 텍스트 분리 도구
텍스트를 분리하는 작업: 청킹
청킹을 하는 이유:
- LLM 모델의 입력 토큰의 개수가 정해져있기 때문이다.
- 텍스트가 너무 긴 경우에는 핵심 정보 이외에 불필요한 정보들이 많이 포함
- 핵심 정보가 유지 될 수 있는 적절한 크기로 나누는 것이 매우 중요
# 청크 사이즈를 500 단위로 자르고 아무런 기준 없이 무조건 단위 단위로 잘라라
split_texts[:5] => 처음 5개 청크만 출력해라
- 글자 단위
from langchain.text_splitter import CharacterTextSplitter
text_splitter = CharacterTextSplitter(
chunk_size = 500,
chunk_overlap = 100,
separator = ' ',
)
split_texts = text_splitter.split_text(text)
for i, chunk in enumerate(split_texts[:5]):
print(f"청크 {i+1}, 길이 {len(chunk)}: {chunk}\n")
- 문장 단위
text_splitter = CharacterTextSplitter(
chunk_size=500, #청크의 최대 글자수
chunk_overlap=100, #겹치는 글자수
separator="." # 문장 단위로 분할
)
split_texts = text_splitter.split_text(text)
# 결과 확인
for i, chunk in enumerate(split_texts[:5]): # 처음 5개 청크만 출력
print(f"청크 {i+1}, 길이 {len(chunk)}: {chunk}\n")
- 줄 바꿈 단위
text_splitter = CharacterTextSplitter(
chunk_size=500,
chunk_overlap=100,
separator='\n',
)
split_texts = text_splitter.split_text(text)
for i, chunk in enumerate(split_texts[:5]):
print(f"청크 {i+1}, 길이 {len(chunk)}: {chunk}\n")
3) Embedding & Store
임베딩: 텍스트 데이터를 숫자로 이루어진 벡터로 변환하는 과정
임베딩 목적: 벡터 표현을 사용하면, 텍스트 데이터를 벡터 공간 내에서 수학적으로 다룰 수 있게 됨
텍스트 간의 유사성을 계산 가능
임베딩 모델 사용: text-embedding-ada-002, text-embedding-3-small, e5-large-v2
벡터 저장소:벡터 형태로 표현된 데이터 즉 임베딩 벡터들을 효율적으로 저장하고 검색할 수 있는 DB
Vector DB => Chroma, FAISS
- 임베딩 모델 선언
from langchain.embeddings import OpenAIEmbeddings
embedding_model = OpenAIEmbeddings(model="text-embedding-3-small")
sample_text1="농촌 계몽운동은 한국 근대화의 중요한 과정이었다."
vector1 = embedding_model.embed_query(sample_text1)
print(f"임베딩 벡터 길이: {len(vector1)}")
print(f"첫 10개 벡터 값: {vector1[:10]}")
그럼 텍스트에서 이거와 관련된 벡터들을 정의해주고 순서대로 정렬해준다.
- ChromaDB 저장
split_texts: 청크 단위로 분리된 텍스트
embedding_model: 임베딩 모델 선언한거
persist_directory: 어느 경로에 저장?
from langchain.vectorstores import Chroma
vectorstore = Chroma.from_texts(split_texts, embedding_model, persist_directory="./chroma_db")
- 유사도 검색
유사도를 기준으로 3문장만 뽑아서 page_content만 가져오
query = "농촌 계몽운동에 대한 내용"
retrieved_docs = vectorstore.similarity_search(query, k=3)
# 결과 출력
print("검색 결과:")
for doc in retrieved_docs:
print(doc.page_content)
print('-'*200)
3. RAG 파이프라인 만들기
- Retriever 선언: Vector DB에서 사용자의 질문과 가장 유사한 청크를 검색 = > 코사인 유사도 기반
- LLM 모델지정: 검색된 문서와 함께 질문을 받아 답변 생성할 LLM 모델 지정
retriever = vectorstore.as_retriever(search_type="similarity", search_kwargs={"k":5})
llm = ChatOpenAI(model_name="gpt-4o-mini")
- 메모리 선언: 대화의 흐름을 유지하고, 이전 질문을 기억하여 문맥을 제공
- 체인 함수로 엮기: Retriever + LLM + Memory를 하나의 체인으로 연결하여 질문-응답 시스템 완성
ConversationalRetrievalChain.from_llm