RAG · 검색증강생성수정 2026-08-07

RAG 파이프라인 (1) 왜 RAG인가 — 도큐먼트 로드와 텍스트 분할

RAG를 8단계로 나눠 전체 지도를 그리고, 사전처리의 첫 두 단계인 문서 로드와 텍스트 분할의 선택지·튜닝 파라미터·실패 모드를 정리한다.

LLM에 사내 문서를 붙이려 할 때 처음 부딪히는 벽은 모델이 아니다. "왜 이 답이 나왔는지 알 수 없다"는 것이다. 파일을 올려서 답이 이상하면 할 수 있는 일이 문서 형태를 바꿔 보는 것뿐인데, 사내 문서 전체의 형태를 바꾸는 것은 현실적으로 불가능하다.

RAG를 직접 구축한다는 것은 그 블랙박스를 8개의 조정 가능한 단계로 펼치는 일이다. 이 글은 그 8단계 지도를 먼저 그리고, 사전처리의 첫 두 단계인 도큐먼트 로드텍스트 분할을 단계별 선택지·튜닝 파라미터·실패 모드까지 다룬다. 이어지는 편에서는 임베딩부터 리랭커까지의 검색 계층을, 프롬프트부터 체인까지의 생성 계층을 다룬다.

용어 정리

용어원어
RAGRetrieval-Augmented Generation검색으로 찾은 외부 문서를 LLM 입력에 붙여 답변을 생성하는 방식. 검색 → 증강 → 생성
할루시네이션HallucinationLLM이 근거 없는 내용을 사실처럼 지어내는 현상
청크Chunk원본 문서를 검색·입력에 알맞게 자른 조각. 임베딩·저장·검색의 최소 단위
청크 크기chunk size한 청크에 담을 텍스트 길이(문자 수 또는 토큰 수)
청크 오버랩chunk overlap인접 청크끼리 겹쳐 두는 구간. 경계에서 문맥이 끊기는 것을 완화
임베딩Embedding텍스트를 고차원 실수 벡터로 바꿔 의미를 수치화한 표현
벡터스토어Vector Store임베딩 벡터를 저장·색인하고 유사도 검색을 제공하는 DB
검색기Retriever질문 벡터와 저장된 벡터를 비교해 관련 청크를 뽑아 오는 컴포넌트
리랭커Reranker검색기가 뽑은 후보를 더 정교한 모델로 재채점해 순위를 다시 매기는 2단계 컴포넌트
프롬프트Prompt지시사항 + 질문 + 검색된 문맥을 조합해 LLM에 넣는 최종 입력
컨텍스트Context프롬프트에 삽입되는 "검색된 문서 본문". RAG의 근거
체인Chain위 단계들을 하나의 실행 파이프라인으로 묶은 것
top-ktop k유사도 상위 k개 문서만 반환하는 것. k는 반환 개수 파라미터
OCROptical Character Recognition이미지·스캔 문서에서 글자를 인식해 텍스트로 바꾸는 기술

RAG를 쓰는 이유

순수 LLM의 한계

#문제설명
1최신 정보 부재학습 컷오프 이후 정보를 모른다
2내부 데이터 부재개인·회사 내부 문서는 애초에 학습에 포함되지 않았다
3도메인 질문 실패그래서 특정 도메인 질문에는 기대하는 답이 나오지 않는다
4할루시네이션문서를 업로드해도 답을 못 찾거나 지어낸다. 문서량이 많아질수록 심해진다

RAG를 적용하면 무엇이 달라지나

#개선설명
1최신 정보 기반 답변저장된 최신 문서를 검색해 답한다
2내부데이터 참조개인·사내 문서를 근거로 답변 가능
3지식 축적문서를 내부 DB에 쌓아 나가고, 그 DB에서 검색해 답변
4할루시네이션 감소답변의 출처를 DB에서 역으로 검색·검증하는 방식으로 환각을 줄인다

도달점은 방대한 지식 기반으로 답변하는 도메인 특화 챗봇이다. "서울특별시에 사는 특정 인물의 아버지 이름"처럼 내부 문서가 있어야만 답할 수 있는 질문이 이 구조가 필요한 전형적인 유형이다.

