RAG 제대로 이해하기 - LLM에 '내 지식'을 붙이는 법
LLM은 똑똑하지만 두 가지 약점이 있습니다. 모르는 걸 아는 척(할루시네이션) 하고, 학습 시점 이후의 최신 정보나 우리 회사 내부 문서를 모른다는 점입니다. 이 둘을 가장 현실적으로 해결하는 방법이 RAG(Retrieval-Augmented Generation, 검색 증강 생성) 입니다.
이 글에서는 RAG가 무엇이고, 어떤 흐름으로 동작하며, 실무에서 어디를 조심해야 하는지를 정리합니다.
1. RAG는 한 문장으로
"질문과 관련 있는 문서를 먼저 찾아서, 그 내용을 프롬프트에 같이 넣어주고 답하게 하는 것."
모델을 다시 학습시키지 않습니다. 대신 답변 직전에 필요한 지식을 컨텍스트로 주입합니다. 비유하자면, 시험을 앞두고 머리에 다 외우게 하는 게 아니라 오픈북 시험처럼 관련 페이지를 펼쳐서 보여주는 것입니다.
2. 왜 파인튜닝이 아니라 RAG인가
"내 데이터를 넣고 싶으면 파인튜닝하면 되지 않나요?" 라는 질문을 많이 합니다. 셋을 비교하면 선택이 쉬워집니다.
| 방법 | 하는 일 | 강점 | 약점 |
|---|---|---|---|
| 프롬프트 | 요청할 때 예시·지침을 직접 넣음 | 가장 쉬움, 즉시 반영 | 넣을 수 있는 양 한계 |
| RAG | 관련 문서를 찾아 컨텍스트로 주입 | 최신성·출처 추적·비용 저렴 | 검색 품질에 좌우됨 |
| 파인튜닝 | 모델 가중치를 재학습 | 말투·형식·도메인 체화 | 비용·시간↑, 지식 갱신 어려움 |
핵심 감각:
- "무엇을 아는가(지식)" 를 바꾸고 싶다 → RAG
- "어떻게 말하는가(스타일·형식)" 를 바꾸고 싶다 → 파인튜닝
- 지식은 계속 바뀌는데 파인튜닝으로 넣으면, 문서 하나 바뀔 때마다 재학습해야 합니다. 그래서 변하는 지식은 RAG가 정답입니다.
3. RAG 파이프라인 전체 흐름
RAG는 크게 ① 색인(Indexing) 과 ② 질의(Query) 두 단계로 나뉩니다.
[색인 - 미리 준비] 문서 → 청킹(Chunking) → 임베딩(Embedding) → 벡터 DB 저장 [질의 - 사용자가 물어볼 때] 질문 → 임베딩 → 벡터 DB에서 유사 청크 검색 → 프롬프트에 주입 → LLM 답변
하나씩 뜯어봅니다.
3-1. 청킹(Chunking) — 문서를 잘게 나누기
문서를 통째로 넣을 수 없으니 의미 단위로 자릅니다.
- 너무 크게 자르면: 관련 없는 내용까지 딸려와 노이즈가 됩니다.
- 너무 작게 자르면: 문맥이 끊겨 검색해도 의미가 안 통합니다.
- 보통 300~800 토큰 + 약간의 오버랩(겹침) 으로 시작해 튜닝합니다.
// 아주 단순화한 청킹 예시 function chunk(text: string, size = 500, overlap = 50): string[] { const chunks: string[] = []; for (let i = 0; i < text.length; i += size - overlap) { chunks.push(text.slice(i, i + size)); } return chunks; }
실무에선 문단·헤딩 경계를 존중하는 "구조 기반 청킹"이 성능이 훨씬 좋습니다.
3-2. 임베딩(Embedding) — 의미를 벡터로
임베딩은 텍스트를 의미가 담긴 숫자 벡터로 바꾸는 것입니다. 의미가 비슷한 문장은 벡터 공간에서 가까이 위치합니다. 그래서 "결제 취소하는 법"과 "환불 요청 방법"이 단어는 달라도 가깝게 잡힙니다.
// 문장 → 벡터 (예: 1536차원) const vector = await embed("결제를 취소하고 싶어요"); // => [0.021, -0.135, 0.008, ... ]
3-3. 벡터 DB & 유사도 검색
청크 벡터들을 벡터 DB(pgvector, Pinecone, Qdrant 등)에 저장해두고, 질문 벡터와 코사인 유사도가 높은 상위 K개를 꺼냅니다.
-- pgvector 예시: 질문 임베딩과 가장 가까운 청크 5개 SELECT content, 1 - (embedding <=> :query_embedding) AS score FROM doc_chunks ORDER BY embedding <=> :query_embedding LIMIT 5;
3-4. 프롬프트 주입 & 생성
찾은 청크를 프롬프트에 붙여 "이 내용을 근거로만 답하라"고 지시합니다.
아래 문서를 참고해서 질문에 답하세요. 문서에 근거가 없으면 "모르겠습니다"라고 답하세요. [문서] {검색된 청크들} [질문] {사용자 질문}
이 "문서에 없으면 모른다고 하라"는 지침 하나가 할루시네이션을 크게 줄이는 실전 팁입니다.
4. 검색 품질을 끌어올리는 실전 기법
RAG의 성능은 결국 "관련 문서를 얼마나 잘 찾느냐" 에 달려 있습니다. 답이 이상하면 대부분 생성이 아니라 검색이 문제입니다.
- 하이브리드 검색: 의미 검색(벡터) + 키워드 검색(BM25)을 합치면, 고유명사·코드·에러메시지 같은 정확 매칭에 강해집니다.
- 리랭킹(Re-ranking): 후보를 넉넉히(예: 20개) 뽑은 뒤, 리랭커 모델로 다시 정렬해 상위 3~5개만 넣습니다.
- 메타데이터 필터: 최신 문서만, 특정 제품군만 등 조건으로 검색 범위를 좁힙니다.
- 쿼리 재작성: 사용자의 짧은 질문을 검색용으로 풍부하게 다시 씁니다.
5. "롱 컨텍스트가 나오면 RAG는 필요 없다?"
요즘 모델은 수십만 토큰까지 넣을 수 있어서 "문서 다 넣으면 되지 않냐"는 얘기가 나옵니다. 하지만 여전히 RAG가 유리한 이유:
- 비용·속도: 매 요청마다 수십만 토큰을 넣으면 느리고 비쌉니다. 필요한 부분만 넣는 게 효율적입니다.
- 정확도: 컨텍스트가 길수록 중간 내용을 놓치는 lost-in-the-middle 현상이 있습니다.
- 출처 추적: RAG는 "이 답의 근거는 이 문서"라고 인용을 붙일 수 있습니다.
즉 롱 컨텍스트는 RAG를 대체한다기보다, RAG가 더 넉넉하게 근거를 넣을 수 있게 보완합니다.
6. 실무에서 자주 밟는 지뢰
- 청크가 나쁘면 다 나쁩니다. 검색이 안 맞으면 청킹부터 다시 보세요.
- 평가를 안 한다. "질문–정답–근거" 세트를 만들어 검색 적중률(recall)과 답변 품질을 수치로 관리해야 개선이 됩니다.
- 임베딩 모델 교체 = 전체 재색인. 임베딩 모델을 바꾸면 저장된 벡터도 전부 다시 만들어야 합니다.
- 권한 처리. 사내 RAG는 "이 사용자가 볼 수 있는 문서"만 검색되도록 필터가 필수입니다.
마무리
RAG는 거창한 기술이 아니라 "관련 문서를 잘 찾아서 프롬프트에 넣는다" 는 단순한 아이디어입니다. 하지만 실제 품질은 청킹 → 임베딩 → 검색 → 리랭킹 → 주입 각 단계의 디테일에서 갈립니다.
처음 시작한다면 이 순서를 추천합니다.
- 문서를 문단 단위로 청킹하고 pgvector에 넣어 가장 단순한 RAG를 만든다.
- 실제 질문 세트로 검색 적중률부터 측정한다.
- 하이브리드 검색 → 리랭킹 순으로 하나씩 붙이며 수치를 확인한다.
"모델을 더 크게" 가 아니라 "검색을 더 정확하게" 가 RAG를 잘하는 길입니다.
