본문 바로가기
AI/Contents

OpenAI의 Embedding을 이용한 ChatGPT Custom Chatbot 시스템 구축해보기

by AI미남홀란드 2023. 11. 15.
728x90

챗봇 개발은 AI와 머신러닝의 빠른 발전으로 인해 점점 더 중요해지고 있습니다. 특히, OpenAI의 GPT 모델을 활용하면 높은 수준의 대화 능력을 가진 챗봇을 만들 수 있습니다. 이 글에서는 ChatGPT를 활용한 Custom ChatBot 개발에 대한 전반적인 가이드와 함께, 토큰 제한과 대용량 데이터 처리 등의 문제를 어떻게 해결할 수 있는지에 대해 설명하겠습니다. 오늘은 ChatGPT로 나만의 커스텀 챗봇을 한번 만들어 보겠습니다.

준비물 : OpenAI API Key, Python

키워드

  • ChatGPT
  • Custom ChatBot
  • OpenAI
  • 토큰 제한
  • 대용량 데이터 처리

Embedding

기업들이 기업용 LLM 을 구축하기 위해서 많은 시간과 돈을 쓰면서 노력을 하고 있다고 합니다. 저의 업무중 일부도 LLM 구축인데요. 우연히 GPT를 쓰던 도중에 제가 생각보다 많이 놓치고 있던 기능들이 있었습니다. Vector Embedding 맨날 얘기만 들었지 "토큰을 임베딩해서 DataBase에 저장한다" 그 후 DB에서 관련된 정보가 들어오면 유사도로 데이터를 찾아서 생성을 해준다 정도의 개념만 알고 있었는데요. 한번 Open AI 홈페이지에 들어가보니 생각보다 많은 기능들이 있었습니다.

OpenAI의 텍스트 임베딩은 텍스트 문자열의 관련성을 측정합니다. 이 임베딩은 검색, 클러스터링, 추천, 이상 탐지, 다양성 측정, 분류 등에 일반적으로 사용됩니다. 임베딩은 부동 소수점 숫자의 벡터(리스트)로, 두 벡터 간의 거리가 그들의 관련성을 측정합니다. OpenAI는 여러 세대의 임베딩 모델을 제공하며, 사용자는 특정 임베딩 모델 ID를 선택하여 API 엔드포인트에 텍스트 문자열을 보낼 수 있습니다.

관련코드에 대해 설명해보겠습니다.

def get_embedding(text, model="text-embedding-ada-002"):
   text = text.replace("\n", " ")
   return openai.Embedding.create(input = [text], model=model)['data'][0]['embedding']
  • 이 함수는 텍스트를 입력으로 받아 OpenAI의 임베딩 API를 호출하여 해당 텍스트의 임베딩을 반환합니다.
  • 개행 문자(\n)는 공백으로 대체됩니다.
  • 기본적으로 "text-embedding-ada-002" 모델을 사용합니다.

-> User가 원하는 Text 형식의 데이터를 함수에 넣으면 띄어쓰기를 기준으로 공백으로 치환되면서 한줄의 문자형식으로 반환이됩니다. 그 후 openAI Embedding API 를 활용해서 embedding 처리를 해주는 함수 입니다.

텍스트 임베딩과 문맥적 검색

이 모델을 활용하면, FAQ나 데이터베이스에 있는 문서들을 미리 벡터 형태로 변환해둘 수 있습니다. 사용자의 질문도 같은 방법으로 벡터화한 뒤, 미리 변환해둔 문서 벡터들과의 유사도를 계산하여 가장 적합한 답변을 찾아낼 수 있습니다.

임베딩 성능과 유사도 측정

GPT-3의 임베딩 성능은 다른 전용 인코더 모델들에 비해 상대적으로 떨어질 수 있습니다. 이에 대한 자세한 내용은 OpenAI의 공식 문서에서 확인할 수 있습니다. 유사도 측정에는 'faiss' 라이브러리를 활용할 수 있습니다.

Chat API

대화 구성과 역할

OpenAI의 Chat API는 다음과 같은 형식으로 대화를 구성합니다