파일 업로드로 충분하지 않은 이유

상용 챗봇의 파일 업로드도 내부적으로 RAG를 쓴다. 결정적 차이는 과정이 열려 있는가에 있다.

도식을 탭하면 확대해서 볼 수 있습니다
비교 축내장 RAG직접 구축 RAG
과정 공개블랙박스. 어떤 과정으로 검색됐는지 알 수 없음전 과정을 직접 설계 → 단계별 분석·제어 가능
실패 원인 분석불가잘 나온 이유 / 못 찾은 이유를 추적 가능
개선 수단문서 형태 변경 정도청킹·임베딩·검색기·리랭커·프롬프트 전부 조정 가능
문서 세부 질의문서 내부의 구체적 질문에 답을 못 하거나 관련 정보를 못 찾는 현상 발생검색 결과를 확인하고 문제 단계를 특정해 고침

투명성과 해석가능성이 직접 구축의 핵심 가치다. 추적 도구를 붙이면 "무엇이 검색됐는지"와 "각 문서의 세부 내용"까지 눈으로 확인할 수 있고, 그래야 어느 단계를 고쳐야 하는지 특정된다.

할루시네이션을 어떻게 줄이나

기법동작
유효 정보 기반 답변 강제"주어진 문맥에서만 답하라, 없으면 모른다고 하라"를 프롬프트로 강제
출처 역추적답변 내용의 근거를 다시 주어진 문서에서 찾게 함

RAG는 할루시네이션을 없애는 방법이 아니라 줄이는 방법이다. 이 구분은 어휘 문제가 아니라 설계 문제다. 검색이 틀린 문서를 가져오면 RAG는 오히려 더 그럴듯한 오답을 만들어 낸다.

전체 파이프라인 — 사전처리 4단계와 런타임 4단계

RAG는 8단계로 구성되며, **사전 준비 단계(14)**와 **런타임 단계(58)**로 나뉜다. 이 구분이 전체의 뼈대다.

도식을 탭하면 확대해서 볼 수 있습니다

이 구분이 중요한 이유는 비용과 지연이 갈리기 때문이다. 앞의 넷은 배치로 한 번 돌고, 뒤의 넷은 질문마다 돈다. 임베딩 모델을 바꾸는 결정과 top-k를 바꾸는 결정은 비용 구조가 완전히 다르다.

단계별 산출물 흐름

단계입력출력실행 시점
1 로드파일·URL·API·DBDocument[] (본문 + 메타데이터)배치
2 분할Document[]잘게 쪼갠 Document[] (청크)배치
3 임베딩청크 텍스트float[] 벡터배치
4 저장벡터 + 원문 + 메타색인된 벡터 DB배치
5 검색질문 문자열관련 청크 top-k런타임
6 프롬프트질문 + 청크완성된 프롬프트 문자열런타임
7 LLM프롬프트응답 메시지런타임
8 체인위 전부최종 문자열/구조화 데이터런타임

각 단계를 도서관에서 공부하는 과정에 대응시키면 역할이 빠르게 잡힌다.

단계이름대응
1도큐먼트 로드책장에서 필요한 책들을 골라 오기
2텍스트 분할큰 책을 챕터별로 나누기
3임베딩책 내용을 요약해 핵심 키워드로 표현하기
4벡터스토어 저장요약 키워드를 색인화해 나중에 빨리 찾게 하기
5검색기질문에 가장 잘 맞는 챕터를 찾기
6프롬프트찾은 정보를 바탕으로 어떻게 물을지 정하기
7LLM수집한 정보로 보고서를 작성하는 학생
8체인 생성위 과정을 하나의 파이프라인으로 묶기

성능은 단계별 기법의 누적으로 오른다

RAG 성능은 한 방에 올라가지 않는다. 각 단계에 기법을 하나씩 적용할 때마다 점진적으로 향상된다.

그래서 "RAG 성능을 어떻게 올리나"라는 질문에는 단일 해법이 존재하지 않는다. 실무에서 답이 되는 것은 어느 단계의 문제인지 진단하고 그 단계의 레버를 조정하는 절차이고, 그 진단 절차는 실패 모드 종합 진단표에 정리했다.

