나만의 RAG Chatbot 만들기 – 4. PDF 파일 읽기
오늘은 PDF를 읽어와서 나만의 RAG를 만들어보겠습니다. 앞선 강의는 아래에서 참고해보세요.
PDF파일
먼저 PDF가 있으시면 그걸로 하시고, 없을 경우 테슬라의 사용자 매뉴얼을 샘플로 테스트하겠습니다.
https://www.tesla.com/ownersmanual/modely/ko_kr/Owners_Manual.pdf
먼저 코드에서는 pdf를 불러오기 위해 패키지를 설치합니다.
기본적으로 랭체인에서 불러오기 위해서는 pypdf를 사용하고 loader를 통해서 파일을 불러옵니다.
pip install pypdf #PDF 불러오기
pip install cryptography #암호화를 위한 패키지
pip install tiktoken #토큰 자르기
from langchain_community.document_loaders import PyPDFLoader
loader = PyPDFLoader('/workspaces/LLMTest/Owners_Manual.pdf') # 아래를 참고하여 파일 위치 확인
pages = loader.load_and_split()
print(len(pages))
print(pages[0])
codespace에 파일 추가는 드래그 앤 드랍 하면 되는데요. 간단하게 드래그앤 드랍으로 추가가 가능합니다.

추가하고 마우스 우 클릭 – 경로 복사 해서 불러올 파일의 주소를 복사 붙여 넣기 합니다.

RAG(검색 증강 생성) 시스템을 구축할 때 가장 중요한 단계 중 하나가 바로 ‘텍스트 분할(Chunking)’입니다. RecursiveCharacterTextSplitter는 LangChain에서 가장 권장되는 기본 텍스트 분할기입니다.
쉽게 말해, 아주 긴 문서(PDF, 블로그 포스트 등)를 AI가 한 번에 읽기 좋은 크기의 덩어리로 잘라주는 역할을 합니다.
from langchain_openai import AzureChatOpenAI
from dotenv import load_dotenv
import os
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
load_dotenv()
MODEL_DEPLOYMENT_NAME = "gpt-4.1" # 실제 Azure 배포명을 입력하세요
llm = AzureChatOpenAI(
deployment_name=MODEL_DEPLOYMENT_NAME,
temperature=1,
)
messages = [
SystemMessage(content='당신은 업무 계획을 세워주는 업무 플래너 머신입니다. 사용자의 업무를 입력 받으면 이를 위한 계획을 작성합니다.'),
HumanMessage(content='신입사원 교육을 해야됩니다.')
]
# answer = llm.invoke(messages)
#print(answer.content)
from langchain_community.document_loaders import PyPDFLoader
loader = PyPDFLoader('/workspaces/LLMTest/Owners_Manual.pdf')
pages = loader.load_and_split()
# print(len(pages))
# print(pages[0].page_content)
import tiktoken
tokenizer = tiktoken.get_encoding('cl100k_base')
def tiktoken_len(text):
tokens = tokenizer.encode(text)
return len(tokens)
from langchain_text_splitters import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=100,
length_function=tiktoken_len
)
texts = text_splitter.split_documents(pages)
print("pages: ", len(pages))
print("texts: ", len(texts))
Ctrl+ K + C가 주석
Ctrl +K+ U가 주석 해제
텍스트 임베딩 모델 생성
RAG를 하기 위해서 langchain에서 임베딩 모델을 생성합니다.
from langchain_openai import AzureOpenAIEmbeddings
embedding_model = AzureOpenAIEmbeddings(
azure_deployment=EMBEDDING_MODEL_NAME,
chunk_size=1000)
그리고 실습을 위해서 임베딩 테스트 데이터로 테스트를 해봅니다.
이게 가장 많이 사용하는 샘플이라고 합니다.
유사한 값끼리 비교해봅니다.
from langchain_openai import AzureChatOpenAI
from dotenv import load_dotenv
import os
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
load_dotenv()
MODEL_DEPLOYMENT_NAME = "gpt-4.1" # 실제 Azure 배포명을 입력하세요
EMBEDDING_MODEL_NAME = 'text-embedding-3-large' # 사용할 OpenAI의 고성능 임베딩 모델 이름을 지정합니다.
from langchain_openai import AzureOpenAIEmbeddings
embedding_model = AzureOpenAIEmbeddings(
azure_deployment=EMBEDDING_MODEL_NAME, # Azure 환경에 배포된 임베딩 모델을 연결합니다.
chunk_size=1000) # 한 번에 보낼 데이터 크기를 설정합니다.
examples = {
"안녕하세요",
"제 이름은 홍길동 입니다.",
"이름이 무엇인가요?",
"랭체인은 유용합니다. ",
"Hello World"
}
embeddings = embedding_model.embed_documents(examples)
from numpy import dot
from numpy.linalg import norm
import numpy as np
def cos_sim(A,B):
# 두 벡터 사이의 각도를 계산하여 1에 가까울수록 의미가 비슷하다고 판별합니다.
return dot(A,B)/(norm(A)*norm(B))
print(cos_sim(embeddings[0], embeddings[1]))
# 질문과 문서 뭉치 중 1번 인덱스 문장의 유사도를 구합니다.
print(cos_sim(embeddings[0], embeddings[2]))
# 질문과 문서 뭉치 중 2번 인덱스 문장의 유사도를 구합니다.

