카테고리 없음

스파르타 AI-8기 TIL(11/25) -> 처음부터 계속하기

kimjunki-8 2024. 11. 25. 21:32

솔직히 말해서 오늘은 팀 과제로 인해 배운점이 없다...라고 하면 거짓말....

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이다.

아쉽게도 코드를 작성하면서 얻은 내용은 이게 거의 전부....