STUDY/LLM

[LLM] RAG

seoyn 2026. 6. 30. 21:19

1. RAG(Retrieval Augmented Generation)

 : LLM์ด ์™ธ๋ถ€ ๋ฌธ์„œ๋ฅผ ๊ฒ€์ƒ‰ํ•ด ์ฐธ๊ณ ํ•œ ๋’ค ๋‹ต๋ณ€ ์ƒ์„ฑํ•˜๋Š” ๊ธฐ๋ฒ•

 - LLM์ด ํ•™์Šตํ•˜์ง€ ์•Š์€ ์ตœ์‹  ์ •๋ณด ๋‹ค๋ฃจ๊ธฐ or ํ• ๋ฃจ์‹œ๋„ค์ด์…˜ ์ค„์ด๊ธฐ์— ํšจ๊ณผ์ 

 

• ํŒŒ์ธํŠœ๋‹ vs RAG ๋น„๊ต

ํ•ญ๋ชฉ ํŒŒ์ธํŠœ๋‹ RAG
๋„๋ฉ”์ธ ์ตœ์ ํ™” ๊ฐ€๋Šฅ ์ œํ•œ์ 
์ตœ์‹  ์ •๋ณด ๋ฐ˜์˜ ๋ถˆ๊ฐ€๋Šฅ ๊ฐ€๋Šฅ
๊ตฌํ˜„ ๋‚œ์ด๋„ ๋†’์Œ ๋ณดํ†ต
์œ ์—ฐ์„ฑ ๋‚ฎ์Œ ๋†’์Œ

 

1-1. RAG ์ž‘๋™ ๋‹จ๊ณ„

โ‘  ์ •๋ณด ์ €์žฅ(์ธ๋ฑ์‹ฑ)

 • Load : ์™ธ๋ถ€ ๋ฐ์ดํ„ฐ(ํŒŒ์ผ, DB, ์›น ๋“ฑ)๋ฅผ ๋ถˆ๋Ÿฌ์˜ด

 • Split(Chunking) : ๊ธด ํ…์ŠคํŠธ๋ฅผ ์ผ์ • ํฌ๊ธฐ์˜ chunk๋กœ ๋ถ„ํ• 

 • Embedding : ๊ฐ chunk๋ฅผ ๋ฒกํ„ฐ๋กœ ๋ณ€ํ™˜

 • Store(Vector DB) : ๋ฒกํ„ฐ DB์— ์ €์žฅํ•ด ์œ ์‚ฌ ๊ฒ€์ƒ‰์ด ๊ฐ€๋Šฅํ•˜๋„๋ก ์ค€๋น„

๊ด€๊ณ„ํ˜• DB : ์ปฌ๋Ÿผ ๋‚ด์šฉ ์ผ์น˜ํ•˜๋Š” ๊ฒƒ, ๋ฒกํ„ฐ DB : ์˜๋ฏธ์ ์œผ๋กœ ์œ ์‚ฌํ•œ ๊ฒƒ ์ฐพ์Œ

 

โ‘ก ๊ฒ€์ƒ‰&์ƒ์„ฑ(Runtime)

 - ์‚ฌ์šฉ์ž ์งˆ๋ฌธRetrieve(์œ ์‚ฌ chunk ๊ฒ€์ƒ‰)ํ”„๋กฌํ”„ํŠธ ๊ตฌ์„ฑLLM ๋‹ต๋ณ€ ์ƒ์„ฑ

 • Retrieve : ์งˆ๋ฌธ ์ž„๋ฒ ๋”ฉ ํ›„ ๋ฒกํ„ฐ DB์—์„œ ์œ ์‚ฌ chunk ๊ฒ€์ƒ‰

 • Query : ๊ฒ€์ƒ‰๋œ chunk์™€ ์‚ฌ์šฉ์ž ์งˆ๋ฌธ์„ ํ”„๋กฌํ”„ํŠธ๋กœ ๊ตฌ์„ฑํ•ด LLM์— ์ „๋‹ฌ

 • Generation : ๋ฐ›์€ ํ”„๋กฌํ”„ํŠธ์— ๋Œ€ํ•œ LLM ์‘๋‹ต ์ƒ์„ฑ

 

