"경비 정산은 언제까지 올려야 하나요?" 사내 규정 검색에 물었는데 1위로 나온 조각이 이거였어요. 「5. 정산 — 출장이 끝나고 7일 안에 출장 보고서와 함께 경비를 정산한다.」
틀린 문장은 아니에요. 다만 출장 규정의 조항이죠. 정답 「다음 달 5일까지」는 2위로 밀렸어요. 이 실패는 청크를 섹션 단위로 깔끔하게 자른 설정에서 나왔어요.
청크 크기를 고민하기 전에, 경계와 이름표를 먼저 봐야 했어요
골든셋 20문항 글에서 150자 청크가 정답 구절을 둘로 가르는 걸 봤어요. 그때 남은 숙제 "그럼 어떻게 잘라야 하나"를 판을 키워 재봤어요. 가상의 사내 규정 10건(섹션 5개씩)에 질문 40개, 검색기는 순수 파이썬 BM25로 고정하고 청킹만 바꿨어요. 정답 구절이 통째로 든 조각을 상위 k 안에 가져오면 성공이에요. 고객사 데이터가 아닌 재현용 예시이고, 숫자는 모두 직접 잰 값이에요.
다섯 가지로 잘라 같은 질문 40개를 던졌어요
| 청킹 전략 | 조각 수 | 색인 글자 수 | 정답이 잘린 문항 | Recall@1 | Recall@3 | LLM에 넘길 글자 수(상위 3개) |
|---|---|---|---|---|---|---|
| A 고정 150자 | 27 | 3,336 | 2 | 0.88 | 0.90 | 418 |
| B 고정 150자 + 겹침 40자 | 32 | 4,216 | 0 | 0.93 | 0.97 | 404 |
| C 문장 경계, 150자까지 채움 | 27 | 3,319 | 0 | 0.88 | 0.97 | 393 |
| D 섹션 단위 | 50 | 3,195 | 0 | 0.88 | 0.97 | 195 |
| E 섹션 + 문서 제목 머리말 | 50 | 3,800 | 0 | 0.93 | 0.97 | 235 |
겹침은 효과가 있었어요. 대신 색인이 26% 불었어요
고정 150자(A)는 「오후 11시」와 「전날 오후 6시」를 조각 경계에서 반으로 잘랐어요. 어느 조각에도 온전히 없으니 검색기가 가져올 방법이 없었죠. 겹침 40자(B)로 잘린 문항은 0이 됐지만, 같은 문서를 26% 더 색인해야 했어요. 문장 경계에서 끊기만 한 C는 색인 크기 그대로 같은 Recall@3을 냈고요. 경계 문제는 겹침으로 덮는 게 아니라, 경계를 옮겨서 없애는 편이 쌌어요.
섹션으로 자르면 LLM에 넘기는 양이 절반이 돼요
Recall@3은 B부터 E까지 전부 0.97이에요. 차이는 오른쪽 끝 열이에요. 섹션 단위(D · E)는 상위 3개를 합쳐도 200자 안팎으로, 고정 길이의 절반쯤이에요. 이 숫자가 생성 단계의 비용이고 잡음이에요. 토큰이 쌓이는 구조는 에이전트 비용 글에서 다뤘어요.
그런데 섹션만 자르면 조각이 자기 이름을 잃어요
맨 앞의 장면이 D에서 나왔어요. 섹션으로 자르면 「5. 정산」 조각엔 어느 규정인지가 없어요. 문서 제목은 맨 위에 한 번만 있었으니까요. 검색기에겐 '경비'와 '정산'이 둘 다 든 출장 조항이 더 그럴듯했던 거예요.
그래서 각 섹션 앞에 문서 제목을 한 줄 붙였어요(E). 코드로는 이 정도예요.
def section_chunks(title, sections, header=True):
# 섹션 하나 = 조각 하나. 앞에 "[문서 제목]"을 붙인다
prefix = f"[{title}] " if header else ""
return [f"{prefix}{head} {body}" for head, body in sections]
Recall@1이 0.88에서 0.93으로 올랐고, '경비 정산' 질문은 다시 경비 규정을 1위로 가져왔어요. 색인은 D보다 19% 늘었지만 LLM에 넘기는 양은 고정 길이의 60%를 밑돌았어요. Anthropic이 2024년 9월 공개한 Contextual Retrieval도 같은 방향이에요. 조각마다 문서 맥락을 50~100토큰 붙였더니, 임베딩과 BM25에 함께 적용했을 때 상위 20개 검색 실패율이 49% 줄었다고 해요.
바로 믿으면 안 되는 세 가지
하나, 코퍼스가 작아요. 0.88과 0.93의 차이는 두 문항이에요. 방향을 보는 실험이지 우열을 확정하는 실험이 아니에요.
둘, 크게 자르면 경계 문제가 가려져요. 300자 고정 길이도 Recall@3 0.975가 나와요. 대신 넘기는 양이 785자로 섹션 단위의 네 배예요. 문제를 푼 게 아니라 덮은 거죠.
셋, 청킹으로 못 고치는 실패가 남았어요. "노트북 잃어버리면 언제까지 신고해요"는 다섯 전략 모두 놓쳤어요. 규정엔 '분실'이라고만 적혀 있었거든요. 어휘 문제라 벡터 검색을 함께 두거나 토크나이저와 사전을 손봐야 해요.
그래서 저희는 이 순서로 봐요
청크 크기는 마지막에 정할 숫자였어요. 문서의 구조(섹션 · 조항 · 표)를 따라 자르고, 조각마다 이름표를 붙이고, 그 다음에 골든셋으로 크기와 겹침을 조정해요. 정답은 코퍼스마다 달라요. 그래서 "몇 자로 자르나요"보다 "청킹을 바꾸면 어느 문항이 깨지는지 바로 보이나요"가 먼저예요. 저희가 RAG를 구축할 때도 이 측정부터 세웁니다.
자주 묻는 질문
Q. RAG 청크 크기는 몇 자(토큰)가 적당한가요? A. 정해진 값은 없어요. 섹션 단위로 먼저 자르고, 너무 긴 섹션만 문장 경계에서 다시 나눈 뒤, 골든셋으로 Recall과 넘기는 글자 수를 함께 보는 걸 권해요.
Q. 겹침(overlap)은 꼭 둬야 하나요? A. 고정 길이라면 경계에서 정답이 잘리는 걸 막아 줘요. 다만 이번엔 색인이 26% 늘었고, 문장 경계 분할이 같은 효과를 추가 비용 없이 냈어요.
Q. 제목 머리말은 벡터 검색에도 효과가 있나요? A. 이번엔 BM25만 쟀어요. Anthropic은 임베딩에 맥락을 붙였을 때도 검색 실패율이 35% 줄었다고 보고했지만, 저희 코퍼스로 직접 재진 않았어요.
Sources
- Introducing Contextual Retrieval — Anthropic (2024-09-19): https://www.anthropic.com/news/contextual-retrieval
- 실험 스크립트(재현용 예시 코퍼스 · 골든셋 40문항 · BM25): SHIFTONE 내부 검증 자료, 이 글을 쓰며 작성

