CPN 한국어 자습서 · 러닝패스 2 / 4 — Building with the Claude API
RAG
Text chunking strategies
RAG 파이프라인의 첫 단계는 큰 문서를 작은 청크로 쪼개는 일입니다. 그런데 이게 의외로 가장 까다로운 단계입니다 — 어떻게 쪼개는지가 검색 품질을 좌우하기 때문입니다. 이 장에서는 잘못 쪼갰을 때 생기는 교차오염 문제를 보고, 세 가지 청킹 전략(크기·구조·의미 기준)을 코드로 살펴봅니다.
Stephen Grider · Anthropic 기술 스태프
이번 영상부터 여러 노트북에 걸쳐 우리만의 RAG 워크플로를 직접 구현하기 시작합니다. 우선은 가능한 한 가장 단순하고 기본적인 RAG부터 만들고, 시간이 지나며 단계를 하나씩 더해 갑니다. 다시 떠올려 보면, 전형적인 RAG 파이프라인은 이렇습니다 — 원본 문서를 가져와 텍스트 청크로 쪼개고, 사용자가 질문하면 관련 있는 청크를 찾아 프롬프트에 넣습니다. 이게 거의 전부입니다.
이 흐름의 1단계가 바로 원본 문서를 텍스트 청크로 쪼개는 일입니다. 믿기 어렵겠지만, 문서를 따로따로 청크로 나누는 이 과정이 RAG 파이프라인에서 더 복잡한 단계 중 하나입니다. 왜냐하면 문서를 어떻게 쪼개는지가 RAG 품질에 엄청난 영향을 주기 때문입니다.
예를 들어 보겠습니다. 어떤 회사의 보고서 같은 짧은 문서가 있다고 합시다. 크게 세 부분으로 나뉩니다 — 헤더, 의료 연구(medical research) 섹션, 소프트웨어 공학(software engineering) 섹션. 한 가지 방법으로, 문서의 줄마다 하나씩 청크를 만든다고 해봅시다. 그러면 다섯 개쯤의 청크가 나오는데, 여기서 흥미로운 일이 벌어집니다.
세 번째 청크는 원래 의료 연구 섹션 안에 있던 텍스트입니다. 그런데 그 안에 bug(벌레/버그)라는 단어가 들어 있습니다. 얼핏 이 문단만 보면 마치 소프트웨어 공학 이야기처럼 보입니다 — 단지 "bug"라는 단어 때문에요. 반대로 아래쪽 소프트웨어 공학 섹션에는 infection vectors(감염 경로)라는 단어가 들어 있습니다. infection은 오히려 의료 쪽 용어죠. 그래서 소프트웨어 섹션인데 안의 표현은 의료처럼 보입니다.
이제 사용자가 "엔지니어가 올해 버그를 몇 개나 고쳤어?"라고 물었다고 합시다. 우리 일은 질문에 가장 관련 있는 청크를 찾는 것입니다. 사용자가 "bug"를 말했으니, 얼핏 보면 "bug"가 든 그 의료 청크가 관련 있어 보입니다. 그래서 그 청크를 골라 프롬프트에 넣을 수 있습니다. 그런데 이건 바로 큰 오류입니다. 사용자는 소프트웨어 공학에 대해 알고 싶었는데, 우리는 엉뚱하게 의료 연구 청크를 가져온 셈입니다.
이렇게 청킹 전략은 아주 쉽게 큰 오류와 나쁜 맥락을 프롬프트에 끼워 넣을 수 있습니다. 그래서 문서를 어떻게 쪼갤지 깊이 고민해야 합니다. 이 영상에서는 방금 본 문제를 다루기 위한 세 가지 청킹 전략을 봅니다 — 크기 기준(size-based), 구조 기준(structure-based), 의미 기준(semantic-based).
첫째, 크기 기준 청킹입니다. 큰 문서를 거의 같은 길이의 문자열 여러 개로 그냥 나눕니다. 가장 구현하기 쉽고, 프로덕션에서 가장 흔히 보게 될 방식이기도 합니다. 예를 들어 약 325자짜리 문서를 임의로 3등분하면 각 청크가 108자쯤 됩니다. 아주 단순하지만 곧바로 큰 단점이 있습니다 — 단어가 중간에 잘리고(예: significant가 두 청크로 쪼개짐), 각 청크가 맥락(바로 위 섹션 헤더 등)을 잃습니다.
이 문제를 풀려고 겹침(overlap) 전략을 씁니다. 여전히 크기 기준으로 자르되, 이웃 청크에서 문자를 조금 겹쳐 가져옵니다. 그러면 텍스트가 어느 정도 중복되지만, 대신 각 청크가 조금 더 많은 맥락을 갖게 됩니다.
둘째, 구조 기준 청킹입니다. 문서의 전체 구조 — 헤더, 문단, 섹션 — 를 기준으로 나눕니다. 우리 문서는 마크다운으로 쓰여 있어 쉽습니다. 섹션마다 더블 해시(##)가 있으니, 그 기호가 보일 때마다 새 섹션이 시작된다고 보고 거기서 쪼개면 됩니다. 다만 현실은 그리 호락호락하지 않습니다 — 마크다운이 아닌 평범한 PDF 평문이라면 이렇게 또렷한 섹션 구분을 얻을 수 없습니다. 구조에 대한 보장이 없으면 구현이 까다롭습니다.
셋째, 의미 기준 청킹입니다. 텍스트를 문장이나 섹션으로 나눈 뒤, 자연어 처리(NLP) 기법으로 연속한 문장끼리 얼마나 관련 있는지 따져 관련 있는 것끼리 묶어 청크를 만듭니다. 설명만 들어도 알 수 있듯 훨씬 고급 기법이라 구현은 깊이 다루지 않습니다. 다만 청킹 방법에 정해진 개수가 없다는 점만 분명히 하고 싶습니다 — 무한히 많은 방법이 있고, 어떤 방식을 쓸지는 우리 use case와 문서에 대한 보장에 달려 있습니다.
노트북 001_chunking을 열어 보면 세 전략이 구현돼 있습니다. 함께 쓸 report.md(가상의 샘플 보고서)도 같은 폴더에 두세요. chunk_by_char는 크기 기준 구현으로, 텍스트·청크 크기·겹침을 인자로 받습니다. chunk_by_sentence는 정규식으로 문장을 나눈 뒤 몇 문장씩 묶고 양쪽에 약간 겹침을 둡니다. chunk_by_section은 구조 기준으로, 줄바꿈 + 더블 해시 + 공백을 기준으로 섹션을 나눕니다.
직접 돌려 보면, chunk_by_char의 기본값(150자/20자 겹침)은 청크가 빈약해서 사용자 질문에 답하기 어렵습니다. 그래서 청크 길이를 500, 겹침을 150으로 키우면 의미 있는 섹션이 형성되고 겹침이 눈에 보이기 시작합니다. chunk_by_sentence의 기본값(5문장/1문장 겹침)은 꽤 좋아 보입니다. 각 청크가 충분한 정보를 담습니다. chunk_by_section은 마크다운 보장이 있을 때 환상적입니다 — 섹션 하나당 청크 하나가 정확히 나옵니다.
결국 어떤 전략을 쓸지는 전적으로 문서의 성격과 구조에 대한 보장에 달려 있습니다. 우리 문서엔 chunk_by_section이 훌륭하지만, 사용자가 형식을 보장할 수 없는 문서를 준다면 섹션 기준은 오래가지 못합니다. 그땐 문장 기준으로 폴백할 수 있는데, 그것도 늘 잘 되진 않습니다 — 예컨대 코드를 청킹한다면, 코드엔 마침표가 엉뚱한 곳에 많아 문장 분할이 이상해집니다. 그래서 결국 오래된 믿음직한 표준 — 문자 기준(chunk_by_char) — 으로 돌아갈 수 있습니다. 최고는 아니어도 대부분의 경우 무난하게 작동합니다.
이 장에서 배우는 것What you'll learn
약 13분청킹은 RAG에서 가장 복잡한 단계 중 하나 — 품질을 좌우
교차오염: 의료 청크의 "bug", 소프트웨어 청크의 "infection" → 오선택
크기 기준(chunk_by_char) + 겹침(overlap)으로 맥락 보완
구조 기준(chunk_by_section) — 마크다운 ##로 분할
의미 기준(semantic) — NLP로 관련 문장 묶기(고급)
전략 선택은 문서·보장에 따라 — section→sentence→char 폴백
RAG 1단계는 문서를 청크로 쪼개는 일인데, 이게 의외로 가장 복잡한 단계입니다. 어떻게 쪼개는지가 검색 품질을 좌우하기 때문입니다. 한 가지 예로 "표면 단어"가 섹션 주제와 어긋날 때 생기는 교차오염을 봅시다.
XDR-471 임상에서 박테리아 한 종이 예상 밖 내성을 보였다. 연구진은 이 bug가 치료 반응을 어떻게 바꾸는지 추적했다.
Project Phoenix 팀은 올해 결함 412건을 해결했다. 보안 검토에서는 잠재적 infection 경로도 함께 점검했다.
의료 청크엔 bug가, 소프트웨어 청크엔 infection이 들어 있습니다. 표면 단어가 섹션의 주제와 어긋나면(교차오염), 단순 단어 검색은 엉뚱한 청크를 가져옵니다.
방금 본 문제를 다루기 위한 청킹 전략은 크게 셋입니다 — 크기 기준, 구조 기준, 의미 기준. 탭을 눌러 같은 텍스트가 각 방식으로 어떻게 쪼개지는지 비교해 보세요.
크기 기준은 단어가 잘리고 맥락이 끊기지만 겹침(주황 표시)으로 보완합니다. 구조 기준은 ## 경계라 가장 깔끔하지만, 마크다운 보장이 없으면 쓸 수 없습니다.
##이면 깔끔. 평문 PDF엔 안 통함.노트북 001_chunking.ipynb에 세 전략이 구현돼 있습니다. 함께 쓸 가상 보고서 report.md를 같은 폴더에 두세요. 먼저 크기 기준 — 텍스트·청크 크기·겹침을 인자로 받습니다.
# 크기 기준 — 문자 수 + 겹침(overlap) def chunk_by_char(text, chunk_size=150, chunk_overlap=20): chunks = [] start_idx = 0 while start_idx < len(text): end_idx = min(start_idx + chunk_size, len(text)) chunk_text = text[start_idx:end_idx] chunks.append(chunk_text) start_idx = end_idx - chunk_overlap if end_idx < len(text) else len(text) return chunks
다음은 문장 기준 — 정규식으로 문장을 나눈 뒤 몇 문장씩 묶고 양쪽에 약간 겹침을 둡니다.
# 문장 기준 — 정규식으로 문장 분리 후 묶음 import re def chunk_by_sentence(text, max_sentences_per_chunk=5, overlap_sentences=1): sentences = re.split(r"(?<=[.!?])\s+", text) chunks = [] start_idx = 0 while start_idx < len(sentences): end_idx = min(start_idx + max_sentences_per_chunk, len(sentences)) current_chunk = sentences[start_idx:end_idx] chunks.append(" ".join(current_chunk)) start_idx += max_sentences_per_chunk - overlap_sentences if start_idx < 0: start_idx = 0 return chunks
마지막으로 구조 기준 — 줄바꿈 + 더블 해시 + 공백을 기준으로 섹션을 나눕니다. 마크다운 보장이 있으면 가장 깔끔합니다.
# 구조 기준 — 마크다운 섹션(##)으로 분할 def chunk_by_section(document_text): pattern = r"\n## " return re.split(pattern, document_text)
직접 돌려 보면 차이가 분명합니다. chunk_by_char의 기본값(150/20)은 청크가 빈약하니 500/150으로 키우면 의미 있는 섹션이 형성됩니다. chunk_by_sentence 기본값도 꽤 좋고, chunk_by_section은 마크다운 보장 시 최고입니다.
# 기본값(150/20)은 빈약 → 키우면 의미 있는 섹션이 형성됨 char_chunks = chunk_by_char(text, chunk_size=500, chunk_overlap=150) sentence_chunks = chunk_by_sentence(text) # 5문장/1겹침 — 꽤 좋음 section_chunks = chunk_by_section(text) # 섹션 1개당 청크 1개 — 최고(마크다운 보장 시) print(len(char_chunks), len(sentence_chunks), len(section_chunks))
# 섹션 기준은 report.md의 ## 섹션 수만큼 깔끔하게 나옴
8 14 14어떤 전략을 쓸지는 문서·보장에 달렸습니다. 보장이 있으면 section, 없으면 sentence로, 그것도 애매하면(예: 코드엔 마침표가 곳곳) char로 폴백합니다. char는 최고는 아니어도 대부분 무난합니다.
##로 깔끔히 분할.Q1크기 기준 청킹의 단어 잘림·맥락 손실을 보완하는 기법은?
겹침은 중복을 늘리는 대신, 잘린 단어를 이어 주고 인접 맥락을 청크에 담아 줍니다.
Q2chunk_by_section이 가장 깔끔하게 작동하는 조건은?
구조 기준은 \n## 경계로 나눕니다. 마크다운 보장이 없으면 또렷한 섹션을 얻을 수 없습니다.
Q3코드처럼 마침표가 곳곳에 있는 문서엔 어떤 폴백이 적절한가요?
코드는 마침표가 엉뚱한 곳에 많아 문장 분할이 이상해집니다. 그래서 믿음직한 표준인 문자 기준으로 폴백합니다.
문서를 청크로 쪼갰습니다. 이제 각 청크의 의미를 숫자로 표현해 비교할 수 있게 만드는 텍스트 임베딩으로 넘어갑니다. → 텍스트 임베딩
전 코스는 계속 무료입니다. 등록하면 이 코스의 남은 76개 레슨을 끝까지 읽을 수 있습니다.
이미 등록하셨다면 그때 쓰신 이메일을 넣어 주세요.