본문 바로가기

알토르

OpenAI Assistant

OpenAI Assistant

기본 개념 정리

1. Assistant

  • Assistant는 ‘AI 역할’을 하는 기능으로, 주로 특정 역할·목적에 맞게 설정된 챗봇
  • 예: 수학 튜터, 고객 지원 챗봇 등으로 사용할 수 있으며, 해당 역할에 맞는 instructions, 도구, 지식 베이스 설정 가능 / 코드 실행 및 파일 업로드, 번역까지 가능
  • Assistant 생성 시 instructions, tools, file_ids 등을 지정해 챗봇 정의

1-1. Instructions

  • 역할 : OPENAI에게 맞춤화 설정 입력

1-2. Tools

1) code interpreter
어시스턴트가 코드를 작성하고 실행

2) retrieval
첨부된 문서를 기반으로 답변 기능

3) functions
어시스턴트에게 사용자 정의 함수를 지정

2. Thread

  • Thread는 대화의 컨테이너
  • 서버 측 저장된 메시지 모음
  • Assistant 와 사용자 간의 하나의 단톡방
  • 메시지만 저장 가능
  • 각 사용자의 하나의 대화 세션을 Thread로 구성
  • 그 안에 여러 Message 저장 가능
  • thread_id 생성으로 특정 대화 세션 식별 가능
  • 해당 id는 하나의 대화방 ID
  • 주요 특징:
    -“스레드” 하나당 여러 메시지를 담고 있어, 이전 대화를 그대로 이어갈 수 있는 컨텍스트 유지 기능
  • metadata를 붙여 “language_preference”, “사용자 구분” 등 다양한 정보 기록
  • 생성(create), 조회(retrieve), 업데이트(update), 삭제(delete) 모두 가능

3. Message

  • 역할 : Thread 내부의 하나의 대화 단위
role: user 또는 assistant로 구분되어, 누가 보낸 메시지인지 표시

content: 실제 대화 내용

파일 attachment, annotations, metadata 등도 포함가능

메시지를 생성하고 관리할 수 있으며,
목록 조회(list), 단일 조회(retrieve), 수정(update), 삭제(delete) 가능

구성 요소 간 관계 도식화

Assistant(역할) —> Thread(대화 컨테이너) —> Message들(대화 내용, 유저/어시스턴트)

사용자가 입력 메시지(Message, role="user")를 보내면 
Thread에 추가되고, 
Assistant가 이를 처리해서 
답변 메시지(Message, role="assistant")를 생성해 
해당 Thread에 쌓입니다.
이후, 대화가 이어지면 
동일한 Thread에 Message들이 누적
컨텍스트가 유지됩니다.

app.py 수정

@app.route("/sendMessage", methods=["POST"])
def send_message():
   
    global thread_id  # 전역 변수 thread_id를 사용
    
    try:
        # 클라이언트가 보낸 JSON 데이터를 파싱
        data = request.get_json()
        # JSON에서 'message' 키의 값을 가져옴 (사용자 입력 메시지)
        message = data.get('message', '')
        
        # 메시지가 비어있으면 에러를 반환
        if not message:
            return jsonify({"error": "메시지가 없습니다."}), 400
        
        # 터미널에 받은 메시지를 출력 (디버깅용)
        print(f"받은 메시지: {message}")
        
        # 스레드가 없으면 새로 생성 (첫 번째 메시지인 경우)
        if thread_id is None:
            # 새로운 대화 스레드를 생성
            thread = client.beta.threads.create()
            # 생성된 스레드의 ID를 저장
            thread_id = thread.id
            print(f"새 스레드 생성: {thread_id}")
        
        # 사용자 메시지를 스레드에 추가
        client.beta.threads.messages.create(
            thread_id=thread_id,  # 대화 스레드 ID
            role="user",          # 메시지 역할 (사용자)
            content=message       # 메시지 내용
        )
        
        # Assistant를 실행하여 응답을 생성
        run = client.beta.threads.runs.create(
            thread_id=thread_id,      # 대화 스레드 ID
            assistant_id=ASSISTANT_ID # OpenAI 대시보드에서 생성한 Assistant ID
        )
        
        # Assistant가 응답을 완료할 때까지 기다림
        while True:
            # 현재 실행 상태를 확인
            run_status = client.beta.threads.runs.retrieve(
                thread_id=thread_id,
                run_id=run.id
            )
            
            # 실행이 완료되면 루프를 종료
            if run_status.status == "completed":
                break
            # 실행이 실패하면 에러를 반환
            elif run_status.status == "failed":
                return jsonify({"error": "Assistant 실행 실패"}), 500
        
        # Assistant의 응답 메시지를 가져옴
        messages = client.beta.threads.messages.list(thread_id=thread_id)
        # 가장 최근 메시지(Assistant의 응답)를 가져옴
        assistant_message = messages.data[0]
        # 응답 내용을 추출
        ai_response = assistant_message.content[0].text.value
        
         # 클라이언트에게 성공 응답을 JSON 형태로 반환
        return jsonify({
            "success": True,
            "message": cleaned_response,
            "original_message": message
        })
        
    except Exception as e:
        # 에러가 발생하면 터미널에 에러 메시지를 출력
        print(f"에러 발생: {str(e)}")
        # 클라이언트에게 에러 응답을 반환
        return jsonify({"error": f"서버 에러: {str(e)}"}), 500

