문서 기반 답변을 위한 RAG 구축하기
왜 RAG가 필요한가
Gemma는 강력한 언어 모델이지만, 모든 사내 문서와 최신 정책을 알고 있는 것은 아닙니다. 모델은 학습 시점까지의 일반 지식을 바탕으로 다음 문장을 예측합니다. 그래서 최근에 바뀐 제품 가격, 사내 보안 규정, 특정 고객사의 계약 조건, 최신 법령, 새 논문 내용처럼 계속 변하는 정보에는 약합니다.
이 문제를 해결하는 대표적인 방법이 RAG입니다. RAG는 Retrieval-Augmented Generation의 약자로, 먼저 관련 문서를 검색하고 그 문서를 Gemma의 입력 컨텍스트에 넣은 뒤 답변을 생성하게 하는 방식입니다. 쉽게 말해 모델에게 "기억해 둔 것만으로 답하지 말고, 지금 찾아온 문서를 보고 답하라"고 지시하는 구조입니다.
RAG는 파인튜닝과 목적이 다릅니다. 파인튜닝은 모델의 말투, 출력 형식, 판단 습관을 조정하는 데 적합합니다. 반면 RAG는 외부 지식을 질문 시점에 연결하는 데 적합합니다. 사내 규정, 제품 매뉴얼, 법령, 논문, 장애 대응 문서처럼 내용이 자주 바뀌거나 출처를 함께 보여줘야 하는 작업은 먼저 RAG로 설계하는 편이 안전합니다.
실무 기준은 간단합니다. 지식이 자주 바뀌면 RAG를 씁니다. 답변의 근거를 남겨야 하면 RAG를 씁니다. 사용자의 권한에 따라 볼 수 있는 문서가 다르면 RAG를 씁니다. 반대로 항상 같은 형식으로 분류하거나, 특정 문체로 고객 응답 초안을 작성하는 문제라면 프롬프트 개선이나 LoRA 파인튜닝을 함께 검토합니다.
AI 생성 이미지
기본 파이프라인 한눈에 보기
문서 기반 답변 시스템은 다음 순서로 동작합니다.
PDF, 웹페이지, 마크다운, 데이터베이스 레코드 같은 원천 문서를 수집한다.
본문이 비어 있거나 깨진 데이터를 제거하고 텍스트를 정리한다.
문서를 검색하기 좋은 크기의 청크로 나눈다.
각 청크에 제목, 출처, 페이지, 작성일, 권한 같은 메타데이터를 붙인다.
청크 텍스트를 임베딩 모델로 벡터화한다.
벡터와 메타데이터를 벡터 데이터베이스에 저장한다.
사용자 질문도 같은 임베딩 모델로 벡터화한다.
질문과 가까운 청크를 검색한다.
검색된 청크를 Gemma 프롬프트에 넣고 근거 기반 답변을 생성한다.
답변에 출처를 표시하고, 근거가 부족하면 거절한다.
이 흐름에서 Gemma는 마지막 생성 단계를 담당합니다. 좋은 RAG 시스템은 모델만 좋다고 완성되지 않습니다. 문서를 잘 수집하고, 잘 나누고, 잘 검색하고, 잘 제한해야 합니다.
원천 문서 전처리
RAG의 품질은 입력 문서 품질에서 시작됩니다. 잘못 추출된 문서, 중복 문서, 오래된 문서, 본문이 비어 있는 문서가 들어가면 Gemma는 그럴듯하지만 틀린 답을 만들 가능성이 커집니다.
먼저 공통 정리 절차를 둡니다. 본문이 없는 레코드는 제거합니다. 이전 실험에서 만들어 둔 임베딩 필드가 있다면 삭제하고 새로 생성합니다. 임베딩은 모델과 설정이 바뀌면 다시 만들어야 하는 값이기 때문입니다. Hugging Face의 Gemma와 MongoDB RAG 예제도 영화 데이터에서 본문이 비어 있는 행을 제거하고, 기존 임베딩 컬럼을 삭제한 뒤 새 임베딩을 생성하는 흐름을 사용합니다.1
PDF는 특히 주의가 필요합니다. PDF는 사람이 보기에는 한 문서처럼 보이지만, 내부 텍스트 순서가 깨져 있거나 표, 머리말, 꼬리말이 섞여 있을 수 있습니다. 일반적인 처리 절차는 다음과 같습니다.
업로드된 PDF를 임시 파일로 저장한다.
PyMuPDF로 파일을 열고 페이지별 텍스트를 추출한다.
여러 줄 공백, 탭, 불필요한 줄바꿈을 하나의 공백으로 정규화한다.
페이지 번호를 함께 저장한다.
이미지 기반 PDF라면 OCR을 적용하거나, 필요할 때
pdf2image로 페이지 이미지를 생성한다.
멀티모달 RAG를 만들 때는 페이지 이미지를 함께 저장할 수도 있습니다. Gemma 3를 활용한 멀티모달 RAG 예제에서는 PDF 페이지를 이미지로 변환하고, PyMuPDF로 페이지 텍스트를 추출한 뒤, 페이지 번호·텍스트·이미지 데이터를 함께 보관하는 방식을 사용합니다.2 다만 처음부터 이미지 RAG까지 구현할 필요는 없습니다. 텍스트 기반 문서 Q&A라면 텍스트 추출과 메타데이터 관리부터 안정화하는 것이 우선입니다.
웹페이지는 본문 추출이 중요합니다. 내비게이션, 광고, 푸터, 관련 글 목록이 함께 들어가면 검색 품질이 떨어집니다. 마크다운 문서는 제목 계층을 살려 섹션 단위로 나누기 좋습니다. 데이터베이스 레코드는 필드 이름을 포함해 의미를 보존해야 합니다. 예를 들어 refund_period: 14 days보다 "환불 가능 기간: 14일"처럼 문장에 가까운 형태로 변환하면 검색과 생성 모두에 유리합니다.
청크와 메타데이터 설계
긴 문서를 그대로 임베딩하면 검색 결과가 흐려집니다. 하나의 문서 안에 여러 주제가 섞여 있으면 질문과 관련 없는 내용까지 함께 검색됩니다. 그래서 문서를 청크로 나눕니다. 청크는 검색과 프롬프트 삽입에 적합한 작은 문서 조각입니다.
청크 기준은 문서 성격에 따라 다릅니다. 정책 문서는 조항이나 섹션 단위가 좋습니다. 매뉴얼은 제목과 하위 절 단위가 좋습니다. PDF 보고서는 페이지 단위로 시작한 뒤, 필요하면 문단 단위로 세분화합니다. FAQ는 질문과 답변 한 쌍을 하나의 청크로 두는 편이 자연스럽습니다.
청크에는 반드시 메타데이터를 붙입니다. 최소한 다음 항목을 권장합니다.
| 필드 | 설명 |
|---|---|
chunk_id | 청크 고유 ID |
document_id | 원본 문서 ID |
title | 문서 제목 또는 섹션 제목 |
source | URL, 파일 경로, 시스템 이름 |
page | PDF나 보고서의 페이지 번호 |
section | 문서 안의 절 또는 조항 |
created_at | 작성일 |
updated_at | 최종 개정일 |
access_level | 공개, 사내, 팀 전용, 관리자 전용 등 |
version | 문서 버전 |
메타데이터는 단순한 부가 정보가 아닙니다. 검색 결과를 필터링하고, 답변에 출처를 표시하고, 오래된 문서를 낮은 우선순위로 밀어내고, 사용자 권한 밖의 문서를 제외하는 핵심 장치입니다.
예를 들어 같은 질문에 대해 2023년 매뉴얼과 2026년 매뉴얼이 동시에 검색될 수 있습니다. 이때 updated_at과 version이 없다면 Gemma는 두 문서를 같은 수준의 근거로 볼 수 있습니다. 반대로 메타데이터가 있으면 "최신 문서를 우선 사용하라"는 규칙을 적용할 수 있습니다.
임베딩과 벡터 데이터베이스
임베딩은 텍스트를 숫자 벡터로 바꾸는 과정입니다. 의미가 비슷한 문장은 벡터 공간에서 가까워집니다. 사용자가 "환불은 며칠 안에 가능한가요?"라고 물었을 때 "구매일로부터 14일 이내 청약 철회 가능"이라는 문서를 찾을 수 있는 이유가 여기에 있습니다.
중요한 원칙은 하나입니다. 문서 청크를 임베딩할 때 사용한 모델과 사용자 질문을 임베딩할 때 사용하는 모델이 같아야 합니다. 그래야 같은 벡터 공간에서 유사도를 비교할 수 있습니다.
저장소로는 MongoDB Atlas Vector Search, Qdrant, Milvus, Weaviate, Elasticsearch vector search, PostgreSQL pgvector 등을 사용할 수 있습니다. 여기서는 개념을 단순화해 MongoDB Atlas Vector Search를 예로 보겠습니다. Hugging Face의 예제에서는 thenlper/gte-large 임베딩 모델로 텍스트를 벡터화하고, MongoDB 문서에 새 embedding 필드를 추가한 뒤, 코사인 유사도 기반 Vector Search 인덱스를 구성합니다.1
기본 구조는 다음과 같습니다.
# 1. 청크 텍스트를 임베딩한다.
chunk["embedding"] = embedding_model.encode(chunk["text"]).tolist()
# 2. 벡터 DB에 텍스트, 메타데이터, 임베딩을 함께 저장한다.
collection.insert_many(chunks)
# 3. 사용자 질문도 같은 모델로 임베딩한다.
query_vector = embedding_model.encode(user_question).tolist()
# 4. 벡터 검색으로 상위 k개 청크를 가져온다.
results = vector_search(
query_vector=query_vector,
limit=5,
filters={"access_level": {"$in": user_allowed_levels}}
)실제 MongoDB Atlas에서는 벡터 필드의 차원 수를 임베딩 모델 출력 차원과 맞춰야 합니다. 예를 들어 1024차원 임베딩을 쓰면 인덱스도 1024차원으로 만들어야 합니다. 차원이 맞지 않으면 검색이 실패하거나 저장 자체가 되지 않습니다.
검색할 때는 top_k를 무조건 크게 잡지 않는 것이 좋습니다. 많은 문서를 넣으면 Gemma가 더 잘 답할 것 같지만, 실제로는 관련 없는 문서가 섞여 답변이 흔들릴 수 있습니다. 처음에는 3~5개 정도로 시작하고, 평가 결과를 보며 조정합니다.
Gemma에 근거를 넣는 프롬프트
검색된 청크를 그대로 Gemma에 붙이면 충분하지 않습니다. 모델이 어떤 자료를 우선해야 하는지, 언제 답하지 말아야 하는지, 출처를 어떻게 표시해야 하는지 명확히 알려야 합니다.
프롬프트는 다음 구조가 안정적입니다.
당신은 사내 문서 기반 답변 도우미입니다.
아래 제공된 근거 문서만 사용해 답변하세요.
근거에 없는 내용은 추측하지 말고 "제공된 문서에서 확인할 수 없습니다"라고 답하세요.
문서가 서로 충돌하면 최신 문서를 우선하고, 충돌 사실을 함께 설명하세요.
답변 끝에 사용한 문서 제목, 페이지, 링크를 표시하세요.
[사용자 질문]
{question}
[근거 문서]
문서 1
- 제목: {title}
- 페이지: {page}
- 개정일: {updated_at}
- 링크: {source}
- 내용: {text}
문서 2
...여기서 중요한 표현은 "제공된 근거 문서만 사용"과 "근거에 없으면 확인할 수 없다고 답하기"입니다. 이 두 문장은 환각을 줄이는 데 큰 역할을 합니다. 물론 완벽한 방어는 아닙니다. 그래서 출력 후에도 출처 형식, 금지 표현, 개인정보 포함 여부, JSON 형식 등을 검증해야 합니다.
답변 형식도 정해 두면 좋습니다.
답변:
- 핵심 결론을 먼저 말합니다.
- 필요한 조건이나 예외를 짧게 설명합니다.
- 근거 문서를 인용합니다.
출처:
1. 문서 제목, p.3, 링크
2. 문서 제목, p.5, 링크이렇게 하면 사용자는 답변을 바로 신뢰하지 않고 원문을 확인할 수 있습니다. 운영자도 잘못된 답변이 나왔을 때 검색 문제인지, 프롬프트 문제인지, 문서 자체의 문제인지 추적하기 쉬워집니다.
검색 실패와 충돌을 다루는 법
RAG 시스템에서 가장 위험한 상황은 검색 결과가 나쁜데도 Gemma가 자신 있게 답하는 경우입니다. 이를 막으려면 검색 품질 제어가 필요합니다.
첫째, 유사도 임계값을 둡니다. 상위 결과의 점수가 너무 낮으면 답변을 생성하지 않고 "관련 문서를 찾지 못했습니다"라고 안내합니다. 이때 사용자가 질문을 구체화할 수 있도록 "제품명, 버전, 문서 종류를 함께 입력해 주세요"처럼 재질문을 유도합니다.
둘째, 메타데이터 필터를 먼저 적용합니다. 사용자가 접근할 수 없는 문서는 검색 결과에 나오면 안 됩니다. 검색 후에 숨기는 방식은 위험합니다. 벡터 검색 단계에서 부서, 권한, 고객사, 문서 상태를 필터링해야 합니다.
셋째, 최신성을 반영합니다. 법령, 가격 정책, 보안 규정처럼 시간이 중요한 문서는 updated_at, effective_date, version을 기준으로 최신 문서에 가중치를 줍니다. 오래된 문서가 검색되면 "이 문서는 오래되었으므로 최신 문서 확인이 필요합니다"라고 표시할 수 있습니다.
넷째, 근거가 충돌하면 숨기지 않습니다. 예를 들어 한 문서는 환불 기간을 7일, 다른 문서는 14일로 말한다면 Gemma가 임의로 하나를 고르게 두면 안 됩니다. 프롬프트에 "충돌하는 근거가 있으면 둘 다 제시하고, 최신 문서나 공식 문서를 우선하라"고 넣습니다. 그래도 판단이 어렵다면 담당 부서 확인을 안내합니다.
다섯째, 재검색 전략을 둡니다. 첫 검색 결과가 부족하면 질문에서 핵심 키워드를 추출해 다시 검색하거나, 문서 유형을 바꿔 검색할 수 있습니다. 예를 들어 "환불"로 결과가 약하면 "청약 철회", "취소 정책", "결제 취소" 같은 동의어를 함께 사용할 수 있습니다.
작은 시스템으로 시작하기
처음부터 완벽한 RAG 플랫폼을 만들 필요는 없습니다. 작은 문서 20~50개로 시작해도 충분합니다. 중요한 것은 전체 흐름을 끝까지 연결하는 것입니다.
시작 단계에서는 다음만 구현합니다.
문서 수집과 본문 정리
청크와 메타데이터 생성
임베딩 생성과 벡터 DB 저장
질문 임베딩과 top-k 검색
Gemma 프롬프트 구성
출처 포함 답변
검색 실패 시 거절 응답
그다음 평가 세트를 만듭니다. 실제 사용자가 물을 만한 질문 30~100개를 준비하고, 기대 답변과 기대 출처를 적습니다. 변경할 때마다 검색된 문서가 맞는지, Gemma 답변이 근거를 벗어나지 않는지 확인합니다.
RAG의 핵심은 "많이 넣기"가 아니라 "맞는 근거를 넣기"입니다. Gemma가 좋은 답변을 하려면 먼저 좋은 문서를 찾아줘야 합니다. 문서 정리, 청크 설계, 메타데이터, 검색 품질, 프롬프트 규칙이 함께 맞물릴 때 문서 기반 답변 시스템은 신뢰할 수 있는 제품 기능이 됩니다.
참고
1Building A RAG System with Gemma, MongoDB and Open Source Models
2Deploying a multimodal RAG application with Gemma 3 and CircleCI on GKE
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.