RAG와 임베딩 · 글 11편RAG와 임베딩
  1. RAG란 무엇인가 - 넣기, 쪼개기, 찾기, 답하기
  2. 문서를 붙여 넣을까, RAG를 만들까
  3. 임베딩이란 무엇인가
  4. 로컬 임베딩 모델 고르기
  5. 문서를 청크로 나누기
  6. 키워드 검색, 의미 검색, 하이브리드 검색
  7. 로컬 의미 검색 만들기 - 젬마 임베딩 실습 따라 읽기
  8. RAG 답변 평가와 자주 생기는 실패
  9. RAG, 긴 컨텍스트, 파인튜닝, LLM Wiki 비교
  10. RAG가 잘 되도록 사무 문서 준비하기
  11. 내부 문서를 색인할 때의 개인정보와 보안

이 글에서 다루는 것RAG를 네 단계의 동사로 풀어 설명합니다. 문서를 넣고, 쪼개고, 질문과 관련된 조각을 찾고, 그 조각만 보고 답하게 하는 구조입니다.

왜 필요한가

언어 모델은 공개된 글로 학습했습니다. 일반 상식은 알지만 우리 부서의 내부 규정, 작년 회의록, 이번 분기 보고서는 모릅니다. 모델이 입력으로 받지 못한 문서에 대해 물으면, 그 문서를 근거로 답한다고 보장할 수 없습니다.

RAG(Retrieval-Augmented Generation, 검색증강생성)는 답하는 시점에 관련 문서를 찾아 질문 옆에 놓아 주는 방법입니다. 모델을 다시 학습시키지 않습니다. 프로그램이 문서에서 필요한 부분을 찾아 질문과 함께 모델에 넣을 뿐입니다.

얻는 것은 두 가지입니다.

  • 모델이 모르는 내부 자료로 답할 수 있습니다.
  • 답변에 출처를 붙일 수 있습니다. 어느 문서 몇 쪽, 몇 조에서 나온 말인지 사람이 다시 확인할 수 있습니다.

업무에서는 두 번째가 더 중요합니다. 답이 그럴듯한지보다 어떤 근거로 답했는지를 확인할 수 있어야 쓸 수 있습니다.

네 단계

번역어는 어렵지만 하는 일은 네 가지입니다.

단계 하는 일 도구 예시
1. 넣기 PDF, HWP, DOCX에서 글자를 뽑아 텍스트로 만듭니다 PyMuPDF, python-docx 등
2. 쪼개기 긴 문서를 검색할 단위(청크)로 나눕니다 500자 안팎, 조항·문단 경계 우선
3. 찾기 질문과 관련된 조각을 고릅니다. 상위 3~5개만 씁니다 키워드 검색(BM25) 또는 임베딩 검색
4. 답하기 고른 조각과 질문을 모델에 넣고, 그 범위에서만 답하게 합니다 Ollama 등으로 실행한 로컬 모델

12단계는 문서를 넣을 때 한 번 합니다. 이 작업을 색인이라고 부릅니다. 34단계는 질문이 들어올 때마다 합니다.

30줄로 보는 전체 흐름

아래는 강의에서 쓰는 가장 단순한 예시를 실행할 수 있게 다듬은 것입니다. 임베딩도 벡터 데이터베이스도 쓰지 않습니다. 질문의 단어가 몇 개 들어 있는지만 세어 조각을 고릅니다.

from pathlib import Path
import fitz      # PyMuPDF
import ollama

# 1. 넣기 - PDF를 텍스트로
docs = []
for pdf in Path("docs").glob("*.pdf"):
    d = fitz.open(pdf)
    txt = "\n".join(p.get_text() for p in d)
    docs.append({"src": pdf.name, "text": txt})

# 2. 쪼개기 - 500자 조각, 50자씩 겹침
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]})

# 3. 찾기 - 질문 단어가 들어 있는 조각에 점수
def search(q, k=3):
    scored = [(c, sum(w in c["text"] for w in q.split())) for c in chunks]
    return [c for c, s in sorted(scored, key=lambda x: -x[1])[:k]]