단계 1 — 도큐먼트 로드

외부에 흩어져 있는 이질적 데이터를 시스템이 다룰 수 있는 단일 형식(Document)으로 수집·정제하는 단계다. 파이프라인의 첫 단추라 여기서의 정확성이 전체 출력 품질을 좌우한다.

도식을 탭하면 확대해서 볼 수 있습니다
하위 작업내용
1. 데이터 소스 선택어떤 종류의 데이터가 필요한지, 어디서 어떻게 수집할지 결정. 웹사이트·DB·API·공개 데이터셋 등
2. 데이터 수집API 호출·웹 스크래핑·DB 쿼리. 인증이나 접근 권한 설정이 필요할 수 있음
3. 필터링·전처리필요한 정보만 추출하고 정제. 불필요한 포맷팅 제거, 언어·문맥 필터 적용
4. 데이터 로드내부에서 쓸 형식으로 변환. 보통 메모리 내 자료구조로, 필요시 DB·파일시스템에 저장

소스별 로더 선택

데이터 유형대표 로더특징·주의점
PDFPyMuPDFLoader속도 빠르고 레이아웃 텍스트 추출 양호. 범용 기본값
PDF (표·이미지 많음)UnstructuredPDFLoader요소(제목·표·이미지) 단위 파싱. 느리지만 구조 보존
PDF (스캔본)OCR 병행텍스트 레이어가 없으면 OCR로 글자를 인식해야 함
Word·한글 문서Docx2txtLoader한글(HWP)은 별도 변환기가 필요한 경우가 많음
Excel·CSVCSVLoader, UnstructuredExcelLoader행 단위 Document화. 표 의미가 사라지지 않게 헤더를 함께 실어야 함
SQL TableDB 커넥터 기반 로더쿼리 결과를 Document로 변환
마크다운(.md)UnstructuredMarkdownLoader헤딩 구조를 살려 두면 뒤의 분할 단계가 쉬워짐
HTMLWebBaseLoader, UnstructuredHTMLLoader스크립트·네비게이션·광고 제거가 관건
웹 크롤링WebBaseLoader최신 뉴스 등 실시간성 자료
학술 논문ArxivLoader전문 지식·논문 수집

전처리에서 반드시 제거할 것

제거 대상이유
불필요한 이미지텍스트 파이프라인에 노이즈
그래프캡션 없이 들어오면 의미 없는 문자열 조각
License 표기모든 문서에 반복 등장해 검색 결과를 오염
광고·메타데이터답변에 포함되면 안 되는 정보

튜닝 포인트

포인트판단 기준
로더 선택속도(PyMuPDF) vs 구조 보존(Unstructured). 표가 답변 품질에 중요하면 후자
메타데이터 설계출처·페이지·작성일·부서 등을 이때 심어야 나중에 필터 검색·출처 표기가 가능
정제 강도과하게 지우면 근거 문장이 사라지고, 덜 지우면 반복 노이즈가 top-k를 잠식
증분 로드문서가 계속 늘어나는 환경이면 전체 재적재가 아니라 변경분만 적재하는 설계 필요

실패 모드

증상원인대응
검색은 되는데 답이 엉뚱헤더·푸터·License가 청크의 대부분을 차지전처리 규칙 추가
특정 문서만 절대 검색 안 됨스캔 PDF라 텍스트 레이어가 없음OCR 경로 분기
표 관련 질문에 전부 실패표가 줄바꿈된 텍스트로 뭉개짐구조 보존 로더 또는 표 전용 파싱
출처를 못 밝힘로드 시 메타데이터를 안 심음로더 단계에서 source·page 부여
# 단계 1: 문서 로드(Load Documents)
from langchain_community.document_loaders import PyMuPDFLoader

loader = PyMuPDFLoader("data/문서.pdf")
docs = loader.load()

단계 2 — 텍스트 분할

