RAG
LLM의 한계
LLM(Large Language Model)은 방대한 텍스트 데이터로 학습되어 다양한 질문에 답변할 수 있다. 하지만 다음과 같은 근본적인 한계가 있다.
- 환각(Hallucination) : 학습 데이터에 없는 내용을 그럴듯하게 지어내는 현상
- 최신 정보 부재 : 학습 데이터 이후의 정보는 알 수 없음
- 도메인 지식 부족 : 특정 회사/서비스의 내부 정보는 학습되지 않음
가장 간단한 방법은 관련 자료를 프롬프트에 직접 포함시키는 것이다.
# 기존 방식 (환각 위험)
"Yes24 총알배송이 뭔가요?"
# 개선된 방식 (컨텍스트 포함)
"다음 자료를 참고해서 답변해주세요:
[총알배송 정책 문서 내용...]
질문 : Yes24 총알배송이 뭔가요?"이 방식의 장점:
- LLM이 실제 자료를 기반으로 답변
- 환각 가능성 대폭 감소
- 최신 정보 반영 가능
코드
# 컨텍스트를 포함한 프롬프트 구성
from langchain_core.prompts import ChatPromptTemplate
prompt_template = ChatPromptTemplate.from_messages([
("system", """당신은 AI 온라인 서점의 고객 서비스 상담원입니다.
다음 자료를 참고하여 고객의 질문에 정확하게 답변해주세요.
자료에 없는 내용은 "해당 정보는 제공된 자료에 없습니다"라고 답변하세요.
[참고 자료]
{context}"""),
("human", "{question}")
])
# 질문
question = "Yes24에서 총알배송이 뭔가요? 어떤 조건에서 가능한가요?"
# 프롬프트 생성 후 LLM 직접 호출
messages = prompt_template.format_messages(context=doc_content, question=question)
response = llm.invoke(messages)
print("📝 질문:", question)
print("\n🤖 자료 기반 응답:")
print(response.content)비교표
| 비교 항목 | LLM만 | 자료 포함 |
|---|---|---|
| 정확도 | 낮음 (추측/환각) | 높음 (자료 기반) |
| 신뢰도 | 낮음 | 높음 |
| 한계 | 도메인 지식 부재 | 자료 크기 제한 |
의미 기반 검색 + DB화 기초
**임베딩(Embedding)**이란?
- 텍스트를 숫자 벡터로 변환하는 기술
- 의미가 비슷한 텍스트는 비슷한 벡터를 가짐
- 예: “총알배송” - “빠른 배송” - “신속 배송” (벡터 유사도 높음)
VectorDB란?
- 임베딩 벡터를 저장하고 검색하는 데이터베이스
- 쿼리 벡터와 유사도가 높은 문서를 빠르게 검색
- 대표 예 : ChromaDB, Pinecone, FAISS
[의미 기반 검색 흐름]
질문: "빠른 배송 알려줘"
↓ 임베딩
질문 벡터: [0.2, -0.5, 0.8, ...]
↓ 유사도 검색
가장 유사한 문서: "총알배송_서비스 혜택.pdf" (유사도 0.92)
코사인 유사도(Cosine Similarity)의 수학적 배경
Vector Store에서 “유사한 문서”를 찾을 때 가장 많이 사용하는 방법은 코사인 유사도이다.
코사인 유사도
- 두 벡터 사이의 각도를 측정 (크기는 무시)
- 값의 범위 : -1 ~ 1 (임베딩 벡터의 각 차원 값은 양수&음수 모두 가능하며, 코사인 유사도는 벡터 간 방향 유사성을 측정)
- 1에 가까울수록 의미가 유사, 0에 가까울수록 관련 없음
| 유사도 방식 | 특징 | 적합한 상황 |
|---|---|---|
| 유클리드 거리 | 벡터 간 직선 거리 | 크기도 중요한 경우 |
| 코사인 유사도 | 벡터 간 각도 (크기 무시) | 텍스트 유사도 측정 |
| 내적(Dot Product) | 빠른 계산 | 정규화된 벡터 |
텍스트 임베딩에서 코사인 유사도가 선호되는 이유:
- 긴 문서와 짧은 문서를 공정하게 비교 가능
- 의미적 방향성만 비교하므로 직관적
청킹 전략
청킹 : 긴 문서를 적절한 크기의 조각(chunk)으로 나누는 기법
청킹이 필요한 이유:
- 토큰 제한 : LLM에는 입력 토큰 제한이 있음
- 검색 정확도 : 작은 단위일수록 관련성 높은 정보만 검색
- 비용 효율 : 불필요한 토큰 사용 감소
주요 파라미터:
chunk_size: 청크의 최대 크기 (문자 수)chunk_overlap: 청크 간 겹치는 부분 (문맥 유지)
[청킹 예시]
원본 : "ABCDEFGHIJ" (10글자)
chunk_size=5, overlap=2
-> ["ABCDE", "DEFGH", "GHIJ"]청킹 전략별 Trade-off 심화 분석
청킹 설정은 검색 품질, 비용, 응답 정확도 사이의 균형을 결정합니다.
chunk_size Trade-off
| chunk_size | 장점 | 단점 | 적합한 상황 |
|---|---|---|---|
| 작음 (100~200) | 세밀한 검색, 정확한 매칭 | 문맥 부족, 청크 수 증가 | FAQ, 정의/용어 검색 |
| 중간 (300~500) | 균형 잡힌 검색 | 최적점 찾기 어려움 | 일반 문서, 정책 안내 |
| 큼 (1000+) | 풍부한 문맥 | 노이즈 포함, 검색 정확도 하락 | 긴 설명문, 연속적 내용 |
chunk_overlap Trade-off
| overlap 비율 | 장점 | 단점 |
|---|---|---|
| 0% | 중복 없음, 저장 효율적 | 문맥 단절 위험, 문장 중간 절단 |
| 10~20% | 문맥 연결 유지, 적절한 중복 | 표준적인 선택 |
| 30%+ | 강한 문맥 유지 | 과도한 중복, 저장/비용 증가 |
실전 선택 기준:
문서 특성 판단:
├── FAQ 형태 (짧은 Q&A)
│ └── chunk_size: 100~200, overlap: 0~10%
├── 정책 문서 (조건/단서 많음)
│ └── chunk_size: 300~400, overlap: 15~20%
├── 기술 문서 (연속적 설명)
│ └── chunk_size: 500~800, overlap: 10~15%
└── 법률/계약 문서 (정확성 중요)
└── chunk_size: 200~300, overlap: 20~25%
청킹 전략 선택 기준표
| chunk_size | 특징 | 적합한 상황 |
|---|---|---|
| 작음 (100~200) | 세밀한 검색, 청크 수 많음 | FAQ, 짧은 문단 |
| 중간 (300~500) | 균형 잡힌 검색 | 일반 문서 |
| 큼 (1000+) | 넓은 문맥, 청크 수 적음 | 긴 설명, 연속적 내용 |
overlap 설정:
- 0: 문맥 단절 위험
- chunk_size의 10~20%: 적절한 문맥 유지
- 너무 큼: 중복 정보, 비용 증가
전체 RAG 파이프라인 (LangGraph)
LangGraph : LangChain 팀에서 개발한 상태 기반 워크플로우 프레임워크
왜 LangGraph인가?
- 명시적 상태 관리 : 각 노드가 상태를 읽고 쓰며, 데이터 흐름이 명확함
- 모듈화된 노드 : 검색, 생성 등 각 단계를 독립적인 노드로 분리
- 확장성 : 조건부 분기, 반복, 병렬 실행 등 복잡한 워크플로우 지원
- 디버깅 용이 : 각 노드의 입출력을 추적하기 쉬움
LangGraph 핵심 구성요소:
- State : 그래프 전체에서 공유되는 데이터 구조
- Node : 상태를 변환하는 함수 (검색, 생성 등)
- Edge : 노드 간 연결 (순차 실행, 조건부 분기)
[LangGraph RAG 파이프라인 구조]
START
│
▼
┌─────────────┐
│ retrieve │ ← 질문으로 관련 문서 검색
│ (Node 1) │
└─────────────┘
│
▼
┌─────────────┐
│ generate │ ← 검색된 문서로 답변 생성
│ (Node 2) │
└─────────────┘
│
▼
ENDLCEL 체인 vs LangGraph:
| 비교 항목 | LCEL 체인 | LangGraph |
|---|---|---|
| 상태 관리 | 암시적 | 명시적 (TypedDict) |
| 분기/반복 | 제한적 | 유연함 |
| 디버깅 | 어려움 | 용이함 |
| 확장성 | 단순 파이프라인 적합 | 복잡한 워크플로우 적합 |
RAG 효과 정리
| 비교 항목 | LLM only | LangGraph RAG |
|---|---|---|
| 정확도 | 낮음 (추측/환각) | 높음 (자료 기반) |
| 신뢰도 | 낮음 | 높음 |
| 최신 정보 | 학습 시점까지만 | 업데이트 가능 |
| 도메인 지식 | 제한적 | 문서에 따라 확장 |
| 출처 추적 | 불가능 | 가능 |
| 확장성 | 제한적 | 노드 추가로 유연한 확장 |
LangGraph의 장점:
- 각 노드(retrieve, generate)의 역할이 명확히 분리됨
- 상태(State)를 통해 데이터 흐름이 투명함
- 향후 조건부 분기, 반복 등 복잡한 로직 추가 용이
핵심 포인트: LangGraph RAG를 통해 정확한 답변 생성이 가능해졌고, 워크플로우 확장도 용이하다.