시맨틱 청킹 (Semantic Chunking)
시맨틱 청킹은 임베딩 유사도를 기반으로 의미적으로 관련된 문장을 하나의 청크로 묶는 방식입니다. 문자 수나 구분자가 아닌, 내용의 의미적 연결성을 기준으로 분할합니다.
- 문서를 문장 단위로 분리
- 각 문장의 임베딩 벡터 생성
- 인접 문장 간 코사인 유사도 계산
- 유사도가 임계값보다 낮은 지점에서 분할
LangChain SemanticChunker
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
# Percentile 방식 (기본 추천)
splitter = SemanticChunker(
embeddings=embeddings,
breakpoint_threshold_type="percentile",
breakpoint_threshold_amount=95, # 유사도 95분위수 이하에서 분할
)
chunks = splitter.split_documents(docs)
for i, chunk in enumerate(chunks[:3]):
print(f"청크 {i+1}: {len(chunk.page_content)}자")
breakpoint_threshold_type 옵션
percentile (기본)
standard_deviation
interquartile
유사도 분포의 백분위수를 기준으로 분할합니다.splitter = SemanticChunker(
embeddings=embeddings,
breakpoint_threshold_type="percentile",
breakpoint_threshold_amount=95,
# 유사도가 하위 5%에 해당하는 지점에서 분할
)
| 값 | 효과 |
|---|
| 90 | 더 많이 분할 (작은 청크) |
| 95 | 균형 잡힌 기본값 |
| 99 | 덜 분할 (큰 청크) |
평균 유사도에서 표준편차만큼 벗어난 지점에서 분할합니다.splitter = SemanticChunker(
embeddings=embeddings,
breakpoint_threshold_type="standard_deviation",
breakpoint_threshold_amount=3,
# 평균 - 3σ 이하에서 분할
)
사분위수 범위(IQR)를 기준으로 분할합니다.splitter = SemanticChunker(
embeddings=embeddings,
breakpoint_threshold_type="interquartile",
breakpoint_threshold_amount=1.5,
# Q1 - 1.5*IQR 이하에서 분할
)
성능 트레이드오프
| 항목 | Recursive Character | Semantic Chunking |
|---|
| 분할 기준 | 문자 수 + 구분자 | 임베딩 유사도 |
| 청크 크기 | 균일 | 불균일 |
| 의미 보존 | 보통 | 우수 |
| 처리 속도 | 빠름 | 느림 (임베딩 API 호출) |
| 비용 | 무료 | 임베딩 API 비용 |
| 적합한 경우 | 대부분의 일반 사용 | 높은 검색 정확도 필요 시 |
비용 추정
# text-embedding-3-small 기준
# 1000 토큰당 약 $0.00002
# 10만자 문서 ≈ 25,000 토큰 ≈ $0.0005
# 대규모 문서 처리 시
# 1000개 문서 × 10만자 = $0.50
시맨틱 청킹은 문서의 모든 문장에 대해 임베딩을 생성하므로, 대용량 문서에서는 비용과 시간이 크게 증가합니다. 프로토타이핑에서는 Recursive Character Splitting으로 시작하고, 검색 품질 개선이 필요한 경우에만 전환하세요.
breakpoint_threshold_type="percentile", breakpoint_threshold_amount=95로 시작하세요. 청크가 너무 크면 값을 낮추고(90), 너무 작으면 높이세요(99).