⚡ 중급2026-09-167분
RAG 파이프라인: 모델이 '모른다'고 말하게 만드는 구조 설계
Retrieval-Augmented Generation은 외부 문서를 검색해 모델 답변에 근거를 붙이는 기법이다. 단순히 문서를 붙여넣는 것과 달리, 검색 품질과 컨텍스트 주입 방식이 환각(hallucination)을 결정한다.
ragretrievalhallucination
RAG로 환각을 줄이고 신뢰 가능한 답변 만들기
LLM의 가장 큰 약점은 학습 데이터에 없는 정보를 지어낸다는 것이다. RAG는 이 문제를 '검색 → 주입 → 생성' 3단계로 해결한다. 직접 파인튜닝하지 않아도 최신 사내 문서나 도메인 지식을 모델에 실시간으로 연결할 수 있다.
RAG의 핵심 구성 요소
RAG를 처음 구현할 때 혼동하기 쉬운 개념부터 정리한다.
| 단계 | 역할 | 실패 시 증상 | |------|------|-------------| | Retrieval | 쿼리와 유사한 청크 검색 | 관련 없는 문서 포함, 정보 누락 | | Augmentation | 검색 결과를 프롬프트에 주입 | 토큰 초과, 컨텍스트 오염 | | Generation | 근거 기반 답변 생성 | 검색 결과 무시하고 환각 |
가장 중요한 원칙: 검색된 문서가 없으면 모델이 '모른다'고 말하도록 프롬프트를 설계해야 한다.
실전 코드: 간단한 RAG 파이프라인 (Python)
import anthropic
from dataclasses import dataclass
client = anthropic.Anthropic()
@dataclass
class Document:
id: str
content: str
source: str
# 실제 환경에서는 벡터DB(Pinecone, Chroma 등)로 교체
DOCUMENT_STORE: list[Document] = [
Document(
id="doc-1",
content="Claude API의 rate limit은 Tier에 따라 다르며, Tier 1 기준 분당 50 요청이다.",
source="anthropic-docs/rate-limits",
),
Document(
id="doc-2",
content="claude-haiku-4-5는 빠른 응답이 필요한 실시간 작업에 최적화된 모델이다.",
source="anthropic-docs/models",
),
Document(
id="doc-3",
content="Streaming 응답은 stream=True 파라미터로 활성화하며, 긴 응답의 UX를 개선한다.",
source="anthropic-docs/streaming",
),
]
def retrieve(query: str, top_k: int = 2) -> list[Document]:
"""단순 키워드 매칭 (실제 서비스에서는 임베딩 유사도 검색 사용)"""
query_words = set(query.lower().split())
scored: list[tuple[float, Document]] = []
for doc in DOCUMENT_STORE:
doc_words = set(doc.content.lower().split())
overlap = len(query_words & doc_words)
if overlap > 0:
scored.append((overlap, doc))
scored.sort(key=lambda x: x[0], reverse=True)
return [doc for _, doc in scored[:top_k]]
def build_rag_prompt(query: str, docs: list[Document]) -> str:
if not docs:
context_block = "검색된 문서가 없습니다."
else:
context_block = "\n\n".join(
f"[출처: {doc.source}]\n{doc.content}" for doc in docs
)
return f"""당신은 Anthropic 문서 기반 Q&A 어시스턴트입니다.
## 규칙
- 반드시 아래 [참고 문서]에 있는 내용만 근거로 답하세요.
- 문서에 없는 내용은 "해당 정보는 제공된 문서에 없습니다."라고 명확히 말하세요.
- 답변 끝에 사용한 출처를 표기하세요.
## 참고 문서
{context_block}
## 질문
{query}"""
def rag_query(query: str) -> str:
# 1. Retrieve
docs = retrieve(query, top_k=2)
# 2. Augment
prompt = build_rag_prompt(query, docs)
# 3. Generate
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=512,
messages=[{"role": "user", "content": prompt}],
)
return response.content[0].text
if __name__ == "__main__":
# 문서에 있는 질문
print(rag_query("rate limit이 얼마야?"))
print("---")
# 문서에 없는 질문 → '모른다'고 답해야 함
print(rag_query("Claude의 학습 데이터 크기는?"))
RAG 파이프라인 체크리스트
프로덕션 배포 전 반드시 확인할 항목들.
- [ ] 청크 크기를 조정했는가? — 너무 크면 관련 없는 내용 포함, 너무 작으면 맥락 손실 (권장: 200~500토큰)
- [ ] '문서 없을 때' 처리가 있는가? — 빈 context 시 환각 확률 급등
- [ ] 출처(source)를 응답에 포함했는가? — 사용자가 원문 검증할 수 있어야 함
- [ ] 검색 결과 수(top_k)가 컨텍스트 한도 내인가? — claude-sonnet-4-6 기준 최대 200K 토큰이지만 비용 고려
- [ ] '문서에 없으면 모른다'는 규칙이 프롬프트에 명시되어 있는가?
- [ ] 검색 품질을 별도로 평가했는가? — 생성 품질보다 검색 품질이 병목인 경우가 더 많음