# 4. 답하기 - 찾은 조각만 보고 답하게 한다
def answer(q):
    hits = search(q)
    ctx = "\n\n".join(f"[{h['src']}]\n{h['text']}" for h in hits)
    prompt = f"아래 문서만 참고해서 답하라. 없으면 '문서에 없음'이라고 답하라.\n\n{ctx}\n\n질문: {q}"
    r = ollama.chat(model="gemma4", messages=[{"role": "user", "content": prompt}])
    return r["message"]["content"]

모델 이름 gemma4는 예시입니다. 내 PC에 받아 둔 모델 이름으로 바꿉니다. 모델을 받는 방법은 Ollama 처음 시작하기에 있습니다.

이 코드는 품질이 좋지 않습니다. “연차”로 물으면 “휴가”라고 적힌 조항을 찾지 못합니다. 그래도 구조는 완성형과 같습니다. 이후 글에서 다루는 내용은 모두 이 네 단계 중 하나를 더 잘하는 방법입니다.

고치는 단계 다루는 글
넣기 RAG가 잘 되도록 사무 문서 준비하기
쪼개기 문서를 청크로 나누기
찾기 임베딩이란 무엇인가, 키워드 검색, 의미 검색, 하이브리드 검색
답하기와 검수 RAG 답변 평가와 자주 생기는 실패

답하기 단계의 프롬프트

답하기 단계에서 모델에게 주는 지시가 답변의 형식을 정합니다. 규정 질문에 쓰는 예시입니다.

아래 규정 조항만 참고해서 답하라.
답변 형식:
- 답: [2~3줄]
- 근거: [규정명 제X조]
조항에 없으면 "규정에 없음"이라 답한다.

[검색된 조항 3건]
{chunks}

질문: {query}

세 가지를 넣습니다. 참고할 범위를 제한하는 문장, 근거를 적게 하는 형식, 근거가 없을 때 할 말입니다. 세 번째를 빼면 모델은 찾은 조각에 답이 없어도 일반 지식으로 문장을 만들어 냅니다.

어떤 업무에 맞는가

강의에서 다루는 전형적인 네 가지입니다. 문서의 성격에 따라 쪼개는 단위와 모델이 맡는 일이 달라집니다.

업무 문서 쪼개는 단위 출처 표기 모델의 역할
규정 질의응답 내부 규정집 조항 규정명, 조·항 해당 조항 인용
회의록 찾기 주간 회의록 1년치 회차와 안건 날짜, 안건명 시간순 정리
유사 민원 찾기 과거 응대 기록 민원 1건 접수번호, 처리 결과 비슷한 사례 제시
법령 검색 법령, 시행령, 시행규칙 조문 법령명, 조문 해석 초안

법령과 규정 답변은 초안으로 다룹니다. 최종 해석은 담당자가 원문을 열어 확인합니다. 프롬프트에도 이 점을 적어 둡니다.

자주 하는 오해

RAG를 하려면 벡터 데이터베이스가 있어야 한다. 그렇지 않습니다. 위 코드처럼 키워드로 찾아도 RAG입니다. 문서가 수백 건 이하라면 키워드 검색으로 시작하고, 표현이 달라서 못 찾는 일이 반복될 때 임베딩을 더합니다.

문서를 폴더에 넣으면 끝난다. 텍스트 추출, 조각 나누기, 출처 붙이기가 먼저입니다. 스캔본이나 표가 많은 문서는 추출 결과를 사람이 확인해야 합니다.

RAG를 붙이면 틀린 답이 없어진다. 검색이 엉뚱한 조각을 가져오면 모델은 그 조각으로 답합니다. 검색 결과를 먼저 화면에 보여 주고 답변과 대조할 수 있게 만듭니다.

Msty에서는

Msty Studio에는 내 문서와 노트를 근거로 답하게 하는 Knowledge Stacks 기능이 있습니다. 위 네 단계를 앱이 대신 해 줍니다. 코드를 쓰지 않고 시작하려면 이 기능을 먼저 써 보고, 결과가 기대와 다를 때 이 시리즈의 글로 어느 단계에서 문제가 생겼는지 짚어 봅니다. Msty의 구성은 Msty란 무엇인가에 정리되어 있습니다.

다음에 읽을 글