⚡ 중급2026-09-087분
AI가 모르는 내 데이터를 알게 하는 RAG 첫걸음
RAG(Retrieval-Augmented Generation)는 외부 문서를 검색해 컨텍스트에 주입하는 방식으로, 모델 재학습 없이 최신·사내 정보를 AI에게 주입할 수 있습니다. 구조만 이해하면 생각보다 빠르게 구축할 수 있습니다.
ragretrievalcontext-injection
RAG가 필요한 순간
모델은 학습 시점 이후의 정보를 모릅니다. 또 사내 문서, 제품 매뉴얼, 고객 데이터는 애초에 학습 데이터에 없습니다. Fine-tuning 없이 이 문제를 해결하는 가장 현실적인 방법이 RAG입니다.
RAG의 3단계 구조
[사용자 질문]
│
▼
① Retrieve → 벡터 DB / 키워드 검색으로 관련 청크 추출
│
▼
② Augment → 검색된 청크를 system/user 메시지에 삽입
│
▼
③ Generate → Claude가 컨텍스트 기반으로 답변 생성
핵심 원칙: 모델에게 "정보를 기억"시키는 게 아니라, 매 요청마다 관련 정보를 꺼내 눈앞에 펼쳐주는 것입니다.
실전 코드 — 간단한 문서 QA 시스템
import anthropic
from typing import TypedDict
client = anthropic.Anthropic()
# 실제 프로젝트에서는 벡터 DB(Pinecone, pgvector 등)로 교체
DOCUMENT_STORE = [
{
"id": "refund-policy",
"content": "환불 정책: 구매 후 30일 이내 미개봉 제품은 전액 환불. "
"개봉 제품은 7일 이내 교환만 가능. 디지털 콘텐츠는 환불 불가.",
"keywords": ["환불", "반품", "교환"],
},
{
"id": "shipping-policy",
"content": "배송 정책: 오전 11시 이전 주문 시 당일 출고. "
"제주/도서산간 지역은 3~5일 추가 소요. 무료배송 기준 5만원 이상.",
"keywords": ["배송", "출고", "무료배송", "도서산간"],
},
{
"id": "membership",
"content": "멤버십: 실버(연 10만원 이상) 5% 할인, 골드(연 30만원 이상) 10% 할인, "
"플래티넘(연 100만원 이상) 15% 할인 + 무료배송.",
"keywords": ["멤버십", "등급", "할인", "실버", "골드", "플래티넘"],
},
]
class RetrievedChunk(TypedDict):
id: str
content: str
score: int
def retrieve(query: str, top_k: int = 2) -> list[RetrievedChunk]:
"""키워드 기반 단순 검색 (실제 프로덕션에서는 임베딩 벡터 검색으로 교체)"""
scored: list[RetrievedChunk] = []
for doc in DOCUMENT_STORE:
score = sum(1 for kw in doc["keywords"] if kw in query)
if score > 0:
scored.append({"id": doc["id"], "content": doc["content"], "score": score})
scored.sort(key=lambda x: x["score"], reverse=True)
return scored[:top_k]
def build_context(chunks: list[RetrievedChunk]) -> str:
if not chunks:
return "관련 문서를 찾을 수 없습니다."
return "\n\n".join(
f"[문서 {i+1}]\n{chunk['content']}" for i, chunk in enumerate(chunks)
)
def rag_query(question: str) -> str:
# ① Retrieve
chunks = retrieve(question)
context = build_context(chunks)
# ② Augment + ③ Generate
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=512,
system=(
"당신은 고객 지원 AI입니다. "
"반드시 아래 제공된 문서 내용만을 근거로 답변하세요. "
"문서에 없는 내용은 '확인이 필요합니다'라고 답하세요.\n\n"
f"=== 참고 문서 ===\n{context}"
),
messages=[{"role": "user", "content": question}],
)
return response.content[0].text
# 실행 예시
print(rag_query("골드 멤버십이면 무료배송 되나요?"))
# → 멤버십 + 배송 문서를 함께 검색해 정확한 답변 생성
RAG 도입 전 확인할 체크리스트
- [ ] 청크 크기 설정 — 너무 크면 노이즈, 너무 작으면 맥락 손실 (일반적으로 300~800 토큰)
- [ ] 검색 방식 결정 — 키워드(BM25) vs 시맨틱(임베딩) vs 하이브리드
- [ ] 출처 명시 로직 포함 — 모델이 어떤 문서를 참조했는지 추적 가능하게
- [ ] 컨텍스트 길이 예산 계산 — 검색 결과 × 청크 크기가 max_tokens를 초과하지 않는지
- [ ] "문서에 없으면 모른다고 해"를 system에 명시 — 환각 방지
- [ ] 검색 결과가 0개일 때 폴백 처리 구현