들어가기 전에
RAG의 검색 성능을 높이기 위해 청크 크기를 줄였다고 가정해보겠습니다.
기존에는 문서를 500자씩 나눴지만, 더 정확한 내용을 찾을 수 있을 것 같아 200자씩 나누도록 변경했습니다.
변경 전
→ 청크 크기 500자
변경 후
→ 청크 크기 200자
청크가 작아지면 하나의 청크에 담기는 주제가 더 명확해질 것 같습니다.
불필요한 내용도 줄어들고, 사용자의 질문과 직접 관련된 문장만 검색할 수 있을 것처럼 보이죠? 그런데 평가 결과는 예상과 달랐습니다.
변경 전 Recall@5: 0.78
변경 후 Recall@5: 0.66
청크를 더 작게 나눴는데 오히려 정답을 찾지 못하는 질문이 늘었습니다. 왜 이런 일이 발생했을까요?
다음과 같은 사내 규정이 있다고 가정해보겠습니다.
입사 1년 미만 직원의 연차 기준
입사한 날부터 1년이 지나지 않은 직원은
1개월을 개근할 때마다 1일의 유급휴가가 발생합니다.
다만 한 달 동안 결근한 날이 있다면
해당 월에는 유급휴가가 발생하지 않습니다.
이 내용을 작은 청크로 나누면 다음처럼 될 수 있습니다.
청크 1: 입사 1년 미만 직원의 연차 기준
청크 2: 입사한 날부터 1년이 지나지 않은 직원은 1개월을 개근할 때마다
청크 3: 1일의 유급휴가가 발생합니다.
청크 4: 다만 한 달 동안 결근한 날이 있다면 해당 월에는 유급휴가가 발생하지 않습니다.
사용자가 다음과 같이 질문했습니다.
입사 1년 미만 직원은 연차가 어떻게 발생하나요?
정답을 만들려면 최소한 다음 내용이 함께 필요합니다.
적용 대상
→ 입사 1년 미만 직원
발생 조건
→ 1개월 개근
발생 일수
→ 1일
예외 조건
→ 결근한 달에는 발생하지 않음
하지만 이 정보가 여러 청크에 흩어졌습니다.
검색 결과에서 일부만 가져오면 LLM은 적용 대상이나 예외 조건을 놓칠 수 있습니다.
청크를 작게 나누면 검색이 더 정확해질 수 있지만, 정답에 필요한 문맥까지 함께 잘릴 수 있는 것이죠.
이번 글에서는 청크 크기를 작게 또는 크게 설정했을 때 어떤 문제가 생길 수 있는지, 여러 청킹 방식을 어떤 기준으로 비교해야 하는지 살펴보겠습니다. 또한 검색 Recall뿐 아니라 문맥의 완전성, 중복 청크, 임베딩 비용과 LLM에 전달되는 토큰까지 함께 평가하는 방법을 정리해보겠습니다.
문서는 왜 나눠야 할까요?
RAG는 긴 문서를 그대로 검색하기보다 작은 단위로 나눠서 저장하는 경우가 많습니다. 이렇게 나눈 문서 조각을 청크라고 합니다.
긴 원본 문서
→ 청크 1
→ 청크 2
→ 청크 3
→ 청크 4
문서를 나누는 이유는 크게 두 가지입니다. 첫 번째는 질문과 관련 있는 부분을 더 정확하게 찾기 위해서입니다.
100페이지짜리 문서를 하나의 검색 단위로 사용하면 질문과 관련 없는 내용이 훨씬 많이 포함됩니다.
예를 들어 하나의 문서에 다음 내용이 모두 들어 있을 수 있습니다.
- 회사 소개
- 복리후생
- 휴가 규정
- 출장비 규정
- 보안 규정
- 퇴직 절차
사용자가 연차에 관해 질문했을 때 문서 전체를 검색하면 검색된 문서 안에 정답은 있지만 관련 없는 내용도 함께 들어옵니다.
문서를 주제별로 적절하게 나누면 연차 규정이 포함된 부분만 찾을 수 있습니다.
두 번째는 LLM에 전달하는 글의 양을 줄이기 위해서입니다. 긴 문서 전체를 매번 LLM에 전달하면 입력 토큰과 비용이 늘어납니다. 문서가 LLM의 입력 한도를 넘을 수도 있죠.
문서 전체 전달
→ 관련 없는 내용까지 포함
→ 입력 토큰 증가
→ 비용과 응답 시간 증가
필요한 청크만 골라서 전달하면 더 적은 글로 답변을 만들 수 있습니다.
따라서 청킹은 단순히 긴 문서를 일정한 크기로 자르는 작업이 아닙니다.
검색하기에 충분히 구체적이면서 답변에 필요한 문맥은 잃지 않도록 문서를 나누는 작업입니다.
이 두 조건을 동시에 만족시키는 것이 쉽지 않습니다.
청크가 작으면 검색하기 쉬워 보입니다
청크가 작으면 하나의 청크에 포함되는 주제가 줄어듭니다. 예를 들어 다음 두 청크를 비교해보겠습니다.
큰 청크
회사는 임직원의 건강 관리를 위해 건강검진을 지원합니다.
건강검진은 매년 1회 지원되며 배우자도 지원 대상에 포함됩니다.
연차는 근속 기간에 따라 발생하며, 입사 1년 미만 직원은
1개월을 개근할 때마다 1일의 유급휴가가 발생합니다.
출장비는 국내 출장과 해외 출장 기준을 구분하여 지급합니다.
작은 청크
입사 1년 미만 직원은
1개월을 개근할 때마다 1일의 유급휴가가 발생합니다.
사용자가 입사 1년 미만 직원의 연차를 물었다면 작은 청크가 질문에 더 집중되어 있습니다.
큰 청크에는 건강검진과 출장비처럼 관련 없는 내용도 포함되어 있습니다.
임베딩 모델이 큰 청크 전체를 하나의 벡터로 표현하면 여러 주제의 의미가 섞일 수 있습니다. 따라서 작은 청크는 다음과 같은 장점이 있습니다.
- 한 청크가 다루는 주제가 명확해질 수 있습니다.
- 질문과 직접 관련된 문장을 찾기 쉬울 수 있습니다.
- 검색 결과에 포함되는 불필요한 내용이 줄어들 수 있습니다.
- LLM에 전달할 청크를 세밀하게 선택할 수 있습니다.
이런 장점 때문에 청크를 작게 만들수록 검색이 좋아질 것처럼 느껴질 수 있습니다. 하지만 작은 청크에는 반대쪽 문제도 있습니다.
너무 작게 나누면 문장의 의미가 사라질 수 있습니다
다음 문장을 살펴보겠습니다.
해당 지원은 입사 후 3개월이 지난 정규직 직원에게만 적용됩니다.
이 문장을 잘못 나누면 다음처럼 됩니다.
청크 1: 해당 지원은 입사 후 3개월이 지난
청크 2: 정규직 직원에게만 적용됩니다.
첫 번째 청크만 보면 무엇이 적용되는지 알 수 없습니다. 두 번째 청크만 보면 어떤 조건의 정규직 직원인지 알 수 없습니다.
문장 자체가 중간에서 끊기면 각 청크가 독립적인 의미를 가지기 어렵습니다.
문장 단위로 나눴더라도 문제가 생길 수 있습니다.
청크 1: 배송비는 판매자가 부담합니다.
이 문장만 보면 모든 반품에서 판매자가 배송비를 부담하는 것처럼 보입니다. 하지만 원본에는 앞 문장이 있을 수 있습니다.
상품 불량이나 오배송으로 반품하는 경우,
배송비는 판매자가 부담합니다.
적용 조건과 결론이 서로 다른 청크로 나뉘면 검색된 문장이 사실과 다른 의미로 해석될 수 있습니다.
특히 다음 내용이 분리되기 쉽습니다.
- 제목과 본문
- 적용 대상과 실제 규정
- 조건과 결론
- 본문과 예외 조항
- 표의 열 이름과 값
- 목록의 상위 항목과 하위 항목
- 대명사와 대명사가 가리키는 대상
청크 하나만 읽어도 의미를 이해할 수 있어야 하지만 너무 작은 청크는 앞뒤 내용을 보지 않으면 이해하기 어려울 수 있습니다.
정답이 여러 청크에 흩어질 수도 있습니다
모든 질문이 한 문장에서 답을 찾는 단순한 질문은 아닙니다. 다음 질문을 살펴보겠습니다.
입사 1년 미만 직원의 연차 발생 기준과
사용하지 않은 연차의 처리 방식을 알려주세요.
이 질문에 답하려면 두 가지 내용을 찾아야 합니다.
- 연차 발생 기준
- 미사용 연차 처리 방식
두 내용은 문서의 서로 다른 문단이나 조항에 있을 수 있습니다. 청크가 작을수록 답변에 필요한 근거가 여러 청크로 나뉠 가능성이 커집니다.
청크 1
→ 연차 발생 조건
청크 2
→ 연차 발생 일수
청크 3
→ 미사용 연차 처리
청크 4
→ 예외 대상
검색 결과에서 청크 1과 청크 2만 가져오고 청크 3을 놓치면 답변의 일부만 만들 수 있습니다.
이 경우 검색된 청크들은 모두 질문과 관련 있을 수 있습니다.
하지만 정답을 완성하기에 충분하지는 않습니다. 그래서 청킹을 평가할 때는 단순히 관련 청크가 검색되었는지만 보지 말고, 정답에 필요한 근거가 충분히 모였는지도 확인해야 합니다.
청크가 크면 문맥은 잘 보존됩니다
큰 청크는 한 주제에 대한 조건, 본문과 예외를 함께 담을 가능성이 높습니다.
입사 1년 미만인 직원은 1개월을 개근할 때마다
1일의 유급휴가가 발생합니다.
다만 한 달 동안 결근한 날이 있다면
해당 월에는 유급휴가가 발생하지 않습니다.
발생한 유급휴가는 입사일로부터 1년이 되는 날까지 사용할 수 있습니다.
이 정도 내용을 하나의 청크로 유지하면 LLM이 연차의 발생 조건과 예외, 사용 기한을 함께 확인할 수 있습니다.
큰 청크에는 다음과 같은 장점이 있습니다.
- 앞뒤 문맥이 함께 유지됩니다.
- 조건과 결론이 분리될 가능성이 낮습니다.
- 예외 조항을 함께 전달하기 쉽습니다.
- 여러 문장을 종합해야 하는 질문에 유리할 수 있습니다.
- 하지만 큰 청크도 항상 좋은 것은 아닙니다.
너무 큰 청크는 다른 내용까지 함께 가져옵니다
큰 청크에는 질문과 직접 관련 없는 내용이 많이 들어갈 수 있습니다.
- 연차 발생 기준
- 연차 신청 방법
- 연차 결재 절차
- 미사용 연차 처리
- 휴직자의 연차
- 퇴직자의 연차 정산
- 경조 휴가
- 병가
사용자는 입사 1년 미만 직원의 연차 발생 기준만 질문했지만 검색된 청크에는 휴직자와 퇴직자의 규정까지 포함될 수 있습니다.
관련 없는 내용이 늘어나면 다음 문제가 발생할 수 있습니다.
- 청크의 중심 주제가 흐려질 수 있습니다.
- 질문과의 검색 점수가 낮아질 수 있습니다.
- LLM이 비슷한 규정을 서로 혼동할 수 있습니다.
- 입력 토큰과 처리 비용이 늘어납니다.
- 한정된 입력 범위에 넣을 수 있는 문서 수가 줄어듭니다.
예를 들어 LLM에 최대 4,000토큰의 검색 문맥을 전달한다고 가정해보겠습니다. 청크 하나가 평균 400토큰이라면 약 10개를 전달할 수 있습니다.
4,000 ÷ 400
→ 약 10개 청크
청크 하나가 평균 1,000토큰이라면 약 4개만 전달할 수 있습니다.
4,000 ÷ 1,000
→ 약 4개 청크
큰 청크는 각각 더 많은 문맥을 담지만, 서로 다른 문서를 함께 확인해야 하는 질문에서는 후보를 충분히 전달하지 못할 수도 있습니다. 결국 청크 크기는 작을수록 좋거나 클수록 좋은 값이 아닙니다.
작은 청크
→ 검색은 세밀해질 수 있지만 문맥이 끊길 수 있음
큰 청크
→ 문맥은 유지되지만 불필요한 내용이 늘어날 수 있음
두 방향 사이에서 적절한 균형을 찾아야 합니다.
같은 크기라도 어디에서 자르느냐가 중요합니다
청크 크기가 같더라도 경계를 어디에 두느냐에 따라 결과가 달라집니다. 다음 원문이 있다고 가정해보겠습니다.
반품 배송비 기준
상품 불량이나 오배송으로 반품하는 경우에는
판매자가 왕복 배송비를 부담합니다.
단순 변심으로 반품하는 경우에는
구매자가 왕복 배송비를 부담합니다.
500자마다 기계적으로 자르면 문장 중간이나 조건과 결론 사이에서 끊길 수 있습니다. 반대로 제목과 문단을 기준으로 나누면 다음처럼 하나의 의미 단위를 유지할 수 있습니다.
반품 배송비 기준
상품 불량이나 오배송으로 반품하는 경우에는
판매자가 왕복 배송비를 부담합니다.
단순 변심으로 반품하는 경우에는
구매자가 왕복 배송비를 부담합니다.
따라서 청킹을 비교할 때는 청크 크기만 확인하면 안 됩니다.
청크 크기
청크가 나뉘는 위치
앞뒤 청크가 겹치는 범위
제목과 메타데이터 포함 여부
검색 단위와 LLM 전달 단위
이 조건들이 함께 검색 결과에 영향을 줍니다.
일정한 크기로 나누는 방법
가장 단순한 방식은 일정한 글자 수나 토큰 수를 기준으로 문서를 나누는 것입니다. 보통 Fixed-size Chunking이라고 부릅니다.
원본 문서
→ 500토큰
→ 500토큰
→ 500토큰
→ 남은 토큰
이 방식은 구현이 단순하고 결과를 예상하기 쉽습니다.
청크 크기가 일정합니다.
생성되는 청크 수를 예상하기 쉽습니다.
임베딩 비용을 계산하기 쉽습니다.
여러 실험에서 같은 기준을 적용하기 쉽습니다.
하지만 문서의 구조를 고려하지 않는다면 문장이나 문단 중간에서 잘릴 수 있습니다.
그래서 실제로는 일정한 크기를 유지하되, 문단·줄바꿈·문장·공백과 같은 구분자를 순서대로 확인하며 나누는 방식을 많이 사용합니다. 예를 들어 다음과 같은 순서로 자를 수 있습니다.
- 먼저 문단 단위로 나눕니다.
- 문단이 너무 길면 줄바꿈 단위로 나눕니다.
- 그래도 길면 문장이나 공백 단위로 나눕니다.
일정한 최대 크기를 유지하면서 자연스러운 문장 구조를 최대한 보존하는 방식입니다. 처음 RAG를 만들 때는 이런 단순한 방식을 기준 구성으로 사용하기 좋습니다. 다른 복잡한 청킹 방식이 실제로 더 좋은지 비교할 대상이 되기 때문입니다.
문장과 문단을 기준으로 나누는 방법
Sentence Chunking은 문장을 기본 단위로 문서를 나눕니다.
문장 1 + 문장 2 + 문장 3
→ 청크 1
문장 4 + 문장 5
→ 청크 2
문장 중간이 잘리지 않기 때문에 고정된 글자 수만 사용하는 방식보다 자연스러운 내용을 유지할 수 있습니다.
문서의 제목과 문단 구조까지 함께 사용하면 다음처럼 나눌 수 있습니다.
제목
소제목
문단
목록
표
코드 블록
문서 자체의 구조를 활용하는 방식은 규정 문서, 매뉴얼, 기술 문서처럼 계층이 분명한 자료에 특히 유용할 수 있습니다.
예를 들어 다음 문서를 살펴보겠습니다.
제3조 연차 휴가
1. 입사 1년 미만 직원
1개월 개근 시 1일의 유급휴가가 발생합니다.
2. 입사 1년 이상 직원
전년도 출근율에 따라 연차가 발생합니다.
단순한 글자 수만 기준으로 자르면 1번과 2번의 내용이 섞일 수 있습니다. 문서 구조를 기준으로 나누면 적용 대상별 규정을 분리할 수 있습니다.
다만 문장이나 문단 길이는 일정하지 않습니다. 한 문단이 지나치게 길거나, 한 문장 안에 많은 조건이 들어 있는 문서라면 추가적인 크기 제한이 필요합니다.
내용이 바뀌는 지점을 찾아 나누는 방법
Semantic Chunking은 문장의 길이보다 내용의 흐름을 기준으로 경계를 찾습니다. 먼저 문서를 문장 단위로 나눈 뒤, 서로 가까운 문장들의 의미를 비교할 수 있습니다.
문장 1과 문장 2
→ 의미가 비슷함
문장 2와 문장 3
→ 의미가 비슷함
문장 3과 문장 4
→ 의미 차이가 큼
→ 여기에서 청크 분리
예를 들어 다음 문서가 있다고 가정해보겠습니다.
입사 1년 미만 직원은 매월 연차가 발생합니다.
1개월을 개근하면 1일의 유급휴가가 주어집니다.
결근한 달에는 해당 휴가가 발생하지 않습니다.
출장비는 이동 거리와 출장 지역에 따라 지급됩니다.
해외 출장은 별도의 일비 기준을 적용합니다.
연차 관련 문장과 출장비 관련 문장의 의미 차이가 커지는 지점에서 청크를 나눌 수 있습니다. 이 방식은 하나의 청크가 비슷한 주제를 유지하도록 만드는 데 도움이 될 수 있습니다. 하지만 더 복잡한 방식이라고 해서 항상 더 좋은 검색 결과를 보장하는 것은 아닙니다.
- 문장마다 임베딩을 계산해야 할 수 있습니다.
- 경계를 결정하는 기준값에 따라 결과가 달라집니다.
- 청크 크기가 지나치게 불규칙해질 수 있습니다.
- 짧은 문서에서는 추가 계산에 비해 효과가 작을 수 있습니다.
- 사용하는 임베딩 모델에 따라 경계가 달라질 수 있습니다.
의미 기반 청킹이라는 이름만 보고 기본 방식보다 더 좋은 것으로 가정하면 안 됩니다. 동일한 평가 데이터와 검색 조건에서 직접 비교해야 합니다.
작은 단위로 찾고 큰 단위로 전달하는 방법
작은 청크는 검색에 유리할 수 있지만 답변에 필요한 문맥이 부족할 수 있습니다. 큰 청크는 문맥은 충분하지만 검색이 어려울 수 있습니다.
두 장점을 결합하기 위해 검색 단위와 전달 단위를 다르게 만들 수 있습니다. 이런 구조를 Parent-Child Chunking이라고 부르기도 합니다.
상위 청크
→ 연차 규정 전체 문단
하위 청크 1
→ 입사 1년 미만 직원의 연차
하위 청크 2
→ 입사 1년 이상 직원의 연차
하위 청크 3
→ 미사용 연차 처리
검색은 작은 하위 청크를 대상으로 진행합니다.
질문
→ 하위 청크 1 검색
하지만 LLM에는 하위 청크가 속한 더 큰 상위 청크를 전달합니다.
검색된 하위 청크
→ 상위 청크 확인
→ 연차 규정 전체 문단 전달
이 방식은 검색의 정확성과 문맥의 완전성을 함께 확보하려는 방법입니다. 다만 구현과 평가가 조금 더 복잡해집니다.
작은 청크와 큰 청크의 관계를 저장해야 합니다.
여러 하위 청크가 같은 상위 청크를 가리킬 수 있습니다.
같은 상위 청크가 중복 전달되지 않도록 처리해야 합니다.
검색된 하위 청크의 점수를 상위 청크에 어떻게 반영할지 정해야 합니다.
평가할 때도 두 단계를 나눠봐야 합니다.
검색 단계
→ 올바른 하위 청크를 찾았는가?
최종 문맥 단계
→ 필요한 상위 문맥을 전달했는가?
하위 청크의 검색 Recall과 최종 전달 문맥의 완전성을 함께 확인해야 합니다.
청크에 문서의 배경을 덧붙이는 방법
작은 청크는 원본 문서에서 떨어져 나오면서 중요한 배경을 잃을 수 있습니다. 다음 청크를 보겠습니다.
전년 동기 대비 8% 증가한 35억 원을 기록했습니다.
이 문장만 보면 어떤 회사인지, 어떤 지표인지, 어느 기간인지 알 수 없습니다. 원본 문서에는 다음 정보가 있었을 수 있습니다.
A사 2025년 2분기 실적 보고서
영업이익은 전년 동기 대비 8% 증가한
35억 원을 기록했습니다.
청크를 검색할 때 문서 제목이나 섹션 정보를 함께 붙일 수 있습니다.
이 청크는 A사의 2025년 2분기 실적 보고서 중
영업이익을 설명하는 부분입니다.
영업이익은 전년 동기 대비 8% 증가한
35억 원을 기록했습니다.
이렇게 청크마다 문서 안에서의 위치와 배경을 덧붙이는 방식을 Contextual Chunking이라고 표현하기도 합니다.
다만 이 용어는 도구나 자료마다 다르게 사용됩니다.
문서 전체를 참고해 청크에 짧은 설명을 붙이는 방식은 Contextual Retrieval 또는 Contextual Embedding이라는 이름으로도 소개됩니다. 핵심은 동일합니다.
작은 청크가 원본 문서에서 떨어져도 무슨 내용을 설명하는지 알 수 있도록 배경 정보를 추가하는 것입니다.
추가하는 정보는 다음과 같을 수 있습니다.
- 문서 제목
- 장과 절의 제목
- 작성 날짜와 문서 버전
- 회사명이나 제품명
- 표가 설명하는 기간
- 청크의 내용을 요약한 짧은 문장
이 방식은 검색에 도움이 될 수 있지만 추가 비용과 위험도 있습니다.
- 모든 청크에 설명을 생성하는 비용이 발생합니다.
- 잘못 생성된 설명이 검색을 방해할 수 있습니다.
- 문서가 변경되면 설명도 다시 만들어야 할 수 있습니다.
- 같은 배경 문장이 반복되어 저장 공간이 늘어날 수 있습니다.
따라서 원본 청크와 배경이 추가된 청크를 같은 평가 세트에서 비교해야 합니다.
청크가 겹치는 범위도 함께 봐야 합니다
청크가 나뉘는 경계에서 정보가 끊기는 문제를 줄이기 위해 앞뒤 청크의 일부 내용을 겹치게 만들 수 있습니다.
이를 Chunk Overlap이라고 합니다.
청크 1:
문장 A
문장 B
문장 C
청크 2:
문장 C
문장 D
문장 E
문장 C가 두 청크에 함께 들어 있습니다. 경계에 걸린 정보가 어느 한쪽 청크에 남을 가능성이 커집니다.
하지만 겹치는 범위를 크게 설정하면 중복 청크가 늘어납니다.
- 검색 결과에 거의 같은 청크가 여러 개 나올 수 있습니다.
- 같은 내용을 반복해서 임베딩하게 됩니다.
- 벡터 저장 공간이 늘어납니다.
- LLM에 같은 내용이 여러 번 전달될 수 있습니다.
- 서로 다른 문서를 전달할 공간이 줄어듭니다.
따라서 청크 크기만 바꾸고 겹치는 범위를 함께 바꾸면 결과를 해석하기 어렵습니다.
청크 크기 500, 겹침 50
청크 크기 300, 겹침 100
이 두 결과가 달라졌다면 청크 크기 때문인지 겹치는 범위 때문인지 알 수 없습니다.
청크 크기를 비교할 때는 겹치는 범위를 동일하게 유지하거나, 비율을 동일하게 유지하는 등 명확한 실험 기준을 정해야 합니다.
어떤 방식이 가장 좋은지는 미리 정할 수 없습니다
청킹 방식을 정리하면 다음과 같습니다.
| 방식 | 나누는 기준 | 장점 | 주의할 점 |
| 고정 크기 | 글자 수 또는 토큰 수 | 단순하고 예측하기 쉬움 | 문장과 문맥이 끊길 수 있음 |
| 문장·문단 기준 | 자연스러운 문서 구조 | 읽기 쉬운 문맥 유지 | 청크 크기가 불규칙할 수 있음 |
| 의미 기반 | 문장 사이의 의미 변화 | 한 청크의 주제가 명확해질 수 있음 | 계산 비용과 기준값 조정 필요 |
| Parent-Child | 작은 검색 단위와 큰 전달 단위 분리 | 검색 정확도와 문맥을 함께 고려 | 저장 구조와 중복 처리 복잡 |
| 문맥 추가 | 제목·섹션·요약을 청크에 추가 | 떨어진 청크의 배경 보완 | 생성 비용과 잘못된 설명 위험 |
이 표만 보고 프로젝트에 가장 좋은 방식을 고를 수는 없습니다. 문서와 질문의 성격이 다르기 때문입니다.
FAQ
→ 하나의 질문과 답변이 짧게 묶여 있음
사내 규정
→ 적용 대상, 조건과 예외가 중요함
재무 보고서
→ 제목, 회사명, 기간과 표가 중요함
기술 문서
→ 장과 절의 계층 구조가 중요함
소스 코드
→ 함수와 클래스 구조가 중요함
같은 방식이 모든 문서에서 동일하게 동작하지 않습니다. 그래서 청킹 전략은 감으로 선택하기보다 평가 데이터로 비교해야 합니다.
청킹 방식만 다르게 비교해야 합니다
청킹 방식을 비교할 때는 나머지 조건을 동일하게 유지해야 합니다.
기준 구성
청킹 방식: 고정 크기
청크 크기: 500토큰
겹치는 범위: 50토큰
임베딩 모델: 동일
검색 방식: 하이브리드 검색
검색 결과 수: 10개
Reranker: 동일
LLM 전달 청크 수: 5개
LLM과 프롬프트: 동일
비교 구성
청킹 방식: 의미 기반
최대 청크 크기: 500토큰
임베딩 모델: 동일
검색 방식: 하이브리드 검색
검색 결과 수: 10개
Reranker: 동일
LLM 전달 청크 수: 5개
LLM과 프롬프트: 동일
두 구성에서 달라지는 핵심은 문서를 나누는 방식입니다. 다음 조건도 동일해야 합니다.
| 항목 | 동일하게 유지할 내용 |
| 원본 문서 | 같은 문서와 같은 버전 |
| 문서 파싱 | 같은 방식으로 추출한 텍스트 |
| 평가 질문 | 같은 질문 세트 |
| 기대 답변 | 같은 정답 기준 |
| 임베딩 모델 | 같은 모델과 차원 |
| 검색 방식 | 같은 벡터·키워드 검색 설정 |
| 검색 K | 같은 상위 결과 수 |
| Reranker | 같은 모델과 후보 수 |
| LLM | 같은 모델과 생성 설정 |
| 프롬프트 | 같은 답변 지시문 |
문서 파싱 방식까지 같아야 하는 이유도 중요합니다. 한 실험에서는 표를 정상적으로 추출하고, 다른 실험에서는 표의 값이 빠졌다면 청킹 결과를 공정하게 비교할 수 없습니다.
500자는 모두 같은 500이 아닙니다
청크 크기를 기록할 때 단위도 명확히 해야 합니다. 도구에 따라 청크 크기가 다음 중 하나일 수 있습니다.
문자 수
단어 수
토큰 수
문장 수
chunk_size=500이라고만 기록하면 무엇을 의미하는지 알기 어렵습니다.
500자
500단어
500토큰
세 값은 서로 완전히 다릅니다. 특히 LLM과 임베딩 모델의 입력 한도를 고려한다면 토큰 단위를 사용하는 것이 편리할 수 있습니다.
반면 문자 단위는 구현과 확인이 간단합니다. 어떤 단위를 사용하든 모든 실험에서 같은 기준을 적용하고 기록해야 합니다.
experiment = {
"chunk_strategy": "recursive",
"chunk_size": 500,
"chunk_size_unit": "tokens",
"chunk_overlap": 50,
"chunk_overlap_unit": "tokens",
}
한국어와 영어가 함께 있는 문서에서는 같은 문자 수라도 토큰 수가 다르게 나올 수 있습니다.
다국어 문서를 다룬다면 실제 임베딩 모델의 토크나이저를 기준으로 청크 길이 분포를 함께 확인하는 것이 좋습니다.
정답 청크 ID로만 평가하면 비교하기 어렵습니다
청킹 방식을 바꾸면 청크의 개수와 ID도 모두 달라집니다. 고정 크기 방식에서는 정답이 다음 청크에 들어 있을 수 있습니다.
fixed-chunk-17
문장 기준으로 다시 나누면 같은 정답이 다른 청크에 들어갑니다.
sentence-chunk-23
의미 기반 청킹에서는 또 다른 ID가 만들어집니다.
semantic-chunk-12
기존 평가 데이터에 fixed-chunk-17만 정답으로 저장해두었다면 다른 청킹 방식을 제대로 평가할 수 없습니다.
따라서 청킹 전략을 비교할 때는 정답을 특정 청크 ID에만 연결하지 않는 것이 좋습니다.
대신 원본 문서 안에서 정답 근거가 있는 위치를 저장할 수 있습니다.
ground_truth = {
"question": "입사 1년 미만 직원의 연차는 어떻게 발생하나요?",
"document_id": "leave-policy-2026",
"evidence_spans": [
{
"start": 1240,
"end": 1318,
"text": (
"입사 1년 미만인 직원은 1개월을 개근할 때마다 "
"1일의 유급휴가가 발생합니다."
),
}
],
}
각 청킹 방식에서 생성된 청크가 이 정답 위치를 포함하는지 확인합니다.
고정 크기 청크가 정답 구간을 포함하는가?
문장 기준 청크가 정답 구간을 포함하는가?
의미 기반 청크가 정답 구간을 포함하는가?
이렇게 하면 청크 ID가 달라져도 같은 근거를 기준으로 비교할 수 있습니다. 원본 위치를 유지하기 어려운 경우에는 다음 정보를 함께 저장할 수 있습니다.
원본 문서 ID
장과 절의 경로
정답 근거 문장
표의 행과 열 식별자
고유한 문단 ID
청킹 방식에 종속되지 않는 정답 기준을 만드는 것이 중요합니다.
검색 Recall만 보면 충분할까요?
청킹 방식을 비교할 때 가장 먼저 확인할 수 있는 지표는 Recall@K입니다.
전체 정답 근거 중 상위 K개 검색 결과에서 몇 개를 찾았는가?
하지만 청킹에서는 Recall 하나만으로 충분하지 않습니다. 작은 청크가 정답 문장의 일부만 포함해도 관련 청크로 표시될 수 있기 때문입니다. 예를 들어 정답에 다음 세 가지가 필요하다고 하겠습니다.
- 적용 대상
- 발생 조건
- 예외 조건
상위 5개 결과에서 적용 대상만 찾았다면 정답 근거의 일부는 검색했습니다. 하지만 정확한 답변을 만들기에는 부족합니다.
그래서 청킹 평가에서는 여러 관점의 지표를 함께 봐야 합니다.
정답 근거를 검색했는지 확인합니다
가장 기본적인 지표는 Hit@K 또는 Recall@K입니다.
Hit@K
상위 K개의 청크 중 정답 근거가 포함된 청크가 하나라도 있는지 확인합니다.
Hit@5 = 1
→ 상위 5개에 정답 근거가 하나 이상 있음
Hit@5 = 0
→ 상위 5개에 정답 근거가 없음
정답 근거가 하나인 단순한 질문에서 이해하기 쉬운 지표입니다.
Recall@K
정답 근거가 여러 개라면 전체 근거 중 몇 개를 검색했는지 확인합니다.
필요한 근거: 4개
상위 5개에서 찾은 근거: 3개
Recall@5
= 3 / 4
= 0.75
여러 문단이나 문서가 필요한 질문에서는 Recall@K가 중요합니다.
한 청크에 필요한 문맥이 충분한지 확인합니다
정답 문장이 검색되었더라도 청크가 너무 작으면 조건이나 예외가 빠질 수 있습니다. 이를 확인하기 위해 문맥 완전성을 별도로 평가할 수 있습니다.
예를 들어 질문마다 반드시 포함되어야 하는 핵심 사실을 표시합니다.
required_facts = [
"입사 1년 미만 직원",
"1개월 개근",
"1일의 유급휴가",
"결근한 달에는 발생하지 않음",
]
검색된 청크들이 이 사실을 얼마나 포함하는지 계산하거나 사람이 검수할 수 있습니다.
4개 사실 중 4개 포함
→ 문맥 완전성 1.0
4개 사실 중 2개 포함
→ 문맥 완전성 0.5
이 지표는 정해진 표준 이름이 있다기보다 프로젝트에서 정의하는 평가 항목에 가깝습니다. 다음과 같이 단순하게 분류할 수도 있습니다.
완전함
→ 청크만으로 정답을 만들 수 있음
부분적임
→ 핵심 근거는 있지만 조건이나 예외가 빠짐
불충분함
→ 정답의 일부만 포함
관련 없음
→ 답변에 사용할 수 없음
중요한 것은 모든 평가자가 같은 기준을 사용하도록 구체적인 예시를 준비하는 것입니다.
관련 없는 내용이 얼마나 들어오는지 확인합니다
청크가 커지면 정답 근거와 함께 관련 없는 내용도 많이 포함될 수 있습니다. Precision@K는 상위 K개의 청크 중 질문과 관련 있는 청크의 비율을 보여줍니다.
하지만 청크 전체가 관련 있다고 표시되어도 내부에 불필요한 문장이 많을 수 있습니다. 그래서 다음과 같은 운영 지표를 추가할 수 있습니다.
- 청크에서 정답 근거가 차지하는 비율
- 검색된 전체 토큰 중 질문과 관련 있는 토큰 비율
- LLM에 전달된 문맥 중 실제 답변에 사용된 부분
이런 값은 자동으로 정확하게 계산하기 어렵습니다. 평가 샘플 일부를 사람이 검토하거나 LLM 평가와 함께 사용할 수 있습니다.
큰 청크가 검색 Recall은 높지만 불필요한 내용도 지나치게 많이 가져오는지 확인하기 위한 항목입니다.
중복된 청크가 얼마나 검색되는지 확인합니다
겹치는 범위가 크면 검색 결과에 비슷한 청크가 여러 개 나올 수 있습니다.
1위: 입사 1년 미만 직원은 1개월 개근 시
1일의 유급휴가가 발생합니다.
2위: 1개월 개근 시 1일의 유급휴가가 발생합니다.
다만 결근한 달에는 발생하지 않습니다.
3위: 다만 결근한 달에는 유급휴가가 발생하지 않습니다.
세 청크가 모두 관련 있지만 사실상 같은 내용이 반복되고 있습니다.
상위 5개의 자리 중 3개를 비슷한 청크가 차지하면 다른 정답 문서를 가져올 공간이 줄어듭니다.
중복 비율은 프로젝트에 맞게 정의할 수 있습니다.
예를 들어 청크 간 단어 집합의 유사도를 계산하거나, 같은 원본 구간이 반복해서 포함된 비율을 측정할 수 있습니다.
상위 5개 중 거의 같은 청크가 2개
→ 중복 청크 비율 40%
정해진 하나의 표준 공식이 있는 것은 아닙니다.
따라서 중복을 판정하는 기준값과 계산 방법을 실험 결과에 함께 기록해야 합니다.
청크 수와 임베딩 비용을 확인합니다
청크가 작아질수록 하나의 문서에서 생성되는 청크 수가 늘어납니다.
10,000토큰 문서
청크 크기 1,000토큰
→ 약 10개 청크
청크 크기 500토큰
→ 약 20개 청크
청크 크기 200토큰
→ 약 50개 청크
겹치는 범위까지 적용하면 청크 수는 더 늘어날 수 있습니다. 청크가 많아지면 다음 비용이 증가합니다.
- 임베딩 생성 요청 수
- 벡터 저장 공간
- 색인 생성 시간
- 문서 변경 시 재처리 비용
- 검색해야 하는 후보 수
의미 기반 청킹이나 청크 설명 생성 방식은 문서를 나누는 과정에도 임베딩 또는 LLM 호출이 추가될 수 있습니다. 그래서 다음 항목을 함께 기록하는 것이 좋습니다.
- 생성된 전체 청크 수
- 평균·최소·최대 청크 길이
- 전체 임베딩 토큰 수
- 인덱스 저장 크기
- 문서 처리 시간
- 문서 한 건당 처리 비용
검색 점수가 조금 좋아졌지만 청크 수가 몇 배 증가했다면 운영 비용을 감수할 가치가 있는지 판단해야 합니다.
LLM에 전달되는 토큰도 확인합니다
검색된 청크 수가 같아도 청크 크기가 다르면 LLM에 전달되는 전체 토큰 수가 달라집니다.
청크 크기 300토큰 × 5개
→ 최대 약 1,500토큰
청크 크기 800토큰 × 5개
→ 최대 약 4,000토큰
큰 청크는 더 많은 문맥을 담지만 입력 비용과 응답 시간이 증가할 수 있습니다. 작은 청크라도 겹치는 내용이 많으면 실제 정보량에 비해 토큰을 낭비할 수 있습니다. 따라서 다음 항목을 확인합니다.
- 질문당 평균 검색 청크 수
- 질문당 평균 문맥 토큰 수
- 중복 제거 전후 토큰 수
- LLM 입력 비용
- 평균 응답 시간
- p95 응답 시간
검색 성능이 비슷하다면 더 적은 토큰으로 같은 답변 품질을 내는 구성이 운영에 유리할 수 있습니다.
최종 답변도 함께 평가해야 합니다
검색 Recall이 높아졌다고 해서 최종 답변이 반드시 좋아지는 것은 아닙니다. 큰 청크가 정답 근거를 포함했지만 관련 없는 규정도 함께 포함해 LLM이 혼동할 수 있습니다.
작은 청크가 정확한 문장을 검색했지만 예외 조건이 빠져 불완전한 답변을 만들 수도 있습니다. 그래서 다음 지표를 함께 확인해야 합니다.
- 최종 답변 정확도
- 기대 답변의 핵심 사실 포함 여부
- 답변과 근거의 일치 여부
- 문서에 없는 내용을 추가했는지
- 답변할 수 없는 질문에 적절히 대응했는지
청킹 전략은 검색 단계의 설정이지만 최종 목표는 사용자가 정확한 답변을 받는 것입니다. 검색 지표와 답변 지표를 함께 봐야 합니다.
비교할 항목을 정리해보겠습니다
청킹 전략을 평가할 때 다음 항목을 비교할 수 있습니다.
| 평가 항목 | 확인하는 내용 |
| Hit@K | 정답 근거가 상위 K개에 하나라도 있는가 |
| Recall@K | 필요한 정답 근거를 얼마나 찾았는가 |
| Precision@K | 상위 K개가 관련 청크로 얼마나 채워졌는가 |
| 문맥 완전성 | 조건·결론·예외가 충분히 포함되었는가 |
| 중복 청크 비율 | 비슷한 청크가 반복해서 검색되는가 |
| 청크 수 | 색인해야 할 문서 조각이 얼마나 생겼는가 |
| 임베딩 토큰 | 문서 저장 과정에서 처리한 토큰 수 |
| 문맥 토큰 | 질문마다 LLM에 전달되는 토큰 수 |
| 답변 정확도 | 최종 답변이 기대 답변과 맞는가 |
| 근거 일치 여부 | 답변이 검색된 문서에 근거하는가 |
| 처리 시간 | 색인과 검색·답변 시간이 얼마나 걸리는가 |
| 비용 | 임베딩과 LLM 호출 비용이 얼마나 발생하는가 |
모든 지표를 반드시 사용해야 하는 것은 아닙니다. 다만 검색 Recall 하나만 보고 청킹 방식을 선택하면 문맥 누락이나 비용 증가를 놓칠 수 있습니다.
가상의 실험 결과를 살펴보겠습니다
다음 표는 설명을 위해 만든 가상의 결과입니다. 모든 실험에서 같은 문서, 평가 질문, 임베딩 모델, 검색 방식과 LLM을 사용했다고 가정하겠습니다.
| 청킹 방식 | Recall@5 | 문맥 완전성 | 중복 비율 | 전체 청크 수 | 평균 문맥 토큰 | 답변 정확도 |
| 고정 200토큰 | 0.69 | 0.58 | 0.22 | 48,000 | 920 | 0.61 |
| 고정 500토큰 | 0.78 | 0.75 | 0.14 | 21,000 | 2,180 | 0.73 |
| 고정 1,000토큰 | 0.76 | 0.86 | 0.08 | 11,500 | 3,940 | 0.70 |
| 문장·문단 기준 | 0.81 | 0.82 | 0.10 | 18,400 | 2,260 | 0.77 |
| 의미 기반 | 0.80 | 0.84 | 0.09 | 17,900 | 2,410 | 0.77 |
| Parent-Child | 0.83 | 0.91 | 0.12 | 35,000 | 2,850 | 0.82 |
| 문맥 추가 | 0.85 | 0.84 | 0.11 | 21,000 | 2,420 | 0.81 |
이 표에서 단순히 가장 높은 Recall만 보면 문맥 추가 방식이 좋아 보입니다. 하지만 각 방식의 특징을 함께 살펴봐야 합니다.
작은 청크는 문맥을 자주 놓쳤습니다
200토큰 청크는 전체 청크 수가 가장 많습니다. 검색 결과가 세밀해질 것처럼 보였지만 Recall과 문맥 완전성이 모두 낮게 나왔습니다.
Recall@5: 0.69
문맥 완전성: 0.58
답변 정확도: 0.61
적용 대상과 조건, 예외 조항이 서로 다른 청크로 분리되었을 가능성을 살펴볼 수 있습니다. 중복 비율도 가장 높습니다.
청크가 작기 때문에 경계 문제를 줄이려고 겹치는 범위를 사용했고, 비슷한 청크가 상위 결과를 여러 자리 차지했을 수 있습니다.
이 실험에서는 작은 청크가 검색을 더 정확하게 만들기보다 정답 문맥을 잘게 흩어놓았습니다.
큰 청크는 문맥은 충분했지만 불필요한 글도 늘었습니다
1,000토큰 청크는 문맥 완전성이 0.86으로 높습니다. 조건과 예외가 한 청크 안에 함께 들어갈 가능성이 높기 때문입니다.
하지만 답변 정확도는 500토큰이나 문단 기준 방식보다 낮았습니다.
평균 문맥 토큰: 3,940
답변 정확도: 0.70
관련 없는 내용이 함께 전달되면서 LLM이 비슷한 규정을 혼동했을 수 있습니다. 한정된 문맥 안에 넣을 수 있는 문서 수도 줄었을 가능성이 있습니다. 큰 청크가 더 많은 정보를 제공했지만 그 정보가 모두 도움이 된 것은 아닙니다.
의미 기반 방식이 압도적으로 좋지는 않았습니다
의미 기반 방식은 고정 500토큰보다 문맥 완전성이 높고 중복 비율이 조금 낮았습니다.
하지만 답변 정확도는 문장·문단 기준 방식과 같았습니다.
문장·문단 기준 답변 정확도: 0.77
의미 기반 답변 정확도: 0.77
의미 기반 청킹에는 추가 임베딩 계산과 경계 결정 과정이 필요합니다. 성능이 비슷하다면 더 단순하고 운영하기 쉬운 문장·문단 기준 방식을 선택할 수도 있습니다. 복잡한 방식이 반드시 더 좋은 것은 아닙니다.
Parent-Child 방식은 문맥 완전성이 높았습니다
Parent-Child 방식은 작은 하위 청크로 검색하고 더 큰 상위 청크를 전달합니다. 그래서 검색 Recall과 문맥 완전성이 모두 높게 나왔습니다.
Recall@5: 0.83
문맥 완전성: 0.91
답변 정확도: 0.82
하지만 저장해야 할 청크 수가 많고 구조가 복잡합니다. 하위 청크와 상위 청크를 모두 관리해야 하기 때문입니다.
정확도 개선이 운영 복잡성과 저장 비용을 감수할 만큼 중요한지 판단해야 합니다.
문맥을 추가하자 검색 Recall이 높아졌습니다
문서 제목과 섹션 정보를 청크 앞에 추가한 방식은 Recall@5가 가장 높았습니다. 원래 청크만 보면 알 수 없었던 회사명, 기간과 문서 종류가 검색에 반영되었을 수 있습니다.
하지만 모든 청크에 배경 설명을 생성하고 관리해야 합니다. 생성된 설명이 정확한지 확인하는 과정도 필요합니다.
이 표의 숫자는 설명을 위한 예시일 뿐입니다. 실제 결과는 문서 종류, 질문 유형, 임베딩 모델과 검색 방식에 따라 달라집니다.
중요한 것은 특정 방식이 가장 좋다는 결론이 아니라 여러 지표를 함께 보고 선택하는 과정입니다.
질문 유형별로 결과를 나눠봐야 합니다
전체 평균에서는 두 청킹 방식의 차이가 작아 보일 수 있습니다.
하지만 질문 유형별로 보면 서로 다른 결과가 나올 수 있습니다.
| 질문 유형 | 200토큰 | 500토큰 | 문단 기준 | Parent-Child |
| 단순 사실 질문 | 0.82 | 0.84 | 0.85 | 0.85 |
| 조건이 포함된 질문 | 0.56 | 0.76 | 0.81 | 0.88 |
| 예외 조항 질문 | 0.44 | 0.70 | 0.79 | 0.86 |
| 비교 질문 | 0.61 | 0.72 | 0.75 | 0.80 |
| 여러 문서가 필요한 질문 | 0.58 | 0.74 | 0.73 | 0.76 |
| 표와 숫자 질문 | 0.65 | 0.69 | 0.70 | 0.71 |
200토큰 방식은 단순한 사실 질문에서는 비교적 잘 동작했습니다. 답이 한 문장에 들어 있다면 작은 청크로도 충분하기 때문입니다.
하지만 조건과 예외가 포함된 질문에서는 성능이 크게 낮아졌습니다. 필요한 내용이 여러 청크로 나뉘었기 때문일 수 있습니다.
Parent-Child 방식은 조건과 예외 질문에서 강점을 보였습니다. 작은 단위로 정확한 부분을 찾고, 더 큰 문맥을 LLM에 전달했기 때문이라고 해석할 수 있습니다. 평균 점수 하나만 보면 이런 차이를 놓칠 수 있습니다.
검색에 실패한 질문을 직접 확인해야 합니다
점수만으로는 왜 특정 청킹 방식이 실패했는지 알기 어렵습니다. 실패한 질문을 모아 다음 항목을 확인해야 합니다.
- 정답 근거가 하나의 청크에 들어 있었는가?
- 조건과 결론이 서로 다른 청크로 나뉘었는가?
- 제목과 본문이 분리되었는가?
- 표의 열 이름과 값이 분리되었는가?
- 정답 청크에 질문과 연결할 단어가 남아 있었는가?
- 중복 청크가 상위 결과를 차지했는가?
- 검색은 되었지만 최종 전달에서 제외되었는가?
예를 들어 작은 청크에서 실패한 질문 30개를 분석했더니 다음과 같은 결과가 나올 수 있습니다.
| 실패 원인 | 질문 수 |
| 조건과 결론 분리 | 11개 |
| 제목과 본문 분리 | 6개 |
| 예외 조항 누락 | 5개 |
| 표 구조 손상 | 4개 |
| 검색 표현 부족 | 3개 |
| 기타 | 1개 |
이 결과에서는 임베딩 모델을 바꾸기보다 청크 경계를 먼저 개선하는 것이 효과적일 수 있습니다.
정답 근거 위치로 Recall을 계산해보기
청킹 방식이 달라져도 같은 정답 기준을 사용할 수 있도록 원본 문서의 위치를 저장해보겠습니다.
from dataclasses import dataclass
@dataclass(frozen=True)
class Chunk:
chunk_id: str
document_id: str
start: int
end: int
text: str
@dataclass(frozen=True)
class EvidenceSpan:
document_id: str
start: int
end: int
두 구간이 얼마나 겹치는지 계산하는 함수를 만듭니다.
def overlap_length(
chunk: Chunk,
evidence: EvidenceSpan,
) -> int:
if chunk.document_id != evidence.document_id:
return 0
overlap_start = max(chunk.start, evidence.start)
overlap_end = min(chunk.end, evidence.end)
return max(0, overlap_end - overlap_start)
청크가 정답 근거의 일정 비율 이상을 포함하면 해당 근거를 찾은 것으로 처리할 수 있습니다.
def covers_evidence(
chunk: Chunk,
evidence: EvidenceSpan,
minimum_coverage: float = 0.8,
) -> bool:
evidence_length = evidence.end - evidence.start
if evidence_length <= 0:
return False
covered_length = overlap_length(chunk, evidence)
coverage = covered_length / evidence_length
return coverage >= minimum_coverage
상위 K개 청크에서 정답 근거를 얼마나 찾았는지 계산합니다.
def evidence_recall_at_k(
ranked_chunks: list[Chunk],
evidence_spans: list[EvidenceSpan],
k: int,
minimum_coverage: float = 0.8,
) -> float:
if not evidence_spans:
return 0.0
top_k = ranked_chunks[:k]
found_count = 0
for evidence in evidence_spans:
found = any(
covers_evidence(
chunk,
evidence,
minimum_coverage,
)
for chunk in top_k
)
if found:
found_count += 1
return found_count / len(evidence_spans)
이 방식은 정답 근거가 청크에 아주 조금 걸쳐 있기만 해도 정답으로 처리되는 문제를 줄일 수 있습니다.
다만 문자 위치가 정확하게 유지되는 문서에서 사용할 수 있습니다.
문서를 정리하거나 표를 변환하면서 원본 위치가 달라진다면 문단 ID나 근거 문장 해시 등 다른 식별 방식을 사용해야 합니다.
청크 자체의 품질도 검사할 수 있습니다
검색 전에 생성된 청크의 상태부터 확인할 수 있습니다. 다음 항목은 코드로 일부 자동 검사할 수 있습니다.
지나치게 짧은 청크가 많은가?
최대 길이를 넘은 청크가 있는가?
제목만 있고 본문이 없는 청크가 있는가?
같은 청크가 중복 생성되었는가?
문장 중간에서 시작하거나 끝나는 청크가 많은가?
표의 열 이름이 사라진 청크가 있는가?
간단한 검사 함수를 만들어보겠습니다.
from collections import Counter
def validate_chunks(
chunks: list[Chunk],
minimum_length: int = 50,
maximum_length: int = 2_000,
) -> list[str]:
errors: list[str] = []
normalized_texts: Counter[str] = Counter()
for chunk in chunks:
text = chunk.text.strip()
normalized = " ".join(text.lower().split())
if len(text) < minimum_length:
errors.append(
f"{chunk.chunk_id}: 청크가 너무 짧습니다."
)
if len(text) > maximum_length:
errors.append(
f"{chunk.chunk_id}: 청크가 최대 길이를 넘었습니다."
)
if not text:
errors.append(
f"{chunk.chunk_id}: 내용이 비어 있습니다."
)
normalized_texts[normalized] += 1
for text, count in normalized_texts.items():
if text and count > 1:
errors.append(
f"동일한 내용의 청크가 {count}개 있습니다: "
f"{text[:50]}"
)
return errors
이 검사는 청크의 의미가 자연스러운지까지 판단하지는 못합니다. 하지만 빈 청크, 지나치게 짧은 청크와 완전히 중복된 청크 같은 기본적인 문제를 빠르게 찾는 데 도움이 됩니다.
실험 설정과 결과를 함께 저장해야 합니다
청킹 실험은 문서를 다시 나누고 임베딩도 다시 생성해야 하므로 결과를 재현하기 어렵습니다. 설정과 데이터 버전을 함께 저장하는 것이 좋습니다.
experiment_result = {
"experiment_id": "chunk-exp-007",
"document_version": "knowledge-base-2026-07-20",
"evaluation_dataset_version": "eval-2026-07-15",
"chunking": {
"strategy": "recursive",
"size": 500,
"size_unit": "tokens",
"overlap": 50,
"overlap_unit": "tokens",
"include_title": True,
},
"retrieval": {
"type": "hybrid",
"candidate_k": 20,
"reranker": "reranker-a",
"context_k": 5,
},
"metrics": {
"evidence_recall_at_5": 0.78,
"context_completeness": 0.75,
"duplicate_chunk_ratio": 0.14,
"answer_accuracy": 0.73,
"average_context_tokens": 2180,
"indexed_chunk_count": 21000,
},
}
특히 다음 항목을 빠뜨리지 않는 것이 좋습니다.
- 원본 문서 버전
- 문서 파싱 방식
- 청킹 방식과 라이브러리 버전
- 청크 크기의 단위
- 겹치는 범위
- 제목과 메타데이터 포함 여부
- 임베딩 모델
- 검색 방식과 K
- 평가 데이터 버전
청킹 방식만 바꾸었다고 생각했지만 라이브러리 버전이나 토크나이저가 달라져 결과가 바뀔 수도 있습니다.
문서 종류에 따라 시작점을 다르게 잡을 수 있습니다
하나의 청킹 방식을 모든 문서에 적용할 필요는 없습니다.
FAQ와 짧은 문서
FAQ는 하나의 질문과 답변이 이미 자연스러운 단위로 구성되어 있습니다.
질문 하나 + 답변 하나
→ 청크 하나
굳이 일정한 크기로 다시 나누기보다 기존 구조를 유지하는 편이 자연스러울 수 있습니다.
사내 규정과 정책 문서
규정 문서는 적용 대상, 조건과 예외가 중요합니다.
장 → 조 → 항
문서의 계층 구조를 유지하고, 필요하다면 작은 조항을 검색한 뒤 상위 항목을 전달하는 방식을 검토할 수 있습니다.
기술 문서와 매뉴얼
기술 문서는 제목, 소제목, 코드와 설명의 관계가 중요합니다. 코드 블록과 그 앞의 설명이 서로 다른 청크로 분리되면 의미를 이해하기 어려울 수 있습니다. Markdown이나 HTML 구조를 활용하는 방식이 도움이 될 수 있습니다.
재무 보고서
회사명, 보고 기간, 표의 제목과 단위가 중요합니다.
35
라는 값만 남으면 의미가 없습니다.
A사 2025년 2분기 영업이익: 35억 원
처럼 배경을 함께 유지해야 합니다. 문서 제목과 섹션 정보를 청크에 포함하거나 표를 별도의 구조로 변환하는 방법을 검토할 수 있습니다.
표와 스프레드시트
표는 단순한 글자 수로 자르면 열 이름과 값의 관계가 쉽게 깨집니다.
구분 | 국내 | 해외
지원금 | 50,000원 | 100,000원
행이나 열의 의미를 유지하는 구조 기반 처리가 필요할 수 있습니다. 청크 크기를 조정하기 전에 파싱된 표가 올바르게 표현되었는지 먼저 확인해야 합니다.
자주 하는 실수
청크 크기와 겹치는 범위를 동시에 바꿉니다
두 조건이 함께 달라지면 점수 변화의 원인을 알기 어렵습니다.
한 번의 실험에서는 하나의 조건만 바꾸는 것이 좋습니다.
서로 다른 K로 비교합니다
200토큰 방식은 상위 10개를 보고, 500토큰 방식은 상위 5개를 보면 공정한 비교가 아닙니다.
같은 K를 사용해야 합니다. 다만 같은 K라도 전달되는 전체 토큰 수가 크게 다를 수 있으므로 토큰 예산을 기준으로 한 비교도 별도로 진행할 수 있습니다.
비교 1
→ 모두 상위 5개 전달
비교 2
→ 모두 최대 3,000토큰 전달
두 관점의 결과를 함께 보면 좋습니다.
정답 청크 ID를 그대로 사용합니다
청킹 방식이 바뀌면 청크 ID도 달라집니다. 원본 문서의 근거 위치나 근거 문장을 기준으로 평가 데이터를 구성해야 합니다.
검색 Recall만 확인합니다
정답 청크는 검색했지만 조건과 예외가 빠질 수 있습니다. 문맥 완전성과 최종 답변 정확도를 함께 봐야 합니다.
평균 청크 크기만 봅니다
평균이 500토큰이어도 실제 분포는 다음과 같을 수 있습니다.
- 대부분 100토큰 이하
- 일부 청크만 2,000토큰 이상
평균뿐 아니라 최소, 최대와 길이 분포를 확인해야 합니다.
청크 수와 비용을 기록하지 않습니다
점수가 조금 높아졌지만 전체 청크 수가 세 배가 되었다면 색인 비용과 저장 공간도 증가합니다. 운영 비용까지 함께 비교해야 합니다.
문서 종류를 구분하지 않습니다
FAQ, 규정, 표와 소스 코드는 구조가 다릅니다. 하나의 방식으로 전체 평균만 계산하면 특정 문서 유형에서 발생하는 문제가 가려질 수 있습니다.
중간 정리
| 확인할 내용 | 작은 청크 | 큰 청크 |
| 검색의 세밀함 | 높아질 수 있음 | 주제가 섞일 수 있음 |
| 문맥 유지 | 끊길 가능성이 있음 | 비교적 잘 유지됨 |
| 조건과 예외 | 여러 청크로 분리될 수 있음 | 함께 들어갈 가능성이 높음 |
| 불필요한 내용 | 적을 수 있음 | 많아질 수 있음 |
| 생성되는 청크 수 | 많음 | 적음 |
| 임베딩·저장 비용 | 늘어날 수 있음 | 줄어들 수 있음 |
| LLM 전달 토큰 | 청크당 적음 | 청크당 많음 |
| 여러 문서 전달 | 비교적 많이 전달 가능 | 전달 문서 수가 줄 수 있음 |
청킹 방식을 비교할 때는 다음 질문을 확인해야 합니다.
- 정답 근거가 상위 K개 안에 들어왔는가?
- 조건과 예외가 함께 포함되었는가?
- 비슷한 청크가 반복해서 검색되지는 않았는가?
- 하나의 문서에서 청크가 몇 개 만들어졌는가?
- 임베딩과 저장 비용은 얼마나 증가했는가?
- LLM에 전달되는 토큰은 얼마나 되는가?
- 최종 답변도 실제로 좋아졌는가?
- 청크 크기 하나만 보고 좋은 전략을 판단할 수는 없습니다.
마치며
문서를 잘게 나누면 검색이 더 정확해질 것처럼 보입니다. 하나의 청크에 담기는 주제가 줄어들고 질문과 직접 관련된 문장만 찾을 수 있기 때문입니다. 하지만 청크가 지나치게 작으면 정답에 필요한 내용도 함께 나뉩니다.
- 적용 대상은 청크 1
- 발생 조건은 청크 2
- 실제 결과는 청크 3
- 예외 조항은 청크 4
검색 결과에서 일부 청크만 빠져도 불완전하거나 잘못된 답변이 만들어질 수 있습니다. 반대로 청크가 너무 크면 조건과 예외는 잘 보존되지만 질문과 관련 없는 내용까지 함께 검색됩니다. LLM에 전달되는 토큰이 늘어나고 비슷한 규정이나 다른 기간의 정보를 혼동할 수도 있습니다.
그래서 청킹 전략에는 모든 RAG에 적용되는 하나의 정답이 없습니다.
- 고정 크기 방식이 단순하다고 나쁜 것도 아닙니다.
- 의미 기반 방식이 복잡하다고 항상 좋은 것도 아닙니다.
- 작은 청크가 정확하다고 항상 충분한 것도 아닙니다.
- 큰 청크가 문맥이 많다고 항상 유용한 것도 아닙니다.
문서와 질문의 특성에 따라 직접 비교해야 합니다.
검색 Recall뿐 아니라 문맥의 완전성, 중복 비율, 청크 수, 임베딩 비용, LLM에 전달되는 토큰과 최종 답변 정확도를 함께 확인해야 하죠.
특히 청킹 방식을 바꿀 때는 정답 청크 ID만으로 평가하면 안 됩니다. 청크의 경계와 ID가 모두 달라지기 때문에, 원본 문서 안의 정답 근거 위치나 근거 문장을 기준으로 평가해야 합니다. 결국 좋은 청크는 단순히 작거나 큰 청크가 아닙니다.
검색할 때는 질문과 충분히 가까우면서, 답변을 만들 때는 필요한 문맥을 잃지 않는 청크입니다.
문서를 어떻게 나누느냐는 답변을 만들기 전의 작은 전처리 설정처럼 보일 수 있습니다.
하지만 정답 근거가 어떤 모습으로 저장되고 검색되는지를 결정한다는 점에서, 청킹은 RAG의 검색 결과와 최종 답변을 모두 바꿀 수 있는 중요한 요소입니다.
참고 자료
- LangChain 공식 문서 - Text Splitter Integrations
- LangChain 공식 문서 - Splitting Recursively
- LlamaIndex 공식 문서 - Semantic Splitter
- LlamaIndex 공식 문서 - Auto Merging Retriever
- Anthropic - Contextual Retrieval
- Late Chunking: Contextual Chunk Embeddings Using Long-Context Embedding Models
- Is Semantic Chunking Worth the Computational Cost?
- Document Segmentation Matters for Retrieval-Augmented Generation
- AutoChunker: Structured Text Chunking and its Evaluation
- Searching for Best Practices in Retrieval-Augmented Generation
'AI' 카테고리의 다른 글
| [바미] 운영 중인 RAG는 어떻게 디버깅할까? (0) | 2026.07.31 |
|---|---|
| [바미] RAG는 어떤 질문에 약할까? (0) | 2026.07.30 |
| [바미] 검색 결과 순서만 바꿨는데 답변이 좋아질까? (2) | 2026.07.28 |
| [바미] RAG가 좋아진 이유가 정말 그 설정 때문일까? (0) | 2026.07.27 |
| [바미] RAG는 왜 틀린 답을 할까? (0) | 2026.07.26 |