실무적으로 어떻게 쓰일까요?

  1. Assistant 생성
assistant = client.beta.assistants.create(
    name="data_science_tutor",
    instructions="This assistant is a data science tutor.",
    tools=[{"type": "retrieval"}, {"type": "code_interpreter"}],
    model="gpt-4-1106-preview",
    file_ids=[...]
)
  1. Thread 생성
thread = client.beta.threads.create()


사용자 메시지 추가

message = client.beta.threads.messages.create(
    thread_id=thread.id,
    role="user",
    content="ELI5: What is neural network?"
)
  1. Thread 실행(run)
run = client.beta.threads.runs.create(
    thread_id=thread.id,
    assistant_id=assistant.id
)
  1. 답변 메시지 받아오기

run이 완료되었는지 주기적으로 polling하고, 완료되면 Thread의 메시지 목록을 조회

 

 

Thread

🔑 핵심 개념

OpenAI Assistants API는 대화를 Thread 단위로 묶습니다.

“대화 회수(컨텍스트 유지)” 기능을 넣으려면 같은 Thread ID를 재사용해야 합니다.

🛠️ 회수(대화 이어가기) 기능 추가 방법

  1. Thread를 한 번만 생성하고 저장

Flask 서버 실행 시 또는 첫 요청 시 thread_id를 생성하고,

이후 요청부터는 같은 thread_id를 사용하도록 관리해야 합니다.

Flask 전역 변수에 저장 (예시)

THREAD_ID = None

@app.route("/chat", methods=["POST"])
def chat():
    global THREAD_ID
    user_message = request.json.get("message")

    # 최초 1회만 Thread 생성
    if THREAD_ID is None:
        thread = client.beta.threads.create()
        THREAD_ID = thread.id

    # Thread 재사용
    client.beta.threads.messages.create(
        thread_id=THREAD_ID,
        role="user",
        content=user_message
    )

    run = client.beta.threads.runs.create(
        thread_id=THREAD_ID,
        assistant_id=ASSISTANT_ID
    )
    ...
  1. React 쪽에서 “대화 세션 유지” 기능 넣기

React에서 새로고침하면 기존 대화 삭제됨

thread_id를 백엔드에서 세션 쿠키나 DB에 저장

또는 프론트 localStorage에 thread_id 보관

→ “대화 회수”가 가능합니다.

  1. 구현하면서 고려할 점

대화 초기화 버튼 → 새로운 thread를 만들어 처음부터 대화 시작하기

대화 내역 불러오기 → /messages API 호출해서 지금까지 대화 보여주기

메타데이터 저장 → 사용자가 누구인지, 어떤 문맥인지 thread metadata에 기록할 수 있음

✅ 정리

지금 상태에선 “매번 새로운 Thread 생성”만 있어서 회수 기능이 없음.

Thread ID를 저장 & 재사용하도록 Flask API를 고쳐야 함.

