์ง๋ ํฌ์คํ ์์๋ Model I/O, LCEL, Prompt, Memory ๋ฑ์ ๋ํด ์ ๋ฆฌํ์๋ค. ์ด๋ฒ์๋ RAG ๋ฐฉ์์ ํตํด ์ธ๋ถ ๋ฌธ์๋ฅผ ์ ๊ณตํ๊ณ , ์ด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ ๋ณด๋ฅผ ์ฐพ์๋ผ ์ ์๋ ์ฑํ ๋ชจ๋ธ์ ๋ง๋ค์ด ๋ณธ๋ค.
RAG(Retrieval-Augmented Generation, ๊ฒ์ ์ฆ๊ฐ ์์ฑ)๋ AI๋ชจ๋ธ์ ์ ํ์ฑ๊ณผ ์ ๋ขฐ์ฑ์ ํฅ์์ํค๊ธฐ ์ํด Pre-training(์ฌ์ ํ์ต) ๋ฐ์ดํฐ ์ด์ธ์ ์ธ๋ถ ๋ฌธ์๋ฅผ ์ฐธ์กฐํ๋๋ก ํ๋ ๊ธฐ์ ์ ์๋ฏธํ๋ค. ์ด๋ฅผ ํตํด AI๋ ์ต์ ์ ๋ณด๋ ์ฌ์ฉ์ ๋ง์ถค์ ์ ํํ ์ ๋ณด๋ฅผ ์ ๊ณตํ ์ ์๊ฒ ๋๋ค.
LangChain์์๋ ํฌ๊ฒ ๋ค์๊ณผ ๊ฐ์ ๊ณผ์ ์ ํตํด RAG๋ฅผ ๊ตฌํํ ์ ์๋ค.
์๋ณธ ๋ฐ์ดํฐ -> ์ ์ฌ(load) -> ๋ถํ (split) -> ์๋ฒ ๋ฉ -> ๋ฒกํฐ ๊ณต๊ฐ ์ ์ฅ(vector store & retriever)
from langchain.chat_models import ChatOpenAI
from langchain.document_loaders import UnstructuredFileLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain.embeddings import OpenAIEmbeddings, CacheBackedEmbeddings
from langchain.vectorstores import FAISS
from langchain.storage import LocalFileStore
from langchain.prompts import ChatPromptTemplate
from langchain.schema.runnable import RunnablePassthrough, RunnableLambda
llm = ChatOpenAI(
temperature=0.1,
)
cache_dir = LocalFileStore("./.cache/practice/")
splitter = CharacterTextSplitter.from_tiktoken_encoder(
separator="\n",
chunk_size=600,
chunk_overlap=100,
)
loader = UnstructuredFileLoader("./files/์ด์ ์ข์ ๋ .txt")
docs = loader.load_and_split(text_splitter=splitter)
embeddings = OpenAIEmbeddings()
cached_embeddings = CacheBackedEmbeddings.from_bytes_store(embeddings, cache_dir)
vectorstore = FAISS.from_documents(docs, cached_embeddings)
retriever = vectorstore.as_retriever()
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"""
You are a helpful assistant.
Answer questions using only the following context.
If you don't know the answer just say you don't know, don't make it up:
\n\n
{context}",
"""
),
("human", "{question}"),
]
)
chain = (
{
"context": retriever,
"question": RunnablePassthrough(),
}
| prompt
| llm
)
result = chain.invoke("๊น์ฒจ์ง๋ ํ์์ ์ด๋๋ก ๋ฐ๋ ค๋ค ์ฃผ์๋?")
print(result)
content='๊น์ฒจ์ง๋ ํ์์ ๋จ๋๋ฌธ ์ ๊ฑฐ์ฅ๊น์ง ๋ฐ๋ ค๋ค ์ฃผ์์ต๋๋ค.'
LLM์ ๋ฌธ์๋ฅผ ์ ๊ณตํ ๋ ๋ฌธ์ ์ ๋ฌธ(ๅ จๆ)์ LLM์ ์ ๋ฌํ๋ค๋ฉด ํ๋กฌํํธ๊ฐ ๊ณผ๋ํ๊ฒ ๊ธธ์ด์ง ์ ์๋ค. ์ด๋ก ์ธํด ์ ํ๋๊ฐ ๋จ์ด์ง๊ณ ๋ต๋ณ์ด ๋๋ฆฌ๋ฉฐ ๋น์ฉ ์๋ชจ๊ฐ ๋ง์์ง๊ฒ ๋๋ค. ๋ฐ๋ผ์ ๋ฌธ์๋ฅผ ๋ถํ ํ์ฌ ํ์ํ ๋ถ๋ถ๋ง ์ ๋ฌํ ํ์๊ฐ ์๋ค. ๋จ, ๋ฌธ๋งฅ์ด๋ ์๋ฏธ๋ฅผ ์์ํ ๋งํผ ์์ ๋จ์์ ๋ถํ ์ ์ข์ง ์๊ธฐ์ ์ ์ ํ ํฌ๊ธฐ๋ก ๋ถํ ํ๋ ๊ฒ์ด ์ค์ํ๋ค.
splitter = CharacterTextSplitter.from_tiktoken_encoder( separator="\n", chunk_size=600, chunk_overlap=100, ) loader = UnstructuredFileLoader("./files/์ด์ ์ข์ ๋ .txt") docs = loader.load_and_split(text_splitter=splitter)
๋ฌธ์๋ฅผ ๋ถํ ํ ๋ ์ฌ์ฉ๋ splitter์ ์ ์ธํ๋ค. LangChain์์ ์ ๊ณตํ๋ splitter์ ์ข ๋ฅ์๋ Recursive, HTML, Markdown ๋ฑ ์ฌ๋ฌ๊ฐ์ง๊ฐ ์์ง๋ง ์ฌ๊ธฐ์๋ Character๋ฅผ ์ฌ์ฉํ๋ค. CharacterTextSplitter๋ ์ฌ์ฉ์๊ฐ ์ง์ ํ ๋ฌธ์๋ฅผ ๊ธฐ์ค์ผ๋ก ๋ฌธ์๋ฅผ ๋ถํ ํ๋ค.
ํ์ผ ๋ก๋ฉ์ ์ฌ์ฉ๋ Loader๋ฅผ ์ ์ธํ๋ค. LangChain์ TextLoader, PyPDFLoader ๋ฑ ๋ค์ํ ํ์ผ ํ์์ ๋ง์ถ Loader๋ฅผ ์ ๊ณตํ๋ค. UnstructuredFileLoader๋ text files, powerpoints, html, pdfs, images๋ฑ ์ฌ๋ฌ ํ์์ ํ์ผ์ ์ง์ํ๊ธฐ์ ํธ๋ฆฌํ๋ค. ๋ฌธ์๊ฐ ์ ์ฅ๋์ด ์๋ ๊ฒฝ๋ก๋ฅผ ์ ๋ ฅํ๋ค.
ํ์ผ์ ๋ก๋ฉํ๋ ๋์์ ๋ถํ ์ ์งํํ๋ค. splitter๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ์ ๋ฌํ๋ฉฐ, ๋ถํ ๋ ๋ฌธ์๋ฅผ ๋ฐํํ๋ค. (return type: list)
Embedding์ด๋ ์ฌ๋์ด ์ฝ๋ ์์ฐ์ด๋ฅผ ์ปดํจํฐ๊ฐ ์ดํดํ ์ ์๋ ์ซ์์ ๋์ด์ธ vector๋ก ๋ณํํ๋ ์์ ์ ์๋ฏธํ๋ค. ๊ฐ๋จํ ์ค๋ช ํ์๋ฉด, text์ ์๋ฏธ๋ณ๋ก ์ ์ ํ ์ ์๋ฅผ ๋ถ์ฌํ๋ ๋ฐฉ์์ผ๋ก ์งํ๋๋ค.
Embedding์ ํตํด ๋ณํ๋ ๋ฌธ์๋ vector store(๋ฒกํฐ ๊ณต๊ฐ)์ ์ ์ฅ๋๋ค. ์ฌ์ฉ์๊ฐ ์ฟผ๋ฆฌ๋ฅผ ์ ๋ ฅํ๋ฉด Retriever๋ ์ฟผ๋ฆฌ์ ์ฐ๊ด์ฑ์ด ๋์ ๋ฌธ์๋ค์ vector store์์ ์ฐพ์์ค๊ณ , ์ด ๋ฌธ์๋ค์ LLM์ ์ ๋ฌํ ํ๋กฌํํธ์ ํฌํจํจ์ผ๋ก์จ ์ ํ๋๊ฐ ๋์ ๋ต๋ณ์ ๊ธฐ๋ํ ์ ์๋ค.
embeddings = OpenAIEmbeddings() cache_dir = LocalFileStore("./.cache/practice/") cached_embeddings = CacheBackedEmbeddings.from_bytes_store(embeddings, cache_dir)
OpenAI๋ Embedding ์๋น์ค๋ฅผ ์ ๊ณตํ๋ค.
Embedding์ ๋น์ฉ์ด ๋ถ๊ณผ๋๋ ์๋น์ค์ด๋ค. ๊ฐ์ ๋ฌธ์๋ฅผ ๋งค๋ฒ ์๋ฒ ๋ฉํ๋ ๊ฒ์ ํจ์จ์ ์ด์ง ์๊ธฐ์, Cache Memory๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ํจ๊ณผ์ ์ด๋ค.
Embedding ๊ฐ์ฒด์ ์บ์๊ฐ ์ ์ฅ๋์ด ์๋ ์์น๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ์ ๋ฌํ๋ค. ์ถํ embedding ๊ฐ์ฒด๊ฐ ํธ์ถ๋ ์ผ์ด ์์ ๋ cached_embeddings๋ฅผ ์ฌ์ฉํ๋ค.
์ด ๋ ๋ง์ฝ ์ด๋ฏธ ์บ์๋์ด ์๋ค๋ฉด ์ ์ฅ๋ ์บ์๋ฅผ ์ฌ์ฉํ๊ณ , ๊ทธ๋ ์ง ์๋ค๋ฉด embedding์ ์งํํ์ฌ ์บ์๋ฅผ ์์ฑํ๋ค.
vectorstore = FAISS.from_documents(docs, cached_embeddings) retriever = vectorstore.as_retriever()
LangChain์ ์ฌ๋ฌ vector sotre๋ฅผ ์ ๊ณตํ๋ค. ์ฌ๊ธฐ์๋ ๋ก์ปฌ์์ ์ง์ ์คํ์ด ๊ฐ๋ฅํ ๋ฌด๋ฃ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ธ FAISS๋ฅผ ์ฌ์ฉํ์๋ค. ๋น์ทํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก Chroma๋ ์ฌ์ฉํด ๋ณผ ์ ์๋ค.
Retriever๋ ์ฌ์ฉ์๋ก๋ถํฐ ๋ฐ์ Query๋ฅผ ํด์ํ์ฌ ๊ทธ์ ๊ด๋ จ์ด ์๋ ๋ฌธ์๋ฅผ ์ฐพ์์์(retrieve) ๋ฐํํ๋ ์ธํฐํ์ด์ค์ด๋ค. ์ฝ๋์์๋ vector store๋ฅผ retriever๋ก ์ฌ์ฉํ์์ง๋ง ๋ฐ์ดํฐ๋ฒ ์ด์ค, ํด๋ผ์ฐ๋ ๋ฑ ์ฌ๋ฌ ์ ํ์ ์ ์ฅ์๋ฅผ retriever๋ก ์ฌ์ฉํ ์๋ ์๋ค.
(ex: retriever = WikipediaRetriever(top_k_results=5)
)
prompt = ChatPromptTemplate.from_messages( [ ( "system", """ You are a helpful assistant. Answer questions using only the following context. If you don't know the answer just say you don't know, don't make it up: \n\n {context} """, ), ("human", "{question}"), ] )
LLM์ ์ ๋ฌ๋ ํ๋กฌํํธ๋ฅผ ์์ฑํ๋ค. LLM์ hallucination(ํ๊ฐ) ํ์์ ๋ฐฉ์งํ๊ธฐ ์ํ ๋ฌธ๊ตฌ๋ฅผ ์ถ๊ฐํ๋ค. (If you don't know the answer just say you don't know, don't make it up)
Retriever๋ก๋ถํฐ ๊ฐ์ ธ์จ ๋ถํ ๋ฌธ์๋ค์ LLM์ ์ ๋ฌํ๋ ๋ฐฉ์์ผ๋ก๋ ์ฌ๋ฌ ๊ฐ์ง๊ฐ ์๋ค.
๊ด๋ จ๋ ๋ฌธ์๋ค์ ๋ชจ๋ prompt์ ์ฑ์ ๋ฃ์ด(stuff) ์ ๋ฌํ๋ค. ํ๋กฌํํธ๊ฐ ๊ณผ๋ํ๊ฒ ๊ธธ์ด์ง ์ ์๋ค๋ ๋จ์ ์ด ์๋ค.
๊ฐ๊ฐ์ ๋ฌธ์๋ค์ ์์ฝํ๊ณ , ์์ฝ๋ ๋ฌธ์๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ต์ข ์์ฝ๋ณธ์ ๋ง๋ค์ด๋ธ๋ค. ํ ํฐ ์ด์(ํ๋กฌํํธ๊ฐ ์ ํ ํ ํฐ ์ ์ด์์ผ๋ก ๊ธธ์ด์ง)๋ฅผ ํด๊ฒฐํ ์ ์๋ค๋ ์ฅ์ ์ด ์์ง๋ง, ๋ชจ๋ ๋ฌธ์๋ฅผ ์์ฝํ๊ธฐ์ ์๋๊ฐ ๋๋ฆฌ๋ค๋ ๋จ์ ์ด ์๋ค.
๋ฌธ์๋ค์ ์ํํ๋ฉฐ ์ค๊ฐ ๋ต๋ณ(Intermediate Answer)์์ฑํ๊ณ , ์ด๋ฅผ ๋ฐ๋ณตํ๋ฉฐ ์ ์ ๋ต๋ณ์ ์ ์ (refine)ํ๋ค. ์ต์ข ์ ์ผ๋ก ์์ง์ ๋ต๋ณ์ด ๋ง๋ค์ด์ง๊ฒ ๋๋ค. ์๋๊ฐ ๋๋ฆฌ๊ณ ๋น์ฉ์ด ๋น์ธ์ง๋ง ๊ฒฐ๊ณผ๋ฌผ์ด ๋ฐ์ด๋๋ค๋ ์ฅ์ ์ด ์๋ค.
๊ฐ๊ฐ ๋ฌธ์์ ๋ํด ๊ฐ๊ฐ์ ๋ต๋ณ์ ๋ง๋ค์ด ๋ด๊ณ , ์ด์ ์ ์๋ฅผ ๋ถ์ฌํ๋ค. ๊ฐ์ฅ ๋์ ์ ์๋ฅผ ๋ฐ์ ๋ต๋ณ์ ์ต์ข ๋ต๋ณ์ผ๋ก ์ค์ ํ๋ค. ๋ฌธ์๊ฐ ๊ตฌ๋ถ๋์ด ์๊ณ ๋์ ์ ์ฌ์ฑ์ ์๊ตฌํ ๋(ํ๋์ ๋ฌธ์์๋ง ์ํ๋ ์ ๋ณด๊ฐ ์์ ๋) ์ ์ฉํ๋ค.
์ฌ๊ธฐ์๋ ๊ฐ์ฅ ๊ฐ๋จํ ๊ตฌ์กฐ์ธ Stuff ๋ฐฉ์์ LCEL๋ก ๊ตฌํํด ๋ณธ๋ค.
llm = ChatOpenAI(temperature=0.1) chain = ( { "context": retriever, "question": RunnablePassthrough(), } | prompt | llm ) result = chain.invoke("๊น์ฒจ์ง๋ ํ์์ ์ด๋๋ก ๋ฐ๋ ค๋ค ์ฃผ์๋?") print(result)
chain.invoke()
๋ฅผ ํตํด ์ ๋ฌ๋ ์ฟผ๋ฆฌ๋ retriever์ ์ ๋ฌ๋๊ณ , ๋ฐํ๋ ๋ฌธ์๋ฅผ {context}์ ๋ฃ๋๋ค. RunnablePassthrough()
๋ฅผ ํตํด ์ฌ์ฉ์์ ์ง๋ฌธ ๋ํ {question}์ ๊ทธ๋๋ก ๋ค์ด๊ฐ๊ฒ ๋๋ค.๊น์ฒจ์ง๋ ํ์์ ์ด๋๋ก ๋ฐ๋ ค๋ค ์ฃผ์๋?
-> ๊น์ฒจ์ง๋ ํ์์ ๋จ๋๋ฌธ ์ ๊ฑฐ์ฅ๊น์ง ๋ฐ๋ ค๋ค ์ฃผ์์ต๋๋ค.
๊น์ฒจ์ง๋ ํ์์ ์ด๋๋ก ๋ฐ๋ ค๋ค ์ฃผ์๋? ํด๋น ๋ถ๋ถ์ ์๋ฌธ๊ณผ ํจ๊ป ์ถ๋ ฅํ์์ค.
-> ๊น์ฒจ์ง๋ ํ์์ ๋จ๋๋ฌธ ์ ๊ฑฐ์ฅ๊น์ง ๋ฐ๋ ค๋ค ์ฃผ์๋ค.
ํด๋น ๋ถ๋ถ์ ์๋ฌธ์ ๋ค์๊ณผ ๊ฐ๋ค:\n\n"๋จ๋๋ฌธ ์ ๊ฑฐ์ฅ๊น์ง ์ผ๋ง์?
off-the-shelf chain ํด๋์ค์ธ RetrievalQA๋ฅผ ์ฌ์ฉํ๋ค๋ฉด ๊ฐ๋จํ๊ฒ Chain์ ์ฌ์ฉํ ์ ์๋ค. ๊ทธ๋ฌ๋ ์๋ฆฌ๊ฐ ๋ชจํธํ๊ณ ์ปค์คํ ํ๊ธฐ ์ด๋ ต๋ค๋ ๋จ์ ์ด ์๋ค. ๋ํ ์ด๋ legacy๋ก ๊ณต์์ ์ผ๋ก LCEL๋ก ์ง์ ๊ตฌํํ๋ ๋ฐฉ์์ด ๊ถ์ฅ๋๋ค.
chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="map_rerank",
retriever=retriever,
)
result = chain.run("๊น์ฒจ์ง๋ ํ์์ ์ด๋๋ก ๋ฐ๋ ค๋ค ์ฃผ์๋?")
print(result)
chain_type
์ ์ํ๋ ๋ฐฉ์์ ์ค์ ํ ์ ์๋ค.
์ด์ ์ข์ ๋ .txt
๋ฌธ์๋ ์ด 41๊ฐ์ ๋ฌธ์๋ก ๋ถํ ๋๋ฉฐ, 4๊ฐ์ ๋ฌธ์๊ฐ LLM์ ์ ๋ฌ๋๋ค.https://python.langchain.com/docs
https://github.com/nomadcoders/fullstack-gpt
https://www.youtube.com/watch?v=tQUtBR3K1TI
https://nomadcoders.co/fullstack-gpt