RAG 파이프라인으로 문서 기반 QA 시스템 구축하기

gclee·2026년 1월 21일

LangChain-RAG

목록 보기
13/13

RAG(Retrieval Augmented Generation)는 검색된 문서를 기반으로 LLM이 답변을 생성하는 기법입니다. 본 포스트에서는 지금까지 배운 내용을 조합하여 완전한 RAG 파이프라인을 구축하는 방법을 정리합니다.


준비 사항

  • Python 3.9 이상
  • Chroma DB 벡터 스토어 구축 완료
  • OpenAI API 키 설정 (.env 파일)

라이브러리 설치

RAG 파이프라인 구축에 필요한 패키지를 설치합니다.

pip install langchain langchain-openai langchain-chroma langchain-huggingface langchain-community python-dotenv
패키지설명
langchainLangChain Hub, RunnablePassthrough 등
langchain-openaiChatOpenAI LLM
langchain-chromaChroma 벡터 스토어
langchain-huggingfaceHuggingFace 임베딩 모델
langchain-communityTextLoader 등 Document Loaders
python-dotenv.env 파일에서 환경 변수 로드

RAG 체인의 전체 구조

RAG 파이프라인은 다음과 같은 흐름으로 동작합니다.

사용자 질문: "이색적인 숙소에는 어떤 곳이 있을까?"
        ↓
    [Retriever]
    (질문과 유사한 문서 검색)
        ↓
Context: "이색적인 숙소에서 머물기. 트리하우스, 동굴호텔..."
        ↓
  [Prompt Template]
  (Context + User Input 조합)
        ↓
      [LLM]
  (프롬프트 기반 답변 생성)
        ↓
  [Output Parser]
  (응답 텍스트만 추출)
        ↓
답변: "이색적인 숙소로는 트리하우스, 동굴호텔, 빙하호텔..."

1. Retriever 준비

MMR 방식으로 다양성을 고려한 Retriever를 생성합니다.

retriever = db.as_retriever(
    search_type="mmr",
    search_kwargs={
        "k": 4,
        "fetch_k": 20,
        "lambda_mult": 0.5
    }
)
파라미터설명
k최종 반환할 문서 개수
fetch_k초기에 가져올 후보 문서 개수
lambda_mult다양성 조절 (0.5 = 유사도와 다양성 균형)

2. LangChain Hub에서 프롬프트 가져오기

LangChain에서 미리 만들어둔 RAG 프롬프트를 사용할 수 있습니다.

from langchain import hub

# LangChain Hub에서 RAG 프롬프트 템플릿 가져오기
prompt_template = hub.pull("langchain-ai/retrieval-qa-chat")

# 프롬프트 내용 확인
print(prompt_template)

Hub 프롬프트 구조

메시지 타입내용
System"Answer any use questions based solely on the context below"
Context검색된 문서들이 삽입되는 위치
Human사용자 질문 (input)

🔗 참고: https://smith.langchain.com/hub/langchain-ai/retrieval-qa-chat


3. RunnablePassthrough 이해하기

RunnablePassthrough는 입력값을 그대로 다음 단계로 전달합니다.

from langchain_core.runnables import RunnablePassthrough

# 사용자 질문 "여행 계획 세우기" → 그대로 input에 전달

RAG 체인에서의 역할

컴포넌트역할
context: retriever질문으로 검색한 문서들을 context에 전달
input: RunnablePassthrough()사용자 입력을 그대로 input에 전달

4. RAG 체인 구축

모든 컴포넌트를 연결하여 완전한 RAG 체인을 만듭니다.

from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough

# Output Parser 준비
parser = StrOutputParser()

# RAG 체인 구축
rag_chain = {
    "context": retriever,
    "input": RunnablePassthrough()
} | prompt_template | llm | parser

체인 구성 설명

  1. {"context": retriever, "input": RunnablePassthrough()} - 입력 데이터 구성
  2. | prompt_template - 프롬프트 메시지 생성
  3. | llm - LLM에 전송하여 응답 받기
  4. | parser - 응답에서 텍스트만 추출

