본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 2. · 14 Views
RAG 검색 증강 생성 완벽 가이드
대규모 언어 모델의 한계를 극복하는 RAG 기술을 처음부터 끝까지 배웁니다. 문서 청킹부터 벡터 DB, LangChain 구현까지 실무에서 바로 활용할 수 있는 내용을 담았습니다.
목차
1. RAG가 필요한 이유
김개발 씨는 사내 챗봇을 개발하라는 미션을 받았습니다. ChatGPT처럼 똑똑한 챗봇을 만들면 되겠지 싶었는데, 막상 시작하니 큰 문제에 부딪혔습니다.
"우리 회사 제품 매뉴얼 내용을 물어보면 엉뚱한 답변만 하네요..."
**RAG(Retrieval-Augmented Generation)**는 대규모 언어 모델이 학습하지 못한 정보를 외부에서 검색해 답변에 활용하는 기술입니다. 마치 오픈북 시험처럼, AI가 답변하기 전에 관련 자료를 먼저 찾아보는 것입니다.
이를 통해 최신 정보나 기업 내부 데이터에 대해서도 정확한 답변이 가능해집니다.
다음 코드를 살펴봅시다.
# RAG의 기본 흐름을 보여주는 의사 코드
from langchain.llms import ChatOpenAI
from langchain.vectorstores import Chroma
# 1단계: 사용자 질문 받기
user_question = "우리 회사 환불 정책이 어떻게 되나요?"
# 2단계: 관련 문서 검색 (Retrieval)
vector_store = Chroma(embedding_function=embeddings)
relevant_docs = vector_store.similarity_search(user_question, k=3)
# 3단계: 검색된 문서와 함께 LLM에 질문 (Augmented Generation)
context = "\n".join([doc.page_content for doc in relevant_docs])
prompt = f"다음 문서를 참고하여 답변하세요:\n{context}\n\n질문: {user_question}"
# 4단계: 최종 답변 생성
response = llm.invoke(prompt)
김개발 씨는 입사 1년 차 백엔드 개발자입니다. 최근 회사에서 AI 챗봇 프로젝트를 맡게 되었습니다.
OpenAI API를 연동해서 ChatGPT 같은 챗봇을 만들면 끝이라고 생각했습니다. 그런데 막상 만들어보니 문제가 생겼습니다.
"우리 회사 연차 정책이 어떻게 되나요?"라고 물으면, 일반적인 한국 기업의 연차 제도를 설명할 뿐 우리 회사만의 특별한 정책은 전혀 모릅니다. 선배 개발자 박시니어 씨가 지나가다 화면을 봤습니다.
"아, GPT는 우리 회사 내부 문서를 학습한 적이 없으니까 당연히 모르지. RAG를 적용해야 해." RAG란 무엇일까요?
쉽게 비유하자면, RAG는 마치 오픈북 시험과 같습니다. 일반적인 LLM은 학창 시절 공부한 내용만으로 시험을 보는 것과 같습니다.
아무리 똑똒해도 배우지 않은 건 답할 수 없습니다. 반면 RAG를 적용하면 시험 볼 때 교과서를 펼쳐볼 수 있는 것처럼, 필요한 정보를 찾아서 답변합니다.
그렇다면 왜 RAG가 필요할까요? 첫째, LLM의 지식 단절 문제가 있습니다.
GPT-4든 Claude든 특정 시점까지의 데이터로 학습됩니다. 어제 발표된 뉴스나 오늘 바뀐 회사 정책은 알 수 없습니다.
둘째, 기업 내부 정보의 보안 문제입니다. 회사 기밀 문서를 OpenAI에 학습시킬 수는 없습니다.
하지만 RAG를 사용하면 내부 문서는 자체 서버에 두고, 필요할 때만 참조하게 할 수 있습니다. 셋째, 환각(Hallucination) 문제입니다.
LLM은 모르는 것도 그럴듯하게 지어내는 경향이 있습니다. RAG를 사용하면 실제 문서를 근거로 답변하므로 환각이 크게 줄어듭니다.
위의 코드를 살펴보겠습니다. 먼저 사용자의 질문을 받습니다.
그 다음이 핵심입니다. similarity_search 함수가 질문과 가장 관련 있는 문서 3개를 찾아옵니다.
이렇게 찾은 문서들을 컨텍스트로 만들어 LLM에게 함께 전달합니다. 실제 현업에서는 어떻게 활용할까요?
고객 지원 챗봇이 대표적입니다. 제품 매뉴얼, FAQ, 이전 상담 내역을 RAG로 연결하면 고객 문의에 정확히 답변할 수 있습니다.
법률 분야에서는 판례 검색, 의료 분야에서는 논문 검색에 활용됩니다. 주의할 점도 있습니다.
RAG는 만능이 아닙니다. 검색 품질이 나쁘면 답변도 엉뚱해집니다.
"쓰레기가 들어가면 쓰레기가 나온다"는 원칙이 여기서도 적용됩니다. 따라서 문서를 어떻게 준비하고 검색하느냐가 RAG 성능의 핵심입니다.
김개발 씨는 고개를 끄덕였습니다. "그렇군요.
LLM에게 책을 쥐어주는 거네요. 근데 그 책을 어떻게 준비해야 하나요?" 박시니어 씨가 미소 지었습니다.
"바로 그게 다음에 배울 청킹이야."
실전 팁
💡 - RAG는 검색(Retrieval) + 증강(Augmented) + 생성(Generation)의 약자입니다
- 문서 품질이 RAG 성능의 80%를 결정합니다
2. 문서 청킹 전략
RAG의 기본 개념을 이해한 김개발 씨는 회사 매뉴얼 PDF 50페이지를 통째로 벡터 DB에 넣으려 했습니다. 그런데 박시니어 씨가 말렸습니다.
"잠깐, 그렇게 하면 검색이 제대로 안 돼. 문서를 적당한 크기로 잘라야 해."
**청킹(Chunking)**은 긴 문서를 검색에 적합한 작은 조각으로 나누는 과정입니다. 마치 두꺼운 백과사전을 색인 카드로 정리하는 것과 같습니다.
청크가 너무 크면 검색 정확도가 떨어지고, 너무 작으면 문맥이 끊어집니다. 적절한 청크 크기와 전략을 선택하는 것이 RAG 성능의 핵심입니다.
다음 코드를 살펴봅시다.
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 원본 문서
document = """
환불 정책 안내
2. 환불 절차
김개발 씨가 의문을 품었습니다. "왜 문서를 잘라야 하나요?
그냥 통째로 넣으면 안 되나요?" 박시니어 씨가 설명을 시작했습니다. "자, 도서관을 생각해봐.
백과사전 한 권을 통째로 보관하면, '고양이의 수명'을 찾을 때 책 전체를 훑어야 해. 하지만 항목별로 색인 카드가 있다면?" "아, 고양이 카드만 꺼내면 되겠네요!" 바로 이것이 청킹의 핵심입니다.
긴 문서를 의미 있는 작은 단위로 나눠서 검색 효율을 높이는 것입니다. 그렇다면 어떤 크기로 나눠야 할까요?
일반적으로 200~500자 정도가 적당합니다. 너무 작으면 "환불"이라는 단어만 있고 실제 정책 내용이 없을 수 있습니다.
너무 크면 환불과 무관한 배송 정책까지 함께 검색됩니다. 여기서 중요한 개념이 chunk_overlap입니다.
문서를 뚝뚝 끊으면 문장이 중간에 잘릴 수 있습니다. "환불은 7일 이내 가능합니다.
단, 신선식품은"에서 잘리면 신선식품 정책이 사라집니다. **겹침(overlap)**을 두면 이전 청크의 끝부분이 다음 청크의 시작에 포함되어 문맥이 유지됩니다.
위 코드에서 separators 설정을 주목하세요. RecursiveCharacterTextSplitter는 문단(\n\n), 줄바꿈(\n), 마침표(.), 공백( ) 순서로 분할을 시도합니다.
가능하면 문단 단위로 나누고, 그래도 크면 문장 단위로 나눕니다. 의미가 최대한 보존되도록 지능적으로 분할합니다.
실무에서는 문서 유형에 따라 전략이 달라집니다. 기술 문서라면 마크다운 헤더 기준으로 나눌 수 있습니다.
코드가 포함된 문서는 코드 블록이 잘리지 않도록 주의해야 합니다. 대화 데이터는 질문-답변 쌍이 분리되지 않도록 해야 합니다.
흔한 실수가 있습니다. 청크 크기를 너무 작게 설정하는 것입니다.
"최대한 정확하게 검색하려면 작을수록 좋지 않나요?"라고 생각하기 쉽습니다. 하지만 "환불"이라는 단어 하나만 있는 청크가 검색되면, 정작 환불 절차는 알 수 없습니다.
또 다른 실수는 모든 문서에 같은 설정을 적용하는 것입니다. FAQ처럼 짧은 문서와 기술 매뉴얼처럼 긴 문서는 다른 전략이 필요합니다.
김개발 씨가 물었습니다. "그러면 최적의 청크 크기는 어떻게 찾나요?" 박시니어 씨가 답했습니다.
"정답은 없어. 실험해봐야 해.
여러 크기로 테스트해보고 검색 품질을 비교하는 거지."
실전 팁
💡 - 일반적인 시작점은 chunk_size=500, chunk_overlap=100입니다
- 문서 유형별로 다른 청킹 전략을 적용하세요
3. 임베딩과 벡터 DB
청킹까지 마친 김개발 씨 앞에 새로운 과제가 놓였습니다. "이 텍스트 조각들을 어떻게 검색하지?
단순히 키워드 매칭으로는 '환불'과 '반품'이 비슷한 의미라는 걸 모를 텐데..."
**임베딩(Embedding)**은 텍스트를 숫자 벡터로 변환하는 기술입니다. 의미가 비슷한 문장은 벡터 공간에서 가까이 위치합니다.
벡터 DB는 이런 벡터들을 저장하고 유사도 검색을 빠르게 수행하는 특수한 데이터베이스입니다. 덕분에 "환불"을 검색하면 "반품", "취소"가 포함된 문서도 함께 찾아집니다.
다음 코드를 살펴봅시다.
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma
# 임베딩 모델 초기화 (텍스트를 벡터로 변환)
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
# 청킹된 문서들
chunks = [
"상품 수령 후 7일 이내에 환불 신청이 가능합니다.",
"신선식품은 수령 후 24시간 이내에만 반품 가능합니다.",
"검수 완료 후 3영업일 이내에 환불 금액이 입금됩니다."
]
# 벡터 DB에 저장 (임베딩 + 저장 한번에 처리)
vector_store = Chroma.from_texts(
texts=chunks,
embedding=embeddings,
persist_directory="./chroma_db" # 로컬 저장 경로
)
# 유사도 검색: "돈 돌려받는 방법" → "환불" 관련 문서 검색됨
results = vector_store.similarity_search("돈 돌려받는 방법", k=2)
박시니어 씨가 화이트보드에 그림을 그리기 시작했습니다. "키워드 검색의 한계부터 이야기해볼까?" 전통적인 검색은 키워드 매칭입니다.
"환불"을 검색하면 "환불"이라는 글자가 있는 문서만 찾습니다. 하지만 고객이 "돈 돌려받고 싶어요"라고 물으면?
"환불"이라는 단어가 없으니 관련 문서를 찾지 못합니다. 이 문제를 해결하는 것이 임베딩입니다.
임베딩은 마치 언어의 지도를 그리는 것과 같습니다. "환불", "반품", "돈 돌려받기", "취소"는 모두 비슷한 의미입니다.
임베딩은 이들을 지도상에서 가까운 위치에 배치합니다. 반면 "배송"이나 "결제"는 멀리 떨어진 곳에 위치합니다.
기술적으로 어떻게 작동할까요? "환불 신청이 가능합니다"라는 문장을 임베딩 모델에 넣으면, [0.021, -0.034, 0.087, ...] 같은 숫자 배열이 나옵니다.
보통 1536개 정도의 숫자로 구성됩니다. 이 숫자들이 문장의 의미를 수학적으로 표현합니다.
벡터 DB는 이 숫자 배열들을 저장하는 특수한 데이터베이스입니다. 일반 DB가 "이름이 김철수인 사람"을 찾는다면, 벡터 DB는 "이 벡터와 가장 가까운 벡터 3개"를 찾습니다.
이를 **유사도 검색(similarity search)**이라고 합니다. 위 코드에서 핵심을 살펴보겠습니다.
Chroma.from_texts가 두 가지 일을 합니다. 먼저 각 청크를 임베딩 모델로 벡터로 변환합니다.
그 다음 변환된 벡터를 Chroma DB에 저장합니다. 나중에 검색할 때는 질문도 벡터로 변환해서 가장 가까운 벡터를 찾습니다.
실무에서 벡터 DB를 선택할 때 고려할 점이 있습니다. Chroma는 로컬에서 쉽게 시작할 수 있어 프로토타입에 적합합니다.
Pinecone은 클라우드 기반으로 확장성이 좋습니다. Weaviate와 Milvus는 대규모 서비스에 적합합니다.
임베딩 모델도 선택해야 합니다. OpenAI의 text-embedding-3-small은 성능과 비용의 균형이 좋습니다.
더 정확한 검색이 필요하면 text-embedding-3-large를 사용합니다. 비용이 부담되면 오픈소스인 sentence-transformers를 고려할 수 있습니다.
주의할 점이 있습니다. 임베딩 모델은 한번 정하면 바꾸기 어렵습니다.
모델마다 출력하는 벡터의 차원과 특성이 다르기 때문입니다. 모델을 바꾸면 기존에 저장한 모든 벡터를 다시 생성해야 합니다.
김개발 씨가 실행 결과를 보며 감탄했습니다. "돈 돌려받는 방법"으로 검색했는데 "환불 신청" 문서가 나왔어요!" 박시니어 씨가 웃으며 말했습니다.
"이게 의미 기반 검색의 힘이야. 이제 이 검색 결과를 어떻게 활용하는지 배워볼까?"
실전 팁
💡 - 처음 시작할 땐 Chroma + OpenAI Embeddings 조합을 추천합니다
- 임베딩 모델은 한국어 성능도 확인하세요
4. 검색 Retrieval 구현
벡터 DB에 문서를 저장한 김개발 씨는 검색 테스트를 해봤습니다. 대부분 잘 작동했지만, 가끔 엉뚱한 결과가 나왔습니다.
"검색 품질을 더 높일 방법이 없을까요?"
**Retrieval(검색)**은 RAG의 R에 해당하는 핵심 단계입니다. 단순 유사도 검색 외에도 다양한 기법으로 검색 품질을 높일 수 있습니다.
**MMR(Maximal Marginal Relevance)**로 다양성을 확보하고, 메타데이터 필터링으로 범위를 좁히며, 하이브리드 검색으로 키워드와 의미 검색을 결합할 수 있습니다.
다음 코드를 살펴봅시다.
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import LLMChainExtractor
# 기본 벡터 스토어 리트리버
base_retriever = vector_store.as_retriever(
search_type="mmr", # 다양성 확보를 위한 MMR 검색
search_kwargs={
"k": 5, # 최종 반환할 문서 수
"fetch_k": 20, # 후보로 가져올 문서 수
"lambda_mult": 0.7 # 관련성(1.0) vs 다양성(0.0) 균형
}
)
# 메타데이터 필터링 예시
filtered_retriever = vector_store.as_retriever(
search_kwargs={
"k": 3,
"filter": {"category": "환불정책"} # 특정 카테고리만 검색
}
)
# 검색 실행
docs = base_retriever.invoke("상품 교환은 어떻게 하나요?")
# 결과: 관련성 높으면서도 다양한 관점의 문서들 반환
김개발 씨가 문제를 발견했습니다. "환불"에 대해 검색하면 비슷비슷한 문서만 5개 나옵니다.
환불 기간, 환불 절차, 환불 예외 등 다양한 정보가 필요한데, 전부 "7일 이내 환불 가능"만 반복됩니다. 박시니어 씨가 설명합니다.
"그건 유사도만 보기 때문이야. 가장 비슷한 문서 5개를 찾으면 당연히 비슷한 내용이 모이지." 이 문제를 해결하는 것이 **MMR(Maximal Marginal Relevance)**입니다.
MMR은 마치 면접관이 지원자를 뽑는 것과 같습니다. 전부 같은 전공의 지원자만 뽑으면 팀이 편향됩니다.
실력도 보지만 다양성도 고려해서 여러 배경의 인재를 선발합니다. 코드에서 lambda_mult 값을 주목하세요.
1.0에 가까우면 관련성만 봅니다. 0.0에 가까우면 다양성만 봅니다.
0.7 정도면 관련성을 70% 고려하면서 다양성도 30% 반영합니다. 실무에서는 0.5~0.8 사이 값을 많이 사용합니다.
fetch_k와 k의 차이도 중요합니다. fetch_k=20은 후보 문서를 20개 가져옵니다.
그 중에서 MMR 알고리즘으로 다양성을 고려해 k=5개를 최종 선택합니다. 후보 풀이 넓을수록 다양한 결과를 얻을 수 있습니다.
메타데이터 필터링은 또 다른 강력한 도구입니다. 문서를 저장할 때 카테고리, 작성일, 부서 등의 메타데이터를 함께 저장할 수 있습니다.
검색할 때 "2024년 이후 작성된 환불 정책만"처럼 범위를 좁힐 수 있습니다. 이렇게 하면 오래된 정책이 검색되는 문제를 방지합니다.
실무에서는 하이브리드 검색도 자주 사용합니다. 의미 검색만으로는 부족할 때가 있습니다.
"SKU-12345" 같은 제품 코드는 의미보다 정확한 키워드 매칭이 필요합니다. 하이브리드 검색은 의미 검색과 키워드 검색을 결합해 두 장점을 모두 취합니다.
검색 결과를 **재순위화(Re-ranking)**하는 방법도 있습니다. 처음 검색한 결과를 더 정교한 모델로 다시 순위를 매깁니다.
비용이 더 들지만 정확도가 크게 향상됩니다. Cohere의 Rerank 모델이나 Cross-Encoder를 사용할 수 있습니다.
주의할 점이 있습니다. 검색 결과 개수(k)를 무작정 늘리면 안 됩니다.
관련 없는 문서까지 포함되면 오히려 답변 품질이 떨어집니다. 또한 LLM의 컨텍스트 길이 제한도 고려해야 합니다.
김개발 씨가 MMR을 적용해봤습니다. "오, 이제 환불 기간, 절차, 예외 사항이 골고루 검색되네요!" 박시니어 씨가 고개를 끄덕였습니다.
"좋아, 이제 검색된 문서를 LLM에게 전달하는 방법을 배워볼까?"
실전 팁
💡 - MMR의 lambda_mult는 0.5~0.8 사이에서 시작하세요
- 메타데이터는 문서 저장 시 미리 잘 설계해야 합니다
5. 생성 Generation 연결
관련 문서를 잘 검색하게 된 김개발 씨에게 마지막 관문이 남았습니다. "검색된 문서를 LLM에게 어떻게 전달해야 할까요?
그냥 다 붙여넣으면 되나요?"
Generation(생성) 단계는 검색된 문서를 컨텍스트로 LLM에게 전달하여 답변을 생성하는 과정입니다. 핵심은 프롬프트 엔지니어링입니다.
문서를 어떤 형식으로 전달하고, 어떤 지시를 주느냐에 따라 답변 품질이 크게 달라집니다. 문서에 없는 내용은 답변하지 않도록 하는 것도 중요합니다.
다음 코드를 살펴봅시다.
from langchain.prompts import ChatPromptTemplate
from langchain.chat_models import ChatOpenAI
from langchain.schema.runnable import RunnablePassthrough
# RAG 프롬프트 템플릿
template = """당신은 고객 지원 상담사입니다.
아래 제공된 문서만을 참고하여 질문에 답변하세요.
문서에 없는 내용은 "해당 정보를 찾을 수 없습니다"라고 답변하세요.
[참고 문서]
{context}
[고객 질문]
{question}
[답변]"""
prompt = ChatPromptTemplate.from_template(template)
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# 검색 결과를 문자열로 변환하는 함수
def format_docs(docs):
return "\n\n---\n\n".join(doc.page_content for doc in docs)
# RAG 체인 구성
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| llm
)
# 실행
answer = rag_chain.invoke("환불은 언제까지 가능한가요?")
김개발 씨가 첫 시도를 했습니다. 검색된 문서 3개를 그대로 붙여넣고 "이 문서를 보고 답변해줘"라고 했습니다.
결과는? LLM이 문서 내용을 무시하고 자기 마음대로 답변했습니다.
박시니어 씨가 웃으며 말했습니다. "프롬프트가 너무 약해.
LLM에게 명확한 지시를 줘야 해." 프롬프트 엔지니어링은 RAG의 마지막 퍼즐 조각입니다. 프롬프트는 마치 시험 문제지의 지시문과 같습니다.
"다음 지문을 읽고 물음에 답하시오"라는 지시가 있어야 학생이 지문을 참고합니다. 그냥 "답하시오"라고만 하면 배경지식으로 답합니다.
위 코드의 프롬프트를 분석해봅시다. 첫째, 역할을 부여합니다.
"고객 지원 상담사"라는 역할을 주면 LLM이 그에 맞는 어조와 태도로 답변합니다. 둘째, 문서 참조를 강제합니다.
"아래 제공된 문서만을 참고하여"라고 명시합니다. 이렇게 해야 LLM이 자체 지식 대신 제공된 문서를 우선합니다.
셋째, 환각 방지 장치를 둡니다. "문서에 없는 내용은 찾을 수 없다고 답변하세요"라고 지시합니다.
이게 없으면 LLM이 모르는 것도 그럴듯하게 지어냅니다. format_docs 함수도 중요합니다.
검색된 문서들 사이에 "---" 구분선을 넣습니다. 이렇게 하면 LLM이 각 문서의 경계를 명확히 인식합니다.
문서가 뒤섞이면 정보가 혼동될 수 있습니다. temperature=0 설정을 주목하세요.
temperature는 답변의 창의성을 조절합니다. 0이면 가장 확실한 답변만 합니다.
RAG에서는 정확성이 중요하므로 낮은 값을 사용합니다. 창작이 아니라 문서 기반 답변이니까요.
실무에서 추가로 고려할 점이 있습니다. 출처 표시가 중요합니다.
"환불은 7일 이내 가능합니다 (출처: 환불정책.pdf)"처럼 어떤 문서에서 온 정보인지 밝히면 신뢰도가 높아집니다. 프롬프트에 출처도 함께 답변하라고 지시할 수 있습니다.
답변 형식 지정도 유용합니다. "1번, 2번 형식으로 정리해서 답변하세요"라고 하면 읽기 쉬운 답변이 나옵니다.
흔한 실수가 있습니다. 문서를 너무 많이 넣는 것입니다.
10개, 20개 문서를 다 넣으면 LLM이 혼란스러워합니다. 중요한 정보가 묻히기도 합니다.
보통 3~5개가 적당합니다. 김개발 씨가 새 프롬프트를 적용했습니다.
"와, 이제 문서 내용을 정확히 참고해서 답변하네요. 모르는 건 모른다고 하고요!" 박시니어 씨가 말했습니다.
"좋아, 이제 기본은 다 배웠어. 마지막으로 이걸 LangChain으로 깔끔하게 정리해볼까?"
실전 팁
💡 - 프롬프트에 반드시 "문서에 없으면 모른다고 해"를 포함하세요
- temperature는 0~0.3 사이로 낮게 설정하세요
6. LangChain으로 RAG 구축
지금까지 배운 청킹, 임베딩, 검색, 생성을 각각 구현한 김개발 씨. 코드가 여기저기 흩어져 있어 관리가 어려웠습니다.
"이걸 하나로 깔끔하게 묶을 방법이 없을까요?"
LangChain은 LLM 애플리케이션 개발을 위한 프레임워크입니다. RAG의 모든 단계를 체인으로 연결하여 깔끔하게 구현할 수 있습니다.
문서 로더, 텍스트 스플리터, 임베딩, 벡터 스토어, 리트리버, LLM을 하나의 파이프라인으로 구성합니다. 복잡한 RAG 시스템도 몇 줄의 코드로 만들 수 있습니다.
다음 코드를 살펴봅시다.
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_community.vectorstores import Chroma
from langchain.chains import RetrievalQA
# 1단계: 문서 로드
loader = PyPDFLoader("company_manual.pdf")
documents = loader.load()
# 2단계: 청킹
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=100)
chunks = text_splitter.split_documents(documents)
# 3단계: 임베딩 & 벡터 저장
embeddings = OpenAIEmbeddings()
vector_store = Chroma.from_documents(chunks, embeddings, persist_directory="./db")
# 4단계: RAG 체인 구성 (검색 + 생성 한번에)
qa_chain = RetrievalQA.from_chain_type(
llm=ChatOpenAI(model="gpt-4o-mini"),
retriever=vector_store.as_retriever(search_kwargs={"k": 3}),
return_source_documents=True # 출처 문서도 반환
)
# 5단계: 질문하기
result = qa_chain.invoke({"query": "연차 신청 방법을 알려주세요"})
print(result["result"]) # 답변
print(result["source_documents"]) # 참조한 문서들
김개발 씨가 지금까지 작성한 코드를 보며 한숨을 쉽니다. 청킹 코드, 임베딩 코드, 검색 코드, 생성 코드가 각각 다른 파일에 흩어져 있습니다.
테스트할 때마다 여러 파일을 실행해야 합니다. 박시니어 씨가 다가옵니다.
"LangChain을 써봐. 레고 블록처럼 조립할 수 있어." LangChain은 LLM 애플리케이션의 스위스 군용 칼입니다.
마치 요리에 필요한 모든 도구가 들어있는 조리 세트처럼, RAG에 필요한 모든 컴포넌트가 준비되어 있습니다. 문서 로더, 텍스트 스플리터, 다양한 벡터 DB 연동, 프롬프트 템플릿 등 필요한 건 대부분 있습니다.
위 코드를 단계별로 살펴보겠습니다. 1단계 - 문서 로드: PyPDFLoader가 PDF 파일을 읽어옵니다.
LangChain은 PDF, Word, HTML, CSV 등 다양한 포맷의 로더를 제공합니다. 심지어 웹페이지나 유튜브 자막도 로드할 수 있습니다.
2단계 - 청킹: 앞서 배운 RecursiveCharacterTextSplitter를 그대로 사용합니다. split_documents 메서드는 문서 객체를 받아서 청크 객체 리스트를 반환합니다.
3단계 - 임베딩과 저장: Chroma.from_documents가 임베딩과 저장을 한번에 처리합니다. persist_directory를 지정하면 로컬에 저장되어 서버를 재시작해도 유지됩니다.
4단계 - RAG 체인: 여기가 LangChain의 진가를 보여주는 부분입니다. RetrievalQA가 검색과 생성을 하나로 묶습니다.
내부적으로 질문을 받으면 자동으로 관련 문서를 검색하고, 그 문서와 함께 LLM에게 전달합니다. return_source_documents=True 옵션이 유용합니다.
이 옵션을 켜면 답변과 함께 어떤 문서를 참조했는지도 반환합니다. 사용자에게 "이 정보는 환불정책.pdf 3페이지에서 가져왔습니다"라고 출처를 알려줄 수 있습니다.
신뢰도가 크게 올라갑니다. 실무에서 LangChain의 장점은 확장성입니다.
처음에 Chroma로 시작했다가 사용자가 늘어 Pinecone으로 바꿔야 할 때, 벡터 스토어 부분만 교체하면 됩니다. 나머지 코드는 그대로입니다.
OpenAI에서 Claude로 바꿀 때도 마찬가지입니다. **LCEL(LangChain Expression Language)**도 알아두면 좋습니다.
파이프 연산자(|)로 컴포넌트를 연결하는 문법입니다. 더 유연한 체인을 만들 수 있습니다.
앞서 본 rag_chain 예제가 LCEL 방식입니다. 주의할 점이 있습니다.
LangChain은 버전이 자주 바뀝니다. 인터넷의 예제 코드가 구버전인 경우가 많습니다.
공식 문서를 반드시 확인하세요. 또한 모든 것을 LangChain에 의존하면 라이브러리에 종속됩니다.
핵심 로직은 이해하고 있어야 합니다. 김개발 씨가 코드를 실행합니다.
"와, 정말 PDF 하나로 사내 챗봇이 만들어졌어요!" 박시니어 씨가 어깨를 두드립니다. "축하해.
이제 RAG의 기본기를 익혔어. 실제 서비스로 발전시키려면 평가, 모니터링, 캐싱 등 더 배울 게 있지만, 오늘은 여기까지.
수고했어!" 김개발 씨는 뿌듯한 마음으로 코드를 커밋했습니다. 내일은 테스트 데이터로 검색 품질을 측정해볼 생각입니다.
실전 팁
💡 - LangChain 공식 문서(python.langchain.com)를 자주 확인하세요
- 처음엔 RetrievalQA로 시작하고, 익숙해지면 LCEL로 전환하세요
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
Helm 마이크로서비스 패키징 완벽 가이드
Kubernetes 환경에서 마이크로서비스를 효율적으로 패키징하고 배포하는 Helm의 핵심 기능을 실무 중심으로 학습합니다. Chart 생성부터 릴리스 관리까지 체계적으로 다룹니다.
보안 아키텍처 구성 완벽 가이드
프로젝트의 보안을 처음부터 설계하는 방법을 배웁니다. AWS 환경에서 VPC부터 WAF, 암호화, 접근 제어까지 실무에서 바로 적용할 수 있는 보안 아키텍처를 단계별로 구성해봅니다.
AWS Organizations 완벽 가이드
여러 AWS 계정을 체계적으로 관리하고 통합 결제와 보안 정책을 적용하는 방법을 실무 스토리로 쉽게 배워봅니다. 초보 개발자도 바로 이해할 수 있는 친절한 설명과 실전 예제를 제공합니다.
AWS KMS 암호화 완벽 가이드
AWS KMS(Key Management Service)를 활용한 클라우드 데이터 암호화 방법을 초급 개발자를 위해 쉽게 설명합니다. CMK 생성부터 S3, EBS 암호화, 봉투 암호화까지 실무에 필요한 모든 내용을 담았습니다.
AWS Secrets Manager 완벽 가이드
AWS에서 데이터베이스 비밀번호, API 키 등 민감한 정보를 안전하게 관리하는 Secrets Manager의 핵심 개념과 실무 활용법을 배워봅니다. 초급 개발자도 쉽게 따라할 수 있도록 실전 예제와 함께 설명합니다.