Langchain RecursiveSplitter

임정민·2025년 8월 29일

메모장

목록 보기
32/34
post-thumbnail
"""
LangChain RecursiveCharacterTextSplitter 완전 가이드
각 옵션의 역할과 우선순위를 예시로 설명

핵심 개념:
1. Separator (구분자): 텍스트를 나누는 기준점들의 우선순위 리스트
2. Chunk Size: 각 청크의 최대 크기
3. Overlap: 청크 간 중복되는 부분의 크기
4. is_separator_regex: 구분자를 정규식으로 처리할지 여부
"""

from langchain.text_splitter import RecursiveCharacterTextSplitter
import re

# ====== 1. 기본 예제 ======
print("=" * 60)
print("1. 기본 RecursiveCharacterTextSplitter 동작 원리")
print("=" * 60)

# 샘플 텍스트
sample_text = """
안녕하세요! 이것은 첫 번째 문단입니다.
여러 줄로 구성되어 있습니다.

이것은 두 번째 문단입니다.
AI와 머신러닝에 대해 설명하겠습니다.

세 번째 문단에서는 딥러닝을 다룹니다.
신경망과 역전파에 대해 알아봅시다.

마지막 문단입니다.
"""

# 기본 설정
basic_splitter = RecursiveCharacterTextSplitter(
    chunk_size=50,  # 청크 최대 크기
    chunk_overlap=10,  # 청크 간 중복 크기
)

chunks = basic_splitter.split_text(sample_text)
print(f"총 청크 수: {len(chunks)}")
for i, chunk in enumerate(chunks):
    print(f"청크 {i+1} (길이: {len(chunk)}): {repr(chunk)}")

print("\n" + "="*60)
print("2. Separator 우선순위 시스템")
print("="*60)

# 기본 separator 리스트 (우선순위 순서)
default_separators = [
    "\n\n",    # 1순위: 빈 줄 (문단 구분)
    "\n",      # 2순위: 줄바꿈
    " ",       # 3순위: 공백
    ""         # 4순위: 문자별 (최후의 수단)
]

print("기본 구분자 우선순위:")
for i, sep in enumerate(default_separators, 1):
    print(f"{i}순위: {repr(sep)}")

# ====== 3. Chunk Size와 Overlap 동작 예제 ======
print("\n" + "="*60)
print("3. Chunk Size와 Overlap 상세 동작")
print("="*60)

text_for_chunking = """첫 번째 문장입니다. 두 번째 문장입니다. 세 번째 문장입니다. 네 번째 문장입니다. 다섯 번째 문장입니다."""

# 다양한 설정으로 테스트
configs = [
    {"chunk_size": 30, "chunk_overlap": 0, "desc": "overlap 없음"},
    {"chunk_size": 30, "chunk_overlap": 10, "desc": "overlap 있음"},
    {"chunk_size": 50, "chunk_overlap": 15, "desc": "큰 청크, 큰 overlap"}
]

for config in configs:
    print(f"\n--- {config['desc']} (size={config['chunk_size']}, overlap={config['chunk_overlap']}) ---")
    splitter = RecursiveCharacterTextSplitter(
        chunk_size=config['chunk_size'],
        chunk_overlap=config['chunk_overlap']
    )
    chunks = splitter.split_text(text_for_chunking)
    
    for i, chunk in enumerate(chunks):
        print(f"청크 {i+1}: {repr(chunk)} (길이: {len(chunk)})")
    
    # 겹치는 부분 확인
    if len(chunks) > 1:
        for i in range(len(chunks)-1):
            current = chunks[i]
            next_chunk = chunks[i+1]
            # 겹치는 부분 찾기
            overlap_found = ""
            for j in range(1, min(len(current), len(next_chunk)) + 1):
                if current[-j:] == next_chunk[:j]:
                    overlap_found = current[-j:]
            if overlap_found:
                print(f"  → 청크 {i+1}{i+2} 간 실제 겹침: {repr(overlap_found)}")

# ====== 4. Custom Separator 예제 ======
print("\n" + "="*60)
print("4. Custom Separator 사용법")
print("="*60)

# 특별한 구분자가 있는 텍스트
special_text = """제목1###내용1입니다. 여러 줄에 걸쳐 작성됩니다.
추가 내용도 있습니다.###제목2###내용2입니다. 이것도 긴 내용입니다.
더 많은 정보를 포함합니다.###제목3###마지막 내용입니다."""

# 커스텀 구분자 사용
custom_splitter = RecursiveCharacterTextSplitter(
    separators=["###", "\n", " ", ""],  # 커스텀 구분자 우선순위
    chunk_size=50,
    chunk_overlap=5
)

chunks = custom_splitter.split_text(special_text)
print("커스텀 구분자 결과:")
for i, chunk in enumerate(chunks):
    print(f"청크 {i+1}: {repr(chunk)}")

# ====== 5. is_separator_regex 옵션 ======
print("\n" + "="*60)
print("5. is_separator_regex 옵션")
print("="*60)

# 정규식 패턴이 필요한 텍스트
regex_text = """섹션1: 내용입니다.
섹션2: 다른 내용입니다.
섹션3: 마지막 내용입니다.
Chapter1: 첫 번째 챕터
Chapter2: 두 번째 챕터"""

# 정규식 구분자 사용
regex_splitter = RecursiveCharacterTextSplitter(
    separators=[r"섹션\d+:", r"Chapter\d+:", "\n", " "],
    chunk_size=40,
    chunk_overlap=5,
    is_separator_regex=True  # 정규식으로 처리
)

chunks = regex_splitter.split_text(regex_text)
print("정규식 구분자 결과:")
for i, chunk in enumerate(chunks):
    print(f"청크 {i+1}: {repr(chunk)}")