즉, client.beta.threads.create()를 매번 하지 않고, 한 번만 생성해서 계속 같은 thread_id 사용.

React에서는 이 thread_id를 쿠키/세션/localStorage로 보관해서 API 호출할 때 같이 보내야 함.

 

 

Assistant API

포트폴리오 프로젝트 구성

포폴 프로젝트/
│
├─ backend/               # Flask 서버
│   ├─ app.py             # 챗봇 API
│   └─ .env               # OPENAI_API_KEY, ASSISTANT_ID
│
├─ frontend/              # React/Next.js 프론트
│   └─ Chat.jsx           # 챗 UI
│
├─ resume.pdf             # 이력서/자소서 PDF
└─ package.json, requirements.txt

1️⃣ Flask 백엔드 (backend/app.py)

from flask import Flask, request, jsonify
from flask_cors import CORS
import os
from dotenv import load_dotenv
from openai import OpenAI
import time

import { v4 as uuidv4 } from "uuid"; # uuid 선언

# 환경 변수 로드
load_dotenv()
api_key = os.getenv("OPENAI_API_KEY")
ASSISTANT_ID = os.getenv("ASSISTANT_ID")  # 이력서 기반 Assistant ID

app = Flask(__name__)
CORS(app)

client = OpenAI(api_key=api_key)

# 전역 Thread ID (대화 회수용)
THREAD_ID = None

@app.route('/sendMessage', methods=['POST'])
def send_message():
    global THREAD_ID
    data = request.json
    username = data.get("username")
    message = data.get("message")
    thread_id_from_front = data.get("thread_id")

    # 기존 Thread 사용 또는 새 Thread 생성
    if thread_id_from_front:
        THREAD_ID = thread_id_from_front
    elif THREAD_ID is None:
        thread = client.beta.threads.create()
        THREAD_ID = thread.id

    # 사용자 메시지 추가
    client.beta.threads.messages.create(
        thread_id=THREAD_ID,
        role="user",
        content=message
    )

    # Assistant 실행
    run = client.beta.threads.runs.create(
        thread_id=THREAD_ID,
        assistant_id=ASSISTANT_ID
    )

    # 답변 완료될 때까지 Polling
    while True:
        status = client.beta.threads.runs.retrieve(thread_id=THREAD_ID, run_id=run.id)
        if status.status == "completed":
            break
        time.sleep(1)

    # 마지막 Assistant 메시지 추출
    messages = client.beta.threads.messages.list(thread_id=THREAD_ID)
    bot_reply = ""
    for msg in reversed(messages.data):
        if msg.role == "assistant":
            bot_reply = msg.content[0].text.value
            break

    return jsonify({
       "username": username,
       "user_message": message,
       "bot_reply": bot_reply,
       "thread_id": THREAD_ID
    }), 200

if __name__ == '__main__':
    app.run(debug=True)

2️⃣ React 프론트 (frontend/Chat.jsx)

const [threadId, setThreadId] = useState(null);

// sendMessage 수정
const res = await fetch("http://localhost:5000/sendMessage", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ username: "Ju-hyeok", message: text, thread_id: threadId }),
});
const data = await res.json();
setThreadId(data.thread_id);

setMessages((m) => [
  ...m,
  { id: crypto.randomUUID(), user: "bot", name: "J", text: data.bot_reply, time: new Date().toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) }
]);

브라우조 오류로 uuidv4로 변경

  • v4는 랜덤 기반 UUID 생성 방식
  • crypto.randomUUID()와 비슷한 역할이지만,
  • 구버전 브라우저에서도 안전하게 사용 가능
import { v4 as uuidv4 } from "uuid";

const myMsg = {
  id: uuidv4(),
  user: "me",
  name: "우주혁",
  text,
  time: new Date().toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }),
};

SSL 인증서 오류 발생

httpx.ConnectError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate in certificate chain
  • openai 클라이언트가 서버에 연결 시
  • 자체 서명(self-signed) 인증서 포함한 SSL을 신뢰하지 못해서 연결 실패
  • Python에서는 기본적으로 공인 인증서만 신뢰