"RAG 핵심 학습 (6/26) — 문맥 보강 청킹: Parent-Child과 Contextual Retrieval"
5편이 청크를 자르는 방법을 다섯 갈래로 풀었다면, 6편은 청크가 부족할 때 무엇을 덧붙일 것인가를 다룬다.
청크 한 조각만으로는 종종 부족하다. "그 정책은 어디에 적용되는가?"라는 질문에 청크는 답에 가까운 문장은 가지고 있지만 어느 정책의 어느 절에서 온 문장인지를 함께 가지지 않을 수 있다. 이를 보완하기 위한 네 가지 보강 기법 — Parent-Child Retrieval, Chunk Header Injection, Document Summary Prefix, Anthropic Contextual Retrieval (2024-09) — 을 한 자리에서 비교한다. Anthropic 보고에 따르면 마지막 기법은 검색 실패율을 49% 줄였다(BM25 결합 시 67% 감소).
0. Prerequisites
- 5편(청킹 5가지 길). 본 편은 청크 생성 이후의 보강을 다룬다.
- 3편(Ingestion 설계)의 metadata 필드 —
document_id,section,version. - LLM 호출이 발생한다는 점(특히 4번째 기법). 호출 비용·캐시 가능 여부를 의식해야 한다.
1. 학습 목표
- 왜 청크 단독으로 부족한지 두 시나리오로 설명한다.
- Parent-Child / Header Injection / Summary Prefix / Contextual Retrieval의 한 줄 차이를 안다.
- Anthropic Contextual Retrieval의 비용·캐시·효과 수치를 인용할 수 있다.
- 어느 기법을 언제 쓸지 결정 표로 안다.
2. 핵심 요약
청크는 검색의 단위지만 답을 만드는 단위는 아니다. 검색은 청크로, 답에 쓰일 컨텍스트는 더 큰 단위로 — 이 분리가 네 기법의 공통 원리다. Parent-Child Retrieval은 작은 청크로 검색하고 부모 문서·부모 섹션을 답에 넣는다(LangChain ParentDocumentRetriever). Chunk Header Injection은 청크 앞에 문서 제목·섹션 헤더를 정적으로 붙여 임베딩에 위치 신호를 준다. Document Summary Prefix는 문서 요약 한 문단을 모든 청크 앞에 붙인다. Anthropic Contextual Retrieval(2024-09)은 각 청크별로 LLM이 50~100 토큰짜리 맞춤 컨텍스트를 생성해 붙이고, prompt caching으로 비용을 1M 토큰당 약 $1.02까지 낮춘다. 검색 실패율 35~49% 감소가 보고된다.
3. 직관 — 같은 청크, 같은 query, 다른 검색 결과
"보안 정책의 예외 처리는 어떻게 신청하는가?"라는 질문을 가정하자. 정답이 들어 있는 청크 본문:
"신청자는 부서장 승인 후 양식 SEC-EX-04를 정보보호팀에 제출한다."
이 문장 자체에는 "보안 정책"이라는 단어도, "예외"라는 단어도 없다. 임베딩 검색은 키워드 일치가 아니라 의미 유사도지만, 문맥이 잘려 있으면 유사도 신호가 약해진다.
네 가지 보강을 같은 청크에 적용해 보자.
네 기법 모두 청크의 본문은 그대로 두되 임베딩 입력 또는 답 컨텍스트에 추가 신호를 얹는다. 차이는 어디에, 무엇을, 얼마의 비용으로 얹느냐다.
4. 정의 — 네 가지 보강 기법
| 기법 | 무엇을 덧붙이나 | 어디에 덧붙이나 | 생성 비용 |
|---|---|---|---|
| Parent-Child Retrieval | 부모 문서/섹션 본문 | 답 컨텍스트 (검색은 청크 그대로) | 0 (재구성만) |
| Chunk Header Injection | 문서 제목 + 섹션 헤더 경로 | 임베딩 입력 + 답 컨텍스트 | 0 (정적 메타데이터) |
| Document Summary Prefix | 문서 전체 요약 1~2문단 | 모든 청크의 임베딩 입력 | 문서당 LLM 1회 |
| Contextual Retrieval (Anthropic 2024-09) | 각 청크별 맞춤 컨텍스트 50~100 토큰 | 임베딩 입력 + (옵션) BM25 | 청크당 LLM 1회 (캐시 적극 활용) |
공통 원리는 "검색은 짧게, 답은 풍부하게"다. 단, 네 기법은 짧은 검색 단위 자체에 신호를 보강할지(2·3·4), 검색은 그대로 두고 답 단위만 키울지(1)에서 갈린다.
5. 식 — 컨텍스트 보강의 비용 모델
청크 1개에 보강 토큰 \(T_{\text{aug}}\)를 붙일 때 인덱스 비용은 다음과 같다.
- \(N\) = 청크 수
- \(T_c\) = 청크 평균 토큰
- \(T_{\text{aug}}\) = 보강 토큰 (기법별)
- \(C_{\text{embed}}\) = 임베딩 단가 ($/1K tokens)
- \(C_{\text{llm}}\) = LLM 단가 ($/1K input tokens, 보강 생성용)
임베딩 비용 (보강된 청크 N개 인덱싱):
$$\text{Cost}_{\text{embed}} = N \cdot \frac{T_c + T_{\text{aug}}}{1000} \cdot C_{\text{embed}}$$
보강 생성 비용 (Contextual Retrieval, 청크당 LLM 1회):
$$\text{Cost}_{\text{gen}} = N \cdot \frac{T_{\text{doc}} + T_{\text{prompt}} + T_{\text{aug}}}{1000} \cdot C_{\text{llm}}$$
여기서 \(T_{\text{doc}}\)이 가장 무겁다. 같은 문서가 N개 청크에 반복 입력되기 때문이다. Anthropic의 prompt caching (1시간 캐시, write 2배·hit 0.1배)이 이를 결정적으로 낮춘다. 자세한 단가는 7.4절.
6. 원리 워크스루 — 네 기법을 코드로
6.1 Parent-Child Retrieval (LangChain)
from langchain.retrievers import ParentDocumentRetriever
from langchain.storage import InMemoryStore
from langchain_text_splitters import RecursiveCharacterTextSplitter
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=2000)
child_splitter = RecursiveCharacterTextSplitter(chunk_size=400, chunk_overlap=50)
retriever = ParentDocumentRetriever(
vectorstore=vectordb, # child 청크만 임베딩됨
docstore=InMemoryStore(), # parent 본문 보관
child_splitter=child_splitter,
parent_splitter=parent_splitter,
)
retriever.add_documents(docs)
results = retriever.invoke("보안 정책 예외 신청 절차?")
핵심: 임베딩 인덱스에는 작은 child가 들어가고, 답 컨텍스트에는 큰 parent가 나간다.
6.2 Chunk Header Injection
def inject_header(chunk: str, doc_title: str, section_path: list[str]) -> str:
header = f"문서: {doc_title}\n경로: {' > '.join(section_path)}\n\n"
return header + chunk
embedded_text = inject_header(
chunk="신청자는 부서장 승인 후 양식 SEC-EX-04를 정보보호팀에 제출한다.",
doc_title="정보보안 정책서 v3.2",
section_path=["5. 예외 처리", "5.2 신청 절차"],
)
LangChain의 MarkdownHeaderTextSplitter는 이 헤더 경로를 자동으로 metadata로 뽑아 준다. metadata를 임베딩 입력 문자열에 합치는 한 줄이 핵심.
6.3 Document Summary Prefix
summary = llm.invoke(
f"다음 문서를 5문장 이내로 요약하라:\n\n{full_document}"
).content
embedded_texts = [
f"{summary}\n\n---\n\n{chunk.page_content}"
for chunk in chunks
]
문서당 LLM 1회로 비용이 작고, 모든 청크가 같은 요약을 공유한다. 문서 단위 컨텍스트를 임베딩 신호에 끼워 넣는다.
6.4 Anthropic Contextual Retrieval
CONTEXT_PROMPT = """<document>{doc}</document>
다음 청크는 위 문서의 일부다:
<chunk>{chunk}</chunk>
검색 시 이 청크를 위치 짓는 짧은 컨텍스트(50~100토큰)를 생성하라.
응답은 컨텍스트만, 다른 설명 없이."""
def contextualize(doc: str, chunk: str) -> str:
return llm.invoke(CONTEXT_PROMPT.format(doc=doc, chunk=chunk)).content
contextual_chunks = [
f"{contextualize(full_doc, c.page_content)}\n\n{c.page_content}"
for c in chunks
]
청크 마다 LLM이 그 청크 위치에 맞는 컨텍스트를 짓는다. 비용은 prompt caching으로 절감 — 같은 <document>가 캐시되어 재청구는 0.1배. Anthropic 권장 설정에서는 1M 청크 기준 약 $1.02 (Claude 3 Haiku 가격 기준 보고).
7. 변형과 사례
7.1 Parent-Child의 변형 — Small-to-Big
- 무엇이 바뀌나: child가 문장 단위, parent가 문단 또는 페이지 단위. 작게 검색하고 크게 답한다.
- 왜 쓰나: 8.2절(청크 너무 작음)의 Topic Disperse 위험을 답 단계에서 보완.
- 무엇이 가능해졌나: 검색 정밀도와 답 풍부함의 분리 — 인덱스는 가볍게, 답은 풍부하게.
- 어디에 적합한가: 긴 정책 문서·법령·기술 매뉴얼. 짧은 청크가 조각나지만 답 단계에서 다시 모아야 하는 경우.
- 한계: docstore가 별도 필요(메모리/디스크). 같은 parent가 여러 child를 통해 중복 매칭될 수 있어 중복 제거 로직 필요.
7.2 Header Injection의 변형 — Breadcrumb Path
- 무엇이 바뀌나:
문서 > 1장 > 1.2절 > 1.2.3 항목같은 전체 계층 경로를 청크 앞에 붙인다. - 왜 쓰나: 질문이 섹션 이름과 매칭될 가능성을 보존. "1.2.3 항목"이라는 토큰이 임베딩에 얇은 신호로 들어감.
- 무엇이 가능해졌나: LLM 호출 0, 비용 0인데 검색 품질이 몇 %p 올라가는 가장 가성비 좋은 보강.
- 어디에 적합한가: 마크다운·기술 문서·매뉴얼. 거의 모든 경우 기본 적용을 권장.
- 한계: 헤더가 없는 문서엔 적용 불가. 경로가 너무 길면 임베딩 입력의 상당 비중을 차지해 본문 신호가 희석.
7.3 Summary Prefix의 변형 — Section Summary
- 무엇이 바뀌나: 문서 전체 요약 대신 섹션 단위 요약을 그 섹션 청크들에만 붙인다.
- 왜 쓰나: 문서가 너무 크고 다주제일 때, 단일 요약은 희석되어 신호가 약하다.
- 무엇이 가능해졌나: 더 국소적인 컨텍스트 보강.
- 어디에 적합한가: 책·연차 보고서·종합 매뉴얼.
- 한계: 요약 LLM 호출이 섹션 수만큼 증가. 섹션 경계 분류가 정확해야 함(Heading 청킹 동반 권장).
7.4 Contextual Retrieval의 핵심 수치 (Anthropic 2024-09)
| 지표 | 원본 | + Contextual Embeddings | + Contextual BM25 결합 | + Reranker |
|---|---|---|---|---|
| Top-20 검색 실패율 | 5.7% | 3.7% | 2.9% | 1.9% |
| 상대 감소 | — | -35% | -49% | -67% |
비용 (1M 청크 기준, Claude 3 Haiku 가격, prompt caching 적용):
- 입력 캐시 write: \( \approx 0.30 \text{USD} \)
- 입력 캐시 hit (이후 호출): \( \approx 0.03 \text{USD/100K tokens} \)
- 출력(생성 컨텍스트): \( \approx 0.72 \text{USD} \)
- 합계: 약 1.02 USD / 1M 청크
캐시 TTL이 5분인 표준 캐시면 비용 모델이 무너지므로, 1시간 캐시(2배 단가)가 필수다(메모리에 기록된 [[feedback_anthropic_cache_pricing]] 참조).
7.5 Contextual Retrieval의 변형 — Self-RAG-style 검증과 결합
- 무엇이 바뀌나: 컨텍스트 생성 시 neighbor 청크도 함께 모델에 넣어 문맥 연속성을 강화.
- 왜 쓰나: 같은 절 내 인접 청크가 공유 전제를 가질 때.
- 무엇이 가능해졌나: 길이가 긴 절에서 청크 경계의 맥락 단절을 부드럽게 처리.
- 어디에 적합한가: 논문·기술 백서.
- 한계: 입력 토큰이 늘어 LLM 호출 비용이 체감 가능하게 상승.
8. 한계와 실패 양상
8.1 Parent-Child의 답 컨텍스트 폭주
- 왜 본질적인가: top-K가 5개여도 각각의 parent가 2~3K 토큰이면 답 컨텍스트가 10~15K 토큰으로 부푼다. Lost in the Middle(Liu 2023)에 노출.
- 진단: 답 컨텍스트 길이를 측정. 상위 95p가 \(>\) 모델 권장(보통 8~16K)이면 위험.
- 완화: parent 크기를 문단~페이지 단위로 제한. Reranker로 child 단계에서 압축.
- 다음 편: 13편 Reranker.
8.2 Header Injection의 경로 노이즈
- 왜 본질적인가: 헤더가 반복되는 단어(예: "정책", "장")로만 차 있으면 모든 청크가 비슷한 prefix를 가져 임베딩이 흐려진다.
- 진단: 같은 문서 청크들이 지나치게 유사한 임베딩(cosine \(>\) 0.95)을 가짐.
- 완화: 경로에서 반복 토큰 제거, 또는 제목 + 가장 가까운 헤더 1단계만 사용.
- 다음 편: 8편(임베딩 모델).
8.3 Summary Prefix의 요약 표류
- 왜 본질적인가: 요약은 문서의 주제를 반영하지만, 문서 안의 특수 항목은 누락된다. 모든 청크 앞에 같은 요약이 붙어 특수 항목과 매칭이 약해질 수 있다.
- 진단: 문서 전체적인 질문엔 강한데 세부 항목 질문에는 기준 점수와 차이가 작아지는 패턴.
- 완화: 섹션 단위 요약으로 전환(7.3절). 또는 요약 길이를 짧게(2문장 이하) 유지.
- 다음 편: 7편(메타데이터로 세부 항목 신호 보강).
8.4 Contextual Retrieval의 환각된 컨텍스트
- 왜 본질적인가: LLM이 청크 위치를 추측하면서 문서에 없는 사실을 컨텍스트로 만들 수 있다. 검색이 환각된 신호를 학습.
- 진단: 생성된 컨텍스트를 샘플링해 원문 단어와의 겹침을 측정. 겹침이 낮으면 환각 위험.
- 완화: 프롬프트에 "문서에 명시된 사실만" 강조, 컨텍스트 길이 상한(예: 100토큰), low temperature, 후처리에서 원문에 없는 고유명사 필터.
- 다음 편: 외전 C(Hallucination 방지).
8.5 캐시 미스로 인한 비용 폭주
- 왜 본질적인가: prompt caching이 작동하지 않으면 매 청크마다 전체 문서 입력을 정가로 청구. 1M 청크 보강 비용이 10배 이상 증가할 수 있다.
- 진단: API 청구서의 cache write/hit/miss 비율 확인. hit 비율이 90% 이하면 경고.
- 완화: 같은 문서의 청크들을 연속 호출해 캐시 hit 유지. 1시간 캐시 모드(2배 단가)를 선택해 TTL 확보.
- 다음 편: 23편(RAG 운영 비용).
8.5 Common Pitfalls
- "청크가 작으면 무조건 Parent-Child." — Header Injection만으로 충분할 때가 많다. 비용 0의 시도가 먼저.
- "Contextual Retrieval이 항상 최고." — 작은 코퍼스(<10K 청크)엔 비용 회수가 어렵다. Header + Sliding으로 충분.
- "문서 요약 한 번이면 모든 청크에 좋다." — 8.3절. 다주제 문서에선 섹션 요약으로.
- "보강 토큰은 짧을수록 좋다." — 너무 짧으면 신호 부족, 너무 길면 본문 희석. 50~150 토큰이 실무 sweet spot.
- "임베딩 입력에만 보강하고 답 컨텍스트엔 원본." — 답에 들어가는 컨텍스트에도 보강을 함께 넘기면 LLM의 생성 품질도 같이 오른다.
9. 정리된 결론
Q1. 네 보강 기법을 비용 0 → 비용 큼 순으로 나열하라.
Chunk Header Injection \(<\) Parent-Child Retrieval \(<\) Document Summary Prefix \(<\) Contextual Retrieval. 앞의 두 기법은 LLM 호출이 없고, Summary는 문서당 1회, Contextual은 청크당 1회. Chapter: 4절.
Q2. Parent-Child의 Lost in the Middle 위험을 식 하나로 추정하라.
답 컨텍스트 ≈ top-K × parent 평균 토큰. 예: K=5, parent=2K → 10K 토큰. 모델 권장 8K를 넘으면 위험. Chapter: 8.1절.
Q3. Anthropic Contextual Retrieval가 보고한 검색 실패율 감소는?
Contextual Embeddings 단독 -35%, BM25 결합 -49%, Reranker 추가 -67% (Top-20, 2024-09 기준). Chapter: 7.4절.
Q4. Contextual Retrieval에서 prompt caching이 결정적인 이유는?
같은 문서가 청크 수만큼 반복 입력되기 때문. 5분 캐시로는 부족하고, 1시간 캐시(2배 단가)가 비용 모델의 전제. Chapter: 7.4절, 8.5절.
Q5. 어느 보강이 환각을 만들 수 있는가, 왜인가?
Contextual Retrieval. LLM이 청크 위치를 추측하면서 문서에 없는 사실을 생성할 수 있다. low temperature, 길이 상한, 후처리 필터로 완화. Chapter: 8.4절.
10. 추가 학습
1차 자료
- Anthropic. Introducing Contextual Retrieval (2024-09 blog). 핵심 수치 7.4절 인용 출처.
- LangChain. ParentDocumentRetriever 문서 및 소스.
- Sarthi, P. et al. RAPTOR: Recursive Abstractive Processing for Tree-Organized Retrieval. ICLR 2024. arXiv:2401.18059. (요약 prefix의 재귀적 일반화)
- Liu, N. F. et al. Lost in the Middle: How Language Models Use Long Contexts. TACL 2024. arXiv:2307.03172. (답 컨텍스트 폭주의 위험 근거)
공식 docs
- LangChain ParentDocumentRetriever:
https://python.langchain.com/docs/how_to/parent_document_retriever/ - LangChain MarkdownHeaderTextSplitter:
https://python.langchain.com/docs/how_to/markdown_header_metadata_splitter/ - Anthropic Prompt Caching:
https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching - LlamaIndex AutoMergingRetriever (Parent-Child 변형):
https://docs.llamaindex.ai/en/stable/examples/retrievers/auto_merging_retriever/
보조 자료
- 사용자 노트 5장 — 청크 보강.
- 사용자 노트 35장 1절 — Parent Document Retrieval과 Whole-document의 연결.
Cheat Sheet
| 기법 | LLM 호출 | 임베딩 추가 토큰 | 검색 실패율 감소 (대략) | 권장 사용처 |
|---|---|---|---|---|
| Header Injection | 0 | 20~80 | 5~10%p | 기본값. 마크다운/구조 문서 |
| Parent-Child | 0 | 0 (구조 변경) | 10~20%p | 긴 정책/법령/매뉴얼 |
| Summary Prefix | 문서당 1 | 100~200 | 10~15%p | 단일 주제 중장문 |
| Contextual Retrieval | 청크당 1 (캐시) | 50~100 | 35~49%p (BM25 결합) | 대규모 인덱스, 정확도 우선 |
결정 기준 한 줄: 비용 0의 Header Injection을 먼저 깔고, 효과가 부족하면 Parent-Child → Summary → Contextual 순으로 추가한다. 네 기법은 대체가 아니라 겹쳐도 된다.
Bridge — 다음 편
다음 — RAG 핵심 학습 (7/26) 메타데이터 설계: 검색 필터·권한·출처.
청크 단위에 무엇을 같이 저장해야 검색이 사용자·시간·문서 종류로 필터링되는가. document_id / chunk_id / version / page / section / security_level / namespace의 7개 핵심 필드와, 권한 모델·출처 추적을 한 자리에서 푼다.
시리즈 전체 안내: 시리즈 목차
댓글
댓글 쓰기