2. Document Loader

 : LLM์— ์ „๋‹ฌํ•  ์ฐธ์กฐ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋„๊ตฌ

 - LangChain : ๋™์ผํ•œ ์ธํ„ฐํŽ˜์ด์Šค๋กœ ๋‹ค์–‘ํ•œ ๋ฆฌ์†Œ์Šค์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋กœ๋“œํ•˜๋„๋ก ์ง€์›

 - ๋ฐ˜ํ™˜ ํƒ€์ž… : list[Document]

Document ์†์„ฑ ์„ค๋ช…
page_content ๋ฌธ์„œ ๋‚ด์šฉ(str)
metadata ๋ฌธ์„œ์— ๋Œ€ํ•œ ๋ถ€๊ฐ€ ์ •๋ณด(dict)
id ๋ฌธ์„œ ์‹๋ณ„์ž(optional)
โ˜‘๏ธ LangChaind์˜ DocumentLoader๊ฐ€ ์•„๋‹Œ ๋‹ค๋ฅธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ์ฝ์–ด๋„ ๋ฌด๋ฐฉ!

 

2-1. TextLoader

 : ํ…์ŠคํŠธ ํŒŒ์ผ(.txt) ์ฝ์Œ

from langchain_community.document_loaders import TextLoader

loader = TextLoader("data/olympic.txt", encoding="utf-8")
docs = loader.load()          # ์ฆ‰์‹œ ์ฝ๊ธฐ -> list[Document]
# docs = loader.lazy_load()   # ์ง€์—ฐ ์ฝ๊ธฐ -> generator[Document]

 

2-2. PDF Loader

 • PyPDFLoader(๊ธฐ๋ฐ˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ : PyPDF2) : ๋น ๋ฅธ ํ…์ŠคํŠธ ์ถ”์ถœ, ๊ฒฝ๋Ÿ‰

 • PyMuPDFLoader(๊ธฐ๋ฐ˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ : PyMuPDF) : ์ด๋ฏธ์ง€/์ฃผ์„ ์ถ”์ถœ ์„ฑ๋Šฅ ์šฐ์ˆ˜

 • PDFPlumberLoader(๊ธฐ๋ฐ˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ : PDFPlumber) : ํ‘œ ๋“ฑ ๋ณต์žกํ•œ ๊ตฌ์กฐ ์ฒ˜๋ฆฌ์— ๊ฐ•์ 

from langchain_community.document_loaders import PyMuPDFLoader

loader = PyMuPDFLoader("data/novel/๊ธˆ_๋”ฐ๋Š”_์ฝฉ๋ฐญ_๊น€์œ ์ •.pdf")
docs = loader.load()  # ํŽ˜์ด์ง€ ๋‹จ์œ„๋กœ ๋ถ„๋ฆฌ๋œ list[Document]

 

2-3. WebBaseLoader

 : requests + BeautifulSoup์œผ๋กœ ์›น ํŽ˜์ด์ง€ ํฌ๋กค๋งํ•ด ๋กœ๋“œ

 - RAG ํ’ˆ์งˆ ํ–ฅ์ƒ ์œ„ํ•ด separator="\n", strip=True ์„ค์ • ๊ถŒ์žฅ

 - SoupStrainer๋กœ ํ•„์š”ํ•œ ํƒœ๊ทธ/์˜์—ญ ์„ ํƒ์  ํŒŒ์‹ฑ ๊ฐ€๋Šฅ

from langchain_community.document_loaders import WebBaseLoader
from bs4 import SoupStrainer