openai.ChatCompletion.create(
  model="gpt-3.5-turbo",
  messages=[
        {"role": "system", "content": "너는 레시피를 알려주는 백종원이야"},
        {"role": "user", "content": "레시피를 물어봅니다"},
        {"role": "assistant", "content": "백종원의 답변"},
        {"role": "user", "content": "레시피 세부 정보에 관해 재차 물어봅니다."}
    ]
)

토큰 제한과 대화 길이

대화가 길어질수록 토큰 수가 증가하므로, 비용이 높아지고 길이 제한에 걸릴 수 있습니다. 이를 해결하기 위해 과거 대화를 적절히 제거하거나 요약할 필요가 있습니다.

다양한 적용 사례

'system' 메시지를 통해 챗봇의 특성을 지정할 수 있습니다. 예를 들어, 레시피 추천에 특화된 챗봇, 또는 특정 주제에 대한 정보를 제공하는 챗봇 등을 만들 수 있습니다. 오늘날 대기업들을 제외하곤 이렇게 응용을 잘해서 서비스를 구축 할 수 있을거라고 생각합니다.

도메인 특화와 사용자 쿼리 처리

'system' 메시지를 통해 챗봇이 특정 도메인의 정보를 기반으로 답변을 생성할 수 있도록 지시할 수 있습니다. 예를 들어, 사용자의 질문에 가장 적합한 문서를 찾아 'system' 메시지에 포함시켜, 챗봇이 그 정보를 바탕으로 답변을 생성하도록 할 수 있습니다.

이러한 방식을 통해, 챗봇은 단순한 대화 뿐만 아니라 특정 도메인의 지식을 활용한 복잡한 질문에도 대응할 수 있습니다.

Custom Chatbot

저는 간단하게 제가 가지고 있는 PDF 파일을 활용하여 챗봇을 만드는 코드를 구현해보겠습니다. GPT4를 활용해서 코드를 구현해달라고 부탁을 하였습니다.

Prompt : 나는 ~~에 관한 설명을 가지고 있는 ChatBot을 만들 예정이야. 그래서 API호출을 해서 Embedding 하는 코드와, ChatBot 의 기본적인 구조가 필요해 엠베딩은 ada-002 모델로, Chatbot은 gpt-3.5-turbo로 구현하고싶어 나는 Python환경 jupyter 노트북에서 테스트를 할예정이야

#OpenAI의 API 키를 설정합니다.
#필요한 라이브러리를 임포트합니다.
openai.api_key = "Your API Key"
import openai 
import faiss
import numpy as np
# 챗봇 객체가 생성될 때 초기 설정을 합니다.
# index: Faiss 인덱스
# embeddings: 문서의 임베딩 리스트
# documents: 실제 문서 리스트
# system_message: 시스템 메시지 (챗봇의 역할을 설명)
class Chatbot:
    def __init__(self, index, embeddings, documents, system_message):
        self.index = index
        self.embeddings = embeddings
        self.documents = documents
        self.system_message = system_message
        self.chat_history = []

    def get_embedding(self, text, model="text-embedding-ada-002"): #주어진 텍스트의 임베딩을 생성합니다
        text = text.replace("\n", " ")
        embedding = openai.Embedding.create(input=[text], model=model)["data"][0]["embedding"]
        print("Generated embedding:", embedding)  # 디버깅을 위한 출력
        return embedding
        
    def find_similar_document(self, user_embedding): #사용자의 질문에 가장 유사한 문서를 찾습니다.
        _, top_indices = self.index.search(np.array([user_embedding]), 1)
        top_index = top_indices[0][0]
        return self.documents[top_index]
	# Chatbot 구조
    def chat(self, user_input):
        user_embedding = self.get_embedding(user_input)
        similar_document = self.find_similar_document(user_embedding)
        similar_document = similar_document[:500]# 찾은 문서의 내용은 500자로 제한 token문제
        system_message = self.system_message + " " + similar_document #시스템 메시지와 유사한 문서의 내용을 합칩니다. 이 정보는 챗봇이 답변을 생성할 때 컨텍스트로 사용됩니다.
        
        # 아래부분부터 유저와 시스템 끼리의 대화를 [] 리스트로 이전기억을 저장합니다. 리소스문제에따라 줄일수있음
        messages = [{"role": "system", "content": system_message}]
        for message in self.chat_history:
            messages.append(message)
        messages.append({"role": "user", "content": user_input})
        response = openai.ChatCompletion.create(
            model="gpt-3.5-turbo",
            messages=messages
        )
        assistant_message = response.choices[0].message.content
        self.chat_history.append({"role": "user", "content": user_input})
        self.chat_history.append({"role": "assistant", "content": assistant_message})
        return assistant_message

