LLM은 학습 시점 이후의 정보나 사내 문서를 모릅니다. RAG는 이 구멍을 메우는 방식입니다.
#### 1) 왜 필요한가
LLM은 두 가지 근본적인 한계가 있습니다.
* **지식 단절** — 학습 시점 이후의 정보를 모릅니다
* **비공개 정보 부재** — 회사 내부 문서, 개인 데이터는 애초에 학습하지 않았습니다
이걸 매번 파인튜닝으로 해결하면 비용도 크고 문서가 바뀔 때마다 재학습해야 합니다. RAG는 **모델은 그대로 두고, 질문에 맞는 외부 문서를 찾아 프롬프트에 끼워 넣는 방식**으로 이 문제를 우회합니다.
#### 2) 전체 흐름 (두 단계로 나눠서 보기)
RAG는 사전 준비 단계와 질의 단계가 분리되어 있다는 걸 이해하는 게 핵심입니다.
**[사전 준비 — Indexing]**
```
문서 (PDF, 위키, DB 등)
↓ 청킹 (Chunking)
작은 텍스트 조각들
↓ Embedding
벡터들
↓ 저장
Vector DB
```
**[질의 시점 — Retrieval + Generation]**
```
사용자 질문
↓ Embedding
질문 벡터
↓ Vector DB에서 유사도 검색
관련 문서 조각 top-k
↓ 프롬프트에 삽입
"다음 문서를 참고해서 답변해: [검색된 문서] 질문: [사용자 질문]"
↓ LLM
최종 답변
```
인터뷰에서 이 두 단계를 구분해서 설명하면 "그냥 써본 사람"이 아니라 "구조를 이해한 사람"으로 보입니다.
#### 3) 코드로 최소 구현
python
```
import chromadb
from openai import OpenAI
client = OpenAI()
db = chromadb.Client()
collection = db.create_collection("company_docs")
# 1. 사전 준비: 문서를 벡터로 저장
docs = [
"연차는 사내 포털 HR시스템에서 신청합니다.",
"출장비는 영수증 첨부 후 팀장 승인을 받아 정산합니다.",
"재택근무는 주 2회까지 팀장 사전 승인하에 가능합니다."
]
collection.add(documents=docs, ids=[str(i) for i in range(len(docs))])
# 2. 질의: 질문과 유사한 문서 검색
question = "휴가는 어떻게 신청하나요?"
result = collection.query(query_texts=[question], n_results=2)
retrieved = "\n".join(result["documents"][0])
# 3. 검색된 문서를 프롬프트에 삽입해서 LLM 호출
prompt = f"""다음 문서를 참고해서 질문에 답하세요.
문서:
{retrieved}
질문: {question}
"""
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt}]
)
print(response.choices[0].message.content)
```
"연차는 어떻게 신청하나요"라는 키워드가 문서에 없어도(문서엔 "HR시스템"이라고만 쓰여 있음) 의미 기반 검색으로 정확히 찾아냅니다. 이게 단순 키워드 검색과의 차이입니다.
#### 4) 청킹(Chunking) — 실무에서 가장 중요한 변수
문서를 통째로 임베딩하면 안 됩니다. 문서가 길면 여러 주제가 섞여서 벡터가 흐려지고, 검색 정확도가 떨어집니다. 그래서 작은 단위로 쪼갭니다.
python
```
def chunk_text(text, chunk_size=500, overlap=50):
chunks = []
start = 0
while start < len(text):
chunks.append(text[start:start + chunk_size])
start += chunk_size - overlap # overlap만큼 겹치게
return chunks
```
**청크 크기가 너무 작으면**: 문맥이 잘려서 "그것", "이 경우" 같은 지시어의 대상을 잃어버립니다.
**청크 크기가 너무 크면**: 여러 주제가 섞여 검색 정확도가 떨어지고, 프롬프트에 불필요한 내용이 많이 들어갑니다.
**overlap을 주는 이유**: 문장이 청크 경계에서 잘려도 다음 청크가 앞부분을 일부 포함해 문맥이 완전히 끊기지 않게 합니다.
실무에서는 문단 단위, 마크다운 헤더 단위처럼 **의미 단위로 자르는 것**(semantic chunking)이 단순 글자 수 자르기보다 성능이 좋습니다.
#### 5) 검색 품질을 높이는 기법들 (면접에서 자주 나옴)
**하이브리드 검색 (Hybrid Search)**
벡터 검색은 의미는 잘 잡지만 고유명사·제품코드·정확한 숫자에는 약합니다. "SL-2024-0871 부품" 같은 코드는 키워드 검색(BM25)이 훨씬 정확합니다. 그래서 벡터 검색 + 키워드 검색을 함께 쓰고 점수를 합산합니다.
**리랭킹 (Re-ranking)**
1차로 벡터 검색해서 top-20을 뽑은 뒤, 더 정교한 모델(Cross-encoder)로 질문과 각 문서를 한 쌍씩 비교해 순위를 다시 매겨 top-3만 최종 사용합니다. 1차 검색은 빠르지만 거칠고, 리랭킹은 느리지만 정밀해서 이 둘을 조합합니다.
**메타데이터 필터링**
"2024년 이후 문서만", "인사팀 문서만" 처럼 벡터 검색 전에 조건으로 먼저 걸러내는 것. 이걸 안 하면 다른 부서 문서가 섞여 들어옵니다.
python
```
collection.query(
query_texts=[question],
n_results=3,
where={"department": "HR", "year": {"$gte": 2024}}
)
```
#### 6) RAG가 실패하는 대표 패턴
| 문제 | 원인 | 해결 |
| --- | --- | --- |
| 엉뚱한 문서가 검색됨 | 청킹이 너무 크거나 작음 | 청크 크기 조정, 의미 단위 분할 |
| 고유명사·숫자 검색 실패 | 벡터 검색만 사용 | 하이브리드 검색 도입 |
| 검색은 됐는데 답이 틀림 | 검색된 문서와 실제 관련성 낮음 | 리랭킹 추가 |
| 최신 정보인데 옛날 문서 나옴 | 메타데이터 필터 없음 | 날짜·버전 필터링 |
| 답변에 근거 없는 내용 섞임 (환각) | 프롬프트에 "문서에 없으면 모른다고 답해" 지시 없음 | 프롬프트 엔지니어링 |
마지막 항목은 실무에서 매우 자주 나옵니다. 프롬프트에 이런 지시를 명시적으로 넣어야 합니다.
```
"주어진 문서에 답이 없으면 '문서에서 찾을 수 없습니다'라고 답하세요.
문서에 없는 내용을 추측해서 답하지 마세요."
```
<br>
***
<br>
#### 면접 예상 질문 대비 (섹션 5에 추가하면 좋을 항목)
**Q. RAG와 Fine-tuning의 차이는 언제 뭘 써야 하나요?**
Fine-tuning은 모델 자체의 말투나 특정 태스크 수행 방식을 바꿀 때 적합하고, RAG는 최신 정보나 자주 바뀌는 문서를 실시간으로 반영해야 할 때 적합합니다. 실무에서는 둘을 함께 쓰는 경우도 많습니다 — 말투는 fine-tuning으로, 지식은 RAG로.
**Q. Vector DB에 문서가 100만 개면 검색이 느려지지 않나요?**
ANN(근사 최근접 이웃) 인덱싱을 쓰기 때문에 정확도를 약간 포기하는 대신 속도를 확보합니다. HNSW 같은 인덱스는 100만 개 중에서도 밀리초 단위로 검색됩니다.
**Q. RAG 성능을 어떻게 평가하나요?**
검색된 문서가 실제로 질문과 관련 있는지(Retrieval 정확도)와, 최종 답변이 검색된 문서에 충실한지(Faithfulness), 답변이 실제로 질문에 유용한지를 나눠서 평가합니다. RAGAS 같은 평가 프레임워크가 자주 쓰입니다.
콘텐츠를 불러오는 중..

댓글목록
등록된 댓글이 없습니다.