loader = WebBaseLoader(
    web_path=["https://...url1", "https://...url2"],
    bs_kwargs={
        "parse_only":SoupStrainer(name"div", attrs={"class":"_article_content})
    },
    bs_get_text_kwargs={"strip":True, "seperator":"\n\n"}
)
docs = loader.load()

 

2-4. RecursiveUrlLoader

 : ์‹œ์ž‘ URL์—์„œ ๋‚ด๋ถ€ ๋งํฌ๋ฅผ ์žฌ๊ท€์ ์œผ๋กœ ๋”ฐ๋ผ๊ฐ€๋ฉฐ ์—ฌ๋Ÿฌ ํŽ˜์ด์ง€๋ฅผ ์ž๋™ ์ˆ˜์ง‘

from langchain_community.document_loaders import RecursiveUrlLoader

loader = RecursiveUrlLoader(
    url="https://docs.python.org/3",
    max_depth=2,            # 0: ์‹œ์ž‘ ํŽ˜์ด์ง€๋งŒ, 1: 1์ฐจ ๋งํฌ๊นŒ์ง€, 2: 2์ฐจ ๋งํฌ๊นŒ์ง€
    prevent_outside=True,   # ๋„๋ฉ”์ธ ์™ธ๋ถ€๋กœ ๋‚˜๊ฐ€์ง€ ์•Š์Œ
    extractor=custom_extractor  # ์‚ฌ์šฉ์ž ์ •์˜ ํ…์ŠคํŠธ ์ถ”์ถœ ํ•จ์ˆ˜
)
โ˜‘๏ธ WebBaseLoaer : ๋‹จ์ผ URL ๋‹จ์œ„, RecursiveUrlLoader : ์‚ฌ์ดํŠธ ์ „์ฒด ํฌ๋กค๋ง

 

2-5. ArxivLoader

 : ArxivLoader ์ž์ฒด๋Š” API ์—…๋ฐ์ดํŠธ๋กœ ์ž‘๋™ํ•˜์ง€ ์•Š์•„, arxiv ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ์ง์ ‘ ๊ฒ€์ƒ‰ ํ›„  PDF ๋‹ค์šด๋กœ๋“œ ํ•ด PyPDFLoader๋กœ ๋กœ๋“œ

import arxiv

search = arxiv.Search(query="Advanced RAG", max_result=10, sort_by=arxiv.SortCriterion.Relevance)
client = arxiv.Client()
result = client.results(search)

- ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ(arxiv.Result)์—์„œ ์ œ๋ชฉ, ์ €์ž, ์นดํ…Œ๊ณ ๋ฆฌ, ์š”์•ฝ, PDF URL ๋“ฑ ์ถ”์ถœํ•ด Document.metadata์— ๋‹ด์€ ์ปค์Šคํ…€ ํ•จ์ˆ˜ lode_arxiv_docs() ์ง์ ‘ ๊ตฌํ˜„

 

2-6. DoclingLoader

 : PDF, DOCX, PPTX, XLSX, HTML, ์ด๋ฏธ์ง€ ๋“ฑ ๋‹ค์–‘ํ•œ ํ˜•์‹์„ ๊ตฌ์กฐํ™”๋œ ๋ฐ์ดํ„ฐ๋กœ ๋ณ€ํ™˜

 - ํŽ˜์ด์ง€ ๋ ˆ์ด์•„์›ƒ, ์ฝ๊ธฐ ์ˆœ์„œ, ํ‘œ ๊ตฌ์กฐ, ์ˆ˜์‹ ๋“ฑ ์ •ํ™•ํžˆ ์ธ์‹

 - OCR ์ง€์›(ํ˜„์žฌ RapidOCR ํ˜ธํ™˜์„ฑ ๋ฌธ์ œ๋กœ ๋น„ํ™œ์„ฑํ™” ๊ถŒ์žฅ)

 - Markdown, HTML, JSON ํ˜•์‹ ์ถœ๋ ฅ ์ง€์›

from langchain_docling import DoclingLoader
from langchain_docling.loader import ExportType

loader = DoclingLoader(
    file_path=["paper1.pdf", "paper2.pdf"],
    export_type=ExportType.MARKDOWN,
    converter=converter  # OCR ์„ค์ • ๋“ฑ ํฌํ•จ
)

 

 

3. Chunking(๋ฌธ์„œ ๋ถ„ํ• )

 : ๋กœ๋“œํ•œ ๋ฌธ์„œ๋ฅผ ์ผ์ •ํ•œ ๊ธฐ์ค€์˜ ๋ฉ์–ด๋ฆฌ(chunk)๋กœ ๋‚˜๋ˆ„๋Š” ์ž‘์—…

 • ๋‚˜๋ˆ„๋Š” ์ด์œ 

 โ‘  ์ž„๋ฒ ๋”ฉ ๋ชจ๋ธ์˜ ์ปจํ…์ŠคํŠธ ๊ธธ์ด ์ œํ•œ ๊ทน๋ณต

 โ‘ก ๊ฒ€์ƒ‰ ์ •ํ™•๋„ ํ–ฅ์ƒ - ์ž‘์€ chunk๊ฐ€ ์งˆ๋ฌธ์— ๋” ์ •ํ™•ํžˆ ๋งค์นญ

 โ‘ข ๊ณ„์‚ฐ ํšจ์œจ์„ฑ - ๋ฒกํ„ฐ ์—ฐ์‚ฐ, ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ์ ˆ๊ฐ

Splitter ๋ชฉํ‘œ : ์˜๋ฏธ ์žˆ๋Š” ๋ฉ์–ด๋ฆฌ๋ฅผ ์œ ์ง€ํ•˜๋ฉด์„œ, ์ตœ๋Œ€ ๊ธธ์ด(chunk_size)๋ฅผ ๋„˜์ง€ ์•Š๋„๋ก ๋‚˜๋ˆ”

 

3-1. CharacterTextSplitter

 : ๋‹จ์ผ ๊ตฌ๋ถ„์ž(default: "\n\n") ๊ธฐ์ค€ ๋ถ„๋ฆฌ

 - ๋‚˜๋ˆˆ ์กฐ๊ฐ์ด chunk_size๋ณด๋‹ค ์ž‘์œผ๋ฉด ๋‹ค์Œ ์กฐ๊ฐ๊ณผ ํ•ฉ์นจ

 - chunk_size๋ณด๋‹ค ํฐ ์กฐ๊ฐ์ด ์ƒ๊ฒจ๋„ ๊ตฌ๋ถ„์ž ์šฐ์„  ์ ์šฉ

 - separator=""์ผ ๋•Œ๋งŒ chunk_overlap ์ ์šฉ

from langchain_text_splitters import CharacterTextSplitter

splitter = CharacterTextSplitter(
    chunk_size=60,
    chunk_overlap=10,
    separator=""  # ๊ธ€์ž ์ˆ˜ ๊ธฐ์ค€์œผ๋กœ ์ž๋ฆ„
)
chunk_overlap : ์ฒญํฌ ๊ฒฝ๊ณ„์—์„œ ์•ž ์ฒญํฌ ๋๋ถ€๋ถ„์„ ๋‹ค์Œ ์ฒญํฌ ์•ž์— ๋ถ™์—ฌ ๋ฌธ๋งฅ ์—ฐ์†์„ฑ ์œ ์ง€

 - ๋นˆ ๋ฌธ์ž์—ด("")๋กœ ์ž˜๋ฆฐ ๊ฒฝ์šฐ๋งŒ ์ ์šฉ(๊ตฌ๋ถ„์ž๋กœ ์ •์ƒ ๋ถ„๋ฆฌ๋œ ๊ฒฝ์šฐ์—๋Š” ๋ฏธ์ ์šฉ) 

 

3-2. RecursiveCharacterTextSplitter

 : ์—ฌ๋Ÿฌ ๊ตฌ๋ถ„์ž๋ฅผ ์šฐ์„ ์ˆœ์œ„๋Œ€๋กœ ์ ์šฉํ•ด ์ž์—ฐ์Šค๋Ÿฌ์šด ๋‹จ์œ„๋กœ ๋ถ„ํ• 

 - ๊ธฐ๋ณธ ๊ตฌ๋ถ„์ž ์šฐ์„ ์ˆœ์œ„ : "\n\n" → "\n" → " " → "" 

 - ์ƒ์œ„ ๊ตฌ๋ถ„์ž๋กœ ๋‚˜๋ˆˆ ์กฐ๊ฐ์ด chunk_size ์ดˆ๊ณผ ์‹œ ํ•˜์œ„ ๊ตฌ๋ถ„์ž๋กœ ์žฌ๊ท€์  ์žฌ๋ถ„ํ• 

from langchain_text_splitters import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,
    chunk_overlap=50,
    separators=["\n\n", "\n", r"[\.?!,~]",  " ", ""],
    is_separator_regex=True  # ๊ตฌ๋ถ„์ž์— ์ •๊ทœ์‹ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
)

# '๋ฌธ์„œ ๋กœ๋“œ + ๋ถ„ํ• ' ํ•œ ๋ฒˆ์—
docs = loader.load_and_split(splitter)

 

3-3. ํ† ํฐ ์ˆ˜ ๊ธฐ์ค€ ๋ถ„ํ• 

 : LLM์€ ํ† ํฐ ์ œํ•œ์ด ์žˆ์œผ๋ฏ€๋กœ ๊ธ€์ž ์ˆ˜๋ณด๋‹ค ํ† ํฐ ์ˆ˜ ๊ธฐ์ค€์ด ๋” ์ •ํ™•

# tiktoken(OpenAI GPT ๊ณ„์—ด ๊ถŒ์žฅ)
splitter = CharacterTextSplitter.from_tiktoken_encoder(
    encoding_name="o200k_base",  # GPT-4 ์ดํ›„ ๋ชจ๋ธ
    chunk_size=500,
    chunk_overlap=50
)
# HuggingFace Tokenizer
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("google/gemma-3-4b-it")
splitter_hf = RecursiveCharacterTextSplitter.from_huggingface_tokenizer(
    tokenizer=tokenizer,
    chunk_size=500,
    chunk_overlap=50
)

 

3-4. MarkdownHeaderTextSplitter

 : Markdown ๋ฌธ์„œ์—์„œ ํ—ค๋”(#, ##, ###) ๊ธฐ์ค€์œผ๋กœ ๋ถ„ํ• . ํ—ค๋” ์ •๋ณด๊ฐ€ metadata์— ์ž๋™ ์ €์žฅ

from langchain_text_splitters import MarkdownHeaderTextSplitter

header_to_split = [
    ("#", "header1"),
    ("##", "header2"),
    ("###", "header3")
]
splitter = MarkdownHeaderTextSplitter(
    headers_to_split_on=header_to_split,
    strip_headers=True # ํ—ค๋” ํ…์ŠคํŠธ๋ฅผ ๋‚ด์šฉ์— ํฌํ•จํ• ์ง€ ์—ฌ๋ถ€
)

# split_text(str)๋งŒ ์ง€์›(split_documents ์—†์Œ)
docs = splitter.split_text(doc_txt)

 - ๋ถ„ํ• ๋œ ๊ฐ Document์˜ metadata์— {"header1": "...", "header2":"..."} ํ˜•ํƒœ๋กœ ๊ณ„์ธต ์ •๋ณด ์ž๋™ ํฌํ•จ

'STUDY > LLM' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[LLM] Qdrant  (1) 2026.07.02
[LLM] RAG - Embedding VectorDB  (0) 2026.07.01
[LLM] Chain - LCEL  (0) 2026.06.27
[LLM] Output Parser  (0) 2026.06.26
[LLM] Prompt  (0) 2026.06.25