5. RAG를 위한 LangChain의 핵심 구성 요소 및 실습[Text Splitters]
1. Text Splitter의 개념
1.1 Text Splitter는 문서를 여러개의 Chunk로 분할하는 것
1.1.1. RAG는 Document Loader로 불러온 문서를 벡터 임베딩으로 변환하여 벡터DB에 저장하고, 이를 활용
1.1.2. LLM에게 문서를 그대로 입력하여 답변하도록 하면 입력값 길이 제한으로 인해 오류가 발생할 수 있다.
예를 들어, GPT-4O모델의 경우에는 12만 8천 토큰, Claude 3.5 sonnect의 경우에는 20만 토큰까지 입려가능하다. 다만 , 한글과 영어에 토큰 개수 차이가 존재한다. 한글 문서를 LLM에게 넘겨줬을때 영어 토큰보다 빨리달고, 한글문서로 따지면 몇장 안되어 에러가 발생한다.
1.1.3. 문서를 여러개의 조각(Chunk)로 분할하여 벡터DB에 저장하고, 이를RAG 시스템에서 활용
chunk된 문서를 임베딩 벡터로 변환하고, 사용자 질문에서 벡터 임베딩과 유사도가 가장 높은것을 힌트 문장으로 선택한다.
2. Text Splitter의 종류
2.1. Charater Text Splitter
Max_token값을 지정한다. 문서들을 자를때 토큰 수가 500이 넘지않게 자르는 방법.
문자를 기준으로 자르므로 구분자 1개(ex: 줄바꿈) 기준으로 분할하므로 max_token을 지키지 못하는 경우가 발생한다. => 에러 발생가능
2.2. Recursive Text Splitter
Max_token값을 지정을 하되, 이 Max_token값을 지키기 위해 여러가지 구분자들을 돌아가면서 적용한다.
구분자를 큰단위부터 작은단위 구분자까지 하나씩 적요을 해보면서 나뉜 chunk들이 Max_token값을 지키고 있는가를 검토하는 과정을 거친다. (ex: 줄바꿈, 마침표, 쉼표 순으로 재귀적으로 분할하므로, max_token 지켜 분할한다. )
2.3. Chunk_overlap
chunk_overlap 매개변수는 텍스트 분할시, 앞뒤로 조금씩 겹치게 만들어 문맥을 더 많이 포함하도록 한다.
chunk_overlap을 적용하지 않으면, 1번과 2번으로 chunk가 나뉘고, 이를 각각 따로따로 레그 시스템에 적용이 돼버려서 2번에서 하고 있는말에서 1번에서 하는 말을 llm은 이해하지 못한다.
chunk_overlap은 문단과 문단사이에 의미적으로 겹치는 부분이 있을경웨 보충을 해주는 경우
즉, 1번 chunk와 2번 chunk 그리고 1번,2번을 겹치는 chunk 총 3개의 chunk로 분할한다.
3. 실습
#Langchain Text Splitter 모듈 다운로드
!pip install -q langchain-text-splitters
#PyPDFium2Loader로 PDF 문서 로드하기
from langchain_community.document_loaders import PyPDFLoader
loader = PyPDFLoader(r"/content/drive/MyDrive/마소캠퍼스/[이슈리포트 2022-2호] 혁신성장 정책금융 동향.pdf")
pages = loader.load()
#CharacterTextSplitter 모듈 로드
from langchain_text_splitters import CharacterTextSplitter
#구분자: 줄넘김, 청크 길이: 500, 청크 오버랩: 100, length_function: 글자수
text_splitter = CharacterTextSplitter(
separator="\n",
chunk_size=500,
chunk_overlap=100,
length_function=len
)
#텍스트 분할
texts = text_splitter.split_documents(pages)
print(texts[0])
3.1. Charater Text Splitter
500이 넘어도 chunk되지 않는다.
3.2. Recursive Text Splitter
from langchain_community.document_loaders import PyPDFium2Loader
loader = PyPDFLoader(r"/content/drive/MyDrive/마소캠퍼스/[이슈리포트 2022-2호] 혁신성장 정책금융 동향.pdf")
pages = loader.load()
from langchain_text_splitters import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter (
separators=["\n\n", "\n", " ", ""],
chunk_size=500,
chunk_overlap=100,
length_function=len,
is_separator_regex=False,
)
texts = text_splitter.split_documents(pages)
print([len(i.page_content) for i in texts])
<결과>
전부 500 이하로 문서를 chunk함을 알 수 있다.
3.3. 문맥 파악 통한 문서 분할, Semantic Chunker
3.3.1 문자분할기(Character)나 재귀적분할기(Recursive)처럼 기계적으로 분할 하지 않고, 유사성 기반으로 분할한다. 특정 값을 기준으로 문장 앞뒤로 계속 유사성을 비교함으로써 chunk한다. => 의미적으로 문장을 분할하므로 rag에 접목하면 의미를 잘 담음 힌트 문장을 사용자의 질문에 엮어가지고 LLM에게 답변을 받기 때문에 훨씬 좋은 답변을 받을 수 있다.
pip install langchain_experimental
from langchain_community.document_loaders import PyPDFLoader
loader = PyPDFLoader(r"/content/drive/MyDrive/마소캠퍼스/[이슈리포트 2022-2호] 혁신성장 정책금융 동향.pdf")
pages = loader.load()
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai.embeddings import OpenAIEmbeddings
text_splitter = SemanticChunker(OpenAIEmbeddings(openai_api_key = "YOUR_API_KEY"))
texts = text_splitter.split_documents(pages)
print("-"*100)
print("[첫번째 청크]")
print(texts[0].page_content)
print("-"*100)
print("[두번째 청크]")
print(texts[1].page_content)
print([len(i.page_content) for i in texts])
코드 설명: SemanticChunker에 임베딩 모델(ex: open AI )을 적용하여 Chunk한다.
<결과>
쪽 분할을 직접 지정하지 않았음에도 불구하고, 앞뒤 chunk가 비슷한 맥락(위에서는 ICT 산업)끼리 쪼개진 것을 확인할수 있다.