실무 Multi-Agent 오케스트레이션 12장 · 문서 로딩과 청킹 4 / 8 ← 이전목차다음 → TechLead Cro

12장. 문서 로딩과 청킹

청킹 — 어디서, 얼마나 크게 자를 것인가

한 줄 요약

청킹(chunking) 은 글을 검색 단위의 조각으로 자르는 일입니다. 조각이 너무 작으면 문맥이 끊기고, 너무 크면 관련 없는 내용이 딸려 옵니다. 크기는 문서의 자연스러운 단위(우리 문서에서는 조항)를 보고 정하고, 조각의 경계에서 내용이 끊기는 것은 겹침(overlap) 으로 줄입니다.


1. 왜 자르는가

자르지 않으면 검색의 결과가 "문서 전체"가 됩니다. 통째로 넣는 것과 다를 바가 없습니다. 그래서 검색해서 돌려받을 단위를 정해 미리 잘라 둡니다. 그 조각이 청크(chunk) 입니다.


2. 크기 — 작아도 탈, 커도 탈

청크가 생기는 일
너무 작다 제목과 본문, 표의 제목과 내용이 서로 다른 청크로 갈린다. 찾은 조각만 봐서는 무슨 이야기인지 모른다
너무 크다 한 청크에 여러 조항이 섞인다. 반품 질문에 멤버십 조항까지 딸려 온다. 토큰도 많이 든다

정답인 숫자는 없습니다. 문서마다 다릅니다. 그래서 순서를 이렇게 잡습니다.

  1. 문서의 자연스러운 단위가 무엇인지 봅니다. 하루마켓 정책 문서는 "제N조"로 나뉘어 있습니다.
  2. 그 단위 하나가 대체로 한 청크에 들어가는 크기를 고릅니다.
  3. 실제로 잘라 보고 조각을 눈으로 읽어 확인합니다.

「따라하기」에서 200자, 500자, 1,500자 세 가지로 잘라 비교합니다. 이 과정의 표준은 500자입니다.

크기의 단위는 글자 수입니다. 토큰 수가 아닙니다. 500자짜리 청크가 몇 토큰인지는 실습문제에서 잽니다.


3. 겹침 — 경계에 걸친 내용을 양쪽에 남긴다

어떻게 자르든 경계에 걸리는 내용이 생깁니다. 표의 앞 절반은 이쪽 청크, 뒤 절반은 저쪽 청크에 들어가는 식입니다.

겹침(overlap) 은 이웃한 두 청크가 경계 부근을 일정 길이만큼 함께 갖게 하는 것입니다.

겹침 없음:   [ 청크 0 ............ ][ 청크 1 ............ ]

겹침 있음:   [ 청크 0 ............ ]
                           [ 청크 1 ............ ]
                           └ 겹침 ┘

우리 문서를 500자, 겹침 100자로 자른 결과에서 실제 예를 봅니다. 첫 청크의 끝과 둘째 청크의 시작입니다.

(청크 0의 끝)
[표 1] 등급 산정 기준 (최근 6개월 실결제액)
등급 산정 기준 산정 주기
일반 가입 시 기본 등급
매월 1일
실버 10만원 이상

(청크 1의 시작)
[표 1] 등급 산정 기준 (최근 6개월 실결제액)
등급 산정 기준 산정 주기
일반 가입 시 기본 등급
매월 1일
실버 10만원 이상
골드 30만원 이상
VIP 60만원 이상
제2조 (등급별 혜택)

청크 0에서는 표가 "실버"에서 끊겼습니다. 골드와 VIP가 없습니다. 그런데 청크 1이 표의 제목부터 다시 시작하므로 표 전체가 청크 1에 온전히 들어 있습니다. "골드 등급이 되려면 얼마나 사야 하나요?"라는 질문에 청크 1이 검색되면 답할 수 있습니다.

같은 문서를 겹침 0으로 잘라 보면 청크 1은 "골드 30만원 이상"부터 시작합니다. 무엇에 대한 숫자인지 알 수 없는 조각이 됩니다.

겹침에는 값이 따릅니다. 같은 내용이 두 청크에 저장되고 청크 수가 늘어납니다. 그래서 크기에 비해 작게 잡습니다. 이 과정은 500자에 100자입니다.