5. RAG 체인 실행

# 관련 내용이 있는 질문
result = rag_chain.invoke("이색적인 숙소에는 어떤 곳이 있을까?")
print(result)
# → "이색적인 숙소로는 트리하우스, 동굴호텔, 빙하호텔 등이 있습니다."

# 관련 내용이 없는 질문
result = rag_chain.invoke("우주의 역사에 대해서 알려줘")
print(result)
# → "제공된 문서 내용에는 우주의 역사에 관한 정보가 포함되어 있지 않습니다."

전체 예제 코드

from dotenv import load_dotenv
load_dotenv()

from langchain_openai import ChatOpenAI
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import CharacterTextSplitter
from langchain_chroma import Chroma
from langchain import hub
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough

# 1. LLM 준비
llm = ChatOpenAI(model="gpt-4o-mini")

# 2. 임베딩 모델 준비
embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-m3")

# 3. 문서 로드 및 분할
text_splitter = CharacterTextSplitter(
    separator="\n",
    chunk_size=100,
    chunk_overlap=0
)
loader = TextLoader("./docs/travel.txt", encoding="utf-8")
documents = loader.load_and_split(text_splitter=text_splitter)

# 4. VectorStore 생성
db = Chroma.from_documents(documents, embeddings)

# 5. Retriever 생성 (MMR 방식)
retriever = db.as_retriever(
    search_type="mmr",
    search_kwargs={"k": 4, "fetch_k": 20, "lambda_mult": 0.5}
)

# 6. 프롬프트 템플릿 가져오기
prompt_template = hub.pull("langchain-ai/retrieval-qa-chat")

# 7. Output Parser 준비
parser = StrOutputParser()

# 8. RAG 체인 구축
rag_chain = {
    "context": retriever,
    "input": RunnablePassthrough()
} | prompt_template | llm | parser

# 9. 질의응답 테스트
print("=== 관련 내용이 있는 질문 ===")
result = rag_chain.invoke("이색적인 숙소에는 어떤 곳이 있을까?")
print(result)

print("\n=== 관련 내용이 없는 질문 ===")
result = rag_chain.invoke("우주의 역사에 대해서 알려줘")
print(result)

출력 예시:

=== 관련 내용이 있는 질문 ===
이색적인 숙소로는 트리하우스, 동굴호텔, 빙하호텔 등이 있습니다.
이러한 독특한 숙소 체험을 통해 특별한 여행을 할 수 있습니다.

=== 관련 내용이 없는 질문 ===
죄송하지만, 제공된 문서 내용에는 우주의 역사에 관한 정보가 포함되어 있지 않습니다.
다른 질문이나 여행에 관련된 내용에 대해 궁금한 점이 있으시면 답변해 드리겠습니다.

RAG 체인 구성요소 요약

구성요소역할코드
Retriever질문과 유사한 문서 검색db.as_retriever()
Prompt TemplateContext + Input 조합hub.pull("...")
LLM프롬프트 기반 답변 생성ChatOpenAI()
Output Parser응답 텍스트 추출StrOutputParser()
RunnablePassthrough입력값 그대로 전달RunnablePassthrough()

주요 개념 정리

개념설명
RAGRetrieval Augmented Generation, 검색 증강 생성
RAG ChainRetriever + Prompt + LLM을 연결한 완전한 QA 파이프라인
hub.pull()LangChain Hub에서 프롬프트 템플릿 가져오기
RunnablePassthrough입력값을 변환 없이 그대로 다음 단계로 전달
ContextRetriever가 검색한 관련 문서들
Hallucination 방지"모르면 모른다고 답해" 지시로 환각 현상 방지

RAG의 장점

일반 LLM만 사용RAG 사용
학습 데이터 기반 답변 (오래된 정보 가능)최신 문서 기반 답변
특정 도메인 지식 부족특정 도메인 전문 지식 활용
Hallucination 발생 가능출처가 명확한 신뢰할 수 있는 답변
-"모르면 모른다" 응답으로 Hallucination 방지

0개의 댓글