-
Langchain Text SplitterLLM 2025. 6. 18. 11:00
앞선 글에서 Chunking Strategy에 대해 설명했다.
Chunk Optimization / 5 Levels Of Text Splitting
필요성Langchain으로 RAG 구현하던 중, 적절한 Chunking Strategy를 선택해야했다.로드한 문서를 임베딩 하기 전에 크게 자를까, 작게 자를까, 뭘 기준으로 자를까 등을 결정하는 작업이다.이는 검색 정
crayeji.tistory.com
적절한 전략을 결정한 후에는 해당 전략에 맞는 Chunking 로직을 직접 구현할 수도 있고,
LangChain을 사용하는 경우에는 TextSplitter의 하위클래스들을 활용할 수도 있겠다.
그 중 몇 가지를 소개해보려고 한다.
0. TextSplitter
https://api.python.langchain.com/en/latest/base/langchain_text_splitters.base.TextSplitter.html
앞으로 소개할 내용들은 TextSplitter라는 추상클래스에서 파생된 것들이다.
(MarkdownHeaderTextSplitter과 HTMLHeaderTextSplitter 제외)
이 추상클래스는 split_text라는 추상메서드를 포함하고있고, 저마다 이를 구현했다고 볼 수 있다.
예를 들면 RecursiveCharacterTextSplitter는 아래와 같이 재귀적으로 splitting하도록 구현되어있다.
def _split_text(self, text: str, separators: List[str]) -> List[str]: """Split incoming text and return chunks.""" final_chunks = [] # Get appropriate separator to use separator = separators[-1] new_separators = [] for i, _s in enumerate(separators): _separator = _s if self._is_separator_regex else re.escape(_s) if _s == "": separator = _s break if re.search(_separator, text): separator = _s new_separators = separators[i + 1 :] break _separator = separator if self._is_separator_regex else re.escape(separator) splits = _split_text_with_regex(text, _separator, self._keep_separator) # Now go merging things, recursively splitting longer texts. _good_splits = [] _separator = "" if self._keep_separator else separator for s in splits: if self._length_function(s) < self._chunk_size: _good_splits.append(s) else: if _good_splits: merged_text = self._merge_splits(_good_splits, _separator) final_chunks.extend(merged_text) _good_splits = [] if not new_separators: final_chunks.append(s) else: other_info = self._split_text(s, new_separators) final_chunks.extend(other_info) if _good_splits: merged_text = self._merge_splits(_good_splits, _separator) final_chunks.extend(merged_text) return final_chunksinit함수가 아래와 같이 정의되어 있으므로, 알아뒀다가 하위클래스 활용시에 참고하자.
def __init__( self, chunk_size: int = 4000, chunk_overlap: int = 200, length_function: Callable[[str], int] = len, keep_separator: Union[bool, Literal["start", "end"]] = False, add_start_index: bool = False, strip_whitespace: bool = True, ) -> None: """Create a new TextSplitter.- chunk_size: 각 청크(chunk)의 최대 길이
- chunk_overlap: 청크 간 중첩(overlap)을 몇 글자까지 둘 것인지(문장 사이 맥락 연결성 확보를 위한 테크닉)
- length_function: 텍스트 길이를 측정하는 함수
- keep_separator: 분할 기준(separator) 문자를 청크에 포함할지 여부를 결정
- add_start_index: True일 경우, 결과로 나오는 각 청크에 원본문 내 시작 인덱스 정보가 추가됨
- strip_whitespace: 청크 앞뒤의 공백 문자(띄어쓰기, 줄바꿈 등)를 제거할지 여부 (True면 공백제거)
1. character.CharacterTextSplitter
하나의 separator를 기준으로 자른다. 기본적으로 \n\n을 만나면 자르도록 되어있다.
class CharacterTextSplitter( _separator: str = '\n\n', _is_separator_regex: bool = False, **kwargs: Any, )- is_separator_regex: True일때는 정규표현식으로 해석, False일때는 단순 문자열로 해석(default)
사용 예시는 아래와 같다. 청크는 500자씩 잘라지며, 그 중 50자는 겹쳐지게 했다.
from langchain_text_splitters import CharacterTextSplitter # 예시 원본문자열 text = ( "1. 첫 번째 문단입니다.\n" "이 문단은 설명을 포함합니다.\n\n" "2. 두 번째 문단입니다.\n" "여기에는 추가 정보가 포함되어 있습니다.\n\n" "3. 세 번째 문단입니다.\n" "이것은 테스트용 문장입니다.\n\n" "4. 네 번째 문단입니다.\n" "마지막 문장입니다." ) # 분할기 설정 splitter = CharacterTextSplitter( separator="\n", # 줄바꿈 기준 분할 chunk_size=50, # 각 청크 최대 50자 chunk_overlap=20, # 20자 겹치게 length_function=len ) # 분할 수행 chunks = splitter.split_text(text) # 결과 출력 print("총 청크 개수:", len(chunks)) for i, chunk in enumerate(chunks): print(f"\n== Chunk {i+1} ==\n{chunk}")한 청크가 50자를 넘지 않으면서, \n\n을 기준으로 쪼개졌다. 또한 chunk overlap에 의해 일부 내용이 겹쳐지게 쪼개졌다.

CharacterTextSplitter — 🦜🔗 LangChain documentation
CharacterTextSplitter class langchain_text_splitters.character.CharacterTextSplitter( separator: str = '\n\n', is_separator_regex: bool = False, **kwargs: Any, )[source] Splitting text that looks at characters. Create a new TextSplitter. Methods Parameters
python.langchain.com
2. character.RecursiveCharacterTextSplitter
CharacterTextSplitter와 달리 separators가 여러 개(문자열 리스트)들어갈 수 있다.
즉, 분할 기준을 여러가지 둔다는 것이다. 이 기준들을 순서대로 재귀적으로 자른다.
def __init__( self, separators: Optional[List[str]] = None, keep_separator: Union[bool, Literal["start", "end"]] = True, is_separator_regex: bool = False, **kwargs: Any, ) -> None: """Create a new TextSplitter.""" super().__init__(keep_separator=keep_separator, **kwargs) self._separators = separators or ["\n\n", "\n", " ", ""] self._is_separator_regex = is_separator_regex이 또한 TextSplitter에서 파생되었으므로, chunk_size / chunk_overlap등을 지원한다.
사용 예시는 아래와 같다. 이번에는 \n\n뿐만 아니라, \n, " ", ""까지 분할 기준으로 삼고,
chuck_size가 50이 넘는 동안은 재귀적으로 계속 잘라나갈 것이다.
from langchain_text_splitters import RecursiveCharacterTextSplitter # 예시 원본 텍스트 text = ( "제1장. 서론\n\n" "이 문서는 테스트용 텍스트입니다. 줄바꿈도 있고, 공백도 있습니다.\n" "내용이 계속 이어집니다.\n\n" "제2장. 본론\n\n" "여기에는 좀 더 긴 설명이 포함되어 있습니다. 여러 문장이 포함됩니다. " "길이가 길면 재귀적으로 더 잘게 나눌 것입니다." ) # RecursiveCharacterTextSplitter 인스턴스 생성 splitter = RecursiveCharacterTextSplitter( chunk_size=50, chunk_overlap=20, separators=["\n\n", "\n", " ", ""], # 문단 → 줄 → 단어 → 문자 순 is_separator_regex=False ) # 텍스트 분할 chunks = splitter.split_text(text) # 결과 출력 print(f"총 청크 수: {len(chunks)}") for i, chunk in enumerate(chunks): print(f"\n--- Chunk {i+1} ---\n{chunk}")여러 분할 기준에 의해 재귀적으로 잘라졌다.
그 예로, 마지막 문장은 길이가 길었기 때문에(chunk_size 50보다) 공백을 기준으로도 쪼개진 것을 알 수 있다.
chunk_overlap 또한 적용되었다.

3. python.PythonCodeTextSplitter
지난 글에서 설명한 5 Levels Of Text Splitting의 Level 3 : Document Based Chunking에 해당한다.
Python코드에서 클래스, 함수, import 등 특징적인 항목을 기준으로 분할한다.
그 외에도 html, json, jsx, konlpy, latex, markdown 등 문서별로 제공된다.PythonCodeTextSplitter — 🦜🔗 LangChain documentation
metadatas (list[dict[Any, Any]] | None)
python.langchain.com
4. html.HTMLHeaderTextSplitter
Level 3 : Document Based Chunking의 예시이다.
HTML 문서에서 헤더 태그(h1~h6 등)를 기준으로 chunk를 구성하는 Text Splitter다.
그러므로 논리적 구조를 유지할 수 있다.
참고로 문서에서 MarkdownHeaderTextSplitter와 HTMLHeaderTextSplitter는 TextSplitter에서 파생된 친구들은 아니라고 Note되어있다. 그러니 활용 방식에 약간 차이가 있다.
[docs] def __init__( self, headers_to_split_on: List[Tuple[str, str]], return_each_element: bool = False, ): """Create a new HTMLHeaderTextSplitter. Args: headers_to_split_on: list of tuples of headers we want to track mapped to (arbitrary) keys for metadata. Allowed header values: h1, h2, h3, h4, h5, h6 e.g. [("h1", "Header 1"), ("h2", "Header 2)]. return_each_element: Return each element w/ associated headers. """ # Output element-by-element or aggregated into chunks w/ common headers self.return_each_element = return_each_element self.headers_to_split_on = sorted(headers_to_split_on)- headers_to_split_on 분할에 사용할 HTML 태그 리스트 (예: `("h1", "Header 1")
- return_each_line_as_document 각 헤더 섹션을 개별 문서로 반환할지 여부 (기본 False). true이면 모든 html요소가 각각 분리된 문서로 리턴된다.
아래는 활용 예시코드이다.
from langchain_text_splitters import HTMLHeaderTextSplitter headers_to_split_on = [("h1", "Main Topic"), ("h2", "Sub Topic")] splitter = HTMLHeaderTextSplitter( headers_to_split_on=headers_to_split_on, return_each_element=False ) html_content = """ <html> <body> <h1>Introduction</h1> <p>Welcome to the introduction section.</p> <h2>Background</h2> <p>Some background details here.</p> <h1>Conclusion</h1> <p>Final thoughts.</p> </body> </html> """ documents = splitter.split_text(html_content) print(documents[4]) # documents 배열의 각 요소를 줄바꿈하며 출력 for document in documents: for line in str(document).splitlines(): print(line) print()결과적으로 [("h1", "Header 1"), ("h2", "Header 2")]는 <h1> 및 <h2> 태그로 콘텐츠를 분할하고,
해당 텍스트 콘텐츠를 문서 메타데이터에 할당한 것을 알 수 있다.
심지어 결과 Line4를 보면 h1, h2 태그 계층도 드러나고 있다.

5. SementicChunker
Level 4: Semantic Chunking에 해당한다.
그러나 Langchain에서는 Experimental 기능으로 분류되어있다. LlamaIndex에서는 정식 기능이므로 참고해도 좋을 것 같다.
- Langchain sementic chunker
SemanticChunker — 🦜🔗 LangChain documentation
SemanticChunker class langchain_experimental.text_splitter.SemanticChunker( embeddings: Embeddings, buffer_size: int = 1, add_start_index: bool = False, breakpoint_threshold_type: Literal['percentile', 'standard_deviation', 'interquartile', 'gradient'] = '
python.langchain.com
- LlamaIndex sementic chunker
Semantic Chunker - LlamaIndex
Node ID: 68006b95-c06e-486c-bbb6-be54746aaf22 Similarity: 0.8465522042661249 Text: I couldn't figure out what to do with it. And in retrospect there's not much I could have done with it. The only form of input to programs was data stored on punched cards,
docs.llamaindex.ai
'LLM' 카테고리의 다른 글
Qdrant 구성 요소 (0) 2025.06.23 Chunk Optimization / 5 Levels Of Text Splitting (1) 2025.06.18