이처럼 챗봇의 전체적인 구조를 만들었습니다. 여기서 role 을 부여해서 보인의 데이터에 알맞는 챗봇으로 구성을 하면 될 것입니다.

Read PDF

# PyPDF2를 활용해서 PDF의 글씨 추출하고 임베딩 하기
import PyPDF2

with open("/Users/jaehyun/Desktop/app/w5500.pdf", "rb") as f:
    reader = PyPDF2.PdfFileReader(f)
    text = ""
    for i in range(reader.numPages):
        page = reader.getPage(i)
        text += page.extractText()
        
documents = text.split("\n\n")

토큰 제한: 문제와 해결 방안

문제 상황

GPT 모델은 토큰 제한이 있어, 이를 초과하면 에러가 발생합니다. 이 문제를 해결하기 위한 몇 가지 전략을 소개하겠습니다.

해결 전략

문서의 길이 제한: find_similar_document 함수에서 반환되는 similar_document의 길이를 제한합니다.
챗 히스토리 제한: self.chat_history를 최근 N개의 메시지만 저장하도록 제한합니다.


from numpy import mean, array

# 가정: documents는 긴 텍스트의 리스트입니다.
max_tokens = 8000  # 예시로 8000 토큰을 최대로 설정
embeddings = []

print("Loop starting...")
for doc in documents:
    token_count = len(doc.split())
    if token_count > max_tokens:
        print("Document exceeds max tokens.")
        # 텍스트를 나누고 각 부분에 대해 임베딩을 생성
        num_parts = -(-token_count // max_tokens)  # 올림을 사용하여 부분의 수 계산
        parts = [doc[i:i+max_tokens] for i in range(0, token_count, max_tokens)]
        part_embeddings = [chatbot_instance.get_embedding(part) for part in parts]
        # 부분 임베딩을 어떻게 다룰지는 여러 방법이 있을 수 있습니다.
        # 예를 들어, 평균을 내거나 첫 번째 부분만 사용할 수 있습니다.
        # 부분 임베딩을 평균내어 하나의 임베딩으로 만듭니다.
        avg_embedding = mean(array(part_embeddings), axis=0).tolist()
        embeddings.append(avg_embedding)
    else:
        print("Document within max tokens.")
        embedding = chatbot_instance.get_embedding(doc)
        embeddings.append(embedding)
        print("Current embeddings list:", embeddings)

faiss 를 활용하여 embedding 계산

import faiss

# 임베딩을 NumPy 배열로 변환
embeddings_np = np.array(embeddings).astype('float32')

# FAISS 인덱스 생성
index = faiss.IndexFlatL2(embeddings_np.shape[1])
index.add(embeddings_np)

use chatbot

system_message = "안녕하세요, RecipeBot 입니다 무엇을 도와드릴까요?" # 시스템메시지 입력
# 클래스화 된 챗봇을 인스턴스 선언
chatbot_instance = Chatbot(index, embeddings, documents, system_message) 

user_input = "안녕하세요, 저는 토마토 계란 볶음에 관한 레시피를 알고싶어요."
response = chatbot_instance.chat(user_input)
print("챗봇: ", response)
# 활용 예시
while True:
  
    user_input = input("당신: ")

    if user_input.lower() == "quit":
        print("챗봇: 안녕히 가세요!")
        break

    response = chatbot_instance.chat(user_input)
    
    print("챗봇: ", response)

위처럼 챗봇을 구성하면됩니다. GPT-4 부턴 저도 매번 놀라고 사용하는데요 정말 이제 챗봇자체도 예전의 Rule-Base 보다 구현하기 쉽게 만들 수 있고 성능 또한 그 이상을 보여주는 것 같습니다. 이번 예제를 통해 다양한 custom 챗봇을 만들어 볼 수 있을거같고 더 나아가 다른 부분을 접목시켜서 의미있는 프로젝트를 만들수 있지 않을까 생각합니다.

 

궁금하신점은 댓글 , 메일 언제든 상관없습니다 많이 물어봐주세요!

728x90