AI & ML/자연어 처리

ChatGPT 기반 RetrievalQA 체인 구축

JYUN(sia) 2024. 8. 13. 10:09

텍스트 파일에서 데이터를 로드하고, 이를 임베딩(벡터화)하여 문서 검색과 질문-응답(RetrievalQA) 체인을 생성한다.

 

 

1. API 키 설정

api_key = os.getenv("OPENAI_API_KEY")

 

'.env' 파일에서 'OPENAI_API_KEY'를 가져온다. 이 API 키는 OpenAi의 모델을 호출하는 데 사용된다. 

본 코드에서는 chatGPT 모델을 사용하여 챗봇 답변을 생성할 것이다. 

 

2. 문서 로드

def load_docs():
    loader = TextLoader('text.txt')
    print(loader)
    documents = loader.load()
    
    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=1000,
        chunk_overlap=200
    )
    splits = text_splitter.split_documents(documents)
    
    return splits

 

긴 문서 전체를 한 번에 처리하면 모델이 모든 내용을 이해하고 기억하기 어렵기에, text.txt 파일에서 문서를 로드 후 분할한다. 

특히 LLM모델의 입력 길이 제한 때문에 긴 텍스트는 잘라서 처리해야 한다. 

1000자 정도의 텍스트로 나누고, 중첩(overlap)을 추가하여 텍스트의 맥락이 잘 보존되도록 한다. 

 

3. 벡터 스토어 생성

def create_vectorstore(splits):
    embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
    vectorstore = Chroma.from_documents(documents=splits, embedding=embeddings)
    return vectorstore

 

text-embedding-3-large 모델을 사용해 임베딩을 생성한다.

임베딩(embedding)은 텍스트를 고차원 벡터 공간에 mapping하여 의미적으로 유사한 텍스트들이 벡터 공간에서 가까운 거리에 위치하도록 한다. 이를 통해 문서나 텍스트 간의 유사성을 수치적으로 계산할 수 있다.


embedding 목적

텍스트 데이터를 고차원 벡터로 변환하여 기계 학습 모델이 이 데이터를 효율적으로 처리하고 이해할 수 있도록 한다.

 

의미적으로 유사한 텍스트들이 벡터 공간에서 가깝게 위치하도록 하는 것이다.
예를 들어 "강아지"와 "개"라는 단어는 서로 다른 단어지만 의미적으로 유사하기 때문에, 임베딩 벡터 공간에서 가까운 위치에 존재하게 된다. 이를 통해 문장이나 단어 간의 의미적 유사성을 수치적으로 계산할 수 있다.


Chroma 벡터 스토어를 사용했는데, 사실 딱히 이유는 없다.. 나중에 각 벡터db를 비교하고 목적에 따라 성능을 비교해봐야 겠다.

더보기

temperature 매개변수

temperature이 낮을 때

  • 모델이 정해진 대로 행동한다. 
    > 같은 입력에 대해 항상 유사한 결과를 생성하려고 한다.
  • 응답이 덜 창의적이며, 예측 가능한 경향이 있다. 
  • 정확하고 일관된 답변이 중요한 경우에 유용하다. 

temperature이 높을 때 

  • 동일한 입력에 대해 다양한 결과를 생성한다.
  • 모델이 랜덤하고 창의적인 응답을 생성한다. 
  • 새로운 아이디어나 창의적인 텍스트를 생성하고자 할 때 유용하다. 

 

 

4. RetrievalQA 생성

def create_rag_chain(vectorstore):
    llm = ChatOpenAI(model_name="gpt-4o", temperature=1, openai_api_key=api_key)

    prompt = ChatPromptTemplate.from_template(
    """아래의 문맥을 사용하여 질문에 답하십시오.
    정보가 없는 질문이 나오면 대답을 거절하세요.
    
   ...
    Context: {context}
    Question: {input}
    Answer:""")

    embeddings = OpenAIEmbeddings()
    vector = FAISS.from_documents(documents, embeddings)
    retriever = vector.as_retriever()
    document_chain = create_stuff_documents_chain(llm, prompt)
    qa_chain = create_retrieval_chain(retriever, document_chain)

    return qa_chain

 

분할된 텍스트와 임베딩을 활용하여 질의응답 체인을 구축하고, 사용자 질문에 대한 적절한 답변을 생성한다. 

 

RetrievalQA 체인

  • Retrieval: 사용자의 질문과 가장 관련성이 높은 문서나 텍스트 조각을 벡터 공간에서 검색한다.
  • QA (Question-Answering): 검색된 문서를 기반으로 질문에 대한 답변을 생성한다.

체인의 구성 요소

  • LLM (Large Language Model): 사용자 질문을 이해하고, 검색된 문서 조각을 바탕으로 자연스러운 답변을 생성한다. 
  • prompt template: 답변을 생성할 때 사용할 지침을 정의한다. 챗봇의 역할, 톤, 언어 등을 설정할 수 있다.
  • vector store: 검색을 위해 임베딩된 문서들이 저장된 공간이다. 여기서 사용자가 입력한 질문과 관련된 텍스트를 찾아낸다.
  • 질의응답 체인 (QA Chain): 이 체인은 검색된 문서를 결합하고, 이를 바탕으로 최종 답변을 생성하는 프로세스를 수행한다.

 

전체 코드

import os
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from langchain_openai import ChatOpenAI
from langchain.chains import create_retrieval_chain
from dotenv import load_dotenv
from langchain_community.vectorstores import FAISS
from langchain_core.prompts import ChatPromptTemplate
from langchain.chains.combine_documents import create_stuff_documents_chain

load_dotenv()
# OpenAI API 키 설정
api_key = os.getenv("OPENAI_API_KEY")
#print(api_key)

# 문서 로드 함수
def load_docs():
    loader = TextLoader('text.txt')
    print(loader)
    documents = loader.load()
    
    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=1000,
        chunk_overlap=200
    )
    splits = text_splitter.split_documents(documents)
    
    return splits


# 벡터 스토어 생성 함수
def create_vectorstore(splits):
    embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
    vectorstore = Chroma.from_documents(documents=splits, embedding=embeddings)
    return vectorstore


# RetrievalQA 체인 생성 함수
def create_rag_chain(vectorstore):
    llm = ChatOpenAI(model_name="gpt-4o", temperature=1, openai_api_key=api_key)

    prompt = ChatPromptTemplate.from_template(
    """아래의 문맥을 사용하여 질문에 답하십시오.
    정보가 없는 질문이 나오면 대답을 거절하세요.
    
    ...
    Context: {context}
    Question: {input}
    Answer:""")

    embeddings = OpenAIEmbeddings()
    vector = FAISS.from_documents(documents, embeddings)
    retriever = vector.as_retriever()
    document_chain = create_stuff_documents_chain(llm, prompt)
    qa_chain = create_retrieval_chain(retriever, document_chain)

    return qa_chain


# 문서 로드 및 벡터 스토어 생성
documents = load_docs()
vectorstore = create_vectorstore(documents)

def run(user_input):
    # RetrievalQA 체인 생성
    qa_chain = create_rag_chain(vectorstore)

    # 예제 질문에 대한 답변 생성
    #question = "강서구 지원정책 알려줘"
    #answer = qa_chain.invoke({"input": question})
    answer = qa_chain.invoke({"input": user_input})

    return answer