LLM 엔지니어링6장 · RAG 의 원리와 로컬 모델로 옮기기6 / 23← 이전목차다음 →MetaContents

6장. RAG 의 원리와 로컬 모델로 옮기기

문서를 조각으로 나누기

반찬을 반찬통에 나눠 담을 때는 한 가지 반찬을 한 통에 담습니다. 큰 덩어리를 아무 데서나 칼로 자르면 한 반찬이 두 통에 반씩 들어가 꺼내 먹기 불편합니다. 문서를 검색 단위로 나눌 때도 뜻이 한 덩어리인 곳에서 끊어야 합니다.

조항마다 끊은 조각과 글자 수로 자른 조각을 견준 그림. 글자 수로 자르면 문장이 중간에서 잘린다

왼쪽은 조항마다 끊은 조각이고, 오른쪽은 아래 예시처럼 60글자마다 자른 조각입니다. 오른쪽 1번처럼 "50만원까지"가 두 조각으로 갈라지면, 어느 조각을 찾아도 답이 반만 들어 있습니다. 2번처럼 앞 조각의 끝을 조금 겹쳐 자르면 이 문제를 덜 수 있습니다. 여기서 반찬이 조항, 반찬통이 문서 조각입니다.

나누는 방법

방법끊는 곳좋은 점아쉬운 점
구조로 나누기빈 줄, 조항, 제목뜻이 한 덩어리로 남는다문서마다 구조가 다르다
글자 수로 자르기정한 글자 수마다어떤 문서에도 쓴다문장이 중간에서 잘린다
겹쳐 자르기글자 수마다, 앞 조각과 겹치게잘린 곳의 뜻을 살린다조각 수와 중복이 는다

이 장의 문서는 조항이 빈 줄로 나뉘어 있어 구조로 나눕니다. 조각의 크기를 고르는 법과 여러 방법의 견주기는 7장에서 자세히 다룹니다.

두 방법 해 보기

Python
text = ("제14조(결제 수단) 신용카드, 계좌이체, 간편결제, 휴대폰 결제를 쓸 수 있습니다. 휴대폰 결제는 한 달에 50만원까지입니다."
        "\n\n제15조(무이자 할부) 5만원 이상을 신용카드로 결제하면 3개월까지 무이자 할부가 됩니다.")

by_rule = text.split("\n\n")                                  # 빈 줄에서 끊는다
by_size = [text[i:i + 60] for i in range(0, len(text), 60)]   # 60글자마다 자른다
print(len(by_rule), "조각:", [c[:12] for c in by_rule])
print(len(by_size), "조각:", by_size[0][-12:], "|", by_size[1][:12])

빈 줄로 나누면 조항 두 개가 그대로 두 조각이 됩니다. 60글자로 자르면 세 조각이 되고, 첫 조각은 "…휴대폰 결제는 한 달에"에서 끝납니다. 답인 "50만원까지입니다"는 다음 조각으로 넘어가 문장이 잘립니다.

코드에 나오는 함수

코드쉬운 뜻
"\n\n"줄바꿈 두 번, 곧 빈 줄이다. 이 문서는 조항 사이에 빈 줄이 있다
text.split("\n\n")빈 줄이 있는 곳마다 글을 잘라 목록으로 만든다
range(0, len(text), 60)0, 60, 120 처럼 60씩 건너뛴 시작 위치를 만든다
text[i:i + 60]i 번째부터 60글자를 잘라 낸다. 끝에 60글자가 안 남으면 남은 만큼만 나온다
c[:12], by_size[0][-12:]앞 12글자, 뒤 12글자만 보여 준다. 조각이 어디서 시작하고 끝나는지 보려는 것이다

이 코드가 뜻하는 것

긴 김밥 한 줄을 써는 두 방법과 같습니다. 재료가 바뀌는 곳에서 써는 방법과, 자로 재서 똑같은 길이로 써는 방법입니다. 자로 재면 크기는 고르지만 재료 한가운데가 잘릴 수 있습니다.

줄하는 일쉽게 말하면
text = (...)조항 두 개를 빈 줄로 이은 글이다썰 김밥 한 줄
by_rule = ...빈 줄에서 끊는다재료가 바뀌는 곳에서 썬다
by_size = ...60글자마다 자른다자로 재서 똑같이 썬다
첫째 print조각 수와 각 조각의 앞부분을 찍는다몇 조각인지, 조각마다 무엇으로 시작하는지 본다
둘째 print첫 조각의 끝과 둘째 조각의 앞을 나란히 찍는다잘린 자리를 들여다본다

결과에서 빈 줄로 나눈 두 조각은 "제14조…", "제15조…"로 시작해 조항 하나씩을 온전히 담습니다. 60글자로 자른 쪽은 "휴대폰 결제는 한 달에"와 "50만원까지입니다" 사이가 끊겼습니다.

작게 줄여서 눈으로 보기

표에 나온 "겹쳐 자르기"를 짧은 문장으로 해 봅니다. 12글자씩 자르되, 다음 조각을 4글자 겹치게 시작합니다.

Python
text = "휴대폰 결제는 한 달에 50만원까지입니다."
size, overlap = 12, 4

# 겹치지 않게: 12글자마다 새로 시작한다
plain = [text[i:i + size] for i in range(0, len(text), size)]
# 겹쳐서: 다음 조각을 12-4 = 8글자 뒤에서 시작한다
step = size - overlap
overlapped = [text[i:i + size] for i in range(0, len(text), step)]
print(plain)
print(overlapped)
출력
['휴대폰 결제는 한 달에', ' 50만원까지입니다.']
['휴대폰 결제는 한 달에', '한 달에 50만원까지입', '원까지입니다.']
결과읽는 법
첫째 줄(겹치지 않게)두 조각이다. "한 달에"와 "50만원"이 서로 다른 조각에 있어 어느 조각도 답을 다 담지 못한다
둘째 줄(겹쳐서)세 조각이다. 가운데 조각 "한 달에 50만원까지입"이 앞뒤 낱말을 함께 담는다
겹친 부분"한 달에", "원까지입"이 두 조각에 함께 들어 있다. 겹친 만큼 조각 수와 중복이 는다

겹쳐 자르면 잘린 자리의 뜻을 살릴 수 있지만, 조각이 둘에서 셋으로 늘었습니다. 표의 "아쉬운 점"이 바로 이것입니다.

주의할 점

  • 조각에 조항 번호와 제목을 남겨 둡니다. 답에 "제14조에 따르면"처럼 근거를 보여 줄 수 있고, 제목의 낱말이 검색을 돕습니다.
  • 표, 목록, 머리글은 나누기 전에 글로 풀거나 한 덩어리로 묶습니다. 표의 한 줄만 떨어져 나가면 무엇에 대한 숫자인지 알 수 없습니다.
← 이전 절6장 · RAG 와 미세조정다음 절 →6장 · 키워드 검색