4. 어디서 자르는가 — 재귀적 분할

크기만 정해 놓고 500자마다 기계적으로 끊으면 단어나 숫자 한가운데가 잘립니다. "3,0"과 "00원"으로 나뉠 수 있습니다.

RecursiveCharacterTextSplitter는 자르기 좋은 자리를 우선순위대로 찾아 자릅니다.

from langchain_text_splitters import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,
    chunk_overlap=100,
    separators=["\n\n", "\n", ". ", " ", ""],
)
chunks = splitter.split_text(text)
인자 뜻
chunk_size 청크 하나의 최대 글자 수
chunk_overlap 이웃 청크와 겹칠 글자 수
separators 자를 자리의 우선순위. 앞의 것부터 시도한다

separators가 동작의 핵심입니다.

순서 구분자 뜻
1 "\n\n" 빈 줄 (문단 사이)
2 "\n" 줄바꿈
3 ". " 문장의 끝
4 " " 띄어쓰기
5 "" 아무 데서나 (글자 단위)

먼저 빈 줄에서 잘라 봅니다. 그래도 조각이 500자를 넘으면 그 조각을 줄바꿈에서 다시 자르고, 그래도 넘으면 문장 끝에서 자릅니다. 큰 구분자에서 작은 구분자로 내려가며 같은 일을 되풀이하므로 재귀적(recursive) 이라는 이름이 붙었습니다. 사람이 긴 글을 나눌 때 "되도록 문단에서, 안 되면 문장에서" 끊는 것과 같은 방식입니다.

우리 문서에서는 1번이 쓰이지 않습니다. 앞 절에서 본 것처럼 PDF에서 꺼낸 글에 빈 줄이 하나도 없기 때문입니다. 실제로는 2번, 줄바꿈에서 잘립니다. 그래서 청크의 경계는 언제나 줄의 끝이고, 조항 제목 같은 한 줄이 중간에서 잘리는 일은 없습니다. 대신 조항이 시작하는 자리에서 끊어 달라는 뜻은 전달되지 않습니다. 이 사실이 「따라하기」의 관찰로 이어집니다.


5. 문서마다 따로 자른다

정책 문서는 두 개입니다. 둘을 이어 붙인 뒤 한꺼번에 자르면, 앞 문서의 끝과 뒤 문서의 시작이 한 청크에 섞입니다.

v1.2 2026-01-02 포토 리뷰 적립 상향(300→500P)
v1.0 2025-06-01 하루클럽 제도 도입
하루마켓 HARU MARKET CS-POL-2026-007 · 고객경험팀
반품·교환·환불 운영 정책
Return, Exchange & Refund Policy — 하루마켓 고객경험팀

위는 두 문서를 합쳐 200자로 잘랐을 때 실제로 나온 청크입니다. 앞의 두 줄은 멤버십 정책의 개정 이력이고 뒤의 세 줄은 반품 정책의 머리말입니다. 이 청크는 어느 문서의 것이라고 말할 수 없습니다.

그래서 최종 청킹은 문서마다 따로 합니다.

for source_name, _, text in load_pdf_texts():      # 문서 하나씩
    for chunk in splitter.split_text(text):
        ...

핵심 정리

  • 청크가 작으면 문맥이 끊기고, 크면 무관한 내용이 섞입니다.
  • 크기는 문서의 자연스러운 단위를 보고 정하고, 잘라 본 조각을 눈으로 확인합니다. 이 과정은 500자입니다.
  • 겹침은 경계에 걸친 내용을 양쪽 청크에 남깁니다. 이 과정은 100자입니다.
  • RecursiveCharacterTextSplitter는 빈 줄 → 줄바꿈 → 문장 → 띄어쓰기 순으로 자를 자리를 찾습니다.
  • 우리 문서에는 빈 줄이 없어 줄바꿈에서 잘립니다.
  • 서로 다른 문서는 따로 자릅니다.
← 이전 절PDF에서 텍스트 꺼내기 — 눈에 보이는 대로 나오지 않는다다음 절 →메타데이터 — 조각에 출처를 적어 둔다
오명운 · macro@prag-ai.com