20231012

RAG 문서 청킹 전략 — 한글 편

청킹(chunking) — RAG에서 제일 과소평가되는 단계. 대부분 RecursiveCharacterTextSplitter에 chunk_size=1000 박고 끝인데, 한글 문서에선 이게 엄청난 차이를 만든다.

한글 특유의 문제

영어는 공백 단위 토큰 비율이 안정적인데 한글은 한 글자당 bpe 토큰이 2~3개. 그래서 character 기준 1000자는 실제로 ada-002 토큰 700~900. 이건 그나마 다행. 문제는 문장 경계다. "...다." "...요." "...음." 같은 종결어미가 혼재하면 naive sentence splitter가 다 틀린다.

시도한 전략들

  1. Fixed size 500자 + overlap 100 — 기준선. 문장 끊김 많음.
  2. Recursive with ["\n\n","\n","다. ","요. "] — 문단 우선, 그다음 문장. 한결 자연스러워짐.
  3. kss 라이브러리 기반 sentence split — 한국어 특화. 정확한데 느림(문서당 수 초).
  4. Semantic chunking (LangChain 신기능) — embedding 거리가 threshold 이상 벌어지면 분리. 품질 좋은데 색인 비용 2배.

평가

200개 질문-정답 셋 만들어서 recall@5 측정:

  • Fixed 500: 0.71
  • Recursive with hangul terminators: 0.79
  • kss + hierarchical (문단 → 문장): 0.84
  • Semantic: 0.86

운영 비용까지 감안하면 kss + hierarchical이 우리 현실적 선택. semantic은 문서 업데이트 빈번한 환경엔 재색인 비용이 부담.

또 배운 것

표(table)나 코드블록은 무조건 하나의 chunk로 보존해야 한다. 가운데 잘리면 retrieval이 완전히 망가진다. 그래서 실제론 원문을 markdown으로 변환한 뒤 "table block은 indivisible" 규칙을 사전 처리로 걸었음.

그리고 chunk마다 문서 메타데이터(제목, 섹션 경로)를 앞에 붙이는 것. 이게 작은 트릭 같지만 retrieval quality에 꽤 영향. "이 조각이 어디서 왔는지"를 embedding 공간에서도 문맥화함.