검색 증강 생성 (Retrieval-Augmented Generation, RAG)에서 청킹 (Chunking, 문서를 검색 단위 조각으로 나누는 작업)은 가장 눈에 띄지 않으면서도 검색 품질에 큰 영향을 주는 단계입니다. 물론 파싱 품질, 원문 데이터, 임베딩, 질의 변환, 하이브리드 검색, 재순위화도 검색 품질을 좌우합니다. 다만 경계에서 손실된 문맥은 후단 검색기만으로 보완하기 어렵다는 점에서, 애초에 어떤 경계로 문서를 잘랐는지는 임베딩 (Embedding) 모델 선택과 함께 설계하되 검색기 (Retriever) 튜닝보다는 먼저 검증해야 하는 결정입니다.
영어 중심 자료의 청킹 예제는 대부분 마침표와 공백으로 문장을 나누고 고정 토큰 수로 자르는 방식을 전제합니다. 한국어는 교착어 (Agglutinative Language)이고 문장 경계와 띄어쓰기 규칙이 영어와 다르기 때문에, 이 전제를 그대로 옮기면 문장이 어중간하게 잘리거나 토큰 한도를 넘겨 뒷부분이 조용히 사라지는 문제가 생깁니다.
이 글은 회의록 수집기 (Ingester)를 설계·운영하며 정리한 한국어 특유의 청킹 결정에 집중합니다. 회의 요약과 녹취록을 청크로 나누고 다국어 임베딩을 생성해 벡터 저장소 (Vector Store)에 저장하는 흐름 중에서, "어떤 경계로 어느 크기로 자를 것인가"라는 질문만 다룹니다.
다음 두 주제는 이 글에서 다시 설명하지 않습니다.
- 멱등 인덱싱 (Idempotent Indexing)과 중복 Chunk 방지, 결정적 Chunk ID — 별도 글에서 상세히 다룹니다: https://aiarchitect.tistory.com/9
- 회의 음성에서 RAG까지의 전체 파이프라인 — 배경 맥락은 이 글을 참고합니다: https://aiarchitect.tistory.com/4
이 글의 초점은 그 파이프라인 안에서 한국어 텍스트를 어떻게 자르는가입니다.
1. 왜 한국어 청킹을 따로 설계하는가
청킹은 언어 중립적으로 보이지만 실제로는 언어의 형태론 (Morphology)과 문장 부호 관습에 강하게 의존합니다. 영어 예제를 그대로 쓸 때 한국어에서 나타나는 대표적인 실패는 다음과 같습니다.
| 증상 | 원인 | 검색에 미치는 영향 |
|---|---|---|
| 문장이 중간에서 잘림 | 마침표·공백 기준 분리가 한국어 종결 패턴을 놓침 | 문맥이 반토막 난 Chunk가 검색됨 |
| Chunk 뒷부분이 사라짐 | 토큰 한도 초과분이 조용히 잘림 (Truncation) | 결론·조치사항이 벡터에 반영되지 않음 |
| 지시어만 남은 Chunk | "그건 다음 주까지" 처럼 앞 전제가 빠짐 | 무엇에 대한 결정인지 알 수 없음 |
| 같은 내용이 여러 크기로 | 오버랩·여러 청킹 버전 혼재·재색인 중복 | 검색 결과에 유사 조각이 반복됨 |
이 문제들은 모델 성능이 아니라 주로 경계 결정과 토큰 예산 관리의 실패에서 비롯됩니다. 문장 중간 절단과 전제 손실은 경계 문제이고, 토큰 초과 잘림은 모델 한도 확인·호출 옵션·재분할 실패라는 예산 관리 문제입니다. 어느 쪽이든 더 좋은 임베딩 모델로 바꿔도 잘못 잘린 Chunk의 문맥 손실은 후단 검색기만으로 되돌리기 어렵습니다. 그래서 한국어 청킹은 파이프라인 초반에 명시적으로 설계해야 합니다.
2. 교착어 특성: 조사·어미가 토큰 경계를 흔든다
한국어는 체언 (명사구) 뒤에 조사 (Postposition)가, 용언 (동사·형용사)의 어간 뒤에 어미 (Ending)가 결합해 하나의 어절 (Eojeol, 띄어쓰기 단위)을 이루는 교착어입니다. 조사와 어미는 결합 대상이 다르므로 구분해서 봐야 합니다. 같은 명사라도 붙는 조사에 따라, 같은 용언이라도 활용되는 어미에 따라 표면 형태가 달라집니다.
체언 + 조사: 회의 → 회의가 / 회의를 / 회의에서 / 회의는 / 회의도
용언 활용: 결정하다 → 결정했다 / 결정하기로 (어간 '결정하-' + 어미)
결정되다 → 결정된 / 결정됨 (어간 '결정되-' + 어미)
이 특성이 청킹에 주는 함의는 두 가지입니다.
- 공백·문자 기준 경계값을 언어 간에 그대로 이식할 수 없다. 현대 임베딩 모델은 대부분 서브워드 토크나이저를 쓰므로 "영어 공백 경계가 안정적"이라는 표현 자체가 단순화된 것입니다. 핵심은 영어 문서에서 잡은 문자·단어 수 경험값을, 조사·어미가 붙어 표면형이 늘어나는 한국어에 그대로 옮기면 의미를 자주 끊는다는 점입니다.
- 문자 대비 토큰 수가 언어마다 다르다. 뒤에서 다룰 토큰 한도 문제의 근본 원인입니다. 어절에 붙는 형태소 (Morpheme)가 서브워드 (Subword)로 잘게 분해되면서 같은 정보량에도 토큰이 더 많이 소비됩니다.
청킹 단계에서는 형태소 분석기를 반드시 도입할 필요는 없습니다. 다만 경계 결정의 최소 단위를 "문자·바이트"가 아니라 "문장·어절"로 잡아야 조사·어미가 잘려 나가는 상황을 피할 수 있습니다.
3. 종결어미 (Sentence-Final Ending)와 문장 경계 모호성
한국어 문장 분리기 (Sentence Splitter)를 설계할 때 핵심은 종결어미 (Sentence-Final Ending) 패턴입니다. 마침표만으로 문장을 나누면 다음과 같은 경우를 놓칩니다.
| 상황 | 예시 | 문제 |
|---|---|---|
| 마침표 없는 종결 | "다음 주까지 처리하기로 했습니다" (녹취록에 부호 없음) | 문장이 끝났는데 경계를 못 찾음 |
| 종결어미 뒤 부호 생략 | "그렇게 진행할게요 이제 다음 안건" | 두 문장이 한 Chunk로 붙음 |
| 마침표가 문장 끝이 아님 | "3.5 버전", "오전 10.30", "예: A. 검토" | 문장이 아닌데 잘림 |
| 인용·괄호 내부 종결 | "그는 '끝났다'고 말했다" | 인용 내부에서 잘못 분리 |
특히 회의 녹취록은 자동 음성 인식 (Speech-to-Text, STT) 결과라서 문장 부호가 불완전하거나 아예 없는 경우가 많습니다. 따라서 부호에만 의존하지 말고 종결어미 패턴을 함께 봐야 합니다.
대표적인 종결어미 계열은 다음과 같습니다.
합쇼체: -습니다 / -ㅂ니다 / -습니까 / -ㅂ니까
해요체: -어요 / -아요 / -에요 / -지요 / -네요 / -군요
해라체: -다 / -는다 / -았다 / -었다 / -자 / -라
명사형종결: -기로 함 / -할 것 / -임 / -함 (회의록 요약에서 흔함)
경계 판정은 "종결어미 후보 + (선택적) 문장부호 + 다음 어절의 화자 라벨/대문자 전환" 같은 신호를 결합해 규칙화하는 편이 안정적입니다. 아래는 개념 코드 (실행용 아님) 입니다. 몇 가지 대표 경계를 설명하려는 예시일 뿐, 프로덕션 문장 분리기가 아닙니다. 실제 값과 종결어미 목록은 회귀 세트로 조정합니다.
# 개념 코드 (실행용 아님): 종결어미·부호 결합 문장 분리 (한국어)
# 목적: "부호만으로는 못 잡는 경계"와 "단어 내부 오인 회피"를 규칙으로 설명한다.
# 한계(아래 본문 참고): 화자 라벨/인용·괄호 보호, 반말 '-다' 일반 활용,
# 인라인 다중 라벨('예: A. ... B. ...') 등은 이 규칙만으로는 완전히 처리되지 않는다.
import re
# 종결어미의 실제 표층형(용언 활용의 표면 형태).
# 주의: NFC 정규화된 문자열에서 '합니다'의 받침은 음절에 합쳐져 있으므로
# 낱자 'ㅂ'이 아니라 실제 표층형(활용형)을 나열한다.
_VERB_FINAL = (
r"(?:습니다|습니까" # 합쇼체
r"|니다|니까" # 갑니다/됩니까처럼 받침이 앞 음절에 실린 형태
r"|어요|아요|여요|에요|예요|해요|지요|네요|군요|게요|까요|세요" # 해요체
r"|는다|았다|었다|였다|겠다|한다|된다|이다" # 해라체(서술)
r"|자|라|어라|아라" # 해라체(청유·명령)
r")"
)
# 명사형 종결(요약체)은 어절 경계(공백·문두) 뒤에서만 인정한다.
# 그래야 '책임'·'포함' 같은 단어 내부의 '임/함'을 종결로 오인하지 않는다.
_NOMINAL_FINAL = r"(?:기로\s?함|하기로\s?함|할\s?것|임|함)"
# 부호 기반 1차 경계: 종결부호 뒤 공백. 소수점(3.5)은 숫자 사이라 여기 걸리지 않는다.
_PUNCT_BOUNDARY = re.compile(r"(?<=[.!?。])\s+")
# 줄머리 목록 라벨('A.' '1)' '가.' '-' 등)만 남은 조각은 다음 조각과 재결합한다.
_LABEL_ONLY = re.compile(r"^(?:[A-Za-z가-힣]\.|[0-9]+[.)]|[-*•])$")
# 종결어미(+선택적 종결부호) 뒤에서 분리.
# 가변 길이 lookbehind는 Python `re`가 지원하지 않으므로(고정 길이만 허용),
# 경계 토큰을 캡처해 split한 뒤 앞 조각에 다시 붙여 재조립한다.
_ENDING_BOUNDARY = re.compile(
r"("
rf"(?:{_VERB_FINAL}|(?:(?<=\s)|(?<=^)){_NOMINAL_FINAL})"
r"[.!?。]?"
r")\s+(?=[가-힣A-Za-z0-9(\[\"'])"
)
def _split_by_endings(seg: str, out: list) -> None:
buf = ""
for i, piece in enumerate(_ENDING_BOUNDARY.split(seg)):
buf += piece
if i % 2 == 1: # 캡처된 종결부 뒤가 경계
if buf.strip():
out.append(buf.strip())
buf = ""
if buf.strip():
out.append(buf.strip())
def split_sentences_ko(text: str) -> list:
out: list = []
# 1) 부호 기준 1차 분리. 라벨만 남은 조각은 뒤 조각과 되붙여 'A. ...'을 보존한다.
raw = [s.strip() for s in _PUNCT_BOUNDARY.split(text) if s.strip()]
segs, i = [], 0
while i < len(raw):
if _LABEL_ONLY.match(raw[i]) and i + 1 < len(raw):
segs.append(f"{raw[i]} {raw[i + 1]}")
i += 2
else:
segs.append(raw[i])
i += 1
# 2) 각 조각에 부호 없는 종결어미 경계 분리를 적용한다.
for seg in segs:
_split_by_endings(seg, out)
return out
# 예시 입력/출력 (개념 확인용)
# >>> split_sentences_ko("그렇게 진행할게요 이제 다음 안건")
# ['그렇게 진행할게요', '이제 다음 안건'] # 부호 없는 해요체 경계
# >>> split_sentences_ko("갑니다 다음 안건입니다")
# ['갑니다', '다음 안건입니다'] # 받침형 '-ㅂ니다' 계열 분리
# >>> split_sentences_ko("책임 소재를 확인합니다")
# ['책임 소재를 확인합니다'] # '책임'의 '임'을 종결로 오인하지 않음
# >>> split_sentences_ko("3.5 버전은 검토했습니다 결과는 좋습니다")
# ['3.5 버전은 검토했습니다', '결과는 좋습니다'] # 소수점은 경계 아님, 뒤 종결어미는 분리
# >>> split_sentences_ko("A. 검토 결과입니다.")
# ['A. 검토 결과입니다.'] # 목록 라벨 마침표에서 자르지 않음
이 규칙은 완벽한 문장 분리기가 아니라 경계 후보를 늘려 주는 보조 장치입니다. 위 예시는 대표 경계를 확인시켜 주지만, 다음은 이 규칙만으로 처리되지 않는 알려진 한계입니다.
- 화자 라벨 전환·인용·괄호 보호는 정규식에 구현하지 않았습니다(설명한 신호 중 일부만 코드화).
- 반말 '-다'의 일반 활용(예: "간다/먹는다"의 다양한 형태)과 구어체 축약형은 목록에 다 담기 어렵습니다.
- 인라인 다중 라벨("예: A. 검토 B. 승인")은 라벨마다 과분할될 수 있습니다.
따라서 실무에서는 검증된 한국어 문장 분리기(예: Kiwi, kss 등)를 기본 구현으로 두고, 위와 같은 도메인 규칙은 그 결과에 얹는 후처리로 쓰고 회귀 테스트로 검증하는 편이 안정적입니다. 과도하게 잘린 오분리는 뒤에서 다룰 문단 결합 단계에서 일부 병합해 보정할 수 있지만(잘못 나뉜 원래 문장 구조를 패킹이 항상 복원하지는 못합니다), 두 문장이 한 조각으로 붙은 미분리는 병합으로 고쳐지지 않으므로 위와 같은 추가 경계 탐색이나 재분할 단계가 필요합니다.
4. 띄어쓰기 변이와 표기 정규화
한국어는 띄어쓰기 규칙이 복잡하고 실사용에서 변이가 큽니다. STT 결과와 사용자 입력이 섞이면 같은 내용이 다양한 표기로 들어옵니다.
| 변이 유형 | 예시 |
|---|---|
| 띄어쓰기 차이 | "회의 록" / "회의록", "할 수 있다" / "할수있다" |
| 자모 분리·합성 | 정규 분해된 자모의 완성형 조합 (NFC 정규화 대상) |
| 전각·반각 | 전각 공백·괄호·숫자 혼입 |
| 반복 공백·개행 | STT·복사 붙여넣기에서 다중 공백 |
청킹 전에 최소한의 표기 정규화 (Normalization)를 두면 경계 판정이 안정됩니다. 다만 적용 순서가 중요합니다. 아래 normalize_prose_ko()는 연속 공백·탭을 한 칸으로 축소하므로, 문서 전체에 먼저 돌리면 코드 들여쓰기·탭 기반 표·preformatted 텍스트가 손상됩니다(9절의 "코드 펜스 원형 보존"과 정면으로 충돌). 그래서 구조 블록(표·목록·코드)을 먼저 추출·보호하고, 그 바깥의 산문(prose)에만 이 정규화를 적용해야 합니다. 함수 이름에 prose를 넣어 적용 범위를 이름에 못 박습니다. 아래는 최소 예시로, 여기서는 NFC 조합과 전각 공백·다중 공백·과다 개행만 다룹니다.
import unicodedata, re
# 주의: 산문(prose) 전용. 구조 블록(표·목록·코드)을 먼저 보호 영역으로 분리한 뒤,
# 그 바깥 텍스트에만 호출한다. 문서 전체에 돌리면 들여쓰기·탭 표가 깨진다.
def normalize_prose_ko(text: str) -> str:
text = unicodedata.normalize("NFC", text) # 정규 분해된 자모를 완성형으로 조합
text = text.replace(" ", " ") # 전각 공백(U+3000) → 반각
text = re.sub(r"[ \t]+", " ", text) # 다중 공백 축소 (산문에서만 안전)
text = re.sub(r"\n{3,}", "\n\n", text) # 과다 개행 축소
return text.strip()
세 가지를 유의합니다. 첫째, 적용 범위입니다. 위 함수는 공백을 축소하므로 반드시 구조 블록 보호 이후 산문에만 적용해야 하며(파이프라인 순서는 18절 도식 참고), 그래서 이름에 prose를 박아 두었습니다. 둘째, NFC가 모든 표기 문제를 해결하지는 않습니다. NFC는 정규 분해된 자모를 완성형 음절로 조합하지만, 호환 자모나 레거시 인코딩, 이미 깨진 문자열까지 되돌리지는 못합니다. 위 표에서 언급한 전각 괄호·숫자 같은 항목을 정규화하려면 별도 규칙(필요 시 NFKC나 문자 매핑)을 추가해야 하며, 위 코드는 그 최소 골격일 뿐입니다. 셋째, 정규화는 청크 텍스트 생성용으로만 적용하고, 원문 오프셋 (Offset)이나 출처 표시가 필요하면 원문을 별도로 보존해야 합니다. 정규화를 과하게 하면 원문과 검색 결과의 위치가 어긋나 출처 하이라이트가 틀어집니다.
5. 임베딩 모델의 토큰 (Token) 한도가 상한을 정한다
청크 크기의 물리적 상한은 임베딩 모델의 최대 입력 토큰 수가 결정합니다. 한도를 넘는 입력이 어떻게 처리되는지는 라이브러리와 옵션에 따라 다릅니다. 예를 들어 Hugging Face 토크나이저의 기본값은 truncation=False라서 설정에 따라 조용한 잘림(Truncation), 경고, 또는 모델 오류가 모두 일어날 수 있습니다. 반면 multilingual-e5-large 모델 카드는 512 토큰 초과분이 잘린다고 명시합니다. 잘림이 조용히 일어나는 경로에서는 잘린 결론이나 조치사항이 벡터에 아예 반영되지 않는 사고가 생기므로, 임베딩 호출 전에 잘림을 적용하지 않은(truncation=False) 상태로 토큰 수를 먼저 검사하고 초과 입력은 명시적으로 재분할하는 것이 안전합니다.
대표적인 공개 다국어 임베딩 모델의 한도는 다음과 같습니다.
| 모델 계열 | 최대 입력 토큰 | 출력 차원 | 비고 |
|---|---|---|---|
| multilingual-e5 (large) | 512 | 1024 | 512 초과분은 잘림. query:/passage: 접두사 사용 |
| BGE-M3 | 8192 | 1024 (dense) | 장문 문서까지 처리, 100+ 언어 지원. 값은 dense 임베딩 차원 |
multilingual-e5 계열은 최대 512 토큰으로, 모델 카드 예시가 truncation=True, max_length=512를 사용하며 512 토큰 초과분은 잘린다고 명시합니다. 즉 잘림이 켜진 경로에서 512 토큰을 넘는 텍스트를 그대로 넣으면 뒷부분이 손실됩니다. 반면 BGE-M3는 최대 8192 토큰을 처리할 수 있어 문단·문서 단위 청킹에 여유가 큽니다. 다만 8192 토큰을 처리한다는 것이 "큰 청크가 더 좋다"는 뜻은 아닙니다. 계산량, 벡터 의미 희석, 검색 근거의 세밀도 때문에 실제 목표 청크 크기는 한도보다 훨씬 작게 잡는 경우가 많습니다.
핵심 원칙은 다음과 같습니다.
- 청크 크기 상한은 모델 토큰 한도보다 안전 여유를 두고 잡는다. 512 토큰 모델이라면 접두사·특수 토큰·토크나이저 변동을 감안해 목표를 그보다 낮게(예: 여유분을 남긴 값) 설정합니다.
- 잘림을 조용히 넘기지 않는다. 한도 초과 시 경고 로그를 남기거나 재분할하도록 파이프라인을 구성합니다.
6. 한국어의 토큰/문자 비율과 잘림 위험
토큰 한도 문제는 언어와 토크나이저 조합에 따라 예상보다 빨리 발생할 수 있습니다. 서브워드 토크나이저 (Subword Tokenizer, BPE/WordPiece/SentencePiece 계열)는 학습 말뭉치의 언어 분포에 따라 특정 문자를 더 잘게 쪼개기도 합니다. 다만 이 글에서 다루는 multilingual-e5와 BGE-M3는 XLM-R 계열의 다국어 토크나이저를 쓰므로, "영어 중심이라 무조건 한국어가 몇 배"라는 식의 일반화는 정확하지 않습니다.
언어별 토큰 효율은 토크나이저와 문서 특성에 따라 크게 다릅니다. 특정 배수를 주장하려면 동일 의미의 한·영 병렬 문장을 각 모델의 토크나이저(특수 토큰 포함 여부 명시)로 직접 인코딩해 평균·중앙값을 재는 자체 측정표가 있어야 합니다. 그런 측정 없이는 배수를 단정하지 말고, 실무에서는 다음 원칙만 지키면 충분합니다.
- 영어 문서에서 정한 문자·단어 기준 청크 크기를 한국어에 그대로 재사용하지 않는다. 같은 문자·단어 수라도 토큰 수가 크게 달라질 수 있어 한도 판정이 어긋난다.
- 청크 크기는 반드시 "실제 사용할 토크나이저의 토큰 수"로 측정해야 한다. 문자 수·바이트 수·단어 수는 근사치일 뿐 한도 판정에는 부적합합니다.
아래는 문자 수 근사와 토큰 수 측정의 차이를 보여 주는 개념 예시입니다.
# 실행 가능한 최소 예시: 반드시 "그 모델의" 토크나이저로,
# "임베딩에 최종 전달할 문자열"의 토큰 수를 재야 한다.
def build_embedding_input(chunk_text: str, header: str = "") -> str:
# 접두사·헤더·화자 라벨을 모두 붙인 뒤가 실제 입력이다.
prefix = f"{header}\n" if header else ""
return f"passage: {prefix}{chunk_text}"
def within_token_budget(chunk_text: str, tokenizer, max_tokens: int,
header: str = "") -> bool:
final_input = build_embedding_input(chunk_text, header)
# 토큰 수 검사에서는 잘림을 끄고(add_special_tokens=True 로 특수 토큰 포함),
# 문자 수/단어 수가 아니라 실제 토큰 수를 센다.
ids = tokenizer.encode(
final_input, add_special_tokens=True, truncation=False,
)
return len(ids) <= max_tokens # max_tokens 에는 모델의 특수 토큰 몫도 포함됨
# 안티패턴: 문자 수로 대충 자르기 (한국어에서 과소·과대 추정)
def naive_char_limit(text: str, max_chars: int) -> str:
return text[:max_chars] # 토큰 한도와 무관 → 잘림 사고
권장 절차는 다음과 같습니다.
1) 대상 임베딩 모델의 토크나이저를 확정한다
2) 청크 후보를 그 토크나이저로 인코딩해 토큰 수를 잰다
3) 안전 여유를 뺀 목표 토큰 수를 넘으면 재분할한다
4) 재분할 후에도 초과하면(초장문 문장) 경고 후 강제 분할
7. 청킹 전략: 재귀 분할 + 토큰 패킹
한국어 청킹은 두 방향을 결합하면 안정적입니다. 큰 구조(문단·개행)에서 아래로 내려가며 경계를 찾는 재귀 분할과, 그렇게 얻은 문장을 토큰 예산 안에서 위로 다시 묶는 토큰 패킹입니다.
[재귀 분할: 큰 구조 → 작은 구조]
문단·개행 ──초과 시──▶ 문장(종결어미·부호) ──초과 시──▶ 어절
[토큰 패킹: 문장 → 상위 단위]
문장(Sentence) ──병합──▶ 문단·의미 단위(토큰 예산 상한 내)
즉 먼저 큰 경계를 시도하고, 토큰 한도를 넘으면 문장·어절로 내려가 분할한 뒤, 그 문장들을 토큰 예산이 허용하는 만큼 다시 병합해 최종 청크를 만듭니다.
| 전략 | 경계 기준 | 장점 | 한계 |
|---|---|---|---|
| 고정 크기 (Fixed-size) | 토큰 수만 | 구현 단순 | 문장·의미 중간 절단 |
| 문장 경계 (Sentence) | 종결어미·부호 | 문맥 보존 | 문장이 너무 짧을 수 있음 |
| 문단·재귀 (Recursive) | 개행 → 문장 → 어절 | 큰 단위 우선 보존 | 규칙 순서 튜닝 필요 |
| 의미 기반 (Semantic) | 문장 임베딩 유사도 | 주제 단위 정렬 | 계산 비용·불안정성 |
실무 기본값으로는 재귀적 분리 후 문장 단위로 병합하는 방식을 권장합니다. 큰 경계(문단·개행)를 먼저 시도하고, 토큰 한도를 넘으면 문장으로, 그래도 넘으면 어절로 내려가는 방식입니다. 의미 기반 분리는 품질 개선 여지가 있지만 계산 비용과 재현성 문제로 회귀 세트가 갖춰진 뒤 도입하는 편이 안전합니다.
여기서 순서가 중요합니다. 회의 발화라면 짧은 응답 병합·전제 보존(10·11절)을 토큰 패킹보다 먼저 수행해야 합니다. 패킹으로 예산을 채운 뒤에 발화를 병합하면 다시 예산을 넘길 수 있기 때문입니다. 그리고 오버랩·헤더·접두사를 붙인 최종 토큰 검사에서 초과가 나오면 재패킹·강제 분할로 되돌아가는 루프를 둡니다(전체 순서는 18절 도식 참고).
8. 슬라이딩 윈도우 (Sliding Window)와 오버랩 (Overlap)
문장·문단 경계로 잘라도 경계에 걸친 문맥이 손실될 수 있습니다. 예를 들어 "이 방식의 문제는" 다음 문장에 결론이 있는데 Chunk가 그 사이에서 끊기면, 두 Chunk 모두 반쪽 정보만 갖습니다.
이를 완화하려고 인접 Chunk에 일부 문장을 겹치는 오버랩 (Overlap) 을 둡니다.
Chunk A: [문장1 문장2 문장3]
Chunk B: [문장3 문장4 문장5] ← 문장3을 겹침(오버랩)
Chunk C: [문장5 문장6 문장7]
| 오버랩 수준 | 효과 | 부작용 |
|---|---|---|
| 없음 | 저장량 최소 | 경계 문맥 손실 |
| 문장 1~2개 | 경계 문맥 보존 | 저장·검색 중복 소폭 증가 |
| 과다(문단 절반 이상) | 문맥 손실 거의 없음 | 중복 Vector 급증, 노이즈·비용 증가 |
권장 원칙은 "문장 경계 기준으로 소량 오버랩" 입니다. 문자 단위가 아니라 문장 단위로 겹쳐야 한국어 조사·어미가 잘리지 않습니다. 오버랩을 늘리면 검색 재현율 (Recall)은 오를 수 있지만 같은 문장이 여러 Chunk에 반복되어 검색 결과 중복과 저장 비용이 함께 증가합니다.
여기서 두 가지 중복을 구분해야 합니다. 오버랩으로 생기는 중복은 의도적으로 만든 것이므로 결정적 Chunk ID나 멱등 인덱싱으로 제거할 대상이 아닙니다. 멱등성은 동일 원문을 재수집·재색인할 때 생기는 중복을 막는 별개의 문제이며, 그 내용은 멱등 인덱싱 글에서 다룹니다: https://aiarchitect.tistory.com/9. 반면 오버랩 때문에 검색 결과에 유사 조각이 반복되는 문제는 검색 후처리로 다뤄야 합니다.
| 후처리 기법 | 효과 |
|---|---|
| 결과 간 유사도 기반 중복 제거 (dedup) | 거의 같은 조각을 결과에서 축소 |
| MMR (Maximal Marginal Relevance) | 관련성과 다양성을 함께 고려해 재순위 |
| 동일 부모 문서별 결과 수 제한 | 한 문서가 상위 결과를 독점하지 않게 함 |
| 인접 Chunk 병합 | 이어진 조각을 하나로 합쳐 반환 |
| parent-child retrieval | 작은 조각으로 찾고 부모 문맥을 반환 |
9. 표·목록·코드는 별도로 처리한다
문장 분리 규칙을 표·목록·코드에 그대로 적용하면 구조가 깨집니다. 종결어미나 마침표 규칙이 표의 셀 구분이나 목록 항목을 문장으로 오인하기 때문입니다.
| 블록 유형 | 잘못된 처리 | 권장 처리 |
|---|---|---|
| 표 (Table) | 행·셀이 개별 문장으로 분리 | 표 전체를 한 블록으로 보존(크면 행 그룹) |
| 목록 (List) | 항목마다 잘려 문맥 손실 | 리스트 헤더 + 항목을 함께 유지 |
| 코드 (Code) | 줄바꿈·기호로 오분리 | 코드 펜스 단위로 원형 보존 |
| 인용 (Quote) | 인용 부호 내부 절단 | 인용 블록 단위 유지 |
권장 절차는 먼저 구조 블록을 인식해 보호 영역으로 표시하고, 그 바깥의 산문(prose)에만 문장 분리 규칙을 적용하는 것입니다. 회의록에서는 결정사항·조치사항이 표나 불릿 목록으로 정리되는 경우가 많아, 이 블록 보호가 검색 품질에 직접적으로 기여합니다.
다만 "표 전체 보존", "코드 펜스 원형 보존"은 토큰 한도와 충돌할 수 있습니다. 보호 블록이 한도를 넘으면 다음 예외 처리가 필요합니다.
- 큰 표: 헤더 행을 각 조각에 반복하면서 행 그룹 단위로 분할한다.
- 긴 목록: 목록 제목(헤더)을 각 조각에 반복해 문맥을 유지한다.
- 긴 코드: 함수·클래스 단위로 나누고, 불가능하면 줄 단위로 fallback 분할한다.
- 긴 URL·공백 없는 문자열: 토크나이저 오프셋(Offset)을 이용해 최종적으로 강제 분할한다. 단,
return_offsets_mapping은 Fast tokenizer에서만 지원되므로 Fast 토크나이저 사용을 전제로 한다.
10. 회의 발화 청킹: 대화 연속성과 화자 전환
회의 녹취록은 일반 문서와 다릅니다. 한 사람의 긴 서술이 아니라 여러 화자의 짧은 발화가 시간 순으로 이어지는 대화입니다. 따라서 청크 경계 결정에 화자와 시간 정보를 함께 넣어야 합니다.
발화 청킹에서 고려할 축은 다음과 같습니다.
| 축 | 신호 | 청킹 결정 |
|---|---|---|
| 화자 전환 (Speaker Turn) | 화자 라벨 변경 | 전환 지점을 경계 후보로 |
| 시간 간격 (Time Gap) | 발화 간 공백이 큼 | 큰 간격에서 재분할 |
| 주제 전환 (Topic Shift) | 안건·주제 마커 | 안건 단위로 상위 경계 |
| 대화 연속성 | 짧은 응답의 연쇄 | 인접 발화를 묶어 문맥 유지 |
핵심은 단순히 화자마다 자르지 않는 것입니다. "네", "맞습니다" 같은 짧은 응답을 개별 Chunk로 만들면 검색에서 아무 의미가 없습니다. 반대로 화자 전환을 경계 신호에서 아예 제외하면 서로 다른 발화가 한 청크에 과도하게 섞일 수 있습니다. 여기서 주의할 점은, 화자 라벨과 시간 정보의 보존은 경계 결정과 별개라는 것입니다. 경계를 시간 기준으로 잡더라도 각 발화의 화자 라벨과 타임스탬프는 메타데이터로 항상 보존해야 하며, 그래야 누가 무엇을 언제 말했는지가 검색 결과에 남습니다.
[화자 A] 인증 방식을 토큰 기반으로 바꾸는 게 좋겠습니다.
[화자 B] 네. ← 단독 Chunk 금지 → 인접 발화에 병합
[화자 A] 그럼 다음 주까지 초안을 공유하겠습니다.
--- (침묵 40초, 큰 시간 간격) --- ← 재분할 후보
[화자 C] 다음 안건은 배포 일정입니다. ← 주제 전환 → 상위 경계
11. 앞 문장 전제 (Antecedent)를 보존한다
대화에서는 지시어와 생략이 잦습니다. "그건 다음 주까지 해주세요"에서 "그건"이 무엇인지는 앞 발화에 있습니다. Chunk가 이 전제를 잃으면 검색 결과가 문법적으로는 멀쩡하지만 의미가 비어 버립니다.
전제 보존을 위한 실무 기법은 다음과 같습니다.
- 화자 전환 경계에서 짧은 앞 문맥을 함께 포함한다(문장 단위 오버랩의 대화판).
- 지시어·생략이 감지되는 발화는 단독 Chunk로 두지 않고 직전 발화와 병합한다.
- 필요하면 청크 텍스트 앞에 주제·안건 헤더를 접두어로 부착해 문맥을 명시한다.
[Chunk 텍스트 예시 — 헤더 접두어로 전제 보강]
(안건: 인증 방식 변경)
화자 A: 인증 방식을 토큰 기반으로 바꾸는 게 좋겠습니다.
화자 A: 그럼 다음 주까지 초안을 공유하겠습니다.
이렇게 하면 "인증 방식 변경 담당자와 기한" 같은 질의가 들어와도 Chunk 안에 안건·화자·조치가 함께 담겨 검색과 답변 근거로 쓸 수 있습니다.
12. 회의·시간·화자·주제 메타데이터 (Metadata) 부착
한국어 청킹의 마지막 단계는 각 Chunk에 검색·필터·출처용 메타데이터를 붙이는 것입니다. 회의 도메인에서 특히 유용한 필드는 다음과 같습니다.
{
"chunkText": "화자 A: 인증 방식을 토큰 기반으로 바꾸는 게 좋겠습니다. ...",
"meta": {
"tenantId": "opaque-tenant-id",
"meetingId": "opaque-meeting-id",
"docType": "transcript",
"agenda": "인증 방식 변경",
"speakers": ["speaker-a"],
"startOffsetMs": 723000,
"endOffsetMs": 821000,
"seq": 7,
"lang": "ko",
"tokenCount": 231,
"embeddingModel": "multilingual-e5-large",
"tokenizerRevision": "<pinned-revision>",
"chunkPolicyVersion": "2026-07-30"
}
}
필드 의미를 몇 가지 못박아 둡니다. startOffsetMs/endOffsetMs는 실제 시각이 아니라 회의 시작 기준 오프셋(밀리초) 이라 이름에 명시했습니다. seq는 문서 내 청크 순번으로 0부터 시작한다고 정의합니다(0-based). embeddingModel·tokenizerRevision·chunkPolicyVersion은 tokenCount와 청킹 결과를 재현하기 위한 고정 값입니다. tenantId는 아래 접근 통제·필터 강제의 파티션 키로 쓰는 불투명 식별자입니다.
메타데이터의 용도는 세 가지입니다.
- 필터 검색: 특정 테넌트·회의·화자·기간·문서 유형(요약 vs 녹취록)으로 검색 범위를 좁힙니다.
- 출처 표시: 답변에 회의·시각·화자를 근거로 붙입니다.
- 재분할·회귀:
tokenCount,seq로 청킹 품질을 추적하고 파라미터 변경 후 비교합니다. 이때tokenCount는 원문 조각이 아니라 안건 헤더·passage:접두사·화자 라벨을 모두 붙여 임베딩 모델에 최종 전달할 문자열을 기준으로 재야 합니다. 접두사·헤더를 붙이면 토큰 수가 늘어나기 때문입니다. 재현성을 위해서는embeddingModel과tokenizerRevision을 고정해야 합니다.
메타데이터의 값에는 실명·계정·원문 식별자를 그대로 넣지 말고, 다른 글들과 일관되게 불투명 식별자 (Opaque Identifier) 를 사용합니다. 주의할 점은 agenda 같은 메타데이터 값에도 고객명·프로젝트명 같은 개인정보가 들어갈 수 있다는 것입니다. 따라서 PII 검사·마스킹은 청크 본문(chunkText)뿐 아니라 agenda 등 자유 텍스트 메타데이터 필드까지 대상으로 해야 합니다. Chunk ID를 어떻게 결정적으로 만들지는 멱등 인덱싱 글을 참고합니다: https://aiarchitect.tistory.com/9.
개인정보·보안 운영 (회의 도메인의 필수 고려사항)
회의록은 민감한 도메인이라, 청킹·인덱싱 설계에는 개인정보 보호가 함께 들어가야 합니다. 여기서 중요한 구분은, 메타데이터 값을 불투명 식별자로 바꾸는 것은 익명화(Anonymization)가 아니라 가명화(Pseudonymization) 라는 점입니다. speaker-a 같은 가명 ID로 바꿔도 청크 본문(chunkText) 자체에는 실명·고객사명·이메일·전화번호·프로젝트명·계약 내용 같은 원문 PII가 그대로 남을 수 있습니다. 따라서 식별자만 바꾸는 것으로는 충분하지 않으며, 최소한 다음을 함께 설계해야 합니다.
- 인덱싱 전 PII 탐지·마스킹: 청크 본문에 대해 인덱싱 전에 개인정보를 탐지하고 마스킹할지 정책을 정한다.
- 접근 통제와 필터 강제: 테넌트·회의별 접근 통제를 두고, 검색 시 사용자 권한에 맞는 필터를 강제한다.
- 보존 기간: 임베딩·원문·로그 각각의 보존 기간을 정한다.
- 로그 마스킹: 검색 로그·오류 로그에 원문이 평문으로 남지 않도록 마스킹한다.
- 삭제 요청 처리: 삭제 요청 시 원문·Chunk·벡터를 일괄 삭제할 수 있어야 한다.
- 매핑 분리: 가명 ID와 실제 사용자의 매핑은 벡터 저장소 밖에서 별도로 관리한다.
13. 청크 크기 vs 검색 품질 트레이드오프 (개념)
청크 크기는 검색 품질의 핵심 다이얼입니다. 너무 작으면 문맥이 손실되고, 너무 크면 노이즈가 섞입니다.
| 청크 크기 | 주요 실패 모드 | 검색·답변에 미치는 영향 |
|---|---|---|
| 너무 작음 | 근거가 여러 조각으로 분산, 지시어·전제 손실 | 필요한 문맥이 흩어져 답변 근거가 부족 |
| 적정 | 문맥과 초점의 균형 | — |
| 너무 큼 | 의미 희석, 불필요한 문맥 증가 | 무관한 문장이 섞여 검색 점수·답변이 희석, 토큰 한도 압박 |
여기서 주의할 점은 작은 청크가 항상 재현율(Recall)이 낮고 큰 청크가 항상 높은 것은 아니라는 것입니다. 실제 recall/precision 방향은 관련성 판정 단위(문서인지 span인지), top-k, 평가 라벨에 따라 달라지며 반대 결과도 나올 수 있습니다. 그래서 위 표는 수치가 아니라 실패 모드로 읽어야 하고, 실제 방향은 다음 절의 평가 세트와 k로 측정해야 합니다. 핵심 직관은 다음과 같습니다.
- 작은 Chunk는 검색기가 정확한 위치를 짚기 쉽지만, 답변 생성 단계에서 문맥이 부족할 수 있습니다.
- 큰 Chunk는 문맥이 풍부하지만 하나의 Chunk 안에 여러 주제가 섞여 검색 점수가 희석될 수 있고, 답변에 무관한 내용이 함께 들어갈 수 있습니다.
따라서 적정 크기는 도메인·질의 유형·임베딩 모델에 따라 달라지며, 다음 절의 평가로 결정해야 합니다. 추측이 아니라 회귀 세트로 정하는 것이 원칙입니다.
14. 임베딩 (Embedding)과 정규화 (Normalization)
청킹 결과는 다국어 임베딩으로 벡터화됩니다. 한국어 청킹 관점에서 임베딩 단계에서 놓치기 쉬운 두 가지가 있습니다.
- 모델별 접두사·정규화 규약을 지킨다. 예를 들어
multilingual-e5계열의 공식 사용 규약은 질의에는query:, 문서에는passage:접두사를 붙이고, 출력 벡터를 L2 정규화 (L2 Normalize)한 뒤 코사인 유사도 (Cosine Similarity)로 검색하도록 안내합니다. 이 규약을 어기면 성능이 떨어집니다. 접두사가 붙는 만큼 토큰 예산도 그만큼 줄어든다는 점을 청크 크기 상한에 반영해야 합니다. - 거리 지표를 모델 권장 정규화 규약과 일관되게 구성한다. 코사인 기반으로 학습된 모델이라도 색인 (Index)에서 반드시 "코사인"만 써야 하는 것은 아닙니다. L2 정규화한 벡터에서는 내적 (Dot Product)이 코사인 유사도 (Cosine Similarity)와 같은 순위를 만들기 때문입니다(내적은 재정규화 비용을 아낄 수 있습니다). 핵심은 모델 권장 정규화를 따르고 저장소의 점수 계산과 일관되게 맞추는 것입니다.
# 개념 예시: e5 계열 접두사 + 정규화 규약
def to_passage_input(chunk_text: str) -> str:
return f"passage: {chunk_text}" # 문서(Chunk)측 접두사
def to_query_input(user_query: str) -> str:
return f"query: {user_query}" # 질의측 접두사
# 임베딩 시 normalize_embeddings=True 로 L2 정규화, 검색은 cosine 사용
Chunk ID·버전·멱등 처리 등 저장 단계의 세부는 이 글의 범위 밖이며 멱등 인덱싱 글에서 다룹니다: https://aiarchitect.tistory.com/9. 여기서 강조할 점은 청킹 파라미터(크기·오버랩·경계 규칙)와 임베딩 모델 선택은 함께 결정해야 한다는 것입니다. 512 토큰 모델과 8192 토큰 모델은 애초에 낼 수 있는 청크 크기가 다르기 때문입니다.
15. 청킹 파라미터 평가 방법 (개념)
청크 크기·오버랩·경계 규칙은 "느낌"으로 정하면 안 됩니다. 파라미터를 바꿔가며 검색 품질을 측정해 결정합니다.
평가의 기본 골격은 다음과 같습니다.
1) 회귀 세트: (질의, 있어야 할 근거 문장/회의) 쌍을 준비
2) 파라미터 격자: 청크 크기 × 오버랩 × 경계 규칙 조합
3) 각 조합으로 재색인 → 동일 질의로 검색
4) 근거가 상위 결과에 포함되는지 측정(개념: recall@k 등)
5) 회의 도메인 질의(결정·담당·기한)에서 특히 확인
| 평가 질의 유형 | 확인 포인트 |
|---|---|
| 사실 확인 ("무엇을 결정했나") | 결정 문장이 상위 결과에 포함되는가 |
| 담당·기한 ("누가 언제까지") | 지시어·전제가 보존됐는가 |
| 주제 요약 ("배포 일정 논의") | 관련 발화가 흩어지지 않고 묶였는가 |
| 부정·예외 ("반대 의견은") | 짧은 반대 발화가 병합돼 검색되는가 |
지표는 recall@k 하나로는 부족합니다. 다음을 함께 보면 평가가 훨씬 단단해집니다.
- 순위 품질: MRR (Mean Reciprocal Rank) 또는 nDCG로 근거가 상위에 오는 정도를 본다.
- 근거 span 포함률: 정답 근거 문장이 검색 결과에 얼마나 담기는지 측정한다.
- 상위 k 중복률: 오버랩·재색인으로 유사 조각이 반복되는 정도를 본다.
- 청크 통계: 평균 청크 토큰 수, 예산 대비 사용률(사용 토큰 ÷ 예산)과 낭비율(1 − 사용률, 남은 예산 비율).
- 운영 비용: 인덱스 크기, 임베딩 시간, 검색 지연.
- 답변 인용 정확성: 검색된 근거로 생성한 답변의 인용이 실제 근거와 맞는가.
평가는 한국어 특유의 실패 유형(문장 중간 절단, 지시어 손실, 토큰 초과 잘림)을 회귀 세트에 반드시 포함해야 의미가 있습니다. 영어 예제 기준 회귀 세트로는 이 문제들이 드러나지 않습니다.
16. 청킹 회귀 세트 (Regression Set) 구성
청킹은 한 번 튜닝하고 끝나지 않습니다. 문장 분리 규칙, 오버랩, 토크나이저, 임베딩 모델이 바뀔 때마다 검색 품질이 흔들릴 수 있습니다. 그래서 청킹 전용 회귀 세트를 두고 변경 시마다 돌립니다.
회귀 세트에 넣을 대표 케이스는 다음과 같습니다.
| 케이스 | 검증 목적 |
|---|---|
| 부호 없는 종결 문장 | 종결어미 기반 경계가 동작하는가 |
| 받침형 종결("갑니다"/"됩니까") | -ㅂ니다·-ㅂ니까 계열이 분리되는가 |
| 해요체("진행할게요") | 해요체 표층형이 경계로 잡히는가 |
| 단어 내부 유사 종결("책임"/"포함") | 임·함을 종결로 오인하지 않는가 |
| 버전·시간 마침표("3.5"/"10.30") | 소수점을 문장 경계로 잘못 잡지 않는가 |
| 목록 라벨("A. 검토") | 라벨 마침표에서 과분할하지 않는가 |
| 초장문 문장(토큰 한도 초과) | 잘림 없이 재분할되는가 |
| 표·목록·코드 블록 | 구조가 보존되는가 |
| 짧은 응답 발화("네") | 단독 Chunk가 안 생기는가 |
| 지시어 발화("그건") | 전제가 함께 담기는가 |
| 큰 시간 간격·주제 전환 | 상위 경계가 잡히는가 |
회귀 세트 자체에는 실제 회의 내용·실명·계정을 넣지 말고, 같은 언어 현상을 재현하는 합성 예제를 사용하는 것이 안전합니다. 이렇게 하면 데이터 유출 없이 청킹 규칙만 검증할 수 있습니다.
17. 안티패턴 (Anti-pattern) 정리
한국어 RAG 청킹에서 반복해서 나타나는 안티패턴과 대안을 정리합니다.
| 안티패턴 | 왜 문제인가 | 대안 |
|---|---|---|
| 고정 문자 수로 자르기 | 한국어 토큰/문자 비율이 달라 한도 초과·의미 절단 | 토크나이저 토큰 수로 측정 |
| 마침표만으로 문장 분리 | STT 녹취록은 부호가 불완전 | 종결어미 패턴 병행 |
| 영어 예제 파라미터 그대로 | 한국어에서 토큰 한도 조기 도달 | 회귀 세트로 재튜닝 |
| 화자마다 잘라 Chunk 생성 | 짧은 응답이 의미 없는 Chunk가 됨 | 인접 발화 병합 |
| 과도한 오버랩 | 중복 Vector·노이즈·비용 증가 | 문장 단위 소량 오버랩 |
| 토큰 초과를 조용히 잘림 | 결론·조치가 벡터에서 사라짐 | 초과 시 경고·재분할 |
| 표·코드에 문장 규칙 적용 | 구조 파괴 | 구조 블록 보호 후 산문만 분리 |
18. 파이프라인에서의 위치와 요약
한국어 청킹은 전체 RAG 파이프라인의 한 단계이며, 앞뒤 단계와 분리해 설계·평가할 수 있어야 합니다.
화자·시간·안건은 문장 패킹보다 앞에서 쓰는 상위 구조 신호입니다. 문서 유형에 따라 상위 구조 탐지 단계가 갈립니다. 순서에서 두 가지가 중요합니다. 첫째, 구조 블록 보호가 정규화보다 먼저 와야 합니다(정규화가 공백을 축소해 표·코드 들여쓰기를 깨뜨리지 않도록). 둘째, 짧은 발화 병합·전제 보존은 토큰 패킹보다 먼저 해야 하고, 최종 토큰 검사에서 초과가 나오면 재패킹·강제 분할로 되돌아가는 루프를 둬야 합니다(병합을 패킹 뒤에 하면 예산을 다시 넘길 수 있으므로).
구조 블록 추출·보호(표·목록·코드)
↓
산문만 정규화(normalize_prose_ko) → 이후 블록 복원
↓
상위 구조 분석
├─ 일반 문서: 제목·문단 경계
└─ 녹취록: 안건·시간·화자 턴
↓
문장 분리(종결어미·부호)
↓
짧은 발화 병합 + 전제 보존(지시어 발화는 직전 발화에 결합)
↓
문장·발화 단위 토큰 패킹(토큰 예산 상한)
↓
오버랩 + 안건 헤더·passage: 접두사 추가
↓
PII 탐지·마스킹(본문·agenda 등) + 권한 메타데이터 부착
↓
접두사·헤더 포함 최종 토큰 수 재검사
│ └─ 초과 시 ─▶ 재패킹·강제 분할로 되돌아감(루프)
↓
임베딩(접두사·정규화) → 저장(멱등 인덱싱: 별도 글)
검색 단계에서 권한 필터는 검색 결과를 받은 뒤 걸러 내는 방식이 아니라, 벡터 검색 질의 단계부터 fail-closed로 강제하는 편이 안전합니다. 권한 메타데이터가 없거나 불명확한 청크는 기본적으로 검색 대상에서 제외됩니다.
정리하면 한국어 청킹 설계의 핵심 결정은 다음과 같습니다.
- 경계는 문자·바이트가 아니라 문장·어절 단위로 잡는다(종결어미 병행).
- 크기 상한은 실제 토크나이저의 토큰 수로 측정하고 모델 한도보다 여유를 둔다.
- 회의 발화는 화자·시간·주제로 재분할하되 짧은 응답 병합과 전제 보존을 함께 한다.
- 크기·오버랩은 회귀 세트로 평가해 정한다(추측 금지).
- 저장·멱등 처리는 별도 글의 결정적 Chunk ID 설계로 연결한다: https://aiarchitect.tistory.com/9.
청킹과 임베딩 모델은 함께 설계해야 하는 짝입니다. 모델이 정해져야 토크나이저와 토큰 상한이 정해지고, 그 상한이 다시 청크 크기·오버랩을 좌우하기 때문입니다. 다만 순서상으로는, 후단 검색기(Retriever)를 튜닝하기 전에 청킹 경계와 토큰 예산을 먼저 검증해야 합니다. 경계에서 손실된 문맥은 후단 검색기만으로는 보완하기 어렵기 때문입니다(인접 Chunk 검색이나 후처리로 일부는 완화할 수 있지만, 근본적인 문맥 손실을 되돌리지는 못합니다). 한국어 RAG의 품질을 좌우하는 앞단의 중요한 지렛대가 바로 청킹 설계입니다.
참고 자료 (Official References)
- multilingual-e5-large (토큰 한도·접두사·정규화 규약): https://huggingface.co/intfloat/multilingual-e5-large
- BGE-M3 (최대 8192 토큰, 다국어·다기능 임베딩): https://huggingface.co/BAAI/bge-m3
- Sentence Transformers — Computing Embeddings (정규화·거리 지표): https://sbert.net/examples/sentence_transformer/applications/computing-embeddings/README.html
- ChromaDB 공식 문서 (벡터 저장·거리 지표): https://docs.trychroma.com/
- pgvector (PostgreSQL 벡터 확장): https://github.com/pgvector/pgvector
- Kiwi (한국어 형태소 분석·문장 분리기): https://github.com/bab2min/Kiwi
- kss (Korean Sentence Splitter): https://github.com/hyunwoongko/kss
- 국립국어원 표준국어대사전 (조사·어미 등 한국어 문법 용어): https://stdict.korean.go.kr/
'RAG · LLM 시스템' 카테고리의 다른 글
| 음성에서 인사이트까지: 실시간 STT·RAG로 만드는 실제 데이터 파이프라인 (1) | 2026.08.02 |
|---|---|
| 웹 리서치 RAG: 공개 웹 문서 추출·청킹·Semantic Search를 MCP로 제공하기 (0) | 2026.07.30 |
| RAG 운영 설계: 멱등 인덱싱 (Idempotent Indexing)과 중복 Chunk 방지 (0) | 2026.07.29 |
| 회의 음성에서 RAG까지: 녹취록·요약·검색·업무 연결 파이프라인 (0) | 2026.07.29 |