PDF 파일에서 불러와서 스플릿하고 임베딩해서 저장까지 합니다.
loader = PyPDFLoader('/workspaces/LLMTest/Owners_Manual.pdf') # PDF 파일을 불러오는 로더 객체를 생성합니다.
pages = loader.load_and_split() # PDF를 읽어오고, 페이지 단위(기본)로 데이터를 나눕니다.
import tiktoken
tokenizer = tiktoken.get_encoding('cl100k_base') # OpenAI 모델(GPT-4 등)이 사용하는 토큰 인코딩 방식을 가져옵니다.
def tiktoken_len(text):
tokens = tokenizer.encode(text) # 텍스트를 토큰으로 변환합니다.
return len(tokens) # 변환된 토큰의 개수를 반환합니다. (글자 수가 아닌 AI 비용/제한 기준)
from langchain_text_splitters import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000, # 각 덩어리(Chunk)의 최대 길이를 1000 토큰으로 설정합니다.
chunk_overlap=100, # 덩어리 간 문맥 연결을 위해 앞뒤 100 토큰씩 겹치게 자릅니다.
length_function=tiktoken_len # 길이를 재는 기준으로 위에서 만든 토큰 계산 함수를 사용합니다.
)
texts = text_splitter.split_documents(pages) # 페이지 단위 문서를 위 설정대로 더 작은 덩어리로 잘라 리스트로 만듭니다.
EMBEDDING_MODEL_NAME = 'text-embedding-3-large' # 사용할 OpenAI의 고성능 임베딩 모델 이름을 지정합니다.
from langchain_openai import AzureOpenAIEmbeddings
embedding_model = AzureOpenAIEmbeddings(
azure_deployment=EMBEDDING_MODEL_NAME, # Azure 환경에 배포된 임베딩 모델을 연결합니다.
chunk_size=1000) # 한 번에 보낼 데이터 크기를 설정합니다.
from numpy import dot
from numpy.linalg import norm
import numpy as np
def cos_sim(A, B):
# 두 벡터(문장) 사이의 거리(유사도)를 계산하는 함수입니다. 1에 가까울수록 의미가 비슷합니다.
return dot(A, B)/(norm(A)*norm(B))
from langchain_community.vectorstores import Chroma
db = Chroma.from_documents(
texts, # 위에서 자른 텍스트 덩어리들을 넣습니다.
embedding=embedding_model, # 각 텍스트를 숫자로 변환(임베딩)할 모델을 지정합니다.
persist_directory="./chroma_db" # 변환된 데이터를 './chroma_db' 폴더에 저장합니다. (나중에 다시 로드 가능)
)
체인 설명
from langchain_community.vectorstores import Chroma
from langchain_openai import AzureOpenAIEmbeddings
from dotenv import load_dotenv
import os
load_dotenv()
EMBEDDING_MODEL_NAME = 'text-embedding-3-large'
embedding_model = AzureOpenAIEmbeddings(
azure_deployment=EMBEDDING_MODEL_NAME,
chunk_size=1000)
persist_directory = './chroma_db' # 데이터베이스가 저장될 디렉토리 경로
# 저장된 데이터베이스 불러오기
db = Chroma(
persist_directory=persist_directory,
embedding_function=embedding_model
)
query = "실내 온도 조절" # 사용자가 궁금해하는 질문 키워드입니다.
results = db.similarity_search(query)
# [핵심] 질문을 숫자로 바꾼 뒤, DB 내의 데이터들과 비교하여 가장 유사한 문서 4개(기본값)를 가져옵니다.
print(results[0].page_content)
print("-" * 100)
print(results[1].page_content)
print("-" * 100)
print(results[2].page_content)
print("-" * 100)
print(results[3].page_content)
전체 RAG 코드는 아래와 같습니다.
from langchain_community.vectorstores import Chroma
from langchain_openai import AzureOpenAIEmbeddings
from dotenv import load_dotenv
import os
from langchain_openai import AzureChatOpenAI
load_dotenv()
MODEL_DEPLOYMENT_NAME = "gpt-4.1" # 실제 Azure 배포명을 입력하세요
llm = AzureChatOpenAI(
deployment_name=MODEL_DEPLOYMENT_NAME,
temperature=1,
)
EMBEDDING_MODEL_NAME = 'text-embedding-3-large'
embedding_model = AzureOpenAIEmbeddings(
azure_deployment=EMBEDDING_MODEL_NAME,
chunk_size=1000)
persist_directory = './chroma_db' # 데이터베이스가 저장될 디렉토리 경로
# 저장된 데이터베이스 불러오기
db = Chroma(
persist_directory=persist_directory,
embedding_function=embedding_model
)
from langchain_classic.chains import RetrievalQA
from re import search
qa = RetrievalQA.from_chain_type(
llm=llm,
chain_type='stuff',
retriever=db.as_retriever(
search_type='mmr', # [검색 알고리즘] Maximum Marginal Relevance(최대 한계 관련성) 방식입니다.
search_kwargs={
'k': 3, # [최종 선택] 최종적으로 AI에게 전달할 문서의 개수입니다.
'fetch_k': 10 # [후보 선발] 먼저 DB에서 관련성이 높은 10개를 후보로 뽑습니다.
# 그 10개 중에서 서로 내용이 가장 안 겹치는 3개(k)를 MMR이 골라냅니다.
}
),
return_source_documents=True # [출처 표시] 답변과 함께 AI가 참고한 '원본 문서 덩어리'도 반환할지 결정합니다.
)
query = '실내 온도 조절 장치의 전기 공급은?'
result = qa.invoke(query)
print(result)

One Comment