본문 바로가기

AI

[AI Study] LangChain으로 RAG 시스템 구축하기

[AI Study] LangChain으로 RAG 시스템 구축하기

문서를 업로드하면 그 내용을 기반으로 답변하는 AI 만들기


🎯 만들 것

"AI 학습 비서 with RAG"

기능:

  1. PDF/TXT 문서 업로드
  2. 자동으로 청크 분할 & 벡터DB 저장
  3. 질문하면 관련 문서 검색
  4. 문서 기반 정확한 답변
  5. 출처 표시

🛠️ 기술 스택

  • Python 3.11+
  • LangChain - AI 워크플로우 프레임워크
  • Chroma - 벡터 데이터베이스
  • OpenAI API - GPT-4o-mini & Embeddings
  • PyPDF - PDF 파일 처리

📦 Step 1. 패키지 설치

1-1 가상환경 활성화

source venv/bin/activate

 

1-2 패키지 설치

pip install langchain langchain-openai langchain-community langchain-text-splitters chromadb pypdf

 

각 패키지 역할:

패키지 역할
langchain AI 워크플로우 자동화
langchain-openai OpenAI API 연동
langchain-community 파일 로더 (PDF, TXT)
langchain-text-splitters 텍스트 분할
chromadb 벡터 데이터베이스
pypdf PDF 읽기

 

💻 Step 2. 문서 업로드 함수 구현

rag_cli.py

from langchain_community.document_loaders import PyPDFLoader, TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
import os

def upload_document(file_path: str):
    """문서를 청크로 분할하고 벡터DB에 저장"""
    
    global vectorstore
    
    print(f"\n📥 문서 처리 중: {file_path}")
    
    # 1️⃣ 파일 로드
    if file_path.endswith('.pdf'):
        loader = PyPDFLoader(file_path)
    else:
        loader = TextLoader(file_path, encoding='utf-8')
    
    docs = loader.load()
    
    # 2️⃣ 텍스트 분할 (청크)
    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=1000,    # 1000자씩 자르기
        chunk_overlap=200   # 200자 겹치기 (문맥 유지)
    )
    
    splits = text_splitter.split_documents(docs)
    
    print(f"✂️  {len(splits)}개 청크로 분할됨")
    
    # 3️⃣ 임베딩 & 벡터DB 저장
    embeddings = OpenAIEmbeddings(
        api_key=os.getenv("OPENAI_API_KEY")
    )
    
    if vectorstore is None:
        vectorstore = Chroma.from_documents(
            documents=splits,
            embedding=embeddings,
            persist_directory="./chroma_db"  # 저장 위치
        )
    else:
        vectorstore.add_documents(splits)
    
    print(f"✅ 문서 저장 완료!")

 

💬 Step 3. 질문-답변 함수 구현 (RAG)

from langchain_openai import ChatOpenAI

def chat(message: str):
    """RAG 기반 질문-답변"""
    
    global vectorstore
    
    if vectorstore is None:
        return "❌ 먼저 문서를 업로드해주세요!"
    
    print("\n🔍 문서 검색 중...")
    
    # 1️⃣ 관련 문서 검색
    retriever = vectorstore.as_retriever(
        search_kwargs={"k": 3}  # 상위 3개 청크
    )
    docs = retriever.invoke(message)
    
    print(f"📎 {len(docs)}개 관련 문서 발견")
    
    # 2️⃣ 검색된 문서를 컨텍스트로 결합
    context = "\n\n".join([doc.page_content for doc in docs])
    
    # 3️⃣ LLM 호출
    llm = ChatOpenAI(
        model="gpt-4o-mini",
        api_key=os.getenv("OPENAI_API_KEY"),
        temperature=0  # 정확한 답변 (창의성 낮춤)
    )
    
    messages = [
        {
            "role": "system",
            "content": "제공된 문서를 기반으로 정확하게 답변하는 AI입니다."
        },
        {
            "role": "user",
            "content": f"""다음 문서를 참고해서 질문에 답변해주세요.

문서:
{context}

질문: {message}"""
        }
    ]
    
    response = llm.invoke(messages)
    answer = response.content
    
    # 4️⃣ 출처 표시
    print("\n📎 참고한 문서:")
    for i, doc in enumerate(docs, 1):
        print(f"\n[출처 {i}]")
        print(doc.page_content[:200] + "...")
    
    return answer

 

🎮 Step 4. 메인 함수 구현

def main():
    """메인 함수"""
    
    print("=" * 50)
    print("📚 AI 학습 비서 with RAG")
    print("=" * 50)
    print("\n명령어:")
    print("  upload <파일경로>  : 문서 업로드")
    print("  list              : 업로드된 문서 목록")
    print("  clear             : 벡터DB 초기화")
    print("  exit              : 종료")
    print("  그 외             : 질문하기")
    print("=" * 50)
    
    while True:
        user_input = input("\n입력: ").strip()
        
        if not user_input:
            continue
        
        # 종료
        if user_input == "exit":
            print("\n👋 학습 비서를 종료합니다!")
            break
        
        # 문서 업로드
        if user_input.startswith("upload "):
            file_path = user_input[7:]
            if os.path.exists(file_path):
                upload_document(file_path)
            else:
                print(f"❌ 파일을 찾을 수 없습니다: {file_path}")
        
        # 질문하기
        else:
            answer = chat(user_input)
            print(f"\n🤖 답변:\n{answer}\n")

if __name__ == "__main__":
    main()

 

📄 Step 5. 테스트 문서 작성

python_tutorial.txt

Python 기초 가이드

