RAG와 임베딩 · 글 11편
RAG와 임베딩이 글에서 다루는 것긴 문서를 검색 단위로 나누는 방법입니다. 크기와 겹침의 출발값, 조항·안건 단위로 나누는 기준, 조각마다 남겨야 할 정보, 표와 예외 조항에서 생기는 문제를 정리합니다.
왜 나누는가
문서를 통째로 비교하면 검색이 둔해집니다. 50쪽짜리 규정집 전체가 “휴가”와 관련 있다고 나와도, 모델에 50쪽을 다 넣을 수는 없습니다. 그래서 질문에 답할 수 있는 크기로 나눕니다. 나눈 단위를 청크 또는 문서 조각이라고 부릅니다.
나누는 방법이 검색 품질을 크게 좌우합니다. 임베딩 모델을 바꾸는 것보다 조각을 다시 나누는 쪽이 효과가 큰 경우가 많습니다.
크기와 겹침의 출발값
강의에서 쓰는 출발값입니다.
| 항목 | 출발값 | 설명 |
|---|---|---|
| 크기 | 500자 안팎(±100) | 한 조각에 조항 하나, 문단 한두 개가 들어가는 정도 |
| 겹침 | 50자 | 앞 조각의 끝부분을 다음 조각 앞에 다시 넣음 |
| 가져올 개수 | 상위 3~5개 | 모델에 넣는 조각 수 |
겹침을 두는 이유는 문장이 조각 경계에서 잘리기 때문입니다. “다만 다음의 경우에는”으로 끝난 조각과 그 뒤의 내용이 떨어지면 둘 다 쓸모가 없어집니다. 겹쳐 두면 적어도 한쪽 조각에는 문장이 온전히 들어갑니다.
글자 수로만 자르는 가장 단순한 코드입니다.
chunks = []
for d in docs:
for i in range(0, len(d["text"]), 450):
chunks.append({"src": d["src"], "text": d["text"][i:i+500]})
450자씩 이동하면서 500자를 잘라내므로 50자가 겹칩니다.
다른 값도 흔히 씁니다. 강사의 예전 문서 챗봇 프로젝트는 1,000자에 200자 겹침으로 나눕니다. 정답은 없습니다. 출발값으로 시작해 평가 질문의 결과를 보면서 조정합니다.
| 증상 | 조정 |
|---|---|
| 찾은 조각에 답의 절반만 들어 있다 | 크기를 키우거나 겹침을 늘림 |
| 찾은 조각에 관련 없는 내용이 많이 섞여 있다 | 크기를 줄임 |
| 짧은 제목 조각만 상위에 나온다 | 제목을 본문 조각에 합침 |
| 여러 주제가 섞인 긴 문단이 엉뚱한 질문에 걸린다 | 문단 경계로 다시 나눔 |
글자 수보다 문서의 경계
글자 수로 자르는 것은 시작용입니다. 문서에 구조가 있으면 그 구조를 따라 나눕니다.
| 문서 | 나누는 단위 | 출처로 남길 것 |
|---|---|---|
| 규정, 지침 | 조항 | 규정명, 조·항 번호 |
| 회의록 | 회차와 안건 | 날짜, 안건명 |
| 민원 응대 기록 | 민원 1건 | 접수번호, 처리 결과 |
| 법령 | 조문 | 법령명, 조문 번호 |
| 보고서 | 제목 아래 문단 | 문서명, 쪽수, 소제목 |
조항 하나가 500자를 넘어도 중간에서 자르지 않는 편이 낫습니다. 반대로 조항이 아주 길면 항 단위로 나누고, 각 조각에 조 제목을 다시 붙입니다.
조건과 예외를 떼어 놓지 않는다
가장 흔한 사고입니다. 규정은 원칙과 예외가 붙어 있습니다.
제12조(출장비) 출장비는 실비로 지급한다. 다만, 관내 출장의 경우에는 정액으로 지급한다.
“출장비는 실비로 지급한다”와 “다만, 관내 출장의 경우에는”이 서로 다른 조각으로 갈리면, 검색은 앞 조각만 찾아오고 모델은 예외를 모른 채 답합니다. 답변은 자연스럽지만 틀립니다.
막는 방법은 세 가지입니다.
- 조항 단위로 나눠 원칙과 예외가 한 조각에 있게 합니다.
- 겹침을 둡니다.
- 제목을 물려줍니다. 긴 조항을 여러 조각으로 나눌 때 각 조각 앞에 “제12조(출장비)“를 다시 붙입니다. 이렇게 하면 뒤쪽 조각도 무엇에 관한 내용인지 스스로 설명합니다.
조각마다 남길 정보
텍스트만 저장하면 나중에 출처를 댈 수 없습니다. 강의 교재는 조각마다 다음 항목을 저장하도록 안내합니다.
| 항목 | 예시 | 쓰임 |
|---|---|---|
| chunk_id | DOC-001-03 | 문서 안에서 조각을 가리키는 번호 |
| doc_id | DOC-001 | 어느 문서인지 |
| heading | 제3조(휴가) | 조각이 속한 제목 |
| location | 제3조 또는 4쪽 | 원문으로 돌아갈 위치 |
| text | 조건과 예외를 포함한 문단 | 검색과 답변에 쓰는 내용 |
| source_path | docs/복무규정.pdf | 원본 파일 |
기준은 하나입니다. 검색 결과에서 원문을 다시 열 수 있어야 합니다. 조각의 번호와 위치로 원본 파일의 해당 쪽을 찾을 수 없다면 그 조각은 근거로 쓸 수 없습니다.
표는 따로 다룬다
표를 일반 본문처럼 추출하면 행과 열의 관계가 사라집니다. 숫자만 줄줄이 이어진 텍스트가 되어, 어떤 숫자가 어느 항목의 값인지 알 수 없습니다.
- 표 제목과 단위(천원, 명, % 등)가 표와 같은 조각에 남아 있는지 확인합니다.
- 표를 조각 경계에서 자르지 않습니다. 머리글 행과 떨어진 뒤쪽 행은 뜻을 잃습니다.
- 표가 중요한 문서는 두 가지 형태로 보관합니다. 하나는 검색용 텍스트, 하나는 행과 열을 살린 구조화 자료(CSV, JSON)입니다.
- 합계, 평균처럼 계산이 필요한 질문은 표를 데이터베이스에 넣고 조회하는 쪽으로 풉니다. 문서 검색으로 숫자를 계산하게 하지 않습니다.
HWP와 PDF에서 표를 뽑을 때 생기는 문제는 RAG가 잘 되도록 사무 문서 준비하기에서 따로 다룹니다.
나눈 뒤 눈으로 확인한다
조각을 만든 뒤 바로 임베딩으로 넘어가지 않습니다. 조각 파일을 열어 다음을 봅니다.
- 처음, 중간, 마지막 조각을 하나씩 열어 원문과 대조합니다.
- 조각이 문장 한가운데에서 시작하거나 끝나는지 봅니다.
- 머리글, 바닥글, 쪽 번호가 본문에 섞여 반복되는지 봅니다. 모든 조각에 같은 기관명이 들어 있으면 그 단어는 검색에서 구별 능력을 잃습니다.
- 표가 있던 자리의 조각을 찾아 읽을 수 있는 상태인지 봅니다.
- 조각 수를 셉니다. 문서 수백 건이 수천 조각이 되는 것이 보통입니다. 지나치게 많거나 적으면 설정을 다시 봅니다.
문서가 바뀌면 조각도 다시 만든다
문서가 개정되면 그 문서의 조각과 벡터를 다시 만들어야 합니다. 원본 파일만 새것으로 바꾸고 색인을 그대로 두면, 화면에는 새 버전이 보이는데 답변은 예전 내용으로 나옵니다. 갱신 기록에 원문의 버전과 색인의 버전을 함께 남깁니다.
설계 메모 한 장
새 문서 묶음을 다루기 전에 아래 항목을 채워 봅니다.
## 쪼개기 단위
- [ ] 조항 / 안건 / 민원 1건 / 문단
- 청크 크기: __자
- 겹침: __자
- 제목 물려주기: 예 / 아니오
- 표 처리: 텍스트 / 구조화 자료 따로 보관
- 조각에 남길 항목: chunk_id, doc_id, heading, location, text, source_path