LLM은 강력하지만 두 가지 근본적인 한계를 가집니다. RAG는 그 한계를 데이터 설계로 돌파하는 핵심 패턴입니다. 이 모듈을 제대로 이해하면 "모델이 틀리는 문제"가 아니라 "시스템이 틀리는 문제"로 시각이 전환됩니다.
LLM의 두 가지 한계
Knowledge Cutoff(지식 단절): 모델은 학습 시점 이후 발생한 사건을 알지 못합니다. 오늘 기준 Claude 4.x의 컷오프는 2025년 초반이므로, 그 이후 변경된 회사 정책·제품 스펙·법령 개정 내용은 모델 안에 없습니다.
Hallucination(환각): 모델은 모르는 것을 "모른다"고 말하는 대신 그럴듯한 내용을 생성하는 경향이 있습니다. 특히 도메인 특화 문서, 사내 데이터, 고유명사가 결합될수록 환각 빈도가 높아집니다.
RAG(Retrieval-Augmented Generation)는 이 두 한계를 동시에 완화합니다. 질문이 들어오면 관련 문서를 먼저 검색해 컨텍스트로 주입하고, 모델은 그 근거에서만 답하도록 설계합니다.
Embedding과 벡터공간 직관
텍스트를 벡터로 변환한다는 것은, 의미가 비슷한 문장을 고차원 공간에서 가까이 배치한다는 뜻입니다. 이 강의에서는 text-embedding-3-small(1536차원)을 사용합니다.
"환불 정책이 어떻게 되나요?" → [0.12, -0.84, 0.33, ...] ← 1536개 숫자
"반품은 언제까지 가능한가요?" → [0.11, -0.81, 0.35, ...] ← 거의 같은 방향
"파이썬 클로저란?" → [0.72, 0.19, -0.60, ...] ← 전혀 다른 방향
의미 거리는 코사인 유사도로 측정합니다.
Cosine Similarity 수식
$$\text{sim}(A, B) = \frac{A \cdot B}{|A| \cdot |B|}$$
내적을 두 벡터의 크기로 나눈 값으로, 결과는 −1 ~ 1 사이입니다. 1에 가까울수록 의미가 유사합니다. 길이(토큰 수)가 달라도 방향만 비교하기 때문에 문서 길이 편향이 없다는 장점이 있습니다.
의사코드로는 다음과 같습니다.
function cosineSim(a: number[], b: number[]): number {
const dot = a.reduce((s, v, i) => s + v * b[i], 0)
const normA = Math.sqrt(a.reduce((s, v) => s + v * v, 0))
const normB = Math.sqrt(b.reduce((s, v) => s + v * v, 0))
return dot / (normA * normB)
}
이 함수 하나로 별도 Vector DB 없이 RAG의 핵심 검색 로직이 완성됩니다.
우리의 선택: No-Vector-DB RAG
| 접근 | 장점 | 단점 | 적합 규모 |
|---|---|---|---|
| pgvector | SQL과 통합, 관리 간편 | 셋업 비용 | 수만~수십만 건 |
| Pinecone | 완전관리형, 빠름 | 유료, 외부 의존 | 수백만 건 이상 |
| Upstash Vector | 서버리스 친화 | 무료 한도 제한 | 수만 건 |
| in-memory + Vercel Blob | 의존성 제로, Vercel에 최적화 | 1만 건 초과 시 느려짐 | 1만 건 미만 |
이 강의가 선택한 방식은 마지막 행입니다. 청크 벡터를 JSON으로 직렬화해 Vercel Blob에 저장하고, 요청 시 로드해 코사인 유사도를 전수 계산합니다. 1만 건 미만에서는 Cold Start 포함 200~400ms 이내로 처리 가능하며, 추가 인프라 없이 프로덕션 배포까지 이어집니다.
주의: 문서 건수가 1만을 초과할 시점에 Upstash Vector나 pgvector로 마이그레이션하는 계획을 미리 설계해 두세요. 청크 스키마만 동일하게 유지하면 검색 레이어 교체가 수월합니다.
청킹 전략
문서를 어떻게 나누는가는 검색 품질에 직결됩니다.
| 전략 | 방식 | 적합 상황 |
|---|---|---|
| Fixed | 글자 수/토큰 수로 고정 분할 | 구조 없는 텍스트, 빠른 프로토타입 |
| Semantic | 문장·단락 경계 기준 분할 | 일반 문서, 블로그 |
| Speaker | 발화자 전환 기준 분할 | 회의록, 인터뷰 스크립트 |
| Recursive | 단락→문장→어절 순 재귀 분할 | 중첩 구조, 기술 문서 |
청크 크기는 보통 300~600 토큰을 권장합니다. 너무 작으면 컨텍스트가 부족하고, 너무 크면 검색 노이즈가 늘어납니다. 각 청크에는 source(파일명), page, chunkIndex 같은 메타데이터를 함께 저장해 출처 인용에 활용합니다.
평가 지표
RAG 파이프라인을 배포한 뒤 세 가지 지표로 품질을 측정합니다.
- Faithfulness: 모델이 검색된 청크 내용만 사용해 답했는가. (근거 이탈 여부)
- Answer Relevance: 답변이 원래 질문에 얼마나 관련 있는가.
- Top-K Recall: 정답 청크가 상위 K개 검색 결과 안에 포함되는 비율. K=3 또는 K=5가 일반적.
환각 차단 4패턴
환각은 모델 문제이기 이전에 시스템 설계 문제입니다. 다음 네 가지를 파이프라인에 내재화하세요.
-
Score 임계값: 코사인 유사도가 0.75 미만인 청크는 컨텍스트에 포함하지 않습니다. 낮은 점수의 청크를 억지로 넣으면 오히려 환각을 유발합니다.
-
출처 인용 강제: 시스템 프롬프트에
"반드시 [출처: {source}] 형식으로 근거를 표기하라"를 명시합니다. 인용이 불가능한 내용은 생성하지 못하게 됩니다. -
청크 사이즈 조정: 답변이 단편적이면 청크를 키우고, 무관한 내용이 섞이면 청크를 줄입니다. 청크 크기는 고정값이 아니라 평가 루프로 조정하는 하이퍼파라미터입니다.
-
찾을 수 없으면 솔직히: 임계값을 넘긴 청크가 없을 때 "관련 문서를 찾지 못했습니다"라고 명시적으로 응답하도록 분기 처리합니다. 이 분기가 없으면 모델은 빈 컨텍스트로 답을 지어냅니다.
설계 원칙: RAG의 목적은 모델을 "더 똑똑하게" 만드는 것이 아니라, 답변의 범위를 문서로 제한하는 것입니다. 근거 없이 유창한 답변보다 근거 있는 짧은 답변이 프로덕션에서 훨씬 안전합니다.
핵심 정리
- LLM의 Knowledge Cutoff와 Hallucination을 데이터 설계로 보완하는 것이 RAG의 역할이다.
text-embedding-3-small(1536차원) + 코사인 유사도 직접 구현으로 별도 Vector DB 없이 1만 건 미만 RAG를 운영한다.- 청크는 300~600 토큰,
source/page/chunkIndex메타데이터를 반드시 함께 저장한다. - 평가는 Faithfulness·Answer Relevance·Top-K Recall 세 지표로 파이프라인을 측정한다.
- 환각 차단은 score 임계값 + 출처 인용 강제 + 청크 사이즈 조정 + 미발견 시 솔직 응답 네 가지로 구성한다.
- 청크 스키마를 일관되게 유지하면, 규모가 커질 때 pgvector·Upstash로의 마이그레이션 비용을 최소화할 수 있다.