안녕하세요. 지난 시간에 이어 이력서 홈페이지 제작 프로젝트 과정 중 AI 챗봇 백엔드 코드를 구현한 내용을 포스팅 하도록 하겠습니다.

기초 지식

우선 AI ChatBot을 구축하기 전 OpenAI 에서 제공해주는 API들에 대해 가볍게 알아보도록 하겠습니다.

Assistant ?

Open AI 공식 문서에는 AI 모델을 호출하고 도구를 사용하여 작업을 수행할 수 있는 도우미라고 표기되어 있는 것으로 확인할 수 있습니다.

즉, Assistant 는 한마디로 자신만의 챗봇 모델이라고 볼 수 있고, OpenAI에서 제공해주는 Assistant API 가 있는데 이는 애플리케이션 내 자신만의 Assistant를 구축할 수 있도록 도와주는 도구(Toolkit)이라 생각하시면 될 것 같습니다.

자세한 내용은 공식 문서 참고

https://platform.openai.com/docs/api-reference/assistants/createAssistant

 

OpenAI Assistant 동작 흐름

앞서 OpenAI Assistant 가 무엇인지 살펴보았습니다. 이제 간단한 그림을 통해 OpenAI Assistant 가 어떻게 동작하는지 가볍게 알아보도록 하겠습니다.

[출처] https://yunwoong.tistory.com/294

  1. Assistant 생성
  2. Thread 생성
  3. Thread 내 Message 생성 : User Message로 OpenAI Assistant 모델 대상으로의 질문을 의미
  4. Thread 에서 Assistant 실행(Run)하여 응답 생성
  5. Thread 내 Message 생성 : Bot Message 로 OpenAI Assistant 모델이 생성한 응답을 의미

OpenAI Assistant 모델의 경우 크게 위와 같은 동작 흐름으로 동작합니다.

해당 백엔드 코드 작성 시 동작 흐름은 이력서 기반의 텍스트 파일을 기반으로 Assistant 를 생성하고, 메시지를 담을 Thread 를 생성한 후 사용자 입력값(질문)을 받아 Thread 내 User Message 를 생성하고, Thread_ID 기반으로 생성한 Assistant를 실행하여 응답을 Thread에 Poll 한 후 클라이언트에게 전달하는 방식으로 구현을 진행하였습니다.

 

Assistant 생성

OpenAI API 를 이용한 생성

from openai import OpenAI
client = OpenAI() # OpenAI API키를 이용하여 클라이언트 객체 생성

assistant = client.beta.assistants.create( # OpenAI API를 사용하여 Assistant를 생성
    name="MyResume Assistant",
    instructions=instructions, # 생성한 모델이 어떠한 방식으로 대답할 지 설정
    tools=[{"type": "file_search"}],
    tool_resources={
       "file_search": {
          "vector_store_ids": ["<vector_store_ids_ID>"] # vector_store_ids 설정 / 나의 정보를 기입한 txt 파일
       }
    },
    model="gpt-3.5-turbo"
)

 

  • name : Assistant 이름 설정
  • intructions : Assistant 가 어떠한 방식으로 대답할 지 설정
  • tools : Assistant 에서 활성화된 도구 목록으로 code_interpreter, file_search, function 를 사용할 수 있음
  • model : ChatGPT 응답 모델 설정

자세한 내용은 공식 문서 참고

https://platform.openai.com/docs/api-reference/assistants/createAssistant

OpenAI 웹 콘솔을 이용한 Assistant 생성

만약 API 를 이용한 Assistant 생성이 불편하거나 어려울 경우 OpenAI 웹 콘솔을 이용한 Assistant 생성 진행하시면 됩니다.

마스킹 처리된 부분의 경우 Assistant_ID 이기에 이후 진행 시 참고하시길 바랍니다.

https://platform.openai.com/assistants

AI Chatbot 백엔드 코드 구현

import openai
from dotenv import load_dotenv # 윈도우 사용 시 주석 처리
import os
import time
import re
from flask import Flask, request, jsonify
from flask_cors import CORS

app = Flask(__name__)
CORS(app)

# Linux 환경에서 .env 파일을 사용하기 위해 아래 코드를 추가
# Load .env
load_dotenv()
API_KEY = os.environ.get('API_KEY')  # .env 파일에 저장된 API_KEY를 가져옴
ASSISTANT_ID = os.environ.get('ASSISTANT_ID')  # .env 파일에 저장된 Assistant ID를 가져옴

# # Windows 환경에서 .env 파일을 사용하기 위해 아래 코드를 추가
# API_KEY = os.getenv('API_KEY', '<API_KEY_VALUE>')  # .env 파일에 저장된 API_KEY를 가져옴
# ASSISTANT_ID = os.getenv('ASSISTANT_ID', '<ASSISTANT_ID_VALUE>')  # .env 파일에 저장된 Assistant ID를 가져옴

client = openai.OpenAI(api_key=API_KEY)  # API_KEY를 사용하여 OpenAI 클라이언트 객체 생성

def poll_run(run, thread):
    while run.status != "completed":
        run = client.beta.threads.runs.retrieve(
            thread_id=thread.id,
            run_id=run.id
        )
        time.sleep(0.5)
    return run

def create_run(thread_id, assistant_id):
    run = client.beta.threads.runs.create(
        thread_id=thread_id,
        assistant_id=assistant_id,
    )
    return run

@app.route('/chat', methods=['POST'])
def chat():
    data = request.get_json() # 사용자 http 요청을 data 변수에 저장
    user_message = data['question'] # 사용자 요청 json 데이터 중 question 키 값 추출
    thread_id = data.get('thread_id', None) # 사용자 요청 json 데이터 중 thread_id 키 값 추출 thread_id 값이 없을 경우 None으로 초기화

    if not user_message:
        return jsonify({"error": "Not Message"}), 400

    if thread_id != None:
        thread = client.beta.threads.retrieve(thread_id) # thread_id 값이 있을 경우 해당 thread_id를 사용하여 쓰레드를 가져옴
    else:
        thread = client.beta.threads.create() # thread_id 값이 없을 경우 새로운 쓰레드를 생성

    message = client.beta.threads.messages.create(
        thread_id=thread.id,
        role="user",
        content=user_message
    )
    run = create_run(thread.id, ASSISTANT_ID)

    poll_run(run, thread)

    messages = client.beta.threads.messages.list(
        thread_id=thread.id
    )

    messages_list = list(messages) # 가장 최근의 assistant 메시지를 찾기 위해 메시지를 리스트로 변환 인덱스 [0]이 항상 최신 메시지

    last_message = None
    for message in messages_list:
        if message.role == "assistant":
            last_message = message
            break

    if last_message:
        response_content = re.sub(r'【\d+:\d+†source】', '', last_message.content[0].text.value) # Assistant의 답변에서 source를 제거
        return jsonify({"response": response_content, "thread_id": thread.id})
    else:
        return jsonify({"error": "No response from assistant"}), 500

if __name__ == '__main__':
    app.run(host='127.0.0.1', port='5050', debug=True)

 

Github

 

GitHub - Korea-Maker/Flask-Chatbot-API: Flask Chatbot API

Flask Chatbot API. Contribute to Korea-Maker/Flask-Chatbot-API development by creating an account on GitHub.

github.com