크고 복잡한 문서를 LLM이 받아들일 수 있는 작은 조각으로 나눠, 질문에 대해 필요한 정보만 압축·선별해 가져오게 만드는 단계다. "구글이 앤스로픽에 투자한 금액은 얼마인가" 같은 한 문장에 답하려고 보고서 전체를 LLM에 넣을 이유가 없다.

분할이 필요한 두 가지 이유

#이유설명
1핀포인트 정보 검색(정확성)세분화하면 질문과 연관 있는 정보만 가져올 수 있다. 각 단위가 특정 주제에 초점을 맞추므로 관련성이 높아진다
2리소스 최적화(효율성)전체 문서를 LLM에 넣으면 비용이 크고, 많은 정보 속에서 효율적으로 발췌하지 못한다. 이것이 할루시네이션으로 이어지기도 한다

분할 과정 4단계

도식을 탭하면 확대해서 볼 수 있습니다
단계내용
1. 문서 구조 파악PDF·웹페이지·전자책 등 형식별 구조 식별. 헤더·푸터·페이지 번호·섹션 제목
2. 단위 선정페이지별/섹션별/문단별 중 무엇으로 나눌지. 문서 내용과 목적에 따라 다름
3. 단위 크기 선정몇 개의 토큰 단위로 나눌지(chunk_size)
4. 청크 오버랩분할 끝부분에서 맥락이 이어지도록 일부를 겹쳐서 나누는 것이 일반적

분할기 선택

분할기기준적합한 상황주의점
CharacterTextSplitter단일 구분자구조가 매우 단순한 텍스트구분자가 없으면 크기 제한을 못 지킴
RecursiveCharacterTextSplitter구분자 우선순위 재귀 적용(문단→줄→문장→단어)범용 기본값의미 경계보다 길이 우선이라 표·코드가 잘림
TokenTextSplitter토큰 수LLM 입력 한도를 정확히 맞춰야 할 때문자 기준보다 계산 비용 있음
MarkdownHeaderTextSplitter헤딩 레벨마크다운 문서. 헤딩을 메타데이터로 보존원문이 마크다운일 때만
HTMLHeaderTextSplitterHTML 태그 계층웹 문서마크업 품질에 의존
코드용 스플리터언어별 문법 단위소스코드 저장소언어 지정 필요
의미 기반 분할문장 임베딩 유사도 급변 지점주제가 자주 바뀌는 긴 문서전처리 임베딩 비용 발생

튜닝 포인트

파라미터기준값튜닝 방향
chunk_size1000작게 → 정밀하지만 문맥 부족. 크게 → 문맥 충분하지만 노이즈·비용 증가
chunk_overlap50보통 chunk_size의 5~20%. 경계 문맥 유실이 잦으면 늘림
분할 단위문단문서가 규격화돼 있으면(계약서·매뉴얼) 섹션 단위가 유리
청크당 메타데이터원문 위치·섹션 제목을 함께 저장해야 출처 표기와 ParentDocument 확장이 가능

트레이드오프 한 줄 요약: 청크가 작을수록 검색 정밀도가 오르고 답변 문맥이 줄어든다. 반대도 성립한다. 정답은 문서 성격과 질문 유형에 달렸고, 실측으로 정한다.

실패 모드

증상원인대응
답이 문장 중간에서 근거가 끊김overlap 부족overlap 증가, 문장 경계 우선 분할기로 교체
검색은 맞는데 LLM이 "모른다"고 함청크가 너무 작아 답에 필요한 문맥 결여chunk_size 증가 또는 ParentDocument 방식
관련 없는 내용이 자꾸 딸려옴청크가 너무 커서 한 청크에 여러 주제 혼재chunk_size 감소
표·목록이 의미를 잃음문자 길이 기준 분할이 표를 관통구조 인식 분할기 사용
# 단계 2: 문서 분할(Split Documents)
from langchain_text_splitters import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=50)
splits = text_splitter.split_documents(docs)

여기까지가 원문을 검색 가능한 조각으로 만드는 구간이다. 다음 편에서는 그 조각을 벡터로 바꿔 저장하고 다시 꺼내 오는 임베딩·벡터스토어·검색기·리랭커를 다룬다.