RAG: 검색 강화 생성
LLM의 내부 지식만 사용하는 대신, 외부 데이터베이스에서 관련 정보를 검색하여 응답 생성에 활용하는 기법
Retrieval-Augmented Generation (검색 강화 생성)
개념
RAG는 LLM의 내부 지식만 사용하는 대신, 외부 데이터베이스에서 관련 정보를 검색하여 응답 생성에 활용하는 기법이다.
동작 구조
- 질문 입력: 사용자가 질문을 제출한다.
- 문서 검색: 벡터 DB에서 관련 문서를 검색한다.
- 컨텍스트 결합: 검색 결과와 질문을 함께 LLM에 전달한다.
- 답변 생성: LLM이 검색된 문서를 참고하여 답변을 생성한다.
왜 RAG가 필요한가?
LLM의 근본적인 한계:
| 한계 | 설명 |
|---|---|
| 지식 단절 | 학습 이후 정보를 알 수 없음 |
| 할루시네이션 | 모르는 것을 지어냄 |
| 출처 불명 | 정보의 근거 확인 어려움 |
RAG의 장점
| 장점 | 설명 |
|---|---|
| 최신 정보 | 학습 데이터 이후 정보 접근 |
| 할루시네이션 감소 | 실제 문서 기반 응답 |
| 투명성 | 출처 확인 가능 |
| 도메인 특화 | 특정 분야 문서로 전문성 확보 |
예시
질문: "2024년 노벨 물리학상 수상자는?"
LLM만 사용 "2024년 노벨 물리학상 수상자에 대한 정보가 학습 데이터에 포함되어 있지 않다." (또는 할루시네이션)
RAG 사용
- 벡터 DB 검색: "2024 노벨 물리학상"
- 관련 문서 검색됨: 뉴스 기사, 위키피디아
- LLM에 문서와 함께 질문 전달
- 답변: "2024년 노벨 물리학상은 존 홉필드와 제프리 힌턴이 수상했다."
벡터 검색 원리
임베딩 생성
문서: "한국의 수도는 서울이다" 임베딩 모델을 통해 변환한다. 벡터: [0.23, -0.45, 0.12, ...]
유사도 검색
질문: "한국의 수도는?" 질문 임베딩: [0.21, -0.43, 0.15, ...] 벡터 DB에서 가장 유사한 문서를 검색한다. 결과: "한국의 수도는 서울이다" (유사도: 0.95)
RAG 파이프라인
- 인덱싱 단계: 문서를 청크로 분할하고, 임베딩을 생성하여 벡터 DB에 저장한다.
- 쿼리 단계: 질문을 임베딩하고, 유사 문서를 검색한 뒤 LLM에 전달하여 답변을 생성한다.
청킹 전략
| 전략 | 설명 | 적합한 경우 |
|---|---|---|
| 고정 크기 | 500자씩 분할 | 일반적 |
| 문장 기반 | 문장 단위 분할 | 짧은 QA |
| 의미 기반 | 단락/섹션 단위 | 긴 문서 |
| 겹침 사용 | 청크 간 중복 포함 | 문맥 보존 |
GraphRAG: 구조화된 지식
기본 RAG는 텍스트 덩어리를 검색한다. GraphRAG는 지식 그래프를 활용한다.
질문: "페니실린은 누가 발견했는가?" 지식 그래프에서 경로를 추적한다: Alexander Fleming → discovered → Penicillin Alexander Fleming → year → 1928 답변: "알렉산더 플레밍이 1928년에 발견"
지식 그래프 예시
Alexander Fleming
- discovered → Penicillin
- nationality → Scottish
- profession → Bacteriologist
Penicillin
- type → Antibiotic
- discovered_year → 1928
RAG vs Fine-tuning
| RAG | Fine-tuning | |
|---|---|---|
| 지식 업데이트 | 문서만 추가 | 재학습 필요 |
| 비용 | 낮음 | 높음 |
| 출처 확인 | 가능 | 어려움 |
| 도메인 적용 | 빠름 | 느림 |
| 정확도 | 검색 품질 의존 | 모델 품질 의존 |
구현 예시
from langchain import VectorStore, LLM
class RAGSystem:
def __init__(self, documents):
# 문서 임베딩 및 저장
self.vector_store = VectorStore.from_documents(
documents,
embedding_model="text-embedding-ada-002"
)
self.llm = LLM(model="gpt-4")
def query(self, question):
# 1. 관련 문서 검색
relevant_docs = self.vector_store.similarity_search(
question,
k=3 # 상위 3개 문서
)
# 2. 프롬프트 구성
context = "\n".join([doc.content for doc in relevant_docs])
prompt = f"""다음 문서를 참고하여 질문에 답하세요.
문서:
{context}
질문: {question}
답변:"""
# 3. LLM 응답 생성
answer = self.llm.generate(prompt)
return {
"answer": answer,
"sources": [doc.source for doc in relevant_docs]
}고급 기법
Hybrid Search
벡터 검색 + 키워드 검색 결합:
질문: "Python GIL이란?"
벡터 검색: 의미적으로 유사한 문서를 검색한다. 키워드 검색: "GIL"이 포함된 문서를 검색한다.
두 결과를 결합하여 더 정확한 검색 결과를 얻다.
Re-ranking
검색 결과를 LLM으로 재정렬:
검색 결과 10개를 가져온 뒤, LLM이 질문과 가장 관련 있는 순서로 재정렬한다. 최종적으로 재정렬된 결과를 활용한다.
Query Expansion
질문 확장:
원래 질문: "파이썬 속도 개선" 확장된 질문: "파이썬 성능 최적화", "Python performance", "파이썬 프로파일링"
확장된 질문들로 더 많은 관련 문서를 검색한다.
한계
- 검색 품질 의존: 관련 문서를 못 찾으면 실패
- 컨텍스트 한계: 너무 많은 문서는 처리 불가
- 실시간 정보: DB 업데이트 지연
- 복잡한 추론: 여러 문서 조합 어려움
관련 개념
- LLM Agent Survey: 메모리 메커니즘
- ReAct: 도구 사용 (검색 포함)
- MADKE: 지식 풀 기반 토론