홈으로
🔍

RAG 검색 증강 생성

문서 기반 AI 시스템 구축의 모든 것

12시간
intermediate
6개 챕터
초급 과정으로 돌아가기

Chapter 3: 청킹 전략의 모든 것

문서를 AI가 이해하기 쉽게 나누는 기술

3.1 청킹이 중요한 이유

임베딩과 검색 품질의 핵심

🎯 청킹의 목적

  • 의미적 완결성: 하나의 청크가 독립적인 의미를 가져야 함
  • 검색 효율성: 적절한 크기로 정확한 정보 검색
  • 컨텍스트 보존: 문맥 정보가 손실되지 않도록

⚠️ 잘못된 청킹의 문제

  • 너무 작은 청크: 문맥 손실, 의미 파편화
  • 너무 큰 청크: 부정확한 검색, 노이즈 증가
  • 일관성 없음: 검색 품질 편차 발생

3.2 주요 청킹 전략

1. 고정 크기 청킹 (Fixed Size Chunking)

설정 예시

chunk_size: 1000
overlap: 200

장점

  • • 구현 간단
  • • 예측 가능한 크기
  • • 빠른 처리 속도

단점

  • • 문맥 단절 가능
  • • 의미 단위 무시
  • • 문장 중간 절단

시각적 예시:

[0-1000자]
[800-1800자]
[1600-2600자]
→ 200자 중첩

2. 의미 단위 청킹 (Semantic Chunking)

작동 원리:

1

문단별 임베딩 생성

각 문단을 벡터로 변환

2

유사도 계산

인접 문단 간 코사인 유사도 측정

3

경계 결정

유사도가 낮은 지점에서 분할

✅ 추천 상황

  • • 기술 문서, 매뉴얼
  • • 논문, 보고서
  • • 구조화된 콘텐츠

⚡ 성능 팁

  • • 임계값: 0.7-0.8 권장
  • • 최소 청크: 200자 이상
  • • 최대 청크: 1500자 이하

3. 문서 구조 기반 청킹

활용 가능한 구조 요소:

Markdown
# ## ### 헤더
HTML
<h1> <p> <div>
LaTeX
\section \chapter
PDF
페이지, 섹션

💡 프로 팁

문서 구조와 의미적 청킹을 하이브리드로 사용하면 최상의 결과를 얻을 수 있습니다. 예: 섹션 단위로 먼저 나누고, 각 섹션 내에서 의미적 청킹 적용

청킹 베스트 프랙티스

📏 크기 가이드라인

  • 일반 문서: 500-1000자
  • 기술 문서: 1000-1500자
  • 대화형: 300-500자
  • 법률/의학: 1500-2000자

🔄 중첩(Overlap) 설정

  • 표준: 10-20%
  • 높은 정밀도: 25-30%
  • 빠른 처리: 5-10%
  • 0% 중첩: 권장하지 않음

🎯 성능 최적화

  • 청크별 메타데이터 추가
  • 문서 타입별 전략 차별화
  • A/B 테스트로 최적값 찾기
  • 사용자 피드백 반영

🔥 실전 코드: 스마트 청킹 구현

from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.text_splitter import CharacterTextSplitter

# 1. 기본 고정 크기 청킹
def basic_chunking(text):
    splitter = CharacterTextSplitter(
        chunk_size=1000,
        chunk_overlap=200,
        separator="\n"
    )
    return splitter.split_text(text)

# 2. 재귀적 문자 분할 (추천!)
def smart_chunking(text):
    splitter = RecursiveCharacterTextSplitter(
        chunk_size=1000,
        chunk_overlap=200,
        separators=["\n\n", "\n", ". ", " ", ""],
        length_function=len,
    )
    return splitter.split_text(text)

# 3. 의미적 청킹 (고급)
def semantic_chunking(text, embeddings):
    # 문단별로 분할
    paragraphs = text.split("\n\n")
    
    # 각 문단 임베딩
    embeddings_list = [embeddings.embed_query(p) for p in paragraphs]
    
    # 유사도 계산 및 병합
    chunks = []
    current_chunk = paragraphs[0]
    
    for i in range(1, len(paragraphs)):
        similarity = cosine_similarity(
            embeddings_list[i-1], 
            embeddings_list[i]
        )
        
        if similarity > 0.7:  # 유사하면 병합
            current_chunk += "\n\n" + paragraphs[i]
        else:  # 다르면 새 청크 시작
            chunks.append(current_chunk)
            current_chunk = paragraphs[i]
    
    chunks.append(current_chunk)
    return chunks