# 정규식을 사용하지 않을 때와 비교
normal_splitter = RecursiveCharacterTextSplitter(
    separators=[r"섹션\d+:", r"Chapter\d+:", "\n", " "],
    chunk_size=40,
    chunk_overlap=5,
    is_separator_regex=False  # 일반 문자열로 처리
)

chunks_normal = normal_splitter.split_text(regex_text)
print("\n정규식 미사용 결과:")
for i, chunk in enumerate(chunks_normal):
    print(f"청크 {i+1}: {repr(chunk)}")

# ====== 6. 실무 예제: 코드 분할 ======
print("\n" + "="*60)
print("6. 실무 예제: Python 코드 분할")
print("="*60)

python_code = '''def function1():
    """첫 번째 함수"""
    print("Hello")
    return True

def function2():
    """두 번째 함수"""
    for i in range(5):
        print(i)
    return False

class MyClass:
    """클래스 예제"""
    def __init__(self):
        self.value = 0
    
    def method1(self):
        return self.value
'''

# 코드용 구분자 설정
code_splitter = RecursiveCharacterTextSplitter(
    separators=[
        "\n\nclass ",    # 클래스 구분
        "\n\ndef ",      # 함수 구분
        "\n\n",          # 빈 줄
        "\n",            # 일반 줄바꿈
        " ",             # 공백
        ""               # 문자
    ],
    chunk_size=100,
    chunk_overlap=10,
    is_separator_regex=False
)

chunks = code_splitter.split_text(python_code)
print("Python 코드 분할 결과:")
for i, chunk in enumerate(chunks):
    print(f"청크 {i+1}:\n{chunk}")
    print("-" * 30)

# ====== 7. 길이 함수 커스터마이징 ======
print("\n" + "="*60)
print("7. 길이 함수 커스터마이징 (토큰 기반)")
print("="*60)

def token_length_function(text: str) -> int:
    """간단한 토큰 길이 계산 (실제로는 tiktoken 등 사용)"""
    # 공백과 특수문자로 토큰 추정
    return len(text.split()) + text.count('.') + text.count(',')

token_splitter = RecursiveCharacterTextSplitter(
    chunk_size=10,  # 10 토큰
    chunk_overlap=2,  # 2 토큰 겹침
    length_function=token_length_function,
    separators=["\n\n", "\n", ". ", " ", ""]
)

test_text = "이것은 테스트 문장입니다. 토큰 기반으로 분할됩니다. 각 단어와 구두점이 토큰으로 계산됩니다."

chunks = token_splitter.split_text(test_text)
print("토큰 기반 분할 결과:")
for i, chunk in enumerate(chunks):
    token_count = token_length_function(chunk)
    print(f"청크 {i+1} (토큰 {token_count}개): {repr(chunk)}")

# ====== 8. 옵션별 우선순위 및 동작 순서 ======
print("\n" + "="*60)
print("8. RecursiveCharacterTextSplitter 동작 순서")
print("="*60)

print("""
동작 순서:
1. separators 리스트의 첫 번째 구분자로 텍스트 분할 시도
2. 분할된 각 부분이 chunk_size보다 작으면 완료
3. 크면 다음 우선순위 구분자로 재귀적 분할
4. 모든 구분자로 분할해도 크면 강제로 chunk_size만큼 자름
5. chunk_overlap만큼 겹치게 청크 구성

우선순위:
1. separators[0] > separators[1] > ... > separators[-1]
2. chunk_size는 절대적 제한
3. chunk_overlap은 chunk_size 내에서 동작
4. length_function으로 길이 계산 방식 변경 가능
""")

# ====== 9. 실제 사용 시 주의사항 ======
print("\n" + "="*60)
print("9. 실제 사용 시 권장 설정")
print("="*60)

# 다양한 용도별 권장 설정
recommended_configs = {
    "일반 텍스트": {
        "separators": ["\n\n", "\n", ". ", " ", ""],
        "chunk_size": 1000,
        "chunk_overlap": 100
    },
    "코드 분할": {
        "separators": ["\n\nclass ", "\n\ndef ", "\n\n", "\n", " ", ""],
        "chunk_size": 500,
        "chunk_overlap": 50
    },
    "마크다운": {
        "separators": ["\n## ", "\n### ", "\n\n", "\n", " ", ""],
        "chunk_size": 800,
        "chunk_overlap": 80
    },
    "대화형 텍스트": {
        "separators": ["\n\n", "\n", ". ", "! ", "? ", " ", ""],
        "chunk_size": 600,
        "chunk_overlap": 60
    }
}

for use_case, config in recommended_configs.items():
    print(f"\n{use_case} 권장 설정:")
    for key, value in config.items():
        print(f"  {key}: {value}")

print("\n" + "="*60)
print("마무리: 핵심 포인트")
print("="*60)

print("""
핵심 포인트:
1. separators는 우선순위 리스트 - 앞쪽이 우선순위 높음
2. chunk_size는 최대 크기 제한 - 이보다 크면 강제 분할
3. chunk_overlap은 문맥 유지를 위한 겹침 - 너무 크면 비효율
4. is_separator_regex=True면 정규식 패턴 사용 가능
5. length_function으로 토큰 기반 계산 등 커스터마이징 가능

실무 팁:
- chunk_overlap은 보통 chunk_size의 10-20% 추천
- 구분자는 의미적 경계를 우선으로 설정
- 정규식은 복잡한 패턴이 필요할 때만 사용
- 용도에 맞게 구분자와 크기 조정 필수
""")
profile
https://github.com/min731

0개의 댓글