- RAG (Retrieval-Augmented Generation) 는 문서 기반 질의응답 시스템을 구현할 때 핵심이 되는 기법입니다.
- 이번 글에서는 LangChain과 OpenAI를 이용해 PDF 문서를 기반으로 질문에 답변하는 RAG 챗봇을 만들어 보겠습니다.
1️⃣ PDF 문서 불러오기
PDF 파일을 파싱해서 텍스트 데이터를 추출합니다.
LangChain의
PyPDFLoader
는 페이지별로 Document
객체를 생성해줍니다.from langchain_community.document_loaders import PyPDFLoader
loader = PyPDFLoader("data/policy.pdf")
documents = loader.load()
2️⃣ 텍스트 청크 분할 (Chunking)
LLM은 긴 텍스트를 한 번에 처리할 수 없기 때문에 문서를 청크로 나눕니다.
chunk_size=1000
, overlap=100
설정으로 문맥이 자연스럽게 이어지도록 합니다.from langchain_text_splitters import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)
chunks = splitter.split_documents(documents)
3️⃣ 임베딩: 문서를 벡터로 변환
텍스트 청크를 고차원 벡터로 변환해 유사도를 계산할 수 있게 합니다.
OpenAI의 임베딩 모델을 사용해
"문장 → 숫자 벡터"
로 변환합니다.from langchain_openai import OpenAIEmbeddings
from dotenv import load_dotenv
import os
load_dotenv()
embeddings = OpenAIEmbeddings(
model="text-embedding-3-large",
api_key=os.getenv("OPENAI_API_KEY")
)
4️⃣ 벡터 DB 저장 및 불러오기 (Chroma)
임베딩된 벡터들을 벡터 DB(여기선 Chroma)에 저장해 빠르게 검색할 수 있도록 합니다.
persist_directory
설정으로 디스크에 저장하고 재사용할 수 있습니다.from langchain_chroma import Chroma
db_path = "chroma_store"
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory=db_path
)
이미 저장된 경우에는 이렇게 불러올 수 있습니다:
vectorstore = Chroma( persist_directory=db_path, embedding_function=embeddings )
5️⃣ 관련 문서 검색 (Retriever)
사용자의 질문을 임베딩한 뒤, 유사한 벡터를 벡터DB에서 검색합니다.
RAG의 Retrieval 단계이며, 가장 유사한 청크
k
개를 반환합니다.retriever = vectorstore.as_retriever(k=3)
results = retriever.invoke("서울시의 환경 정책은?")
for doc in results:
print(doc.page_content)
6️⃣ LLM을 이용해 답변 생성
검색된 문서를 context로 전달해 LLM이 자연어 응답을 생성하게 합니다.
LangChain의
create_stuff_documents_chain
은 여러 문서를 한 번에 처리할 수 있습니다.from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_openai import ChatOpenAI
chat = ChatOpenAI(model="gpt-4o-mini")
prompt = ChatPromptTemplate.from_messages([
("system", "아래 context를 참고해 질문에 답하세요:\n\n{context}"),
MessagesPlaceholder("messages"),
])
qa_chain = create_stuff_documents_chain(chat, prompt)
7️⃣ 대화 이력 저장 및 실행
질문과 답변을 저장해 대화를 이어갈 수 있도록 합니다.
LangChain의
ChatMessageHistory
를 사용하면 대화형 챗봇에 활용할 수 있습니다.from langchain.memory import ChatMessageHistory
history = ChatMessageHistory()
history.add_user_message("서울시의 온실가스 정책 알려줘")
answer = qa_chain.invoke({
"messages": history.messages,
"context": results,
})
history.add_ai_message(answer)
print(answer)
8️⃣ 질의 확장 (Query Augmentation)
사용자가 모호하게 질문한 경우, 기존 대화 내용을 참고해 명확한 질문으로 바꿉니다.
예:
"서울은?"
→ "서울시의 환경 정책은 무엇인가요?"
from langchain_core.output_parsers import StrOutputParser
qa_prompt = ChatPromptTemplate.from_messages([
MessagesPlaceholder("messages"),
("system", "질문을 더 명확하게 바꿔 주세요:\n\n{query}"),
])
qa_chain = qa_prompt | chat | StrOutputParser()
augmented_query = qa_chain.invoke({
"messages": history.messages,
"query": "서울은?",
})
print("확장된 질문:", augmented_query)
✅ 전체 요약
단계 | 설명 |
PDF 불러오기 | 문서에서 텍스트 추출 |
청크화 | 문서를 분할해 문맥 단위로 처리 |
임베딩 | 텍스트를 벡터로 변환해 유사도 기반 검색 가능 |
벡터DB | 벡터를 저장하고 검색하는 인프라 |
Retriever | 질문과 가장 유사한 문서를 찾는 단계 |
LLM 응답 | 검색된 문서를 바탕으로 자연어로 답변 생성 |
질의 확장 | 모호한 질문을 명확하게 재구성 |
📌 마무리
- LangChain, OpenAI, ChromaDB를 조합하면 나만의 도메인 문서를 기반으로 한 챗봇을 간단히 만들 수 있습니다.
- 여기에 UI(예: Streamlit) 또는 Tool Agent까지 붙이면 실용적이고 확장성 있는 챗봇으로 발전시킬 수 있습니다.
Share article