CLOVA Studio ์ด์์ Posted May 13 ๊ณต์ ํ๊ธฐ Posted May 13 ย ๋ค์ด๊ฐ๋ฉฐ ๋ณธ ๊ฐ์ด๋๋ ํด๋ก๋ฐ ์คํ๋์ค์ ๋ญ์ฒด์ธ(Langchain)์ ํ์ฉํ์ฌ Multimodal RAG(๋ฉํฐ๋ชจ๋ฌ ๊ฒ์ ์ฆ๊ฐ ์์ฑ) ์์คํ ์ ๊ตฌ์ถํ๋ ๋ฐฉ๋ฒ์ ์๋ดํฉ๋๋ค.ย ์ต๊ทผ ๋น์ ๋ชจ๋ธ์ ์์ฉํ๊ฐ ๊ฐ์ํ๋๋ฉด์ ๊ธฐ์ ๋ค์ ๋ด๋ถ์ ๋ค์ํ ์ด๋ฏธ์ง ๊ธฐ๋ฐ ๋ฐ์ดํฐ๋ฅผ ํจ์จ์ ์ผ๋ก ๊ฒ์ํ๊ณ ํ์ฉํ๋ ค๋ ๋์ฆ๊ฐ ์ฆ๊ฐํ๊ณ ์์ต๋๋ค. ํนํ ๊ธฐ์กด ํ ์คํธ ์ค์ฌ RAG ์์คํ ์ ์ด๋ฏธ์ง ๋ฐ์ดํฐ๊น์ง ํฌํจํ๋๋ก ํ์ฅํ๋ ์ฌ๋ก๊ฐ ๋์ด๋๋ ์ถ์ธ์ ๋๋ค.ย ์ด ๊ธ์์๋ PDF ํ์์ ๋ฐ์ดํฐ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ง์์๋ต ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ Multimodal RAG ์์คํ ์ ๋ญ์ฒด์ธ์ ํตํด ๊ตฌํํด๋ณด๊ฒ ์ต๋๋ค. ๊ตฌํํ๊ณ ์ ํ๋ Multimodal RAG ์์คํ ์ ๊ตฌ์กฐ๋๋ ์๋์ ๊ฐ์ต๋๋ค. ย ๋ฉํฐ๋ชจ๋ฌ ์๋ฒ ๋ฉ ์์ด๋ ๊ตฌํ ๊ฐ๋ฅํ Multimodal RAG ๊ตฌ์กฐ๋ฅผ ์๊ฐํฉ๋๋ค. ์ด ๋ฐฉ์์ ๋น์ ๋ชจ๋ธ์ ํ์ฉํด ์ด๋ฏธ์ง๋ฅผ ํ ์คํธ๋ก ๋ณํํ ํ, ํด๋น ํ ์คํธ๋ฅผ ์๋ฒ ๋ฉํ์ฌ ๊ฒ์์ ํ์ฉํ๋ ์ ๊ทผ๋ฒ์ ๋๋ค. LangChain ํ๋ ์์ํฌ๋ฅผ ํตํด CLOVA Studio์ ๋ชจ๋ธ๊ณผ Chroma, FAISS์ ๊ฐ์ ์ธ๋ถ ๋ฒกํฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ํจ๊ณผ์ ์ผ๋ก ์ฐ๋ํ ์ ์์ต๋๋ค. ย ์ ์ฒด ๊ณผ์ ์ ์ ๋จ์๋ก ์ค์ตํ ์ ์๋๋ก ๊ตฌ์ฑ๋์ด ์์ต๋๋ค(ํ์ผ๋ช : multimodal_RAG.ipynb). ์ด ๊ฐ์ด๋์ ํต์ฌ์ ์ด๋ฏธ์ง ๊ธฐ๋ฐ ๋ฌธ์๋ฅผ ๋ค๋ฃจ๋ ๊ธฐ์ ๋ค์ด ์ฝ๊ฒ ๋์ ํ ์ ์๋ ๋ฒ์ฉ์ ์ธ ๋ฉํฐ๋ชจ๋ฌ RAG ๊ตฌํ ๋ฐฉ์์ ์๊ฐํ๋ ๋ฐ ์์ต๋๋ค. Quote ๋ฒ์ ์ ๋ณด ์๋ ์์ ์ฝ๋๋ Python 3.12.2 ํ๊ฒฝ์์ ์คํ ๊ฒ์ฆ์ ์๋ฃํ์ผ๋ฉฐ, ์ต์ Python 3.9 ์ด์์ ๋ฒ์ ์ด ํ์ํฉ๋๋ค. ๋ค์ ์ง์นจ์ ์ฐธ๊ณ ํ์ฌ ํ์ํ ๋ชจ๋ ๋ชจ๋์ ์ค์นํด์ฃผ์ธ์. requirements.txt Main File multimodal_RAG.ipynb ย 1. ์ฌ์ ์ค๋น โ Langchain ํจํค์ง ์ค์น ๋ฉํฐ๋ชจ๋ฌ RAG ์์คํ ๊ตฌํ์ ์ํด์๋ LangChain ํ๋ ์์ํฌ์ CLOVA Studio API ์ฐ๋์ด ํ์ํฉ๋๋ค. ์ต๊ทผ ์ถ์๋ langchain-naver ํจํค์ง๋ฅผ ํตํด CLOVA Studio์ ์ต์ ๋น์ ๋ชจ๋ธ HCX-005๋ฅผ LangChain๊ณผ ์ํํ๊ฒ ์ฐ๋ํ ์ ์์ต๋๋ค. ์๋ ๋ช ๋ น์ด๋ก LangChain ๊ด๋ จ ํจํค์ง๋ฅผ ์ค์นํ์ธ์. %pip install -qU openai langchain langchain-naver ย ย โก ์ฝ๋ ๊ณตํต ๋ชจ๋ imports ํ์ํ ๊ธฐ๋ณธ ๋ชจ๋๋ค์ ๋ฏธ๋ฆฌ importํฉ๋๋ค. import os import getpass import uuid import re from urllib.parse import urlparse import http import json import time ย โข API ํค ๋ฐ๊ธ ๋ฐ๊ธฐ CLOVA Studio์ API ํค ๋ฐ๊ธ์ด ํ์ํฉ๋๋ค.ย "ํ๋กํ > API ํค > ํ ์คํธ > ํ ์คํธ ์ฑ ๋ฐ๊ธ" ๊ฒฝ๋ก๋ฅผ ํตํด ํค๋ฅผ ๋ฐ๊ธ๋ฐ์ ์ ์์ต๋๋ค. ๋ฐ๊ธ๋ ํค๋ ํ ๋ฒ๋ง ํ์๋์ด ์ฌํ์ธ์ด ๋ถ๊ฐ๋ฅํ๋ฏ๋ก, ๋ฐ๋์ ๋ณต์ฌํ์ฌ ๋ณ๋๋ก ์์ ํ๊ฒ ๋ณด๊ดํด์ผ ํฉ๋๋ค Quote "์ต์คํ๋ก๋ฌ > ๋ฌธ๋จ๋๋๊ธฐ, ์๋ฒ ๋ฉ > ํ ์คํธ ์ฑ ์์ฑ"์ผ๋ก ์ด๋ํ์ฌ, ์ ์ ํ ์ด๋ฆ์ ํ ์คํธ ์ฑ์ ์์ฑํฉ๋๋ค. ์ด ์ฑ์ ์ถํ chunking๊ณผ embedding ๊ณผ์ ์์ ํ์ฉ๋ฉ๋๋ค. ๋ฐ๊ธ๋ฐ์ API KEY๋ ํ๊ฒฝ ๋ณ์๋ก ์ ์ฅํ์ฌ ๊ด๋ฆฌํฉ๋๋ค. os.environ["CLOVASTUDIO_API_KEY"] = getpass.getpass("CLOVA Studio API Key: ") ย โฃ ์ฐธ์กฐํ ๋ฌธ์ (PDF ๋ฐ์ดํฐ) ์ค๋น ๋ณธ ์์ ์์๋ 'AI ๋ชจ๋ธ ํ๋ํ๊ธฐ: ํ์ต ๋ฐ์ดํฐ ํ์ฉ๋ถํฐ ์ฑ๋ฅ ํฅ์๊น์ง'๊ณผ '๋น์ ์ AI์๊ฒ ํ๋์ ๋งก๊ฒจ๋ผ: ์คํฌ๊ณผ Function Calling'ย ํ์ด์ง๋ฅผ PDF ํ์์ผ๋ก ๋ณํํ์ฌ ๋ฐ์ดํฐ๋ก ํ์ฉํฉ๋๋ค. ์ด ๋ฌธ์๋ค์ ๋จ์ ํ ์คํธ๋ฟ๋ง ์๋๋ผ ํ, ๊ทธ๋ํ, ๋ค์ด์ด๊ทธ๋จ, ์ฝ๋ ๋ฑ ๋ค์ํ ํํ์ ์ด๋ฏธ์ง๊ฐ ํฌํจ๋์ด ์์ด, ์ด๋ฏธ์ง ๊ธฐ๋ฐ ์ ๋ณด๊ฐ ์ค์ ๊ฒ์์ ์ด๋ป๊ฒ ํ์ฉ๋๋์ง ํ ์คํธํ๊ธฐ์ ์ ํฉํฉ๋๋ค. ํ์์ ๋ฐ๋ผ PDF ๋ฌธ์๋ฅผ ๊ต์ฒดํ๊ฑฐ๋ ๋ด์ฉ์ ํธ์งํ์ฌ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ค๋นํ PDF ๋ฌธ์๋ data/ ํด๋์ ์ ์ฅํ์ฌ ์ค๋นํฉ๋๋ค. ๐ cookbook/ โโโ multimodal_RAG.ipynb/ โโโ data/ ย โ ย ย ย ย ย ย โโโย ๋ชจ๋ธํ๋.pdf ย โ ย ย ย ย ย ย โโโย แแ ณแแ ตแฏ.pdf ย 2. ๋ฌธ์ ์ ์ฒ๋ฆฌํ๊ธฐ ๋ฉํฐ๋ชจ๋ฌ RAG ์์คํ ์์๋ ํ ์คํธ๋ฟ๋ง ์๋๋ผ ์ด๋ฏธ์ง ๋ฑ ๋ค์ํ ํํ์ ๋ฐ์ดํฐ๋ฅผ ๋ถํ ํ๊ณ , ์ด๋ฅผ ๋ฒกํฐ๋ก ๋ณํํ์ฌ ๊ฒ์์ ํ์ฉํด์ผ ํฉ๋๋ค. PDF ๋ฌธ์๋ ์ผ๋ฐ ํ ์คํธ ์ธ์๋ ๊ทธ๋ํ, ํ ์ด๋ธ๊ณผ ๊ฐ์ ์๊ฐ์ ์์๊ฐ ํฌํจ๋์ด ์์ด, ํ ์คํธ ๊ธฐ๋ฐ RAG๋ณด๋ค ๋ ์ ๊ตํ๊ณ ๋ณตํฉ์ ์ธ ์ ์ฒ๋ฆฌ ๊ณผ์ ์ด ์๊ตฌ๋ฉ๋๋ค. โ PDF ๋ฌธ์์์ ํ ์คํธ์ ์ด๋ฏธ์ง ์ถ์ถํ๊ธฐ (Load) ย PDF ํ์ผ์์ ํ ์คํธ์ ์ด๋ฏธ์ง๋ฅผ ๊ฐ๊ฐ ์ถ์ถํ๋ ๊ณผ์ ์ด ํ์ํฉ๋๋ค.ย ํ์ฌ ๋ฉํฐ๋ชจ๋ฌ ๋ฌธ์์ ์ ๋ณด ์ถ์ถ์ ์ง์ํ๋ ๋ํ์ ์ธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก๋ย PyPDF,ย PyMuPDF,ย LlamaParse,ย Unstructured.io,ย TorchMultimodalย ๋ฑ์ด ์์ต๋๋ค.ย ๊ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ํ ์คํธ ์ถ์ถ ์ ํ๋, ์ด๋ฏธ์ง ์ฒ๋ฆฌ ๋ฐฉ์, ๊ตฌ์กฐ ๋ณด์กด ์ฌ๋ถ ๋ฑ ๊ตฌํ ๋ฐฉ์์์ ์ฐจ์ด๋ฅผ ๋ณด์ ๋๋ค. ๋ฐ๋ผ์ ํน์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์ ๋์ ์ผ๋ก ์ฐ์ํ๋ค๊ณ ๋ณด๊ธด ์ด๋ ต์ต๋๋ค.ย ๋ณธ ์์ ์์๋ PyMuPDF๋ฅผ ํ์ฉํ์ฌ ํ์ด์ง๋ณ๋ก ์ด๋ฏธ์ง๋ฅผ ์ ์ฅํ๊ณ ํ ์คํธ๋ฅผ ๊ตฌ์กฐํํ๋ ๋ฐฉ์์ ์ฑํํ์ต๋๋ค. %pip install pymupdf ย ์๋ ์ฝ๋๋ PDF ํ์ผ์์ ์ถ์ถํ ์ด๋ฏธ์ง์ ํ ์คํธ๋ฅผ ์๋ ๊ฒฝ๋ก ๊ธฐ์ค์ ์ง์ ๋ ํด๋(output_dir)์ ์ ์ฅํ๋๋ก ๊ตฌ์ฑ๋์ด ์์ต๋๋ค. ์ด ๊ณผ์ ์ ํตํด ๊ฐ ํ์ด์ง์ ํฌํจ๋ ์ด๋ฏธ์ง์ ํ ์คํธ ์ถ์ถ ๊ฒฐ๊ณผ๋ฅผ ์ง์ ํ์ธํ ์ ์์ต๋๋ค. ์คํ ํ ์ ์ฒด ๋๋ ํ ๋ฆฌ ๊ตฌ์กฐ๋ ๋ค์๊ณผ ๊ฐ์ด ๊ตฌ์ฑ๋ฉ๋๋ค. ๐ cookbook/ โโโ multimodal_RAG.ipynb/ โโโ data/ ย โ ย ย ย ย ย ย โโโ ๋ชจ๋ธํ๋.pdf ย โ ย ย ย ย ย ย โโโ ์คํฌ.pdf ย โ ย ย ย ย ย ย โโโ extracted_images_๋ฌธ์/ ย โ ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย โโโ merged_text.txt ย โ ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย โโโ page_1_img_1.png ย โ ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย โโโ page_2_img_1.png ย โ ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย โโโ page_9_img_1.png ย โ ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย โโโ page_10_img_1.png ย โ ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย โโโ page_11_img_1.png ย โ ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย โโโ page_12_img_1.png ย โ ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย โโโ page_15_img_1.png ย โ ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย โโโ page_15_img_2.png ย โ ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย โโโ page_16_img_1.png ํ ์คํธ๋ ํ์ด์ง ๋จ์๋ก ์ ๋ฆฌ๋๋ฉฐ, ์ดํ LangChain์ Document ๊ฐ์ฒด๋ก ๋ณํ๋์ด ์๋ฒ ๋ฉ ์ฒ๋ฆฌ์ ํ์ฉ๋ฉ๋๋ค. ์ด๋ฏธ์ง๋ "page_{page_number}img{img_index}.{image_ext}" ํ์์ผ๋ก ์ ์ฅ๋ฉ๋๋ค. ์ด๋ฌํ ๋ช ๋ช ๊ท์น์ ์ด๋ฏธ์ง๊ฐ ์ด๋ ํ์ด์ง์์ ์ถ์ถ๋์๋์ง ์ฝ๊ฒ ์ถ์ ํ ์ ์๊ฒ ํ๋ฉฐ, ์ดํ ๋ฉํ๋ฐ์ดํฐ๋ก ํ์ฉํ๊ธฐ์๋ ๋งค์ฐ ํจ๊ณผ์ ์ ๋๋ค. ์ด๋ ๊ฒ ๊ตฌ์ฑ๋ ๋ฌธ์๋ ํฅํ ๊ฒ์ ๊ธฐ๋ฐ ์ง์์๋ต(RAG) ์์คํ ์ ์ปจํ ์คํธ๋ก ํ์ฉ๋ฉ๋๋ค. import fitz # PyMuPDF from langchain_core.documents import Document def extract_documents_from_pdf(pdf_path: str, output_dir: str = "data/extracted_images_๋ฌธ์"): os.makedirs(output_dir, exist_ok=True) merged_text_path = os.path.join(output_dir, "merged_text.txt") merged_text = "" doc = fitz.open(pdf_path) documents = [] for i, page in enumerate(doc): page_number = i + 1 page_text = page.get_text("text").strip() images_info = [] # ์ด๋ฏธ์ง ์ถ์ถ for img_index, img in enumerate(page.get_images(full=True)): xref = img[0] base_image = doc.extract_image(xref) image_bytes = base_image["image"] image_ext = base_image["ext"] image_filename = f"page_{page_number}_img_{img_index+1}.{image_ext}" image_path = os.path.join(output_dir, image_filename) with open(image_path, "wb") as img_file: img_file.write(image_bytes) images_info.append(image_path) # LangChain Document๋ก ๋ณํ documents.append(Document( page_content=page_text, metadata={ "source": os.path.basename(pdf_path), "page": page_number, "images": ", ".join(images_info) } )) # ๋ณํฉ ํ ์คํธ ์ ์ฅ์ฉ merged_text += f"\n\n--- Page {page_number} ---\n\n{page_text}" # ์ ์ฒด ํ ์คํธ ์ ์ฅ with open(merged_text_path, "w", encoding="utf-8") as f: f.write(merged_text) return documents, merged_text_path pdf_path = "data/๋ชจ๋ธํ๋.pdf" # ๋ค๋ฅธ ํ์ผ๋ก ํ ์คํธํ ๊ฒฝ์ฐ ์๋ง์ ๊ฒฝ๋ก ์ ๋ ฅ docs, merged_path = extract_documents_from_pdf(pdf_path) print(f"์ถ์ถ๋ ๋ฌธ์ ํ์ด์ง ์: {len(docs)}") print(f"๋ณํฉ๋ ํ ์คํธ ๊ฒฝ๋ก: {merged_path}") print(docs[0]) # ํ๋ ํ์ธ ๊ฒฐ๊ณผ ย โก ์ด๋ฏธ์ง โ ํ ์คํธ ์์ฝํ๊ธฐ (Convert) PDF์์ ์ถ์ถํ ์ด๋ฏธ์ง๋ ๋จ์ํ ์ ์ฅํ๋ ๊ฒ๋ง์ผ๋ก๋ ๊ฒ์์ด๋ ์๋ต ์์ฑ์ ์ฆ์ ํ์ฉํ๊ธฐ ์ด๋ ต์ต๋๋ค. ์ด๋ฏธ์ง ๋ด์ฉ์ ๊ฒ์ ๊ฐ๋ฅํ ์ ๋ณด๋ก ๋ณํํ๊ธฐ ์ํด, CLOVA Studio์ ๋น์ ๋ชจ๋ธ์ ํ์ฉํ์ฌ ์๊ฐ ์ ๋ณด๋ฅผ ํ ์คํธ๋ก ์์ฝํฉ๋๋ค. ์ด๋ ๊ฒ ์์ฑ๋ ์ค๋ช ์ ์ดํ RAG ์์คํ ์์ ๋ฌธ๋งฅ(Context)์ผ๋ก ํ์ฉ๋ ์ ์๋๋ก ๊ตฌ์ฑ๋ฉ๋๋ค. ย 2.1) HyperCLOVA X ๋น์ ๋ชจ๋ธ์ ์ํ ์ด๋ฏธ์ง ์ ์ฒ๋ฆฌ CLOVA ๋น์ ๋ชจ๋ธ ์ฌ์ฉ ์ ๋ค์๊ณผ ๊ฐ์ ์ด๋ฏธ์ง ์ ํ ์ฌํญ์ด ์์ต๋๋ค. Quote ์ ๋ ฅ ์กฐ๊ฑด ๋ฐ ์ ๋ก๋ ์ฌ์ ์ง์ ํฌ๋งท: PNG, JPEG, WEBP, BMP ํ์ผ ์ฉ๋ ์ ํ: ์ด๋ฏธ์ง๋น ์ต๋ 20 MB ์ต๋ ์ฌ์ด์ฆ: ๊ธด ๋ณ ๊ธฐ์ค 2240pxย ์ดํ ๊ฐ๋ก:์ธ๋ก ๋น์จ ์ ํ: 1:5ย ๋๋ 5:1ย ์ดํ ์ฐธ๊ณ :ย 2025๋ 4์ 17์ผ ๊ธฐ์ค, CLOVA Studio์ย HCX-005 ๋ชจ๋ธ์ ์ฌ์ฉ์ ํ ํด์ย ์ต๋ 1์ฅ์ ์ด๋ฏธ์ง๋ง ์ ๋ ฅํ ์ ์์ง๋ง,ย ์์ฒญ ํ ๋ฒ์ ์ต๋ 5์ฅ์ ์ด๋ฏธ์ง๋ฅผ ํฌํจํ ๋ฉ์์ง ์ ๋ ฅ์ ๊ฐ๋ฅํฉ๋๋ค. ย PDF์์ ์ถ์ถํ ์ด๋ฏธ์ง๋ ํด์๋๊ฐ ๋งค์ฐ ํฌ๊ฑฐ๋ ๊ฐ๋กยท์ธ๋ก ๋น์จ์ด ๋น์ ์์ ์ผ๋ก ๊ธด ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค. ์ด๋ฌํ ์ด๋ฏธ์ง๋ฅผ CLOVA Studio์ ๊ทธ๋๋ก ์ ๋ ฅํ๋ฉด 'Invalid image ratio' ์๋ฌ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค. ์ด๋ฌํ ์ค๋ฅ๋ฅผ ์ฌ์ ์ ๋ฐฉ์งํ๊ธฐ ์ํด ์ด๋ฏธ์ง๋ฅผ ๊ฒ์ฌํ๊ณ ๋ฆฌ์ฌ์ด์ฆํ๋ ์ ์ฒ๋ฆฌ ๊ณผ์ ์ ์ํํ๋ ๊ฒ์ด ํ์ํฉ๋๋ค. %pip install -qU Pillow ์๋ ํจ์๋ ํ๋์ ๋ก์ปฌ ์ด๋ฏธ์ง ๊ฒฝ๋ก๋ฅผ ์ ๋ ฅ๋ฐ์ ์กฐ๊ฑด์ ๋ง์กฑํ๋ฉด output_dir์ ๊ทธ๋๋ก ๋ณต์ฌํ๊ณ , ์กฐ๊ฑด์ ๋ง์ง ์์ผ๋ฉด ๋ฆฌ์ฌ์ด์ฆ ํ output_dir์ ์ ์ฅํ๋ ์ญํ ์ ์ํํฉ๋๋ค. from PIL import Image from pathlib import Path import shutil def check_and_resize_image_to_outdir( path: Path, outdir: Path, allowed_formats=("PNG", "JPEG", "WEBP", "BMP"), max_bytes=20 * 1024 * 1024, max_length=2240, max_ratio=4.5, save_format="PNG" ): try: # ์ฉ๋ ์ด๊ณผ ํ์ธ if path.stat().st_size > max_bytes: print(f"[โ] ์ฉ๋ ์ด๊ณผ: {path.name}") return with Image.open(path) as image: format = image.format.upper() if format not in allowed_formats: print(f"[โ] ํฌ๋งท ๋ถ๊ฐ: {path.name} ({format})") return w, h = image.size ratio = max(w, h) / min(w, h) needs_resize = max(w, h) > max_length or ratio > max_ratio if not needs_resize: # ์กฐ๊ฑด ๋ง์กฑ โ ๊ทธ๋๋ก ๋ณต์ฌ dest = outdir / path.name shutil.copy(path, dest) print(f"[โ] ์กฐ๊ฑด ๋ง์กฑ โ ๋ณต์ฌ๋จ: {path.name}") return # ๋ฆฌ์ฌ์ด์ฆ ํฌ๊ธฐ ๊ณ์ฐ if ratio > max_ratio: if w > h: new_w = min(w, max_length) new_h = int(new_w / max_ratio) else: new_h = min(h, max_length) new_w = int(new_h / max_ratio) else: if w >= h: new_w = min(w, max_length) new_h = int(h * (new_w / w)) else: new_h = min(h, max_length) new_w = int(w * (new_h / h)) resized = image.resize((new_w, new_h), Image.LANCZOS).convert("RGB") dest = outdir / path.name resized.save(dest, format=save_format, optimize=True) print(f"[โ] ๋ฆฌ์ฌ์ด์ฆ๋จ โ ์ ์ฅ๋จ: {dest.name} ({new_w}x{new_h})") except Exception as e: print(f"[โ] ์ฒ๋ฆฌ ์คํจ: {path.name} โ {e}") ์๋ ๋ฉ์ธ ์คํ ์ฝ๋๋ฅผ ํตํด CLOVA Studio์ ๋น์ ๋ชจ๋ธ ๊ธฐ์ค์ ์ ํฉํ ์์ ํ ์ด๋ฏธ์ง ์ ์ ๊ตฌ์ฑํ ์ ์์ต๋๋ค. ๐ cookbook/ โโโ multimodal_RAG.ipynb/ โโโ data/ ย โ ย ย ย ย ย ย โโโ ๋ชจ๋ธํ๋.pdf ย โ ย ย ย ย ย ย โโโ ์คํฌ.pdf ย โ ย ย ย ย ย ย โโโ extracted_images_๋ฌธ์/ ย ย ย ย ย ย ย ย ย ย ย โ PDF์์ ์ถ์ถ๋ ์๋ณธ ์ด๋ฏธ์ง ย โ ย ย ย ย ย ย โโโ filtered_images/ ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย โ ์กฐ๊ฑด์ ๋ง๋ ์ด๋ฏธ์ง๊ฐ ์ ์ฅ๋๋ ๊ณณ (output_dir) from pathlib import Path input_dir = Path("data/extracted_images_๋ฌธ์") output_dir = Path("data/filtered_images") output_dir.mkdir(parents=True, exist_ok=True) valid_exts = [".png", ".jpg", ".jpeg", ".webp", ".bmp"] image_files = [p for p in input_dir.glob("*") if p.suffix.lower() in valid_exts] print(f"์ด {len(image_files)}๊ฐ์ ์ด๋ฏธ์ง ์ฒ๋ฆฌ ์์") for img_path in image_files: check_and_resize_image_to_outdir(img_path, outdir=output_dir) ๊ฒฐ๊ณผ ย 2.2) Ncloud Storage ์ฌ์ฉํด์ ์ด๋ฏธ์ง ์ ์ฅํ๊ธฐ CLOVA Studio์ ๋น์ ๋ชจ๋ธ์ ๋ก์ปฌ ๊ฒฝ๋ก์ ์ด๋ฏธ์ง ํ์ผ์ด ์๋, ์น URL ํํ์ ์ด๋ฏธ์ง๋ฅผ ์ ๋ ฅ๊ฐ์ผ๋ก ์ง์ํฉ๋๋ค. ๋ฐ๋ผ์ ๋ชจ๋ธ์ ํ์ฉํ๊ธฐ ์ํด์๋ ๋จผ์ ์ถ์ถํ ์ด๋ฏธ์ง๋ฅผ ๊ฐ์ฒด ์คํ ๋ฆฌ์ง(Ncloud Storage, S3, ๊ตฌ๊ธ ๋๋ผ์ด๋ธ ๋ฑ)์ ์ ๋ก๋ํ ํ, ํด๋น URL์ ์์งํ์ฌ ์ ๋ฆฌํ๋ ์์ ์ด ํ์ํฉ๋๋ค. ์ด๋ฒ cookbook์์๋ย Ncloud Storageย ๋ฅผ ์ฌ์ฉํ๊ฒ ์ต๋๋ค. (์ฐธ๊ณ :ย Ncloud Storage ๊ฐ์ด๋) Quote ์ฐธ๊ณ :ย 2025๋ 4์ 17์ผ ๊ธฐ์ค, Ncloud Storage ์ํ์ OBT(Open Beta)๋ก ์ ๊ณต๋ฉ๋๋ค. Ncloud Storage๋ Open Beta ๊ธฐ๊ฐ๋์ ์ฉ๋ ์ ํ ์์ดย ๋ฌด๋ฃ๋ก ์ด์ฉ์ด ๊ฐ๋ฅํฉ๋๋ค. Open Beta ๊ธฐ๊ฐ ์ข ๋ฃ ํ์๋ ์ ์ฅ๋ ๋ฐ์ดํฐ์ ์ ์ฅ ์ฉ๋๊ณผ API ์์ฒญ์ ๋ํด ๊ณผ๊ธ์ผ๋ก ์ ํ๋ฉ๋๋ค. Open Beta ๊ธฐ๊ฐ์ ์๋น์ค ์ ๊ณต์ ๋ํ SLA๋ ๋ณด์ฅ๋์ง ์์ต๋๋ค. ํฌํธ ๋ง์ดํ์ด์ง > ๊ณ์ ๊ด๋ฆฌ >ย ์ธ์ฆํค ๊ด๋ฆฌ์์ API ์ธ์ฆํค๋ฅผย ์์ฑํฉ๋๋ค. ์์ฑํ Access Key์ Secret Key๋ ํ๊ฒฝ ๋ณ์๋ก ์ค์ ํด์ค๋๋ค. # ๋ค์ด๋ฒ ํด๋ผ์ฐ๋์์ ๋ฐ๊ธ๋ฐ์ ํค๋ฅผ ์ ๋ ฅํ์ธ์ os.environ["AWS_ACCESS_KEY_ID"] = getpass.getpass("NCP Access Key: ") os.environ["AWS_SECRET_ACCESS_KEY"] = getpass.getpass("NCP Secret Key: ") # ๊ธฐ๋ณธ ๋ฆฌ์ ์ค์ os.environ["AWS_DEFAULT_REGION"] = "kr" Ncloud Object Storage๋ Amazon S3 API์ ํธํ๋๋ฉฐ,ย Python์์๋ ์ด๋ฅผ ์ ์ดํ๊ธฐ ์ํด 'boto3' ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํฉ๋๋ค. %pip install boto3 ๋ค์์ ์๋ก์ด ๋ฒํท์ ์์ฑํ๊ณ ์ ์ฒ๋ฆฌํ ์ด๋ฏธ์ง๋ฅผ ๋ชจ๋ ์ ๋ก๋ํ๋ ์ฝ๋์ ๋๋ค. ๋ฒํท ์ด๋ฆ์ ์ต์ 3์์์ ์ต๋ 63์๋ก ๊ตฌ์ฑํ ์ ์์ผ๋ฉฐ, ์๋ฌธ์, ์ซ์ ๋ฐ ํ์ดํ(-)๋ง ํฌํจํด์ผ ํฉ๋๋ค. ์์์์๋ 'multi-rag'๋ฅผ ๋ฒํท ์ด๋ฆ์ผ๋ก ์ฌ์ฉํ์ต๋๋ค. from glob import glob import boto3 from botocore.client import Config from botocore.exceptions import ClientError import mimetypes # ์ค์ BUCKET_NAME = "multi-rag" LOCAL_FOLDER = "data/filtered_images" ENDPOINT_URL = "https://kr.ncloudstorage.com" REGION = os.environ["AWS_DEFAULT_REGION"] ACCESS_KEY = os.environ["AWS_ACCESS_KEY_ID"] SECRET_KEY = os.environ["AWS_SECRET_ACCESS_KEY"] # boto3 ํด๋ผ์ด์ธํธ ์ด๊ธฐํ s3 = boto3.client( "s3", aws_access_key_id=ACCESS_KEY, aws_secret_access_key=SECRET_KEY, endpoint_url=ENDPOINT_URL, region_name=REGION, config=Config(signature_version="s3v4") ) # 1. ๋ฒํท ์์ฑ try: s3.head_bucket(Bucket=BUCKET_NAME) print(f"์ด๋ฏธ ์กด์ฌํ๋ ๋ฒํท์ ๋๋ค: {BUCKET_NAME}") except ClientError as e: if e.response['Error']['Code'] == '404': print(f"๋ฒํท์ด ์กด์ฌํ์ง ์์ ์์ฑํฉ๋๋ค: {BUCKET_NAME}") s3.create_bucket(Bucket=BUCKET_NAME) else: raise # 2. ์ด๋ฏธ์ง ์์ง IMAGE_EXTENSIONS = ("*.jpeg", "*.jpg", "*.png", "*.bmp", "*.webp") image_files = [] for ext in IMAGE_EXTENSIONS: image_files.extend(glob(os.path.join(LOCAL_FOLDER, ext))) print(f"์ด {len(image_files)}๊ฐ ์ด๋ฏธ์ง ํ์ผ์ ์ฐพ์์ต๋๋ค.") # 3. ์ด๋ฏธ์ง ์ ๋ก๋ ๋ฐ URL ์ ์ฅ url_list = [] # ๊ฒฐ๊ณผ ์ ์ฅํ ๋ฆฌ์คํธ for file_path in image_files: file_name = os.path.basename(file_path) try: # ์ ๋ก๋ s3.upload_file(file_path, BUCKET_NAME, file_name) # MIME ํ์ ์ถ์ mime_type, _ = mimetypes.guess_type(file_name) if not mime_type: mime_type = "application/octet-stream" # Signed URL ์์ฑ signed_url = s3.generate_presigned_url( "get_object", Params={ "Bucket": BUCKET_NAME, "Key": file_name, "ResponseContentDisposition": "inline", "ResponseContentType": mime_type }, ExpiresIn=3600 #1์๊ฐ๋ง ) print(f"URL: {signed_url}") url_list.append({signed_url}) except ClientError as e: print(f"์ ๋ก๋ ์คํจ: {e}") print("๋ชจ๋ ์ด๋ฏธ์ง ์ ๋ก๋ ๋ฐ ๋งํฌ ์์ฑ ์๋ฃ!") ๊ฒฐ๊ณผ ย 2.3) ๋น์ ๋ชจ๋ธ ์ฌ์ฉํด์ ์ด๋ฏธ์ง ์์ฝ ํ๊ธฐ PDF ๋ฌธ์๋ฅผ ๋ถ์ํ๋ค ๋ณด๋ฉด ์ธํฌ๊ทธ๋ํฝ, ๊ทธ๋ํ, ํ ์ด๋ธ, ์ฝ๋ ์บก์ฒ์ ๊ฐ์ ๋ค์ํ ํํ์ ์ด๋ฏธ์ง๋ค์ด ๋ฑ์ฅํฉ๋๋ค.ย ์ด๋ฏธ์ง๊ฐ ๋ด๊ณ ์๋ ์ ๋ณด์ ํ์๊ณผ ๋ด์ฉ์ด ์ด๋ฏธ์ง๋ณ๋ก ์์ดํ๊ธฐ ๋๋ฌธ์, ์์ฝ ๋จ๊ณ์์๋ ์ด๋ฏธ์ง ์ ํ์ ๋ง๋ ์ ๊ทผ๋ฒ์ด ํ์ํฉ๋๋ค.ย ํ๋กฌํํธ๋ฟ๋ง ์๋๋ผ ํจ๊ป ์ฌ์ฉํ๋ ํ๋ผ๋ฏธํฐ ์ค์ (config) ๋ํ ์์ฝ ํ์ง์ ํฐ ์ํฅ์ ๋ฏธ์นฉ๋๋ค. ์คํ์ ํตํด ๋น๊ต์ ์์ ์ ์ด๊ณ ์ผ๊ด๋ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์ฌ์ค ์ค์ ๊ฐ์ ํจ๊ป ์ ์ํ์ง๋ง, ์ด๋ฏธ์ง ํน์ฑ์ด๋ ๋ฌธ์ ๋๋ฉ์ธ์ ๋ฐ๋ผ ์ ์ ํ ์กฐํฉ์ ๋ฌ๋ผ์ง ์ ์์ต๋๋ค. ๋ฐ๋ผ์ ์ฌ์ฉํ๋ ํ๋กฌํํธ์ ์ด๋ฏธ์ง ์ ํ์ ๋ง๊ฒ config ๊ฐ์ ์ง์ ์กฐ์ ํด๋ณด๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค. ์๋๋ ๋ฌธ์ ๋ด ์ด๋ฏธ์ง๋ฅผ ๋ค๋ฃฐ ๋ ์ผ๋ฐ์ ์ผ๋ก ํ์ฉ๋๋ ๋ฒ์ฉ ํ๋กฌํํธ์ ํ๋ผ๋ฏธํฐ ๊ฐ์ ์์์ ๋๋ค. from langchain_core.messages import SystemMessage, HumanMessage from langchain_naver import ChatClovaX chat_llm = ChatClovaX( model="HCX-005" ) # ์ด๋ฏธ์ง URL image_url = url_list[-1] # System, User prompt ๊ตฌ์ฑ system_message = SystemMessage( content=( "๋น์ ์ ๋ฌธ์ ๋ด ๋ค์ํ ํํ์ ์ด๋ฏธ์ง๋ฅผ ๋ถ์ํ์ฌ, ๊ฒ์ ๊ธฐ๋ฐ ์ง๋ฌธ์๋ต ์์คํ (RAG)์ ํ์ฉ ๊ฐ๋ฅํ ํ ์คํธ ์ค๋ช ์ ์์ฑํ๋ AI์ ๋๋ค." "์ด๋ฏธ์ง๋ ์ธํฌ๊ทธ๋ํฝ, ํ, ๊ทธ๋ํ, ์ฝ๋ ์บก์ฒ, ๋ค์ด์ด๊ทธ๋จ, ํ๋ฉด ๊ตฌ์ฑ ๋ฑ ๋ค์ํ ์ ํ์ผ ์ ์์ผ๋ฉฐ, ๋ค์ ๊ธฐ์ค์ ๋ฐ๋ผ ์์ฝ์ ์์ฑํ์ธ์." "- ์ด๋ฏธ์ง์ ์ฃผ์ ์ ๋ชฉ์ ์ ๋ช ํํ๊ฒ ํ์ ํ๊ณ ์์ฐ์ด๋ก ์์ฝํฉ๋๋ค." "- ์ด๋ฏธ์ง๊ฐ ์ ๋ฌํ๋ ๊ตฌ์กฐ๋ ํ๋ฆ์ด ์๋ค๋ฉด ์์ฐจ์ ์ผ๋ก ์ค๋ช ํฉ๋๋ค. (์: ๋จ๊ณ, ๊ด๊ณ, ๋น๊ต ๋ฑ)" "- ํ, ๊ทธ๋ํ, ์์น ์ ๋ณด๋ ์ ์ฒด ํ๋ฆ๊ณผ ํน์ง์ ์ธ ์ฐจ์ด๋ง ์์ฝํ๊ณ , ์์น ๋์ด์ ํผํฉ๋๋ค." "- ์ฝ๋ ์บก์ฒ์ธ ๊ฒฝ์ฐ ๊ธฐ๋ฅ๊ณผ ์ญํ ์ค์ฌ์ผ๋ก ์์ฝํ๋ฉฐ, ํจ์/๋ณ์/๋ชจ๋๋ช ๋ฑ ํต์ฌ ์ ๋ณด๋ง ํฌํจํฉ๋๋ค." "- ์๊ฐ์ ์์(์์, ๋ํ, ๋ฐฐ์น ๋ฑ)๋ ์ ๋ณด ์ ๋ฌ์ ํ์ํ ๊ฒฝ์ฐ์๋ง ๊ฐ๋จํ ์ค๋ช ํฉ๋๋ค." "- OCR๋ก ์ถ์ถ๋ ํ ์คํธ๊ฐ ์๋ค๋ฉด ํต์ฌ ๋ด์ฉ ์์ฃผ๋ก ์ ๋ฆฌํ์ฌ ํฌํจํฉ๋๋ค." "- ์ค๋ช ์ ๊ฒ์ ๊ฐ๋ฅํ ํต์ฌ ํค์๋๋ฅผ ํฌํจํ๊ณ , ๊ฐ์์ด๋ ํด์ ์์ด ์ฌ์ค ์ค์ฌ ๋ฌธ์ฅ์ผ๋ก ๊ตฌ์ฑํด์ผ ํฉ๋๋ค." "- ์ต์ข ์ถ๋ ฅ์ 3~5๋ฌธ์ฅ ์ด๋ด์ ๋จ์ผ ๋ฌธ๋จ์ผ๋ก ๊ตฌ์ฑ๋๋ฉฐ, RAG ์์คํ ์ ์ปจํ ์คํธ๋ก ์ง์ ํ์ฉ ๊ฐ๋ฅํด์ผ ํฉ๋๋ค." ) ) human_message = HumanMessage(content=[ {"type": "text", "text": "์ด ์ด๋ฏธ์ง๋ ๋ฌธ์ ๋ด ์๊ฐ ์๋ฃ์ ๋๋ค. ํต์ฌ ์ ๋ณด๋ฅผ ์์ฝํด ์ฃผ์ธ์."}, {"type": "image_url", "image_url": {"url": image_url}} ]) # ๋ฉ์์ง ๊ตฌ์ฑ messages = [ system_message, human_message ] # ํ๋ผ๋ฏธํฐ ์ค์ config={ "generation_config": { "temperature": 0.25, "repetition_penalty": 1.1 } } # ๋ชจ๋ธ ํธ์ถ response = chat_llm.invoke(messages,config) print("[CLOVA ์๋ต]\n", response.content) ๊ฒฐ๊ณผ [CLOVA ์๋ต] ์ด ์ด๋ฏธ์ง๋ 'Tuning'์ด๋ผ๋ ์ ๋ชฉ ์๋ ํ๋์ ๊ณ์ด์ ๊ทธ๋ํฝ์ผ๋ก ๊ตฌ์ฑ๋ ์๊ฐ ์๋ฃ๋ฅผ ๋ณด์ฌ์ค๋๋ค. ๋ฐฐ๊ฒฝ์ ์ง์ ๋จ์์ด๋ฉฐ ์๋จ์๋ ํ๊ธ๋ก ๋ ์ค๋ช ๋ฌธ์ด ์์ต๋๋ค. ์ด ์ค๋ช ๋ฌธ์ ํ๋กฌํํธ๋ง์ผ๋ก ์ฑ๋ฅ ํฅ์์ ํ๊ณ๊ฐ ์์ด ์์ฒด ์กฐ๋ฌํ ์ปค์คํ ๋ฐ์ดํฐ๋ฅผ ํ์ฉํ ๋ชจ๋ธ ํ๋(Tuning) ๊ณผ์ ์ ๊ฑฐ์ณ ์ฑ๋ฅ์ ๋์ฑ ํฅ์์ํฌ ์ ์๋ค๋ ๋ด์ฉ์ ๋ด๊ณ ์์ต๋๋ค. ํ๋จ์ ํ๋ ๋ชจ์ ๊ทธ๋ํ๋ ์๊ฐ์ ๋ฐ๋ฅธ ์ฑ๋ฅ ํฅ์์ ๋ํ๋ด๋ฉฐ, ๊ฐ ์ง์ ๋ง๋ค '1์ฐจ', '2์ฐจ', '3์ฐจ'๋ผ๋ ํ ์คํธ๊ฐ ํ์๋์ด ์ฌ๋ฌ ๋ฒ์ ๊ฐ์ ๋จ๊ณ๋ฅผ ์๋ฏธํฉ๋๋ค. ๋ํ ํ๋จ์๋ ์ํ์ ์์ด์ฝ์ด ์ธ ๊ฐ ์์ผ๋ฉฐ ๊ฐ๊ฐ '์์ง๋์ด๋ง'์ด๋ผ๋ ๋จ์ด์ ํ์ดํ๊ฐ ์ฐ๊ฒฐ๋์ด ์์ด, ์ด๋ฌํ ์์ง๋์ด๋ง ์์ ์ด ๋ฐ๋ณต๋๋ฉด์ ์ฑ๋ฅ์ด ์ ์ฐจ ํฅ์๋จ์ ์๊ฐ์ ์ผ๋ก ํํํ์ต๋๋ค. ์ค๋ฅธ์ชฝ ๋๋ถ๋ถ์๋ 'ํ๋'์ด๋ผ๊ณ ์ ํ ํฐ ์ํ ๋ฒํผ์ด ๊ฐ์กฐ๋์ด ์์ผ๋ฉฐ ์ด๋ ์ต์ข ์ ์ธ ์ฑ๋ฅ ์ต์ ํ๋ฅผ ์์งํฉ๋๋ค. ์ ์ฒด์ ์ผ๋ก ์ด ์ด๋ฏธ์ง๋ ํน์ ๊ณผ์ ์์์ ์ง์์ ์ธ ๊ฐ์ ๋ฐ ์ต์ ํ์ ์ค์์ฑ์ ๋ํ๋ด๋ ๊ฒ์ผ๋ก ๋ณด์ ๋๋ค. ์๋๋ ๊ฐ ์ด๋ฏธ์ง ์ ํ์ ํนํ๋ ํ๋กฌํํธ๋ฅผ ์ ์ฉํด ์์ฑํ ์์ ๊ฒฐ๊ณผ๋ก, ์ด๋ฏธ์ง์ ์ ํ์ ๋ฐ๋ผ ์์ฝ ๋ฐฉ์์ด ์ด๋ป๊ฒ ๋ฌ๋ผ์ง๋์ง ๋น๊ตํด๋ณผ ์ ์๋๋ก ๊ตฌ์ฑํ์ต๋๋ค. ย ์๋ ์ฝ๋๋ ๋ชจ๋ ์ด๋ฏธ์ง์ ๋ํด ์์ฝ์ ์์ฑํ๋ ์ฝ๋์ ๋๋ค. # ๊ฒฐ๊ณผ๋ฅผ ์ ์ฅํ ๋์ ๋๋ฆฌ image_summary_results = [] # URL ๋ฐ๋ณต โ ํ๋กฌํํธ ์์ฑ โ ๋ชจ๋ธ ํธ์ถ โ ๋์ ๋๋ฆฌ ์ ์ฅ for url in url_list: file_name = os.path.basename(url) clean_filename = file_name.split("?")[0] try: # URL๋ง ๋ฐ๊ฟ์ human_message ์ฌ์์ฑ human_message.content[1]["image_url"]["url"] = url messages = [system_message, human_message] response = chat_llm.invoke(messages,config) # ๊ฒฐ๊ณผ ๋์ ๋๋ฆฌ์ ์ ์ฅ image_summary_results.append({clean_filename: response.content}) print(f"[โ] ์ ์ฅ ์๋ฃ: {url}") except Exception as e: print(f"[โ] ์คํจ: {url} โ {e}") ๊ฒฐ๊ณผ ย 2.4) ์ด๋ฏธ์ง ์์ฝ ํ ์คํธ๋ฅผ Document ํ์์ผ๋ก ๋ณํํ๊ธฐ ์๋ ์ฝ๋๋ ์ด๋ฏธ์ง๋ก๋ถํฐ ์์ฑ๋ ์ค๋ช ํ ์คํธ(content)์ ์ด๋ฏธ์ง์ ์์น ์ ๋ณด ๋ฑ ๋ฉํ๋ฐ์ดํฐ๋ฅผ ํจ๊ป ๋ด์ LangChain์ Document ํ์์ผ๋ก ๋ณํํ๋ ๊ณผ์ ์ ๋๋ค. ์ด์ ์ด๋ฏธ์ง ์ถ์ถ ๋จ๊ณ์์ ํ์ผ๋ช ์ ํ์ด์ง ๋ฒํธ๊ฐ ํฌํจ๋๋๋ก ๊ตฌ์ฑํ์ต๋๋ค.ย ์ด๋ฅผ ํ์ฉํด ์ด๋ฏธ์ง์ ์์น ์ ๋ณด๋ฅผ ๋ฉํ๋ฐ์ดํฐ๋ก ๊ตฌ์ฑํ ์ ์์ต๋๋ค. ์ด๋ ๊ฒ ๋ณํ๋ Document๋ ํ ์คํธ ๋ฌธ๋จ๊ณผ ๋์ผํ ๋ฐฉ์์ผ๋ก ๋ฒกํฐ ์๋ฒ ๋ฉ์ด ๊ฐ๋ฅํ๋ฉฐ, ์ด๋ฏธ์ง์์ ์ถ์ถ๋ ์ ๋ณด ์ญ์ ํ ์คํธ ๊ธฐ๋ฐ ์ง์์ฒ๋ผ ๊ฒ์๋๊ณ ์๋ต์ ๋ฐ์๋ ์ ์์ต๋๋ค. ์ด๋ ๋ฉํฐ๋ชจ๋ฌ RAG ๊ตฌ์กฐ์ ํต์ฌ์ ์ธ ์ ์ฒ๋ฆฌ ๋จ๊ณ์ ๋๋ค. image_docs = [] for item in image_summary_results: # ๊ฐ ๋์ ๋๋ฆฌ์์ ํ์ผ๋ช ๊ณผ ์์ฝ ํ ์คํธ ์ถ์ถ file_name = list(item.keys())[0] summary = item[file_name] # ์ ๊ท์์ผ๋ก ํ์ด์ง ๋ฒํธ ์ถ์ถ match = re.search(r'page_(\d+)_img_\d+\.\w+', file_name) page_number = int(match.group(1)) if match else None # LangChain Document ์์ฑ image_docs.append(Document( page_content=summary, metadata={ "source": "๋ชจ๋ธํ๋.pdf", "page": page_number, "images": file_name } )) print(f"์ด {len(image_docs)}๊ฐ์ Document ์์ฑ ์๋ฃ") print(image_docs[0].page_content) print(image_docs[0].metadata) # ํ๋ ํ์ธ ๊ฒฐ๊ณผ ย โข ๋ฌธ๋จ ๋๋๊ธฐ (Chunking) ํ ์คํธ์ ์ด๋ฏธ์ง ๊ฐ๊ฐ์ ์ ๋ณด๋ฅผ ๊ฒ์์ ์ ํฉํ ๋จ์๋ก ๋ถํ ํ๋ ๊ฒ์ด ๋งค์ฐ ์ค์ํฉ๋๋ค. ๋๋ฌด ๊ธด ํ ์คํธ๋ ๊ฒ์ ์ ํ๋๋ฅผ ์ ํ์ํค๊ณ , ์ง๋์น๊ฒ ์๊ฒ ์ชผ๊ฐ๋ฉด ๋ฌธ๋งฅ์ด ๋จ์ ๋ ์ ์์ด ์ ์ ํ ๋ถํ ๊ธฐ์ค์ด ํ์ํฉ๋๋ค. ์ด๋ฒ์๋ Clova Studio์์ ์ ๊ณตํ๋ ๋ฌธ๋จ ๋๋๊ธฐ API๋ฅผ ํ์ฉํ์ฌ ์์ฐ์ค๋ฝ๊ณ ์๋ฏธ ๋จ์๋ก ๊ตฌ๋ถ๋ ๋ฌธ์ ์ฒญํฌ๋ฅผ ์์ฑํฉ๋๋ค. ๋จ์ 3๊ฐ์ ๋จ๊ณ(chunking, Embedding, Vector Store)์ ๋ํ ๋ ์์ธํ ๋ด์ฉ์ย ๐ฆ๐ ๋ญ์ฒด์ธ(Langchain)์ผ๋ก Naive RAG ๊ตฌํํ๊ธฐ cookbookย ๋ฅผ ์ฐธ๊ณ ํด์ฃผ์ธ์. ย 3.1) ๋ฌธ์ chunking # -*- coding: utf-8 -*- class CompletionExecutor: def __init__(self, host, api_key, request_id): self._host = host self._api_key = api_key self._request_id = request_id def _send_request(self, completion_request): headers = { 'Content-Type': 'application/json; charset=utf-8', 'Authorization': self._api_key, 'X-NCP-CLOVASTUDIO-REQUEST-ID': self._request_id } conn = http.client.HTTPSConnection(self._host) conn.request('POST', '/testapp/v1/api-tools/segmentation', json.dumps(completion_request), headers) response = conn.getresponse() result = json.loads(response.read().decode(encoding='utf-8')) conn.close() return result def execute(self, completion_request): res = self._send_request(completion_request) if res['status']['code'] == '20000': return res['result']['topicSeg'] else: print("[CLOVA ์๋ต ์ค๋ฅ]", res['status']) return 'Error' file_path = "data/extracted_images_๋ฌธ์/merged_text.txt" with open(file_path, "r", encoding="utf-8") as f: text_content = f.read() if __name__ == '__main__': completion_executor = CompletionExecutor( host='clovastudio.stream.ntruss.com', api_key="Bearer "+os.environ["CLOVASTUDIO_API_KEY"], request_id=str(uuid.uuid4()) ) chunked_docs = [] for doc in docs: # docs๋ ํ์ด์ง๋ณ๋ก ์ถ์ถํ Document ๋ฆฌ์คํธ segments = completion_executor.execute( # ์ด์ ๋ธ๋ก๊ทธ ์ฐธ๊ณ ํด ํ๋ผ๋ฏธํฐ ์ค์ {"postProcessMaxSize": 100, # ํ์ฒ๋ฆฌ ์ ํ๋์ ๋ฌธ๋จ์ด ๊ฐ์ง ์ ์๋ ์ต๋ ๊ธ์ ์ (์: 1000์ ์ดํ๋ก ์๋ผ์ค) "alpha": -100, # ๋ฌธ๋จ ๋๋๊ธฐ ๋ฏผ๊ฐ๋ ์กฐ์ ํ๋ผ๋ฏธํฐ (๊ธฐ๋ณธ: 0.0 / -100์ผ๋ก ๋๋ฉด ์๋ ์กฐ์ ) - ๊ฐ์ด ํด์๋ก ๋ ์๊ฒ ๋๋๊ณ , ์์์๋ก ๋ ๋๋จ "segCnt": -1, # ์ํ๋ ๋ฌธ๋จ ๊ฐ์ ์ค์ (-1์ด๋ฉด ์๋ ๋ถํ , 1 ์ด์์ ์ ์ ์ ๋ ฅ ์ ํด๋น ๊ฐ์๋ก ๊ณ ์ ) "postProcessMinSize": -1, # ํ์ฒ๋ฆฌ ์ ํ๋์ ๋ฌธ๋จ์ด ๊ฐ์ ธ์ผ ํ ์ต์ ๊ธ์ ์ (์: 300์ ์ด์ ์ ์ง) "text": doc.page_content, # ์ค์ ๋ถํ ํ ์๋ณธ ํ ์คํธ "postProcess": True} # ํ์ฒ๋ฆฌ ์ฌ๋ถ ์ค์ (True: ๋ฌธ๋จ ๊ธธ์ด ๊ท ์ผํ / False: ๋ชจ๋ธ ์ถ๋ ฅ ๊ทธ๋๋ก ์ฌ์ฉ) ) for seg in segments: chunked_docs.append(Document( page_content=' '.join(seg), metadata=doc.metadata )) print(chunked_docs) print("chunk ๊ฐ์ :",len(chunked_docs)) ๊ฒฐ๊ณผ ย 3.2) ์ด๋ฏธ์ง chunking ์ด๋ฏธ์ง ์ค๋ช ์ ๋ํด์๋ ์ผ๋ฐ ํ ์คํธ์ ๋ฌ๋ฆฌ ๋ณ๋๋ก chunking์ ํ์ง ์๊ณ ํ ๋ฉ์ด๋ฆฌ๋ก ๊ทธ๋๋ก ์ ์งํ๋ ๊ฒ์ด ํจ๊ณผ์ ์ ๋๋ค. ๊ทธ ์ด์ ๋ ๊ฐ๋จํฉ๋๋ค. ์ด๋ฏธ์ง ์ค๋ช ํ ์คํธ๋ ์ผ๋ฐ์ ์ผ๋ก ๊ธธ์ด๊ฐ ์งง๊ณ , ํ๋์ ์ด๋ฏธ์ง๊ฐ ํ๋์ ์๋ฏธ ๋จ์๋ฅผ ๋ด๊ณ ์๊ธฐ ๋๋ฌธ์ ๋๋ค. ์ด๋ฌํ ๋ด์ฉ์ ์๋ผ์ ๋๋๋ฉด ์คํ๋ ค ๋ฌธ๋งฅ์ด ๋จ์ ๋๊ฑฐ๋ ์๋ฏธ๊ฐ ๋ชจํธํด์ง ์ ์์ต๋๋ค. ํ ๊ฐ์ ๋ ๋ฆฝ์ ์ธ ์ฒญํฌ๋ก ์ฒ๋ฆฌํ๋ ๊ฒ์ด ๊ฒ์ ์ ํ๋ ์ธก๋ฉด์์๋ ๋ ์์ ์ ์ธ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์ฌ์ค๋๋ค. ๋ฐ๋ผ์ ์ด๋ฏธ์ง ์ค๋ช ์ ๋ณ๋ ๋ถํ ์์ด, 1 ์ด๋ฏธ์ง ์์ฝ = 1 Document ํํ๋ก ๊ตฌ์ฑํ์ฌ ๊ธฐ์กด ํ ์คํธ ์ฒญํฌ๋ค๊ณผ ํจ๊ป ๋ณํฉํ์ฌ ์ฌ์ฉํฉ๋๋ค. # image_docs๋ฅผ chunked_docs์ ์ถ๊ฐ (์๋ณธ์ ๊ทธ๋๋ก ์ ์ง) combined_docs = chunked_docs + image_docs print(f"์ ์ฒด chunk ๊ฐ์: {len(combined_docs)}") ย ํ ์คํธ์ ์ด๋ฏธ์ง์ ๋ํด chunking ์์ ์ ์ํํ ๊ฒฐ๊ณผ, ์ด 19๊ฐ์ ์ฒญํฌ(ํ ์คํธ 10๊ฐ + ์ด๋ฏธ์ง 9๊ฐ)๊ฐ ์์ฑ๋์์ต๋๋ค. ์ด์ ์ค์ ๋ก ์์ฑ๋ ์ฒญํฌ ์ค ์ผ๋ถ๋ฅผ ์ถ๋ ฅํ์ฌ ์ด๋ค ํํ๋ก ๊ตฌ์ฑ๋์ด ์๋์ง ํ์ธํด๋ณด๊ฒ ์ต๋๋ค. ์๋๋ ์์ฑ๋ ์ฒญํฌ ์ค ์ฒ์ 3๊ฐ์ ์ํ์ ๋๋ค. # ์ํ ์ฒญํฌ ์ถ๋ ฅ print("\n์ํ ์ฒญํฌ (์ฒ์ 3๊ฐ):") for i, chunk in enumerate(combined_docs[:3], 0): print(f"\n์ฒญํฌ {i+1}:") print(f"๋ด์ฉ: {chunk.page_content}") print(f"metadata: {chunk.metadata}") print(f"๊ธธ์ด: {len(chunk.page_content)}์") ๊ฒฐ๊ณผ ย โฃ ๋ฒกํฐ ๋ฐ์ดํฐ ๋ณํ (Embedding) ์ด์ ๋ฌธ๋จ ๋จ์๋ก ์๊ฒ ๋๋ ์ง ํ ์คํธ์ ์ด๋ฏธ์ง ์ค๋ช ์ ๋ฒกํฐ๋ก ๋ณํํ ์ฐจ๋ก์ ๋๋ค. ์ด๋ฒ ์์ ์์๋ CLOVA Studio์ ํ ์คํธ ์๋ฒ ๋ฉ ๋ชจ๋ธ์ ํ์ฉํด ๋ฒกํฐ๋ฅผ ์์ฑํฉ๋๋ค. langchain-naver์ ClovaXEmbeddings๋ฅผ ํตํด CLOVA Studio์ ์๋ฒ ๋ฉ ๋ฐ ์๋ฒ ๋ฉ v2 API๋ฅผ ์์ฝ๊ฒ ํ์ฉํ ์ ์์ต๋๋ค. ์๋ฒ ๋ฉ V2๋ bge-m3 ๋ชจ๋ธ์ ์ฌ์ฉํ๋ฉฐ, ์ด ๋ชจ๋ธ์ ์๋ฒ ๋ฉ ๊ณผ์ ์์ ์ ์ฌ๋ ํ๋จ์ ์ํด ์ฝ์ฌ์ธ ๊ฑฐ๋ฆฌ(Cosine)๋ฅผ ๊ฑฐ๋ฆฌ ๋จ์๋ก ์ฌ์ฉํฉ๋๋ค. ๋ชจ๋ธ์ ์ค์ ํ์ง ์์ผ๋ฉด clir-emb-dolphin ๋ชจ๋ธ์ด ๊ธฐ๋ณธ๊ฐ์ผ๋ก ์ง์ ๋๋ฏ๋ก, ClovaXEmbeddings์ ๋ชจ๋ธ์ bge-m3๋ก ๋ช ์์ ์ผ๋ก ์ค์ ํด์ฃผ์ด์ผ ํฉ๋๋ค. from langchain_naver import ClovaXEmbeddings clovax_embeddings = ClovaXEmbeddings(model='bge-m3') # ์๋ฒ ๋ฉ ๋ชจ๋ธ์ ์ค์ text = "์๋ฒ ๋ฉ ์ฌ์ฉ ์์ ์ ๋๋ค~" clovax_embeddings.embed_query(text) ๊ฒฐ๊ณผ ย โค ๋ฒกํฐ ๋ฐ์ดํฐ ์ ์ฅ (Vector Store) ์๋ฒ ๋ฉ๋ ๋ฒกํฐ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๊ณ ๋์ค์ ํจ์จ์ ์ผ๋ก ๊ฒ์ํ๊ธฐ ์ํด์๋ ๋ฒกํฐ ์ ์ฅ์๊ฐ ํ์ํฉ๋๋ค. ์ด ์์ ์์๋ ๋ก์ปฌ ํ๊ฒฝ์์ ๋ณด๋ค ์ฝ๊ฒ ํ์ฉํ ์ ์๋ Chroma์ FAISS๋ฅผ ์ฌ์ฉํ์ต๋๋ค. Langchain์ย Vector DB ๋น๊ต ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํ์ฌ ์์ ์ ๊ฐ๋ฐ ํ๊ฒฝ์ ์ ํฉํ ์๋ฃจ์ ์ ์ ํํ๋ฉด ๋ฉ๋๋ค. ์ ์ฒด ๋ฌธ์๋ฅผ add_documents()๋ก ํ ๋ฒ์ ์ถ๊ฐํ๋ฉด ๋ด๋ถ์ ์ผ๋ก ๋ง์ ๊ฐ๋ณ ์๋ฒ ๋ฉ API ์์ฒญ์ด ๋ณ๋ ฌ๋ก ์คํ๋์ด ์๋ฌ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค. ์ด๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด ์์ฒญ ์ฌ์ด์ time.sleep() ๊ฐ๊ฒฉ์ ๋์ด ์ฒ๋ฆฌ ์๋๋ฅผ ์กฐ์ ํจ์ผ๋ก์จ ์๋ฌ ๋ฐ์๋ฅ ์ ๋ฎ์ท์ต๋๋ค. ย 5.1) Chroma Chroma๋ Python ๊ธฐ๋ฐ์ ์คํ์์ค ๋ฒกํฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ก, ์ฌ์ฉ์ด ๊ฐํธํ๊ณ ๋น ๋ฅธ ํ๋กํ ํ์ดํ์ ์ ํฉํฉ๋๋ค. ํนํ ๋ก์ปฌ ํ๊ฒฝ์์๋ ๋น ๋ฅด๊ฒ ์คํํ ์ ์์ด ๊ฐ๋ฐ ์ด๊ธฐ ๋จ๊ณ์์ ๋ง์ด ํ์ฉ๋ฉ๋๋ค. #chroma ๋ค์ด๋ฐ๊ธฐ %pip install -qU langchain-chroma import chromadb from langchain_chroma import Chroma # ์๋ฒ ๋ฉ ๋ชจ๋ธ ์ ์ clovax_embeddings = ClovaXEmbeddings(model='bge-m3') # ๋ก์ปฌ ํด๋ผ์ด์ธํธ ์์ฑ client = chromadb.PersistentClient(path="./chroma_langchain_db") # ์ปฌ๋ ์ ์ค๋น (์ด๋ฆ ์ค๋ณต ์ฃผ์!) collection_name = "clovastudiodatas_docs" client.get_or_create_collection( name=collection_name, metadata={"hnsw:space": "cosine"} ) # ๋ฒกํฐ์คํ ์ด ๊ฐ์ฒด ์์ฑ vectorstore_Chroma = Chroma( client=client, collection_name=collection_name, embedding_function=clovax_embeddings ) # ๋ฌธ์ ์ถ๊ฐ: ์ต์ ๋ฐฉ์์ vectorstore.add_documents ์ฌ์ฉ print("Adding documents to Chroma vectorstore...") for doc in combined_docs: try: vectorstore_Chroma.add_documents([doc]) time.sleep(0.5) except Exception as e: print(f"[โ] ์คํจ: {doc.metadata} โ {e}") print("All documents have been added to the vectorstore.") ๊ฒฐ๊ณผ ย 5.2) FAISS FAISS๋ ๋๊ท๋ชจ ๋ฒกํฐ ๊ฒ์์ ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก, ์๋์ ํ์ฅ์ฑ ์ธก๋ฉด์์ ๋งค์ฐ ๊ฐ๋ ฅํ ์ฑ๋ฅ์ ์ ๊ณตํฉ๋๋ค. ํนํ ๋์ฉ๋ ๋ฌธ์๋ฅผ ์ฒ๋ฆฌํ ๋ ํจ์จ์ ์ด๋ฉฐ, ๊ฒ์ ์ ํ๋๋ ๋ฐ์ด๋ ํธ์ ๋๋ค. #FAISS ๋ค์ด๋ก๋ %pip install -qU langchain-community faiss-cpu import faiss from langchain_community.vectorstores import FAISS from langchain_community.docstore.in_memory import InMemoryDocstore # ์๋ฒ ๋ฉ ๋ชจ๋ธ ์ ์ clovax_embeddings = ClovaXEmbeddings(model='bge-m3') # FAISS ์ธ๋ฑ์ค ์์ฑ (1024๋ bge-m3 ์ฐจ์ ์์ ๋ง์ถฐ์ผ ํจ) index = faiss.IndexFlatIP(1024) # ๋ด์ ๊ธฐ๋ฐ ๊ฒ์ # FAISS ๋ฒกํฐ์คํ ์ด ์์ฑ vectorstore_FAISS = FAISS( embedding_function=clovax_embeddings, index=index, docstore=InMemoryDocstore(), index_to_docstore_id={} ) # ๋ฌธ์ ์ผ๊ด ์ถ๊ฐ (์๋ ์๋ฒ ๋ฉ ์ฒ๋ฆฌ) print("Adding documents to FAISS vectorstore...") for doc in combined_docs: try: vectorstore_FAISS.add_documents([doc]) time.sleep(0.5) except Exception as e: print(f"[โ] ์คํจ: {doc.metadata} โ {e}") print("All documents have been added to FAISS vectorstore.") ๊ฒฐ๊ณผ ย 3. ์ง์ ์๋ตํด๋ณด๊ธฐ โ ์ง๋ฌธํ๊ธฐ ๋ฌธ์ ์๋ฒ ๋ฉ๊ณผ ๋ฒกํฐ ์ ์ฅ์ ๊ตฌ์ฑ์ด ์๋ฃ๋์๋ค๋ฉด, ์ด์ ์ค์ ์ง๋ฌธ์ ์ ๋ ฅํ๊ณ ๊ด๋ จ ๋ด์ฉ์ ์ฐพ์ ๋ต๋ณ์ ์์ฑํ๋ ๊ณผ์ ์ ์งํํด๋ณด๊ฒ ์ต๋๋ค. LangChain์์๋ RetrievalQA ์ฒด์ธ์ ํตํด ์ด ํ๋ฆ์ ๊ฐ๋จํ๊ฒ ๊ตฌํํ ์ ์์ต๋๋ค. ย 1.1) Retriever ์์ฑํ๊ธฐ ๋จผ์ ์ฌ์ฉ์ ์ง๋ฌธ์ ๋ฐ๋ผ ์ฐ๊ด ๋ฌธ์๋ฅผ ๊ฒ์ํ๊ณ , ํด๋น ๋ฌธ์๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋ต๋ณ์ ์์ฑํ๋ ์ฒด์ธ์ ๊ตฌ์ฑํฉ๋๋ค. ์๋ ์ฝ๋๋ ์์คํ ํ๋กฌํํธ์ ์ฌ์ฉ์ ํ๋กฌํํธ๋ฅผ ๊ตฌ๋ถํ์ฌ ์ค์ ํฉ๋๋ค. ์์คํ ํ๋กฌํํธ์์๋ LLM์ด ๊ธฐ์กด ์ง์์ ์ฌ์ฉํ์ง ์๊ณ ๊ฒ์๋ ๋ฌธ์(context)์ ๊ธฐ๋ฐํ์ฌ ๋ต๋ณํ๋๋ก ์๋ดํฉ๋๋ค. ์ฌ์ฉ์ ํ๋กฌํํธ์๋ ๋ฌธ์ ๋ด์ฉ๊ณผ ์ง๋ฌธ์ด ํจ๊ป ์ ๋ฌ๋ฉ๋๋ค.ย ์ง๋ฌธ์ ์ ๋ ฅํ๋ฉด ๊ด๋ จ ๋ฌธ์๋ฅผ ๊ฒ์ํ ํ ๋ต๋ณ์ ์์ฑํฉ๋๋ค. ๊ฒฐ๊ณผ์๋ ๋ต๋ณ๋ฟ๋ง ์๋๋ผ ์ด๋ค ๋ฌธ์๊ฐ ์ฐธ์กฐ๋์๋์ง๋ ํ์ธํ ์ ์์ต๋๋ค. ย from langchain_core.prompts import ChatPromptTemplate, SystemMessagePromptTemplate, HumanMessagePromptTemplate from langchain.chains import RetrievalQA # System ๋ฐ User ๋ฉ์์ง๋ฅผ ๋๋ ๊ตฌ์ฑ system_template = ( "๋น์ ์ ์ง๋ฌธ-๋ต๋ณ(Question-Answering)์ ์ํํ๋ ์น์ ํ AI ์ด์์คํดํธ์ ๋๋ค. ๋น์ ์ ์๋ฌด๋ ์๋ ๊ฐ์ง๊ณ ์๋ ์ง์์ ๋ชจ๋ ๋ฐฐ์ ํ๊ณ , ์ฃผ์ด์ง ๋ฌธ๋งฅ(context) ์์ ์ฃผ์ด์ง ์ง๋ฌธ(question) ์ ๋ตํ๋ ๊ฒ์ ๋๋ค." "๋ง์ฝ, ์ฃผ์ด์ง ๋ฌธ๋งฅ(context) ์์ ๋ต์ ์ฐพ์ ์ ์๋ค๋ฉด, ๋ต์ ๋ชจ๋ฅธ๋ค๋ฉด `์ฃผ์ด์ง ์ ๋ณด์์ ์ง๋ฌธ์ ๋ํ ์ ๋ณด๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค` ๋ผ๊ณ ๋ตํ์ธ์." ) user_template = ( "๋ค์์ ๊ฒ์๋ ๋ฌธ์ ๋ด์ฉ์ ๋๋ค:\n\n{context}\n\n" "์ ์ ๋ณด๋ฅผ ๋ฐํ์ผ๋ก ๋ค์ ์ง๋ฌธ์ ๋ตํด์ฃผ์ธ์:\n{question}" ) prompt_template = ChatPromptTemplate.from_messages([ SystemMessagePromptTemplate.from_template(system_template), HumanMessagePromptTemplate.from_template(user_template), ]) # ์ํ๋ vectorstore ์ ํํด์ ์ฌ์ฉ retriever = vectorstore_Chroma.as_retriever( search_type="similarity_score_threshold", search_kwargs={"score_threshold": 0.1, "k": 3} ) # retriever = vectorstore_FAISS.as_retriever( # search_type="similarity_score_threshold", # search_kwargs={"score_threshold": 0.1, "k": 3} # ) # Retrieval QA ์ฒด์ธ ๊ตฌ์ฑ qa_chain = RetrievalQA.from_chain_type( llm=chat_llm, chain_type="stuff", retriever=retriever, chain_type_kwargs={"prompt": prompt_template}, return_source_documents=True ) # ์คํ question = "๋ฐ์ดํฐ์ ๊ท๋ชจ๊ฐ ์ปค์ง์๋ก 2๋๋ฅ์ ์ค๋ฅ ๋ฐ์ ํ๋ฅ ์ ์ด๋ป๊ฒ ๋ผ?" result = qa_chain.invoke({"query": question}) print("์ง๋ฌธ:", question) print("์๋ต:", result["result"]) # ๋ชจ๋ธ์ ์ค์ ์๋ต for i, doc in enumerate(result["source_documents"]): # ๋ต๋ณ์ ์ฐธ๊ณ ํ ๋ฌธ์ print(f"\n[์ถ์ฒ ๋ฌธ์ {i+1}]\n๋ด์ฉ: {doc.page_content}\n๋ฉํ๋ฐ์ดํฐ: {doc.metadata}") ย โก ๋ต๋ณํ์ธ ํ ์คํธ๋ง ์ฌ์ฉํ์ ๋๋ ๋ชจ๋ธ์ด ๊ด๋ จ ์ ๋ณด๋ฅผ ์ฐพ์ง ๋ชปํด ์ ํ์ ์ธ ๋ต๋ณ์ ์ ๊ณตํ์ง๋ง, ์ด๋ฏธ์ง ์์ฝ ํ ์คํธ๊น์ง ํจ๊ป ํ์ฉํ์ ๋๋ ํจ์ฌ ๊ตฌ์ฒด์ ์ด๊ณ ๊ด๋ จ์ฑ ๋์ ๋ต๋ณ์ ์์ฑํ ์ ์์์ต๋๋ค. ๋ฉํฐ๋ชจ๋ฌ RAG ์์คํ ์ด ์ด๋ป๊ฒ ๊ฒ์ ํ์ง๊ณผ ์๋ต ์ ํ๋๋ฅผ ํฅ์์ํค๋์ง ํ์ธํ์ค ์ ์์ต๋๋ค. ย ๋งบ์๋ง ์ด๋ฒ cookbook์์๋ ํ ์คํธ๋ฟ๋ง ์๋๋ผ ์ด๋ฏธ์ง ๊ธฐ๋ฐ ์ ๋ณด๋ฅผ ํจ๊ป ํ์ฉํด ๊ฒ์ ์ ํ๋๋ฅผ ๋์ผ ์ ์๋ Multimodal RAG ์์คํ ์ ๊ตฌ์ฑํด๋ณด์์ต๋๋ค. ๋จ์ํ ๋ฌธ์๋ฅผ ์๋ฒ ๋ฉํ๋ ๋จ๊ณ๋ฅผ ๋์ด, ์ด๋ฏธ์ง ์ ์๊ฐ ์ ๋ณด๋ฅผ ๋น์ ๋ชจ๋ธ์ ํตํด ์์ฝํ๊ณ ๋ฒกํฐ DB์ ์ ์ฅํ์ฌ ๊ฒ์์ ํ์ฉํจ์ผ๋ก์จ ๋์ฑ ํ๋ถํ ์ง์์๋ต์ด ๊ฐ๋ฅํด์ก์ต๋๋ค. ํนํ ํ ์คํธ๋ง์ผ๋ก๋ ์ถฉ๋ถํ ๋์ํ๊ธฐ ์ด๋ ค์ ๋ ์ง๋ฌธ์๋ ์ด๋ฏธ์ง ๊ธฐ๋ฐ ๋ฌธ์๋ฅผ ํตํด ์ ํํ ๋ต๋ณ์ ๋์ถํ ์ ์์๋ ์์๋ฅผ ํตํด, ๋น์ ํ ๋ฐ์ดํฐ๋ฅผ ํจ๊ณผ์ ์ผ๋ก ๊ตฌ์กฐํํ๊ณ ํ์ฉํ๋ ๋ฐฉ๋ฒ์ ๊ฐ๋ฅ์ฑ์ ํ์ธํ ์ ์์์ต๋๋ค. ์ด ๊ฐ์ด๋๋ฅผ ํตํด ๋ฉํฐ๋ชจ๋ฌ RAG ์์คํ ์ ์ฝ๊ฒ ๊ตฌ์ถํ๊ณ ๋น์ ๋ชจ๋ธ์ ์ค์ ํ์ฉ ํ๋ฆ์ ์ดํดํ ์ ์๊ธฐ๋ฅผ ๊ธฐ๋ํฉ๋๋ค. ย LangChainย ๊ณต์ย ๋ฌธ์:ย https://python.langchain.com/docs/integrations/providers/naver/ LangChain ์ฐ๋ ์ฌ์ฉ ๊ฐ์ด๋:ย https://guide.ncloud-docs.com/docs/clovastudio-dev-langchain Python์ฉ AWS SDK (Boto3) ๊ฐ์ด๋ย :ย https://guide.ncloud-docs.com/docs/storage-storage-8-2 ย ย ๋งํฌ ๋ณต์ฌ ๋ค๋ฅธ ์ฌ์ดํธ์ ๊ณต์ ํ๊ธฐ More sharing options...
Recommended Posts
๊ฒ์๊ธ ๋ฐ ๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋ก๊ทธ์ธ ํด์ฃผ์ธ์.
๋ก๊ทธ์ธ