솔직히 말해서 오늘은 팀 과제로 인해 배운점이 없다...라고 하면 거짓말....
from langchain_community.vectorstores import FAISS
from langchain_community.document_loaders import WebBaseLoader
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain.document_loaders import PyPDFLoader
####################
####### RAG 챗봇 구축
###################
# 1. LLM 모델 불러오기
llm = ChatOpenAI(model="gpt-4o-mini")
# 2. 문서 불러오기
loader = PyPDFLoader("/content/[2024 한권으로 OK 주식과 세금].pdf")
docs = loader.load()
# 3. 문서 chunking 하기
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
splits = text_splitter.split_documents(docs)
# 4. 자른 chunk들을 embedding 하기
embeddings = OpenAIEmbeddings(model="text-embedding-ada-002")
# 5. vector store 구축하기
vectorstore = FAISS.from_documents(documents=splits, embedding=embeddings)
# 6. retriever 구축하기
retriever = vectorstore.as_retriever()
# 7. 프롬프트 템플릿 구축하기
prompt = prompt = ChatPromptTemplate.from_template("""
오로지 아래의 context만을 기반으로 질문에 대답하세요:
{context}
질문:
{question}
""")
# 8. 1~7. 요소들을 chain으로 조합하여 RAG 구축 완료
def format_docs(docs):
return "\n\n".join(doc.page_content for doc in docs)
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
####################
####### 구축한 RAG 챗봇 실행
###################
rag_chain.invoke("상장주식 대주주 기준이 50억 원 이상으로 완화되었는데 언제부터 적용되는 건가요?")
여기서 내가 이해하지 못한 부분은 context": retriever | format_docs이다. 아니, retriever는 값을 검색해서 답을 가져와 주는 것이 아니였던가? 그런데 여기서는 그냥 invoke를 하면 알아서 검색한 후 결과값을 가져와서 format_docs에 넣어준다니! 일단은 저 방법이
retriever.get_relevant_documents(query)와 같다는 것을 인지했다. 그 덕분에 풀리지 않던 나의 시계가 풀리기 시작했다.
바로 요놈 이 녀석을 이해하지 못한 내가. 오늘 드디어 완벽히 이해했다. 안 보고 코드를 쓰라는 것에는 약간 힘들 수 있겠지만, 지금은 흐름을 완전히 정복했다!class documenttoprompt: def __init__(self, contextual_prompt): self.contextual_prompt = contextual_prompt def invoke(self, input): input_context = input['context'] if isinstance(input_context, list): context_doc = '\n'.join(doc.page_content for doc in input) else: context_doc = input formatted_prompt = self.contextual_prompt.format_messages( documents = context_doc, question = input.get('question', '') ) return formatted_prompt class Retriever: def __init__(self, retriever): self.retriever = retriever def invoke(self,input): if isinstance(input, dict): query = input.get('question', "") else: query = input response_doc = self.retriever.get_relevant_documents(query) return response_doc
Chain은 언뜻 보기 힘들 수 있어도, 이것만 기억하자. 코드의 연속적인 움직임
a = b라고 하자 나중에
c = b - d라고 b를 쓴다 그리고 또 나중에
f = c + h라고 c를 또 쓴다. 언뜻 보면 정말 쉬워보지만, 이것이 Chain의 핵심 개념이라고 할 수있다. 여러 코드가 서로 서로 계속해서 쓰이고 쓰인다. 그것이 Chain이다.
아쉽게도 코드를 작성하면서 얻은 내용은 이게 거의 전부....