k
korAI
중급 전체
중급2026-05-097분

Claude가 모르는 최신 정보를 알려주는 법: RAG 입문 실전 구현

RAG(Retrieval-Augmented Generation)는 외부 문서를 검색해 프롬프트에 주입함으로써 Claude의 지식 한계를 뛰어넘는 패턴이다. 핵심 원리부터 간단한 Python 구현까지 한 번에 정리한다.

ragretrievalcontext-injection

Claude가 '모른다'고 할 때

Claude의 학습 데이터는 특정 시점에서 끊긴다. 사내 문서, 최신 뉴스, 제품 매뉴얼처럼 Claude가 학습하지 않은 정보를 다뤄야 할 때 RAG가 답이다.

RAG의 흐름:

사용자 질문 → 관련 문서 검색 → 문서를 컨텍스트에 삽입 → Claude 응답

Fine-tuning과 달리 모델 재학습이 필요 없고, 문서를 언제든 업데이트할 수 있다.

RAG의 세 가지 핵심 단계

1. 인덱싱 (Indexing)

문서를 청크(chunk)로 분할하고 검색 가능한 형태로 저장한다.

  • 청크 크기: 일반적으로 200~500토큰
  • 너무 작으면 문맥 손실, 너무 크면 노이즈 증가

2. 검색 (Retrieval)

질문과 관련된 청크를 찾는다.

  • 키워드 검색: BM25 등 전통 방식, 빠르고 가볍다
  • 벡터 검색: 임베딩 유사도, 의미 기반 검색에 강하다
  • 하이브리드: 둘을 결합해 정확도 향상

3. 생성 (Generation)

검색된 문서를 프롬프트에 넣어 Claude에게 전달한다.

  • 검색 결과는 user 메시지 또는 system에 삽입
  • 출처 명시를 요구하면 hallucination 감소

Python 간단 구현 (키워드 기반 RAG)

실제 벡터 DB 없이 동작하는 최소 RAG 패턴이다. 프로덕션에서는 이 검색 부분만 Chroma·Pinecone 등으로 교체하면 된다.

import anthropic
from typing import TypedDict

client = anthropic.Anthropic()

# --- 1. 문서 저장소 (실제로는 벡터 DB) ---
DOCS: list[dict] = [
    {
        "id": "doc1",
        "content": "Claude Sonnet 4는 2025년 출시된 Anthropic의 모델로, "
                   "코딩과 추론 능력이 크게 향상되었다.",
        "source": "Anthropic 공식 블로그",
    },
    {
        "id": "doc2",
        "content": "RAG는 검색 증강 생성의 약자로, 외부 문서를 "
                   "실시간으로 프롬프트에 주입해 LLM의 응답 정확도를 높인다.",
        "source": "AI 용어집",
    },
    {
        "id": "doc3",
        "content": "Anthropic의 API 요금은 입력 토큰과 출력 토큰을 "
                   "별도로 과금하며, 모델마다 단가가 다르다.",
        "source": "Anthropic 요금 페이지",
    },
]


class RetrievedDoc(TypedDict):
    id: str
    content: str
    source: str
    score: int


# --- 2. 간이 키워드 검색 ---
def retrieve(query: str, top_k: int = 2) -> list[RetrievedDoc]:
    """질문 키워드와 문서 내용을 단순 겹침으로 점수 계산."""
    query_words = set(query.lower().split())
    scored: list[RetrievedDoc] = []
    for doc in DOCS:
        doc_words = set(doc["content"].lower().split())
        score = len(query_words & doc_words)
        scored.append({**doc, "score": score})  # type: ignore
    scored.sort(key=lambda d: d["score"], reverse=True)
    return [d for d in scored[:top_k] if d["score"] > 0]


# --- 3. RAG 프롬프트 조립 + 생성 ---
def rag_query(question: str) -> str:
    docs = retrieve(question)

    if not docs:
        context = "관련 문서를 찾지 못했습니다."
    else:
        context = "\n\n".join(
            f"[출처: {d['source']}]\n{d['content']}" for d in docs
        )

    prompt = f"""아래 문서를 참고해 질문에 답하라.
반드시 출처를 언급하고, 문서에 없는 내용은 추측하지 마라.

=== 참고 문서 ===
{context}

=== 질문 ===
{question}"""

    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=512,
        messages=[{"role": "user", "content": prompt}],
    )
    return response.content[0].text  # type: ignore


# 실행
answer = rag_query("RAG가 무엇인지 설명해줘")
print(answer)

프로덕션으로 가는 업그레이드 경로

| 단계 | 키워드 RAG | 벡터 RAG | 하이브리드 | |------|-----------|---------|----------| | 검색 정확도 | 낮음 | 높음 | 가장 높음 | | 구현 난이도 | 쉬움 | 중간 | 어려움 | | 추천 도구 | 내장 dict | Chroma, Pinecone | Weaviate | | 시작 기준 | PoC·학습용 | 실서비스 | 대규모 서비스 |


✅ RAG 구현 체크리스트

  • [ ] 문서 청크 크기를 200~500토큰 범위로 설정했는가?
  • [ ] 검색 결과가 없을 때의 fallback 메시지를 처리했는가?
  • [ ] 프롬프트에 출처 명시 요구를 포함했는가?
  • [ ] 검색된 문서가 max_tokens 한도를 초과하지 않는가?
  • [ ] 문서 업데이트 시 인덱스도 함께 갱신하는 파이프라인이 있는가?
  • [ ] 응답에 문서에 없는 내용이 포함되는지 주기적으로 감사하는가?