RAG(Retrieval-Augmented Generation)๋
์ธ๋ถ ์ง์(๋ฌธ์ ๋ฑ)์ ๊ธฐ๋ฐ์ผ๋ก LLM์ด ๋ ์ ํํ๊ฒ ๋ต๋ณ์ ์์ฑํ๋๋ก ์ค๊ณ๋ ์ํคํ ์ฒ.
ํฌ๊ฒ ๋๊ฐ์ง ๋จ๊ณ๋ก ๋ณผ ์ ์๋ค.
1. Indexing ๋จ๊ณ
- ๋ฌธ์ ๋ถ๋ฌ์ค๊ธฐ(Load): ๋จผ์ ๊ฐ์ข ๋ฌธ์์ ๋ฐ์ดํฐ๋ฅผ ์์คํ ์ ๋ถ๋ฌ์จ๋ค.
- ์๊ฒ ๋๋๊ธฐ(Split): ๋ถ๋ฌ์จ ๋ฌธ์๋ ์์ ๋จ์(์ฒญํฌ)๋ก ๋ถํ . ์ด๋ ๊ฒ์๊ณผ ์๋ฒ ๋ฉ์ ํจ์จ์ฑ์ ๋์ด๊ธฐ ์ํ ์ ์ฐจ
- ์ซ์๋ก ๋ณํ(Embed): ๊ฐ ์ฒญํฌ๋ฅผ AI๊ฐ ์ดํดํ ์ ์๋๋ก ์๋ฒ ๋ฉ ๋ฒกํฐ(์ซ์ ๋ฐฐ์ด)๋ก ๋ณํ
- ์ ์ฅ์์ ๋ณด๊ด(Store): ๋ณํ๋ ์๋ฒ ๋ฉ ๋ฒกํฐ๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ ๋ฒกํฐ์คํ ์ด์ ์ ์ฅ. ์ด ์ ์ฅ์๋ ๋์ค์ ๊ฒ์ํ ๋ ์ฌ์ฉ๋๋ค.
๋ชฉ์ : AI๊ฐ ๋ต๋ณ์ ์์ฑํ ๋ ์ฐธ๊ณ ํด์ผ ํ๋ ์ ๋ณด๋ฅผ ๋น ๋ฅด๊ณ ํจ์จ์ ์ผ๋ก ์ฐพ์ ์ ์๋๋ก ๋ฏธ๋ฆฌ ์ค๋นํ๋ ๊ณผ์
2. Retrieval & Generation ๋จ๊ณ
- ์ฌ์ฉ์ ์ง๋ฌธ(Question): ์ฌ์ฉ์๊ฐ ์ง๋ฌธ์ ์ ๋ ฅํ๋ค.
- ์ ์ฌ ์ฒญํฌ ๊ฒ์(Retrieve): ์์คํ ์ ์ง๋ฌธ๊ณผ ๊ฐ์ฅ ๋น์ทํ ์๋ฏธ๋ฅผ ๊ฐ์ง ์ฒญํฌ๋ฅผ ์๋ฒ ๋ฉ ๊ณต๊ฐ์์ ๊ฒ์ํ๋ค.
- ํ๋กฌํํธ ์์ฑ(Prompt): LLM์๊ฒ ์ ๋ฌํ ํ๋กฌํํธ๋ฅผ ํด๋น ์ฒญํฌ์ ํจ๊ป ๋ง๋ ๋ค.
- ์๋ต ์์ฑ(Answer): LLM์ด ๊ฒ์๋ ์ ๋ณด์ ํ๋กฌํํธ๋ฅผ ๋ฐํ์ผ๋ก ์ ํํ๊ณ ๊ทผ๊ฑฐ ์๋ ๋ต๋ณ์ ์์ฑํ๋ค.
๋ชฉ์ : ์ค์๊ฐ์ผ๋ก ์ฌ์ฉ์์ ์ง๋ฌธ ์๋์ ๊ฐ์ฅ ๊ด๋ จ ๊น์ ์ธ๋ถ ์ง์๋ง ์ ๋ณํ์ฌ LLM์ด ๋ต์ ์์ฑํ๋๋ก ํ๋ค.
LLM๋ง์ ์ฌ์ฉํ๋ฉด ์ธ๋ถ ์ง์ ์ ๊ทผ ์์ด ํ๋ จ๋ ํ๋ผ๋ฏธํฐ๋ง์ ์ฌ์ฉํด ๋ต์ ์์ฑํ๋ค. ์ด๋ ๊ธฐ์ต์ ์์กดํ ํ์ด์ ๋น์ทํ๋ค.
๋ฐ๋ฉด, RAG๋ ์ธ๋ถ ๋ฌธ์๋ฅผ ์คํ๋ถ ์ํ์ฒ๋ผ ์ค์๊ฐ ๊ฒ์ํด์, ์๋กญ๊ฑฐ๋ ์์ธํ ์ ๋ณด๋ฅผ ๋ฐํ์ผ๋ก ๋ ์ ํํ ๋ต๋ณ์ ๋ง๋ค ์ ์๋ค.
- ๋ฌธ์ ์๋ฒ ๋ฉ๊ณผ ๋ฒกํฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ์ฅ
- ๋ฌธ๋งฅ์ ๋ณด๋ฅผ ์๋ฒ ๋ฉ ๋ชจ๋ธ์ ์ด์ฉํด ๋ฒกํฐ๋ก ๋ณํ
- ์ด ๋ฒกํฐ๋ค์ ๋ฒกํฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅ๋์ด ๊ฒ์์ ํ์ฉ
- ์ฟผ๋ฆฌ ์๋ฒ ๋ฉ๊ณผ ๊ฒ์
- ์ฌ์ฉ์์ ์ง๋ฌธ์ ์ฟผ๋ฆฌ ์๋ฒ ๋ฉ์ผ๋ก ๋ณํํ๊ณ , ๋ฒกํฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ฟผ๋ฆฌ ์๋ฒ ๋ฉ๊ณผ ๊ฐ์ฅ ๊ฐ๊น์ด ๋ฌธ์ ์๋ฒ ๋ฉ์ ๊ฒ์ํด ๊ด๋ จ ์๋ฃ๋ฅผ ์ฐพ์๋ธ๋ค.
- ์ด ๊ฒ์ ๊ฒฐ๊ณผ๋ค์ ํ๋กฌํํธ์ ์ถ๊ฐ๋์ด ์์ฑ ๋ชจ๋ธ์ ์ ๋ ฅ๋๋ค.
ํ ํฐํ(ํ ํฐ ์ชผ๊ฐ๊ธฐ)
RAG์์ ํ ํฐํ๋ ๋ฌธ์๋ฅผ ๋ชจ๋ธ์ ์ ๋ ฅํ ๋ ๋ชจ๋ธ์ ์ต๋ ํ ํฐ ์ ์ ํ ๋๋ฌธ์ ๋ฌธ์๋ฅผ ์ฌ๋ฌ ์์ ์กฐ๊ฐ์ผ๋ก ๋ถํ ํ๋ ์์ ์ ์๋ฏธํ๋ค.
์ด ๊ณผ์ ์์ ํ ์คํธ๋ฅผ ํ ํฐ ๋จ์ ๊ธฐ์ค์ผ๋ก ๋๋์ด ๊ฐ ์ฒญํฌ๊ฐ ์ต๋ ํ ํฐ ์๋ฅผ ๋์ง ์๋๋ก ํ๋ฉฐ, ํ ํฐ overlap์ ํตํด ์ธ์ ์ฒญํฌ ๊ฐ์ ์ผ๋ถ ํ ํฐ์ ๊ฒน์น๊ฒ ํ์ฌ ๋ฌธ๋งฅ ์์ค์ ๋ฐฉ์งํ๋ค.
from langchain_text_splitters import CharacterTextSplitter
text_splitter = CharacterTextSplitter(
separator="\n\n",
chunk_size=100,
chunk_overlap=20,
length_function=len,
is_separator_regex=False
)
texts = text_splitter.split_documents(docs)
- ๋ฌธ์๋ฅผ ํ ํฐ ๋จ์๋ก ์ชผ๊ฐ ์ต๋ chunk_size ์ดํ๋ก ์ ์ง
- ๋ถํ ์ ๊ตฌ๋ถ์(์ค๋ฐ๊ฟ, ๋ง์นจํ ๋ฑ)๋ฅผ ํ์ฉํด ์์ฐ์ค๋ฌ์ด ์๋ฆ
- chunk_overlap ํ๋ผ๋ฏธํฐ๋ก ์ธ์ ์ฒญํฌ์ ํ ํฐ ์ผ๋ถ ๊ฒน์นจ ์ ์ฉ
- ์ด๋ ๊ฒ ์ชผ๊ฐ์ง ์ฒญํฌ๋ค์ ์๋ฒ ๋ฉํ์ฌ ๋ฒกํฐ DB์ ์ ์ฅํด RAG์์ ๊ฒ์ ๋ฐ ์์ฑ์ ํ์ฉ
์ด๋ ๊ฒ ๋๋๋ฉด LLM์ด ์ ๋ ฅ ์ ํ์ ์ด๊ณผํ์ง ์์ผ๋ฉด์ ์ค์ํ ๋ฌธ๋งฅ์ ์ ์งํ๋ฉฐ ๋ฌธ์ ๋ด์ฉ์ ํจ๊ณผ์ ์ผ๋ก ์ฐธ์กฐํ ์ ์๋ค.
LangChain ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์๋ CharacterTextSplitter, RecursiveCharacterTextSplitter ๊ฐ์ ๋๊ตฌ๋ก ํ ํฐ ๊ธฐ๋ฐ ๋ถํ ์ ๊ตฌํ
ํ ํฐ๊ณผ ์ฒญํฌ?
- ํ ํฐ์ ํ ์คํธ๋ฅผ ๋ ์ด์ ์ชผ๊ฐค ์ ์๋ ๊ธฐ๋ณธ ๋จ์. ๋จ์ด, ๊ตฌ๋์ , ์ซ์ ๋ฑ์ผ๋ก ๋๋๋ฉฐ, ํ ํฐํ ์์ ์ ํตํด ์์ฑ๋๋ค.
- ์ฒญํฌ๋ ์ฌ๋ฌ ํ ํฐ์ ๋ชจ์ ์๋ฏธ ์๋ ๋จ์๋ก ๋ฌถ์ ๊ฒ. ์๋ฅผ ๋ค์ด “New York City”๋ 3๊ฐ์ ํ ํฐ(“New”, “York”, “City”)์ด์ง๋ง ํ๋์ ์๋ฏธ ๋จ์์ธ ์ฒญํฌ๋ก ๋ค๋ค์ง ์ ์๋ค.
ํฌ๊ธฐ๋ก ๋๋๊ธฐ
์ฒญํฌ ์ฌ์ด์ฆ๋ฅผ 100์ผ๋ก ์ค์ ํ๋๋ฐ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ฉด 100๋ณด๋ค ํฐ ์ฒญํฌ ์ฌ์ด์ฆ๋ก ์ชผ๊ฐ์ง๋ ๊ฒ์ ํ์ธํ ์ ์๋๋ฐ, ๊ฒฐ๊ณผ๊ฐ ๋ ํฌ๊ฒ ์ชผ๊ฐ์ง๋ ์ด์ ๋ CharacterTextSplitter๊ฐ ์ง์ ํ ๊ตฌ๋ถ์(separator)๋ฅผ ๊ธฐ์ค์ผ๋ก ๋จผ์ ํ ์คํธ๋ฅผ ๋๋๊ณ , ๊ทธ ๊ตฌ๊ฐ์ด ์ฒญํฌ ํฌ๊ธฐ๋ณด๋ค ํฌ๋ฉด ๊ทธ ๊ตฌ๊ฐ ์์ฒด๊ฐ ์ฒญํฌ ํฌ๊ธฐ๋ณด๋ค ํฐ ๊ฒฐ๊ณผ๋ฅผ ๋ง๋ค๊ธฐ ๋๋ฌธ์ด๋ค.
๊ตฌ์ฒด์ ์ผ๋ก ๋งํ๋ฉด, separator=”\n\n” ์ ๊ธฐ์ค์ผ๋ก ๋ฌธ๋จ ๋จ์๋ก ๋จผ์ ๋ถํ ํ ๋ค์ ๊ฐ ๋ฌธ๋จ ์กฐ๊ฐ์ ์ฒญํฌ๋ก ๋ง๋ ๋ค. ๊ทธ๋ฐ๋ฐ ์ด๋ค ๋ฌธ๋จ์ด 100์๋ณด๋ค ๋ ๊ธธ๋ฉด ๊ทธ ๋ฌธ๋จ ์ ์ฒด๊ฐ ํ๋์ ์ฒญํฌ๋ก ์ ์ง๋์ด 100์๋ณด๋ค ํฐ ์ฒญํฌ๊ฐ ๋์ฌ ์ ์๋ค. ์ด ๋ฐฉ์์ ๋ฌธ๋งฅ ๋จ์ ์ ๋ง๊ธฐ ์ํด ์ง์ ํ ๊ตฌ๋ถ์๊ฐ ์ฐ์ ์๋๊ธฐ ๋๋ฌธ์ด๋ค. ์ฆ, 100์๋ฅผ ๊ธฐ์ค์ผ๋ก ์ชผ๊ฐฐ์ ๋, ๊ทธ ๋ฌธ๋จ๊น์ง๋ ํ ์ฒญํฌ๋ก ๋ณด๋๋ฐ, ์ด ๋ฌธ๋จ์ด ๊ธธ ๊ฒฝ์ฐ์๋ ํฐ ์ฒญํฌ์ฌ์ด์ฆ๊ฐ ๋ ์ ์๋ ๊ฒ์ด๋ค.
๊ทธ๋ ๋ค๋ฉด 100์ดํ๋ก ์ชผ๊ฐ๋ ค๋ฉด ์ด๋ป๊ฒ ํด์ผํ ๊น?
์ฒญํฌ ํฌ๊ธฐ๋ฅผ ์๊ฒฉํ ์งํค๋ฉด์ ๊ตฌ๋ถ์ ์ํฅ ์์ด ์ชผ๊ฐ๋ ค๋ฉด separator๋ฅผ ๋น ๋ฌธ์์ด(””)๋ก ์ค์ ํ๊ฑฐ๋, ์ ๊ท์ ๋ฑ์ผ๋ก ๋ ์ธ๋ฐํ ๋ถ๋ฆฌ ๊ธฐ์ค์ ์ง์ ํ๋ฉด ๋๋ค. ๋๋ RecursiveCharacterTextSplitter ๊ฐ์ ๋๊ตฌ๋ฅผ ์จ์ ๋ฌธ๋จ ๋ด์์๋ ์ฌ๊ท์ ์ผ๋ก ์ชผ๊ฐค ์ ์๋ค.
์ ๋ฆฌํ๋ฉด, ์ฒญํฌ ์ฌ์ด์ฆ๋ ์ต๋ ํฌ๊ธฐ ๋ชฉํ์ผ ๋ฟ์ด๊ณ separator๋ฅผ ๊ธฐ์ค์ผ๋ก ์ฐ์ ๋ถํ ํ๊ธฐ ๋๋ฌธ์ ๊ตฌ๊ฐ์ด ๊ธธ๋ฉด ๊ทธ ๊ตฌ๊ฐ์ด ์ฒญํฌ ํฌ๊ธฐ๋ณด๋ค ์ปค์ง ์ ์๋ค.
from langchain_text_splitters import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
separators=["\n\n", "\n", " ", ""],
chunk_size=100,
chunk_overlap=20,
length_function=len,
is_separator_regex=False
)
texts = text_splitter.split_documents(docs)
RecursiveCharacterTextSplitter๋ฅผ ์ฌ์ฉํด์ ์ฌ๋ฌ ๋จ๊ณ์ ๊ตฌ๋ถ์(“\n\n”, “\n”, “ “, “”)๋ฅผ ์์ฐจ์ ์ผ๋ก ์ฌ์ฉํด ํ ์คํธ๋ฅผ ์ฌ๊ท์ ์ผ๋ก ๋ถํ ํ์๋ค.
- ์ฒ์์๋ ๊ฐ์ฅ ํฐ ๋จ์ ๊ตฌ๋ถ์์ธ ๋ ์ค๋ฐ๊ฟ(”\n\n”)์ผ๋ก ํ ์คํธ๋ฅผ ๋๋๊ณ , ๊ฐ ๋ถํ ์ด chunk_size(100)๋ฅผ ์ด๊ณผํ๋ฉด
- ๋ค์ ๋จ๊ณ ๊ตฌ๋ถ์์ธ ํ ์ค๋ฐ๊ฟ(”\n”) ๊ธฐ์ค์ผ๋ก ๋ค์ ์ชผ๊ฐ ๋ค.
- ์ฌ์ ํ ํฌ๋ฉด ๊ณต๋ฐฑ(” “)์ผ๋ก ๋ค์ ์ชผ๊ฐ ํ
- ๊ทธ๋๋ ํฌ๋ฉด ๋ง์ง๋ง์ผ๋ก ๋ถ๋ฆฌํ ์ ์๋ ์์ฃผ ์์ ๋จ์(””) ์ฆ ๋ฌธ์ ๋จ์๋ก ๋ถํ ํ๋ค.
๋ฐ๋ผ์ `RecursiveCharacterTextSplitter`๋ ํ ์คํธ๋ฅผ chunk_size ์ดํ๋ก ์๊ฒฉํ๊ฒ ์ชผ๊ฐ๋ฉด์ ๋ฌธ๋งฅ ๋จ์(๋ฌธ๋จ > ๋ฌธ์ฅ > ๋จ์ด > ๋ฌธ์)๋ฅผ ์ต๋ํ ์ ์งํ๋ ์ฌ๊ท์ ์ ๋ต์ผ๋ก ๋์ํ๊ธฐ ๋๋ฌธ์ ๊ฒฐ๊ณผ๋ฌผ์ด ๋ชจ๋ chunk_size ์ดํ๊ฐ ๋๋ค.
์๋ฏธ๋ก ๋๋๊ธฐ(semantic)
OpenAI์ ์๋ฒ ๋ฉ ๋ชจ๋ธ
news=[news์ ๋ํ ๋ด์ฉ]
openai_embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
embeddings = openai_embeddings.embed_documents(news)
embedded_query = openai_embeddings.embed_query("๋์ ํ๋ณด ํ ๋ก ")
embedded_query[:10]
- OpenAIEmbeddings: OpenAI์ text-embedding-3-small ๋ชจ๋ธ์ ์ฌ์ฉํด์ ๋ด์ค ๊ธฐ์ฌ์ ๊ฒ์ ์ฟผ๋ฆฌ๋ฅผ ์๋ฒ ๋ฉ ๋ฒกํฐ๋ก ๋ณํ
- embed_documents(news): ๋ฆฌ์คํธ ๋ด ๊ฐ ๋ด์ค ๊ธฐ์ฌ๋ฅผ ํ๋์ฉ ์๋ฒ ๋ฉํ์ฌ ๋ฒกํฐ๋ก ๋ณํ
- embed_query("๋์ ํ๋ณด ํ ๋ก "): ์ง์๋ ์๋ฒ ๋ฉ ๋ฒกํฐ๋ก ๋ณํ. "๋์ ํ๋ณด ํ ๋ก "=๋ด๊ฐ ์ง๋ฌธํ๊ณ ์ถ์ ๋ด์ฉ
HuggingFace ์๋ฒ ๋ฉ ๋ชจ๋ธ
from langchain_huggingface import HuggingFaceEmbeddings
news=[news์ ๋ํ ๋ด์ฉ]
hf_embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-m3")
embeddings = hf_embeddings.embed_documents(news)
embedded_query = hf_embeddings.embed_query("๋์ ํ๋ณด ํ ๋ก ")
embedded_query[:10]
- HuggingFaceEmbeddings: HuggingFace Hub์์ ์ง์ ํ ์ฌ์ ํ์ต ์๋ฒ ๋ฉ ๋ชจ๋ธ์ ์ด์ฉํด ํ ์คํธ๋ฅผ ๋ฒกํฐ๋ก ๋ณํํ๋ ๋ํผ ๊ฐ์ฒด
ํ ์คํธ ํ์ผ์์ ์ฌ๋ฌ ๋ด์ค ๊ธฐ์ฌ๋ฅผ ๋ถ๋ฌ์ ์ ๋นํ ํฌ๊ธฐ๋ก ์ฒญํนํ ๋ค, ๋ค๊ตญ์ด ์๋ฒ ๋ฉ ๋ชจ๋ธ๋ก ๋ฒกํฐํํ์ฌ ๋ฒกํฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค(Chroma)์ ์ ์ฅํ๊ณ , ์ง์์ ์ ์ฌํ ๋ฌธ์ ๊ฒ์์ ์ํํ๋ ์ ํ์ ์ธ RAG ๊ธฐ๋ฐ ๊ฒ์ ์์คํ ์ ๊ตฌํํด๋ณด์.
RAG ํ์ดํ๋ผ์ธ
ํ ์คํธ ํ์ผ → ์ฒญํฌ ๋ถํ → ์๋ฒ ๋ฉ → ๋ฒกํฐDB ์ธ๋ฑ์ฑ → ์์ฐ์ด ๋๋ ๋ฒกํฐ ๊ธฐ๋ฐ ์ ์ฌ๋ ๊ฒ์๊น์ง
๋ฌธ์ ๋ก๋ ๋ฐ ๋ถํ
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import CharacterTextSplitter
text_splitter = CharacterTextSplitter(
chunk_size=100,
chunk_overlap=20
)
loader = TextLoader("./docs/news.txt", encoding="utf-8")
documents = loader.load_and_split(text_splitter=text_splitter)
- TextLoader๊ฐ ๋ก์ปฌ ํ์ผ(`./docs/news.txt`)์์ ๋ด์ค ๊ธฐ์ฌ ํ ์คํธ๋ฅผ UTF-8๋ก ๋ถ๋ฌ์จ๋ค.
- CharacterTextSplitter()์ ๋ถ๋ฌ์จ ๊ธด ํ ์คํธ๋ฅผ 100์ ์ดํ, 20์ ์ค๋ณต์ผ๋ก ์ฒญํฌ ๋จ์๋ก ๋๋๊ณ ,
- loader.load_and_split(...)`๋ ์ด ๋ ๊ณผ์ ์ ๊ฒฐํฉํด, ์ฌ๋ฌ ๊ฐ์ ๋ถํ ๋ ์ฒญํฌ ํํ์ ๋ฌธ์ ๋ฆฌ์คํธ(documents)๋ฅผ ๋ง๋ ๋ค.
์๋ฒ ๋ฉ ๋ฒกํฐ ๋ณํ
from langchain_chroma import Chroma
from langchain_huggingface import HuggingFaceEmbeddings
model_name = "BAAI/bge-m3"
hf_embeddings = HuggingFaceEmbeddings(model_name=model_name)
db = Chroma.from_documents(documents, hf_embeddings)
- HuggingFace Hub์์ ์ง์ ํ ์ฌ์ ํ์ต ์๋ฒ ๋ฉ ๋ชจ๋ธ์ ์ฌ์ฉํด ๊ฐ ์ฒญํฌ ๋ฌธ์๋ฅผ ์๋ฏธ์ ์๋ฒ ๋ฉ ๋ฒกํฐ๋ก ๋ณํํ๊ณ ,
- ๋ฒกํฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ตฌ์ถ
- Chroma.from_documents(documents, hf_embeddings)๋ ์ ์๋ฒ ๋ฉ ๋ฒกํฐ๋ค์ Chroma ๋ฒกํฐDB์ ์ ์ฅํ์ฌ, ๋ด์ฉ ๊ธฐ๋ฐ ๊ฒ์์ด ๊ฐ๋ฅํ ์ธ๋ฑ์ค๋ฅผ ์์ฑํ๋ค.
1. ํ ์คํธ ์ง์ ์ง์(๋ฌธ์์ด → ๋ด๋ถ ์๋ฒ ๋ฉ → ๊ฒ์)
result = db.similarity_search("๋์ ํ๋ณด ํ ๋ก ")
- db.similarity_search("๋์ ํ๋ณด ํ ๋ก "): ์ง์๋ฅผ ์๋ฒ ๋ฉํ๊ณ , DB ๋ด์์ ๊ฐ์ฅ ์ ์ฌํ ๋ฌธ์ ์ฒญํฌ๋ค์ ๊ฒ์ํด ๋ฐํ
- ์ฌ๊ธฐ์ ์๋ฒ ๋ฉ ๋ชจ๋ธ์ Chroma ๊ฐ์ฒด ์์ฑ ์ ์ ๋ฌํ HuggingFace์๋ฒ ๋ฉ์ด ๊ทธ๋๋ก ์ฐ์ธ๋ค.
2. ์๋ฒ ๋ฉ ๋ฒกํฐ๋ก ์ง์ ์ง์(๋ฌธ์์ด → ์๋ฒ ๋ฉ (์ง์ ) → ๋ฒกํฐDB์ ๋ฒกํฐ ์ ๋ฌ → ๊ฒ์)
embedded_query = hf_embeddings.embed_query("๋์ ํ๋ณด ํ ๋ก ")
result = db.similarity_search_by_vector(embedded_query)
- ์๋ฒ ๋ฉ์ ์ด๋ฏธ ๋ฐ๋ก ๋ง๋ ๊ฒฝ์ฐ๋, ๋ฒกํฐ ์ฐ์ฐ์ ์ง์ ๋ค๋ฃจ๊ณ ์ ํ ๋ ์ฐ๋ ๋ฐฉ๋ฒ
- ํด๋น ์๋ฒ ๋ฉ ๋ฒกํฐ์ ๋ฒกํฐDB ๋ด ๋ชจ๋ ๋ฌธ์์ ์ฝ์ฌ์ธ ์ ์ฌ๋๋ฅผ ๊ณ์ฐํด, ์๋ฏธ์ ์ผ๋ก ๊ฐ์ฅ ๊ฐ๊น์ด ๋ฌธ์๋ค์ ๋ฐํ
vectordb๋ฅผ ์ ์ ์ฅ(persist)ํด์ ์ฐ๋๊ฐ?
- ๋ฐ์ดํฐ ์ ์ง: ์์คํ ์ด ๊บผ์ง๊ฑฐ๋ ์ฌ์์ํด๋ ์ด๋ฏธ ์๋ฒ ๋ฉํ ๋ฌธ์ ๋ฒกํฐ๋ค์ ์ฌ์์ฑํ ํ์ ์์ด ์ ์ฅ๋ ์ํ๋ฅผ ์ ์ง๊ฐ๋ฅ
- ์ฑ๋ฅ ํฅ์: ๋งค๋ฒ ๋ฌธ์๋ฅผ ์๋ฒ ๋ฉํ์ง ์๊ณ , ์ ์ฅ๋ ๋ฒกํฐ๋ฅผ ๋ฐ๋ก ๋ถ๋ฌ์์ ๋น ๋ฅด๊ฒ ๊ฒ์ ๊ฐ๋ฅ, ํ์ฅ์ฑ๊ณผ ์๋ต์๋
- ์ฌ์ฌ์ฉ ๋ฐ ์ด์์ฑ: ์ ์ฅ๋ DB๋ฅผ ์ฌ๋ฌ ์๋น์ค๋ ์๋ฒ์์ ๊ณต์ ๋ฐ ์ฌ์ฌ์ฉ ๊ฐ๋ฅ
db = Chroma.from_documents(documents, hf_embeddings, persist_directory="./news_chroma_db")
result = db.similarity_search("๋์ ํ๋ณด ํ ๋ก ", k=1)
- ๋ถํ ๋ ๋ฌธ์๋ค์ ์๋ฒ ๋ฉ ๋ฒกํฐ๋ก ๋ณํ ํ, ํด๋์ ์ ์ฅํ๋ฉฐ Chroma ๋ฒกํฐ DB๋ฅผ ์์ฑ
- ์ฟผ๋ฆฌ๋ฅผ ์๋ฒ ๋ฉ ๋ฒกํฐ๋ก ๋ฐ๊ฟ DB์์ ๊ฐ์ฅ ์ ์ฌํ ๋ฌธ์ ์ฒญํฌ 1๊ฐ๋ฅผ ์ฐพ์ ๋ฐํ
- ์ฆ, ํ ์คํธ ํ์ผ์ ์์ ์กฐ๊ฐ์ผ๋ก ๋๋ ์ ์๋ฒ ๋ฉ ๋ฒกํฐ๋ฅผ ๋ง๋ค์ด ๋ฒกํฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅํ๊ณ ,
- ์ ์ฅ๋ ๋ฒกํฐ๋ฅผ ๊บผ๋ด์ ์ฟผ๋ฆฌ์ ์๋ฏธ์ ๊ฐ์ฅ ๊ฐ๊น์ด ๋ฌธ์๋ค์ ๋น ๋ฅด๊ฒ ์ฐพ๋ ์์คํ ์ ๋ง๋๋ ๊ฒ
1.similarity_search(query_str, k=...)
ํ
์คํธ ์ฟผ๋ฆฌ๋ฅผ ๋ฐ์ ์๋ฒ ๋ฉ์ผ๋ก ๋ณํ ํ, ๊ฐ์ฅ ์ ์ฌํ k๊ฐ ๋ฌธ์๋ฅผ ๋ฐํ
๋ฐํ๊ฐ์ ๋ฌธ์ ๋ฆฌ์คํธ
2.similarity_search_with_score(query_str, k=...)
ํ
์คํธ ์ฟผ๋ฆฌ๋ฅผ ์๋ฒ ๋ฉ ํ ์ ์ฌ๋์ ํจ๊ป ๋ฐํ
๋ฐํ๊ฐ์ ํํ ๋ฆฌ์คํธ์ด๋ฉฐ, ์ ์๋ก ๊ฒฐ๊ณผ ์ ๋ขฐ๋๋ฅผ ํ์
3.similarity_search_with_relevance_scores(query_str, k=...)
์ ์๋ฅผ relevance score (์ฐ๊ด๋ ์ ์) ํํ๋ก ์ ๊ณต
์ด๋ฒ์ ๋ค๋ฅธ ๋ฒกํฐ DB์ธ FAISS๋ฅผ ์ฌ์ฉํด๋ณด์
import faiss
from langchain_community.vectorstores import FAISS
from langchain_community.docstore.in_memory import InMemoryDocstore
from langchain_openai import OpenAIEmbeddings
# 1. ์๋ฒ ๋ฉ ์ด๊ธฐํ
embeddings = OpenAIEmbeddings()
# 2. ์๋ฒ ๋ฉ ๋ฒกํฐ ์ฐจ์ ํ์ธ
dimension = len(embeddings.embed_query("hello world"))
# 3. FAISS ์ธ๋ฑ์ค ์์ฑ (์: L2 ๊ฑฐ๋ฆฌ ๊ธฐ์ค Flat ์ธ๋ฑ์ค)
index = faiss.IndexFlatL2(dimension)
# 4. FAISS ๋ฒกํฐ ์ ์ฅ์ ์ด๊ธฐํ
vector_store = FAISS(
embedding_function=embeddings,
index=index,
docstore=InMemoryDocstore(),
index_to_docstore_id={}
)
# 5. ๋ฌธ์ ๋ฆฌ์คํธ๋ฅผ ๋ฒกํฐํ ํ ์ ์ฅ
texts = ["๋ฌธ์1 ๋ด์ฉ", "๋ฌธ์2 ๋ด์ฉ", "๋ฌธ์3 ๋ด์ฉ"]
vector_store.add_texts(texts)
# 6. ์ฟผ๋ฆฌ ์๋ฒ ๋ฉ ํ ์ ์ฌ ๋ฌธ์ ๊ฒ์
query = "์ ์ฌ ๋ฌธ์ ์ฐพ๊ธฐ"
results = vector_store.similarity_search(query, k=3)
for res in results:
print(res.page_content)
- FAISS๋ ๊ณ ์ฑ๋ฅ ๋ฒกํฐ ์ ์ฌ๋ ๊ฒ์์ ์ต์ ํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก, ๋๊ท๋ชจ ๋ฐ์ดํฐ์ ๊ณผ ๋น ๋ฅธ ๊ฒ์์ ๊ฐ์
- Chroma๊ฐ ๋ฒกํฐ DB์ ์๋ฒ ๋ฉ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ก์ ๋ ์ฌ์ฉ์ ์นํ์ ์ธ DB ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ ๋ฐ๋ฉด, FAISS๋ ๊ณ ์ ์ธ๋ฑ์ฑ๊ณผ GPU ๊ฐ์ ๊ธฐ๋ฅ์ ์ ๊ณต
- FAISS๋ ๋ฒกํฐ ์ธ๋ฑ์ค๋ฅผ ๋ณ๋๋ก ์์ฑํ๋ฉฐ, L2 ๊ฑฐ๋ฆฌ๋ ์ฝ์ฌ์ธ ์ ์ฌ๋ ๋ฑ ๋ค์ํ ๊ฑฐ๋ฆฌ ์ธก์ ์ ์ง์
- Chroma๋ ๋ฒกํฐ DB๋ก ๋ณผ ์ ์๋ค. ์๋ฒ ๋ฉ ๋ฒกํฐ๋ฟ ์๋๋ผ ๋ฌธ์, ๋ฉํ๋ฐ์ดํฐ ๋ฑ์ ํจ๊ป ์ ์ฅํ๊ณ ๊ด๋ฆฌํ๋ ๊ธฐ๋ฅ์ ๊ฐ์ท์ผ๋ฉฐ, ๋์คํฌ ๊ธฐ๋ฐ ์์ ์ ์ฅ์ ๊ธฐ๋ณธ ์ง์ํฉ๋๋ค. ๊ฒ์๊ณผ ์ ์ฅ์ ์ํ DB ๊ธฐ๋ฅ์ด ํฌํจ๋ ์์ ํ ์๋ฃจ์ ์ ๋๋ค.
- FAISS๋ ๋ฒกํฐ ๊ฒ์์ฉ ๋ผ์ด๋ธ๋ฌ๋ฆฌ. ๊ณ ์ฑ๋ฅ ๋ฒกํฐ ์ธ๋ฑ์ค ์์ฑ๊ณผ ์ ์ฌ๋ ๊ฒ์์ ํนํ๋์ด ์์ผ๋, ์์ฒด์ ์ผ๋ก DB ์ ์ฅ์ ์ ๊ณตํ์ง ์์ผ๋ฏ๋ก, ํ์ํ ๊ฒฝ์ฐ ๋ณ๋ ์ ์ฅ์ ์ค์ ๋ฐ ์ธ๋ฑ์ค ํ์ผ ์ ์ฅ/๋ก๋๋ฅผ ์ง์ ๊ตฌํํด์ผ ํ๋ค.
Retriever
from langchain_chroma import Chroma
db = Chroma(persist_directory="./news_chroma_db", embedding_function=hf_embeddings)
retriever = db.as_retriever(
search_type="similarity_score_threshold",
search_kwargs={"score_threshold": 0.25}
)
result = retriever.invoke("๋์ ํ๋ณด ํ ๋ก ")
result
- retriever๋ ๋ฒกํฐDB์์ ์ฃผ์ด์ง ์ฟผ๋ฆฌ์ ๊ฐ์ฅ ๊ด๋ จ์ฑ ๋์ ๋ฌธ์๋ฅผ ์ฐพ์ ๋ฐํํ๋ ์ญํ ์ ํ๋ ๊ฒ์๊ธฐ ์ธํฐํ์ด์ค
- LangChain์์ db.as_retriever() ๋ฉ์๋๋ฅผ ํธ์ถํ๋ฉด ๋ฒกํฐ ์ ์ฅ์๋ฅผ ๊ธฐ๋ฐ์ผ๋ก Retriever ๊ฐ์ฒด๊ฐ ์์ฑ๋๋ฉฐ, ์ด Retriever๋ ๊ฒ์ ๋ฐฉ์(์ ์ฌ๋, ์ ์ ์๊ณ๊ฐ ๊ธฐ๋ฐ, MMR ๋ฑ)์ ์ ํํ ์ ์๋ค.
- retriever.invoke()์ ํธ์ถํ๋ฉด, ๋ด๋ถ์ ์ผ๋ก ๋ฒกํฐ ์๋ฒ ๋ฉ์ ํตํด ๊ด๋ จ ๋ฌธ์๋ฅผ ๋ฒกํฐ ๊ฒ์ ํ ์ ์ฌํ ๋ฌธ์ ๋ฆฌ์คํธ๋ฅผ ๋ฐํํ๋ค.
- Retriever๋ ์์ฐ์ด ์ฟผ๋ฆฌ์ ๋ํด ์๋ฏธ์ ์ผ๋ก ์ฐ๊ด์ฑ ๋์ ๋ฌธ์๋ฅผ ๋น ๋ฅด๊ฒ ์ฐพ๋ ์ญํ ์ ์ง์คํ๋ค.
- ๋ฐ๋ผ์ retriever๋ ๋ฒกํฐ ์คํ ์ด์์ ๊ฐ์ฅ ๊ด๋ จ ์๋ ์ ๋ณด๋ฅผ "๊ฒ์ํ๋ ๊ฒ์๊ธฐ"๋ผ๊ณ ์๊ฐํ๋ฉด ๋๋ค.
MultiQueryRetriever
from langchain.retrievers.multi_query import MultiQueryRetriever
retriever = MultiQueryRetriever.from_llm(retriever=db.as_retriever(), llm=llm)
retriever.invoke("๋์ ํ๋ณด ํ ๋ก ")
- LangChain์์ ์ ๊ณตํ๋ ๊ณ ๊ธ ๊ฒ์๊ธฐ, ์ฌ์ฉ์๊ฐ ์ ๋ ฅํ ๋จ์ผ ์ฟผ๋ฆฌ๋ฅผ LLM์ ํตํด ์ฌ๋ฌ ๊ฐ์ ์๋ฏธ๊ฐ ๊ฒน์น๋ฉด์๋ ๋ค์ํ ๊ด์ ์ ์ฟผ๋ฆฌ๋ก ์๋ ๋ณํ.
- ์ด๋ ๊ฒ ์์ฑ๋ ์ฌ๋ฌ ์ฟผ๋ฆฌ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋ฒกํฐ ์ ์ฅ์์์ ๊ฐ๊ฐ ๊ฒ์์ ์ํํ ํ, ๊ฒฐ๊ณผ๋ฅผ ํตํฉํด ๋ ํ๋ถํ๊ณ ์ ํํ ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ์ ๊ณต
- LLM์ด ์ฟผ๋ฆฌ๋ฅผ ํจ๋ฌํ๋ ์ด์ง(paraphrasing)ํ์ฌ ๋ค์ํ ๊ฐ๋์์ ๋ฌธ์ ๊ฒ์ ๊ฐ๋ฅ -> ๋จ์ผ ์ง๋ฌธ์ ๋ชจํธํจ์ด๋ ์๋ฏธ์ ๋ถ์กฑํจ์ ์ฌ๋ฌ ์ฟผ๋ฆฌ๋ก ๋ณด์ํด ๊ฒ์ ์ฑ๋ฅ์ ๋์ผ ์ ์๋ค.
- ๊ฐ ์ฟผ๋ฆฌ์ ๋ํ ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ์ค๋ณต ์ ๊ฑฐํ๋ฉฐ ํตํฉํ์ฌ ๋ฆฌํด
์ฝ๋๋ฅผ ๋ณด๋ฉด MultiQueryRetriever.from_llm() ๋ฉ์๋๋ ๊ธฐ์กด retriever์ LLM์ ๊ฒฐํฉํด MultiQueryRetriever ์ธ์คํด์ค๋ฅผ ์์ฑํ๊ณ , retriever.invoke() ๋๋ get_relevant_documents()๋ก ์ฌ๋ฌ ์ฟผ๋ฆฌ ๊ธฐ๋ฐ ๊ฒ์์ ์คํ
์ด ์ ๋ฆฌ ํด๋ณด์
์ ์ฒด ๊ตฌ์กฐ ๊ฐ์
- ๋ฌธ์ ๋ก๋ ๋ฐ ๋ถํ → 2. ์๋ฒ ๋ฉ ์์ฑ ๋ฐ ๋ฒกํฐ ์ ์ฅ → 3. ๊ฒ์๊ธฐ ์ค์ → 4. ํ๋กฌํํธ ํ ํ๋ฆฟ → 5. RAG ์ฒด์ธ ๊ตฌ์ฑ → 6. ์ง๋ฌธ ์ฒ๋ฆฌ
1. ํ๊ฒฝ ์ค์ ๋ฐ ๋ชจ๋ธ ์ด๊ธฐํ
from dotenv import load_dotenv
load_dotenv()
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o-mini")
2. ์๋ฒ ๋ฉ ๋ชจ๋ธ ์ค์
from langchain_huggingface import HuggingFaceEmbeddings
model_name = "BAAI/bge-m3"
hf_embeddings = HuggingFaceEmbeddings(model_name=model_name)
- ํ ์คํธ๋ฅผ ๋ฒกํฐ๋ก ๋ณํํ์ฌ ์ ์ฌ๋ ๊ฒ์ ๊ฐ๋ฅํ๊ฒ ํจ
3. ๋ฌธ์ ๋ก๋ ๋ฐ ๋ถํ
text_splitter = CharacterTextSplitter(
separator="\n",
chunk_size=0, # ์ค๋ฐ๊ฟ ๊ธฐ์ค์ผ๋ก๋ง ๋ถํ
chunk_overlap=0
)
loader = TextLoader("./docs/travel.txt", encoding="utf-8")
documents = loader.load_and_split(text_splitter=text_splitter)
- travel.txt ํ์ผ์ ์ค๋ฐ๊ฟ(\n) ๊ธฐ์ค์ผ๋ก ๋ถํ
- chunk_size=0์ด๋ฏ๋ก ๊ฐ ์ค์ด ํ๋์ ๋ฌธ์ ์ฒญํฌ๊ฐ ๋จ
4. ๋ฒกํฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ตฌ์ถ
from langchain_chroma import Chroma
db = Chroma.from_documents(documents, hf_embeddings)
- ๊ฐ ๋ฌธ์๋ฅผ ์๋ฒ ๋ฉ์ผ๋ก ๋ณํํ์ฌ ์ ์ฅ
5. ๊ฒ์๊ธฐ ์ค์
retriever = db.as_retriever(
search_type="mmr",
search_kwargs={"k": 5, "fetch_k": 20, "lambda_mult": 0.5}
)
- MMR (Maximal Marginal Relevance): ๊ด๋ จ์ฑ๊ณผ ๋ค์์ฑ์ ๊ท ํ์ ๋ง์ถ ๊ฒ์
- k=5: ์ต์ข 5๊ฐ ๋ฌธ์ ๋ฐํ
- fetch_k=20: ๋จผ์ 20๊ฐ ํ๋ณด ๋ฌธ์๋ฅผ ๊ฐ์ ธ์จ ํ MMR๋ก 5๊ฐ ์ ๋ณ
- lambda_mult=0.5: ๊ด๋ จ์ฑ(0.5)๊ณผ ๋ค์์ฑ(0.5)์ ๊ท ํ
6. ํ๋กฌํํธ ํ ํ๋ฆฟ
prompt_template = hub.pull("langchain-ai/retrieval-qa-chat")
- LangChain Hub์์ ๊ฒ์ฆ๋ RAG์ฉ ํ๋กฌํํธ ํ ํ๋ฆฟ ์ฌ์ฉ
- ๊ฒ์๋ ๋ฌธ์๋ฅผ ์ปจํ ์คํธ๋ก ํ์ฉํ์ฌ ๋ต๋ณ ์์ฑํ๋๋ก ์ค๊ณ๋จ
7. ์ถ๋ ฅ ํ์ ์ค์
from langchain_core.output_parsers import StrOutputParser
parser = StrOutputParser()
LLM ์ถ๋ ฅ์ ๋ฌธ์์ด๋ก ๋ณํํด์ฃผ๋ ๋๊ตฌ
8. RAG ์ฒด์ธ ๊ตฌ์ฑ
rag_chain = {
"context": retriever, # ๊ฒ์๊ธฐ์์ ๊ด๋ จ ๋ฌธ์ ๊ฐ์ ธ์ด
"input": RunnablePassthrough() # ์ฌ์ฉ์ ์ง๋ฌธ ๊ทธ๋๋ก ์ ๋ฌ
} | prompt_template | llm | parser
์ฒด์ธ ์คํ ํ๋ฆ:
- ์ฌ์ฉ์ ์ง๋ฌธ → retriever๊ฐ ๊ด๋ จ ๋ฌธ์ ๊ฒ์
- ๊ฒ์๋ ๋ฌธ์(context)์ ์ง๋ฌธ(input) → prompt_template์ ์ฃผ์
- ์์ฑ๋ ํ๋กฌํํธ → llm(GPT-4o-mini)์์ ๋ต๋ณ ์์ฑ
- ๋ต๋ณ → parser๋ก ๋ฌธ์์ด ํํ๋ก ํ์ฑ
9. ์คํ
rag_chain.invoke("๋์ ํ๋ณด ํ ๋ก ")
- ์ง๋ฌธ์ ์ ๋ ฅํ๋ฉด travel.txt์์ ๊ด๋ จ ๋ด์ฉ์ ๊ฒ์
- ๊ฒ์๋ ์ ๋ณด๋ฅผ ๋ฐํ์ผ๋ก ์ด์์ ์ธ ์์์ ๋ํด ๋ต๋ณ
- ๋ฌธ์์ ์๋ ๋ด์ฉ๋ง ์ฐธ์กฐํ์ฌ ๋ต๋ณํ๊ธฐ์ ์ ํ์ฑ ๋ณด์ฅ ๊ฐ๋ฅ
์ถ๊ฐ๋ก ๋์ปค ๋ฐ์ธ๋ฉ ๋ถ๋ถ ์ ๋ฆฌ
CMD["--server.address=0.0.0.0"]
- ๋คํธ์ํฌ์์ ๋ชจ๋ ์ธํฐํ์ด์ค์์ ๋ค์ด์ค๋ ์์ฒญ์ ํ์ฉํ๋๋ก ์ค์ ํ๋ ๊ฒ
- ์๋ฒ๊ฐ 127.0.0.1(localhost)๋ก ๋ฐ์ธ๋ฉ๋๋ฉด, ํด๋น ์ปดํจํฐ ๋ด๋ถ์์๋ง(๋ก์ปฌ ์ ์๋ง) ์ ์ ๊ฐ๋ฅ
- 0.0.0.0์ผ๋ก ๋ฐ์ธ๋ฉํ๋ฉด, ์๋ฒ๊ฐ ์์ ์ ๋ชจ๋ IP ์ฃผ์(๋ด๋ถ ๋คํธ์ํฌ, ์ธ๋ถ ๋คํธ์ํฌ ํฌํจ)์์ ๋ค์ด์ค๋ ์์ฒญ์ ๋ฐ๊ฒ ๋ฉ๋๋ค.
- ์ฆ, ๋ค๋ฅธ ์ปดํจํฐ๋ ๋คํธ์ํฌ ์ฅ๋น์์๋ ํด๋น ์๋ฒ์ ์ ์ ๊ฐ๋ฅํ๋๋ก ํ์ฉํ๋ ์ค์
- --server.address=127.0.0.1
- ๋ด ์ปดํจํฐ์์๋ง ์ ์ ๊ฐ๋ฅ. ๋์ปค ์ปจํ ์ด๋ ํธ์คํธ์์๋ ์ ์ ๋ถ๊ฐ
- --server.address=0.0.0.0
- ๋ชจ๋ ๋คํธ์ํฌ ์ธํฐํ์ด์ค์์ ์ ์ ๊ฐ๋ฅ. ๋์ปค ์ปจํ ์ด๋ ์ธ๋ถ ํธ์คํธ์์ ์ ๊ทผ ๊ฐ๋ฅ
์ค์ต
๋ด๊ฐ ์ข์ํ๋ ๋ฆฌ๋ฒํ์ ๋ํ ์ํคํผ๋์๋ก ๋ฒกํฐ DB๋ฅผ ๊ตฌ์ฑํ๊ณ , ์ง๋ฌธ์ ํด๋ดค๋ค.
๋ด๊ฐ ์ ์๋ ์ฃผ์ ๋ผ ๋ต๋ณ์ด ์ ํํ์ง ์๊ธฐ ์ฌ์์ '๋ฆฌ๋ฒํ'๋ก ์ฃผ์ ๋ฅผ ์ก์๋ดค๋๋ฐ, ์ฑ๋ฅ์ด ๊ฝค ๊ด์ฐฎ๋ค.

https://github.com/seongjju/liverpool-fc-rag
'๐ฆญ AI&Big Data' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| LLM (0) | 2025.09.11 |
|---|---|
| ๋ญ์ฒด์ธ (0) | 2025.09.09 |