Python은 1991년 귀도 반 로섬이 만든 프로그래밍 언어입니다.

주요 특징:
- 문법이 간단하고 읽기 쉬움
- 다양한 라이브러리 지원
- 인공지능, 웹 개발, 데이터 분석에 사용

기본 문법:
변수 선언: x = 10
조건문: if x > 5: print("크다")
반복문: for i in range(10): print(i)

함수 정의:
def greet(name):
    return f"Hello, {name}!"

리스트:
numbers = [1, 2, 3, 4, 5]
numbers.append(6)

딕셔너리:
user = {"name": "철수", "age": 25}

 

🚀 Step 6. 실행 및 테스트

6-1 프로그램 실행

python rag_cli.py

 

6-2 문서 업로드

==================================================
📚 AI 학습 비서 with RAG
==================================================

명령어:
  upload <파일경로>  : 문서 업로드
  list              : 업로드된 문서 목록
  clear             : 벡터DB 초기화
  exit              : 종료
  그 외             : 질문하기
==================================================

입력: upload python_tutorial.txt

📥 문서 처리 중: python_tutorial.txt
✂️  3개 청크로 분할됨
✅ 문서 저장 완료!

 

6-3 질문하기

입력: Python은 누가 만들었어?

🔍 문서 검색 중...
📎 3개 관련 문서 발견

📎 참고한 문서:

[출처 1]
Python은 1991년 귀도 반 로섬이 만든 프로그래밍 언어입니다.
주요 특징:
- 문법이 간단하고 읽기 쉬움...

[출처 2]
인공지능, 웹 개발, 데이터 분석에 사용됩니다...

🤖 답변:
Python은 1991년에 귀도 반 로섬(Guido van Rossum)이 만들었습니다.

 

6-4 더 많은 질문

입력: Python 함수는 어떻게 정의해?

🤖 답변:
def 키워드를 사용하여 함수를 정의합니다.
예: def greet(name): return f"Hello, {name}!"

입력: 리스트에 요소를 추가하는 방법은?

🤖 답변:
append() 메서드를 사용합니다.
예: numbers.append(6)

 

📊 작동 원리 다이어그램

[사전 작업 - 문서 업로드]
python_tutorial.txt
    ↓
PyPDFLoader/TextLoader
    ↓
전체 텍스트 (1500자)
    ↓
RecursiveCharacterTextSplitter
    ↓
청크 1 (1000자)
청크 2 (700자)  ← 200자 겹침
    ↓
OpenAIEmbeddings
    ↓
청크 1 → [0.23, -0.51, ...]
청크 2 → [0.25, -0.48, ...]
    ↓
Chroma DB 저장 (./chroma_db/)


[질문 시 - 실시간]
질문: "Python은 누가 만들었어?"
    ↓
OpenAIEmbeddings
    ↓
질문 벡터: [0.24, -0.49, ...]
    ↓
Chroma DB 검색 (유사도 계산)
    ↓
청크 1 (유사도: 0.95) ✅
청크 2 (유사도: 0.82) ✅
    ↓
상위 2개 청크 선택
    ↓
GPT에게 전달:
"문서: [청크 1] [청크 2]
 질문: Python은 누가 만들었어?"
    ↓
GPT 답변:
"귀도 반 로섬이 만들었습니다"

💰 비용 분석

문서 처리 (1회):

텍스트 (1500자) → 청크 2개
청크 2개 → 임베딩 생성 (약 500 토큰)
비용: $0.00008 (0.1원)

 

질문 1회:

질문 임베딩: 10 토큰 → $0.000002
청크 검색: 무료 (로컬 계산)
GPT 호출: 200 토큰 → $0.00008
총 비용: $0.000082 (0.1원)

 

100회 질문:

문서 처리: $0.00008 (1회만)
질문 100회: $0.0082
총: $0.0083 (약 11원)

 

비교:

일반 ChatGPT (100회): $0.008
RAG (100회): $0.0083
차이: $0.0003 (0.4원)

→ 거의 비슷한 비용으로 정확도 ↑↑

🎓 핵심 개념 복습

1. 청크 (Chunk)

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,    # 1000자씩
    chunk_overlap=200   # 200자 겹침
)

→ 긴 문서를 작은 조각으로

 

2. 임베딩 (Embedding)

embeddings = OpenAIEmbeddings()
vector = embeddings.embed_query("Python")
# → [0.23, -0.51, 0.82, ...]

→ 텍스트를 숫자로

 

3. 벡터DB (Vector Database)

vectorstore = Chroma.from_documents(
    documents=splits,
    embedding=embeddings
)

→ 의미로 검색하는 DB

 

4. RAG (Retrieval-Augmented Generation)

docs = vectorstore.search(query)  # 검색
context = join(docs)               # 증강
answer = llm(context + query)      # 생성

→ 검색 + GPT


 

✅ Day 2 완료 체크리스트

  • LangChain 패키지 설치
  • 문서 로더 구현 (PDF, TXT)
  • 텍스트 분할 (청크) 구현
  • 임베딩 생성
  • 벡터DB (Chroma) 설정
  • 유사도 검색 구현
  • RAG 파이프라인 완성
  • CLI 인터페이스 구현
  • 출처 표시 기능

🚀 다음 단계

다음은 Agent를 배웁니다!

Agent = 스스로 판단하고 도구 선택하는 AI

예:
질문: "2024년 노벨 물리학상은?"
→ Agent: "문서에 없네? 웹 검색!"

질문: "Python 함수는?"
→ Agent: "문서에 있네? 벡터DB 검색!"

📚 참고 자료


반응형