LangChain: RecursiveCharacterTextSplitter로 긴 글 자르기

LangChain에서 Document loader를 이용해 문서를 읽어들인 후 문서가 길면 LLM에서 소화할 수 있도록 chunk로 분할해야 합니다. 이런 작업을 해주는 클래스들이 langchain.text_splitter 모듈에 들어 있습니다. 해당 모듈에는 다양한 클래스들이 있고, 계속 추가되고 있습니다. 몇 가지만 적어보겠습니다.

Text Splitter

  • CharacterTextSplitter: 특정 문자를 이용해 문자열을 나눕니다.
  • RecursiveCharacterTextSplitter: 아래에서 자세히 살펴보겠습니다.
  • MarkdownHeaderTextSplitter: 마크다운 파일의 헤더를 기준으로 나눕니다.
  • HTMLHeaderTextSplitter: HTML 파일의 헤더를 기준으로 나눕니다.
  • TokenTextSplitter: 토큰 수를 기준으로 나눕니다. OpenAI의 tiktoken을 이용합니다.

RecursiveCharacterTextSplitter

RecursiveCharacterTextSplitter는 지정한 chunk_size 이하가 되도록 문자열을 자르는데, 기본적으로 ["\n\n", "\n", " ", ""]와 같은 문자를 이용해 자릅니다. 순서대로 가장 먼저 “\n\n”으로 자르고, 그래도 chunk_size 보다 긴 chunk는 “\n”으로 자르고, 그래도 길면 ” “로 자르는 방식으로 chunk를 만듭니다. 단순히 글자수로 자르는 것에 비해 문단, 문장, 단어 등으로 자르기 때문에 글의 의미를 보존하는데 좋은 방법이라 할 수 있습니다.

구분 문자(separators)는 원하는 문자들의 리스트로 바꿀 수 있습니다. chunk_size를 계산하는 함수는 기본적으로 len인데, 토크나이저를 지정해서 토큰 수로 chunk_size를 계산할 수도 있습니다. 정규표현식을 이용해 나누는 것도 가능합니다.

예제를 살펴보겠습니다. 프로젝트 구텐베르그 사이트에서 명상록 텍스트 파일을 받아 파일로 저장했습니다.

import requests
url = "https://www.gutenberg.org/cache/epub/2680/pg2680.txt"
response = requests.get(url)

with open("Meditations_pg.txt",'wb') as fh:
    fh.write(response.content)

다음엔 Document loader 중 텍스트 파일을 읽는데 사용하는 TextLoader를 이용해 앞에서 쓴 텍스트 파일을 읽습니다.

from langchain.document_loaders import TextLoader

loader = TextLoader("Meditations_pg.txt")
data = loader.load()

data는 Document 자료형의 원소를 하나 가진 리스트입니다.

len(data[0].page_content) # 417445

길이가 길기 때문에 LLM에 그대로 입력으로 쓸 수는 없습니다. 분할해봅시다.

from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size = 1000,
    chunk_overlap  = 50,
    length_function = len,
    is_separator_regex = False,
)
texts = text_splitter.split_documents(data)

한 chunk의 길이가 1000자 이내가 되도록 나눴습니다.

len(texts) # 595
type(texts) # list

texts[0]
## 결과
Document(page_content='\ufeffThe Project Gutenberg eBook of Meditations\n    \nThis ebook is for the use of anyone anywhere in the United States and\nmost other parts of the world at no cost and with almost no restrictions\nwhatsoever. You may copy it, give it away or re-use it under the terms\nof the Project Gutenberg License included with this ebook or online\nat www.gutenberg.org. If you are not located in the United States,\nyou will have to check the laws of the country where you are located\nbefore using this eBook.\n\nTitle: Meditations\n\n\nAuthor: Emperor of Rome Marcus Aurelius\n\nRelease date: June 1, 2001 [eBook #2680]\n                Most recently updated: March 9, 2021\n\nLanguage: English\n\n\n\n*** START OF THE PROJECT GUTENBERG EBOOK MEDITATIONS ***\n\n\n\nMEDITATIONS\n\nBy Marcus Aurelius\n\n\n\n\nCONTENTS\n\n\n     NOTES\n\n     INTRODUCTION\n\n     FIRST BOOK\n\n     SECOND BOOK\n\n     THIRD BOOK\n\n     FOURTH BOOK\n\n     FIFTH BOOK\n\n     SIXTH BOOK\n\n     SEVENTH BOOK\n\n     EIGHTH BOOK\n\n     NINTH BOOK\n\n     TENTH BOOK', metadata={'source': 'Meditations_pg.txt'})

이렇게 split_documents를 이용하면 Document로 구성된 리스트를 분할할 수 있습니다. 문자열로 구성된 리스트를 분할하고 싶다면 create_documents를, 그냥 문자열을 분할하고 싶다면 split_text 메소드를 이용하면 됩니다.

with open("Meditations_pg.txt",'r') as fh:
    txt = fh.read()
texts2 = text_splitter.create_documents([txt])

len(texts2) # 595
type(texts2) # list

texts2[10]
## 출력
Document(page_content='from him, and he was assassinated. Marcus now went to the east, and\nwhile there the murderers brought the head of Cassius to him; but the\nemperor indignantly refused their gift, nor would he admit the men to\nhis presence.')

create_documents의 결과도 Document 자료형의 리스트로 나오는 것을 볼 수 있습니다. 반면에, split_text를 이용해 문자열을 분할하면 그냥 문자열의 리스트가 나옵니다.

texts3 = text_splitter.split_text(txt)

len(texts3) # 595
type(texts3) # list

texts3[10]
## 출력
'from him, and he was assassinated. Marcus now went to the east, and\nwhile there the murderers brought the head of Cassius to him; but the\nemperor indignantly refused their gift, nor would he admit the men to\nhis presence.'

댓글 남기기