이미지 로딩 중...

OpenAI Assistant API 사용하기 완벽 가이드 - 슬라이드 1/13
A

AI Generated

2025. 11. 16. · 4 Views

OpenAI Assistant API 사용하기 완벽 가이드

OpenAI Assistant API를 활용하여 강력한 AI 어시스턴트를 만드는 방법을 단계별로 배워봅니다. 기본 설정부터 실전 활용까지, 실무에서 바로 사용할 수 있는 완벽한 가이드입니다.


목차

  1. Assistant 생성하기 - AI 비서의 첫 걸음
  2. Thread 생성하기 - 대화방 만들기
  3. Message 추가하기 - 대화 시작하기
  4. Run 실행하기 - AI 답변 생성하기
  5. 답변 조회하기 - AI의 응답 가져오기
  6. Function Calling 사용하기 - AI가 함수 호출하기
  7. Function 결과 제출하기 - 함수 실행 후 AI에게 알리기
  8. Code Interpreter 사용하기 - AI가 코드 실행하기
  9. File Search 사용하기 - AI가 문서 검색하기
  10. Streaming 응답받기 - 실시간으로 답변 표시하기
  11. Assistant 업데이트하기 - AI 설정 변경하기
  12. 비용 최적화하기 - 효율적으로 API 사용하기

1. Assistant 생성하기 - AI 비서의 첫 걸음

시작하며

여러분이 고객 문의에 답변하는 챗봇을 만들어야 하는데, 단순한 질의응답을 넘어서 파일을 읽고, 코드를 실행하고, 심지어 여러분의 데이터베이스와 연동까지 해야 한다면 어떻게 하시겠어요? 기존의 ChatGPT API만으로는 이런 복잡한 작업을 관리하기가 정말 어렵습니다.

매번 대화 내용을 저장하고, 파일을 관리하고, 상태를 유지하는 코드를 직접 작성해야 했던 경험, 있으시죠? 이 모든 것을 관리하려면 수백 줄의 코드가 필요하고, 버그도 많이 생기고, 유지보수도 힘들어집니다.

바로 이럴 때 필요한 것이 OpenAI Assistant API입니다. Assistant API는 이런 복잡한 작업들을 OpenAI가 대신 관리해주는 강력한 도구예요.

여러분은 단지 어시스턴트를 만들고, 필요한 기능을 설정해주기만 하면 됩니다.

개요

간단히 말해서, Assistant API는 OpenAI가 제공하는 '상태를 가진 AI 비서'를 만드는 서비스입니다. 기존 Chat API와 달리 대화 내용, 파일, 도구 사용을 모두 OpenAI가 관리해줍니다.

왜 이 API가 필요한지 실무 관점에서 보면, 고객 지원 챗봇, 코딩 도우미, 문서 분석 시스템 같은 복잡한 AI 서비스를 훨씬 쉽게 만들 수 있기 때문입니다. 예를 들어, 수백 페이지의 매뉴얼을 읽고 질문에 답변하는 챗봇을 만든다면, Assistant API를 사용하면 단 몇 줄의 코드로 가능합니다.

기존에는 대화 기록을 데이터베이스에 저장하고, 파일을 S3에 업로드하고, 컨텍스트를 관리하는 복잡한 시스템을 직접 구축해야 했다면, 이제는 OpenAI가 제공하는 Assistant 객체 하나로 모든 것이 해결됩니다. 이 API의 핵심 특징은 첫째, 영구적인 Thread(대화방) 관리, 둘째, 파일 업로드와 Code Interpreter 통합, 셋째, Function Calling을 통한 외부 시스템 연동입니다.

이러한 특징들이 복잡한 AI 애플리케이션을 단순하게 만들어주는 핵심입니다.

코드 예제

from openai import OpenAI

# OpenAI 클라이언트 초기화
client = OpenAI(api_key="your-api-key-here")

# Assistant 생성 - AI 비서의 성격과 능력 정의
assistant = client.beta.assistants.create(
    name="코딩 도우미",
    instructions="당신은 친절한 Python 코딩 선생님입니다. 초보자도 이해하기 쉽게 설명해주세요.",
    model="gpt-4-turbo-preview",
    tools=[{"type": "code_interpreter"}]  # 코드 실행 기능 활성화
)

print(f"Assistant 생성 완료! ID: {assistant.id}")
# 이 ID는 저장해두세요 - 나중에 재사용할 수 있습니다

설명

이것이 하는 일: 이 코드는 OpenAI 서버에 여러분만의 AI 어시스턴트를 등록하는 작업입니다. 마치 회사에 새로운 직원을 등록하는 것처럼, AI 비서의 이름, 역할, 사용할 도구를 정의합니다.

첫 번째로, OpenAI 클라이언트를 초기화합니다. 여기서 API 키는 여러분의 신분증 같은 것으로, OpenAI 대시보드에서 발급받을 수 있어요.

이 키가 있어야 OpenAI 서비스를 사용할 수 있습니다. 두 번째로, assistants.create() 메서드로 실제 어시스턴트를 만듭니다.

name은 나중에 여러 어시스턴트를 관리할 때 구분하기 위한 이름이고, instructions는 이 AI의 성격과 역할을 정의하는 가장 중요한 부분입니다. 마치 직원에게 업무 매뉴얼을 주는 것과 같아요.

세 번째로, model 파라미터로 어떤 GPT 모델을 사용할지 선택하고, tools 배열로 이 AI가 사용할 수 있는 도구를 지정합니다. code_interpreter를 활성화하면 Python 코드를 실행하고, 데이터를 분석하고, 그래프를 그릴 수 있습니다.

여러분이 이 코드를 실행하면 OpenAI 서버에 어시스턴트가 생성되고, 고유한 ID가 반환됩니다. 이 ID를 데이터베이스에 저장해두면, 다음번에는 새로 만들지 않고 기존 어시스턴트를 재사용할 수 있어요.

실무에서는 어시스턴트 생성 비용을 절약하고, 일관된 AI 성격을 유지할 수 있다는 큰 이점이 있습니다.

실전 팁

💡 Instructions는 구체적으로 작성하세요. "친절하게 답변해주세요"보다는 "초등학생도 이해할 수 있게 단계별로 설명하고, 코드 예제를 항상 포함하세요"처럼 구체적일수록 원하는 답변을 얻을 수 있습니다.

💡 어시스턴트 ID는 반드시 데이터베이스나 환경변수에 저장하세요. 매번 새로 만들면 비용이 발생하고, 이전 설정이 사라집니다.

💡 모델은 용도에 따라 선택하세요. gpt-4-turbo는 복잡한 작업에, gpt-3.5-turbo는 간단한 대화에 적합합니다. 비용과 성능의 균형을 고려하세요.

💡 tools 배열에는 code_interpreter, retrieval, function 세 가지 타입을 조합할 수 있습니다. 필요한 기능만 활성화하면 응답 속도가 빨라집니다.

💡 개발 단계에서는 같은 어시스턴트를 재사용하되, instructions를 업데이트하는 방식으로 테스트하세요. 매번 새로 만들면 관리가 어려워집니다.


2. Thread 생성하기 - 대화방 만들기

시작하며

여러분이 카카오톡으로 친구와 대화할 때를 생각해보세요. 어제 나눈 대화가 그대로 남아있고, 오늘 다시 앱을 켜도 이어서 대화할 수 있죠?

만약 매번 대화를 처음부터 다시 시작해야 한다면 정말 불편할 거예요. AI 챗봇도 마찬가지입니다.

사용자가 "내 주문 상태 알려줘"라고 물었을 때, 이전에 어떤 대화를 나눴는지 기억하지 못하면 제대로 된 답변을 할 수 없어요. 기존 Chat API에서는 이 대화 기록을 개발자가 직접 저장하고 관리해야 했습니다.

바로 이럴 때 필요한 것이 Thread입니다. Thread는 하나의 대화방이라고 생각하면 쉬워요.

사용자마다 하나의 Thread를 만들어주면, OpenAI가 알아서 모든 대화 내용을 저장하고 관리해줍니다.

개요

간단히 말해서, Thread는 Assistant와 사용자 간의 대화가 일어나는 공간입니다. 카카오톡의 채팅방, 슬랙의 채널처럼 대화 내용이 계속 쌓이는 곳이에요.

왜 Thread가 필요한지 실무 관점에서 보면, 사용자별로 독립적인 대화 컨텍스트를 유지해야 하기 때문입니다. 예를 들어, 쇼핑몰 챗봇이라면 사용자 A의 장바구니 문의와 사용자 B의 배송 문의가 섞이면 안 되겠죠?

각 사용자마다 별도의 Thread를 만들어서 관리합니다. 기존에는 대화 기록을 배열로 만들어서 매번 API에 전달했다면, 이제는 Thread ID만 전달하면 OpenAI가 자동으로 이전 대화를 불러옵니다.

여러분은 데이터베이스 설계도, 컨텍스트 관리 로직도 필요 없어요. Thread의 핵심 특징은 첫째, 영구 보관(삭제하기 전까지 계속 유지), 둘째, 무제한 메시지 저장, 셋째, 자동 컨텍스트 윈도우 관리입니다.

OpenAI가 알아서 토큰 제한에 맞게 오래된 메시지를 요약하거나 제외해주기 때문에 여러분은 신경 쓸 필요가 없어요.

코드 예제

from openai import OpenAI

client = OpenAI(api_key="your-api-key-here")

# 새로운 대화방(Thread) 생성
thread = client.beta.threads.create()

print(f"Thread 생성 완료! ID: {thread.id}")
# 예시: thread_abc123xyz...

# 이 Thread ID를 사용자 정보와 함께 데이터베이스에 저장
# 예: user_threads 테이블에 (user_id, thread_id) 저장
# 다음에 같은 사용자가 접속하면 이 Thread를 재사용

설명

이것이 하는 일: 이 코드는 OpenAI 서버에 새로운 대화방을 만드는 작업입니다. 마치 카카오톡에서 새로운 채팅방을 만드는 것과 비슷해요.

한 번 만들면 계속 사용할 수 있습니다. 첫 번째로, threads.create() 메서드를 호출하면 OpenAI 서버에 빈 Thread가 생성됩니다.

별도의 파라미터 없이도 생성할 수 있어요. 필요하다면 메타데이터를 추가해서 어떤 용도인지 표시할 수도 있습니다.

두 번째로, 생성된 Thread 객체에서 id를 추출합니다. 이 ID는 "thread_"로 시작하는 고유한 문자열이에요.

이 ID가 있어야 나중에 이 대화방에 메시지를 보내거나 대화 내용을 조회할 수 있습니다. 세 번째로, 실무에서는 이 Thread ID를 반드시 저장해야 합니다.

일반적으로 데이터베이스에 사용자 ID와 함께 저장해요. 예를 들어, 사용자 "user123"이 로그인하면 해당 사용자의 Thread ID를 조회해서 이전 대화를 이어갈 수 있습니다.

여러분이 이 패턴을 사용하면 사용자가 언제 다시 방문하더라도 이전 대화 맥락을 유지할 수 있습니다. 예를 들어, 어제 "Python 리스트"에 대해 물어봤던 사용자가 오늘 "그거 예제 더 보여줘"라고 하면, AI가 문맥을 이해하고 적절한 답변을 할 수 있어요.

이는 사용자 경험을 크게 향상시킵니다.

실전 팁

💡 사용자당 하나의 Thread를 유지하는 것이 기본 패턴입니다. user_id를 키로 하고 thread_id를 값으로 하는 매핑 테이블을 만드세요.

💡 Thread는 영구적으로 보관되므로 비용이 걱정된다면 오래된 Thread를 주기적으로 삭제하는 배치 작업을 만드세요. 예: 30일 이상 사용하지 않은 Thread 삭제.

💡 한 사용자가 여러 주제로 대화하고 싶다면 주제별로 여러 Thread를 만들 수 있습니다. 예: "코딩 질문" Thread, "일반 대화" Thread를 분리.

💡 Thread 생성 시 metadata 파라미터로 사용자 정보를 저장하면 나중에 분석할 때 유용합니다. 예: {"user_id": "123", "created_at": "2024-01-15"}.

💡 개발 중에는 테스트용 Thread가 많이 쌓이므로, 개발/프로덕션 환경을 분리하고 개발 환경의 Thread는 정기적으로 정리하세요.


3. Message 추가하기 - 대화 시작하기

시작하며

여러분이 친구에게 카카오톡 메시지를 보낼 때를 떠올려보세요. 채팅방을 열고, 메시지를 입력하고, 전송 버튼을 누르죠.

그러면 상대방이 그 메시지를 보고 답장을 합니다. AI 챗봇도 똑같은 과정이 필요합니다.

Thread를 만들었다는 것은 채팅방을 만든 것과 같아요. 하지만 아직 아무 대화도 시작하지 않은 빈 방이죠.

이제 사용자가 질문을 하면, 그 질문을 Thread에 메시지로 추가해야 합니다. 바로 이럴 때 사용하는 것이 Message API입니다.

사용자의 질문을 Thread에 추가하면, 나중에 Assistant가 그 질문을 보고 답변을 생성할 수 있습니다. 이 과정이 AI 대화의 핵심이에요.

개요

간단히 말해서, Message는 Thread에 추가되는 하나하나의 대화 내용입니다. 사용자의 질문도 Message고, AI의 답변도 Message예요.

각 Message는 누가 보냈는지(role)와 내용(content)을 가지고 있습니다. 왜 Message API가 필요한지 실무 관점에서 보면, 사용자의 입력을 받아서 AI에게 전달하는 유일한 방법이기 때문입니다.

예를 들어, 웹사이트에서 사용자가 채팅 입력창에 "Python 배우는 방법 알려줘"라고 입력하면, 이 텍스트를 Message로 만들어서 Thread에 추가해야 합니다. 기존 Chat API에서는 메시지 배열을 직접 관리했다면, Assistant API에서는 Thread에 메시지를 추가하기만 하면 됩니다.

OpenAI가 알아서 순서대로 저장하고, 토큰 제한도 관리해줍니다. Message의 핵심 특징은 첫째, role 구분(user/assistant), 둘째, 파일 첨부 가능, 셋째, 메타데이터 저장 가능입니다.

이를 통해 단순 텍스트를 넘어 파일 기반 질문도 할 수 있고, 나중에 데이터 분석을 위한 정보도 저장할 수 있어요.

코드 예제

from openai import OpenAI

client = OpenAI(api_key="your-api-key-here")

# 기존에 만든 Thread ID 사용
thread_id = "thread_abc123xyz"  # 데이터베이스에서 조회한 ID

# 사용자의 질문을 Thread에 추가
message = client.beta.threads.messages.create(
    thread_id=thread_id,
    role="user",  # 사용자가 보낸 메시지
    content="Python에서 리스트와 튜플의 차이점을 초보자도 이해하기 쉽게 설명해줘"
)

print(f"Message 추가 완료! ID: {message.id}")
# 이제 이 질문은 Thread에 영구적으로 저장됨

설명

이것이 하는 일: 이 코드는 사용자가 입력한 질문을 OpenAI 서버의 Thread에 추가하는 작업입니다. 마치 채팅방에 메시지를 보내는 것처럼, Thread라는 대화방에 새로운 메시지를 넣는 거예요.

첫 번째로, threads.messages.create() 메서드를 호출합니다. 여기서 thread_id는 앞서 만든 대화방의 ID예요.

이 ID를 통해 어느 대화방에 메시지를 넣을지 지정합니다. 실무에서는 사용자가 로그인할 때 데이터베이스에서 해당 사용자의 Thread ID를 조회해서 사용합니다.

두 번째로, role 파라미터로 누가 보낸 메시지인지 표시합니다. "user"는 사용자가 보낸 메시지, "assistant"는 AI가 보낸 답변을 의미해요.

일반적으로 사용자 입력을 추가할 때는 항상 "user"를 사용합니다. 세 번째로, content에 실제 메시지 내용을 넣습니다.

이것이 AI가 읽고 이해할 질문이에요. 메시지가 추가되면 OpenAI 서버에 영구적으로 저장되고, 나중에 Run을 실행할 때 AI가 이 메시지를 읽고 답변을 생성합니다.

여러분이 이 패턴을 사용하면 사용자와 AI 간의 자연스러운 대화 흐름을 만들 수 있습니다. 예를 들어, 사용자가 "리스트 예제 보여줘", "더 복잡한 예제는?", "이거 성능은 어때?" 같은 연속적인 질문을 하면, 각 질문을 Message로 추가하고 AI가 문맥을 이해하면서 답변합니다.

이는 단발성 질문만 처리하는 기존 챗봇보다 훨씬 자연스러운 경험을 제공합니다.

실전 팁

💡 사용자 입력을 받을 때는 반드시 검증하세요. 빈 문자열이나 너무 긴 텍스트(32,768자 제한)는 에러를 발생시킬 수 있습니다.

💡 파일 첨부가 필요하면 file_ids 파라미터를 사용하세요. 예: 사용자가 CSV 파일을 업로드하면 파일을 먼저 OpenAI에 업로드하고 그 ID를 Message에 첨부합니다.

💡 메타데이터를 활용해서 메시지 출처를 저장하면 나중에 분석할 때 유용합니다. 예: {"source": "mobile_app", "user_id": "123"}.

💡 한 번에 여러 메시지를 추가할 수 있습니다. 예를 들어, 대화 컨텍스트를 설정할 때 시스템 메시지를 먼저 추가한 후 사용자 질문을 추가할 수 있어요.

💡 Message는 삭제할 수 없으므로 민감한 정보(비밀번호, 개인정보 등)는 절대 넣지 마세요. 필요하다면 마스킹 처리 후 추가하세요.


4. Run 실행하기 - AI 답변 생성하기

시작하며

여러분이 선생님에게 질문지를 제출했다고 상상해보세요. 질문지를 냈다고 해서 선생님이 자동으로 답을 적어주는 건 아니죠.

선생님께 "이거 좀 봐주세요"라고 말해야 선생님이 질문지를 읽고 답변을 작성하기 시작합니다. Assistant API도 마찬가지예요.

Thread에 사용자의 Message를 추가했다고 해서 AI가 자동으로 답변하지 않습니다. 메시지는 그냥 Thread에 쌓여있을 뿐이죠.

AI에게 "이제 이 질문들을 보고 답변해줘"라고 명령을 내려야 합니다. 바로 이럴 때 사용하는 것이 Run입니다.

Run은 Assistant에게 Thread의 메시지들을 읽고 답변을 생성하라고 지시하는 명령이에요. Run을 실행해야 비로소 AI가 작동하기 시작합니다.

개요

간단히 말해서, Run은 Assistant가 Thread의 메시지를 읽고 답변을 생성하는 작업 프로세스입니다. 여러분이 "시작!" 버튼을 누르는 것처럼, Run을 만들면 AI가 동작하기 시작해요.

왜 Run이 필요한지 실무 관점에서 보면, AI의 실행을 제어할 수 있기 때문입니다. 예를 들어, 사용자가 메시지를 5개 연속으로 보냈는데 매번 AI가 답변하면 비용도 많이 들고 응답도 느려요.

Run을 사용하면 사용자가 "전송" 버튼을 눌렀을 때만 AI가 답변하도록 제어할 수 있습니다. 기존 Chat API에서는 API를 호출하면 즉시 응답이 왔다면, Assistant API에서는 Run을 생성하고 → 상태를 확인하고 → 완료될 때까지 기다리는 비동기 프로세스입니다.

처음엔 복잡해 보이지만, 이 방식이 더 유연하고 강력합니다. Run의 핵심 특징은 첫째, 비동기 실행(백그라운드에서 처리), 둘째, 상태 추적(queued → in_progress → completed), 셋째, 도구 사용 자동화(Code Interpreter, Function Calling 등)입니다.

AI가 복잡한 작업을 수행하는 동안 여러분의 서버는 다른 일을 할 수 있어요.

코드 예제

from openai import OpenAI
import time

client = OpenAI(api_key="your-api-key-here")

thread_id = "thread_abc123xyz"
assistant_id = "asst_abc123xyz"

# Run 시작 - Assistant에게 답변 생성 지시
run = client.beta.threads.runs.create(
    thread_id=thread_id,
    assistant_id=assistant_id
)

print(f"Run 시작! ID: {run.id}, 상태: {run.status}")

# Run이 완료될 때까지 상태 확인 (폴링)
while run.status in ["queued", "in_progress"]:
    time.sleep(1)  # 1초 대기
    run = client.beta.threads.runs.retrieve(
        thread_id=thread_id,
        run_id=run.id
    )
    print(f"현재 상태: {run.status}")

print("Run 완료!")

설명

이것이 하는 일: 이 코드는 AI에게 "Thread의 메시지들을 읽고 답변을 생성해줘"라고 명령을 내리고, 작업이 완료될 때까지 기다리는 전체 프로세스입니다. 마치 세탁기를 돌리고 완료될 때까지 상태를 확인하는 것과 비슷해요.

첫 번째로, threads.runs.create()로 새로운 Run을 시작합니다. 여기서 thread_id는 대화방 ID, assistant_id는 어느 AI를 사용할지 지정하는 ID예요.

이 메서드를 호출하면 OpenAI 서버가 백그라운드에서 AI를 실행하기 시작합니다. 두 번째로, Run이 생성되면 초기 상태는 "queued"(대기 중)입니다.

OpenAI 서버에서 여러 요청을 처리하기 때문에 잠시 대기할 수 있어요. 곧 "in_progress"(진행 중)로 바뀌고, AI가 실제로 메시지를 읽고 답변을 생성합니다.

세 번째로, while 루프로 Run의 상태를 계속 확인합니다. runs.retrieve()로 최신 상태를 가져오고, 1초마다 확인해요.

이런 방식을 "폴링(polling)"이라고 하는데, 작업이 완료될 때까지 반복적으로 체크하는 거예요. 실무에서는 1-2초 간격이 적당합니다.

네 번째로, 상태가 "completed"가 되면 while 루프를 빠져나옵니다. 이제 AI의 답변이 Thread에 새로운 Message로 추가된 상태예요.

만약 "failed"나 "expired" 상태가 되면 에러 처리를 해야 합니다. 여러분이 이 패턴을 사용하면 AI가 복잡한 작업(코드 실행, 파일 분석 등)을 수행하는 동안 서버가 블로킹되지 않습니다.

예를 들어, AI가 대용량 CSV 파일을 분석하는 데 30초가 걸려도, 여러분의 서버는 다른 사용자의 요청을 계속 처리할 수 있어요. 이는 확장성 있는 AI 서비스를 만드는 핵심 패턴입니다.

실전 팁

💡 프로덕션 환경에서는 무한 루프를 방지하기 위해 최대 대기 시간을 설정하세요. 예: 60초 후에도 완료되지 않으면 타임아웃 처리.

💡 폴링 대신 Webhook을 사용하면 더 효율적입니다. OpenAI가 Run 완료 시 여러분의 서버로 알림을 보내주므로 폴링이 필요 없어요.

💡 Run 상태가 "requires_action"이면 Function Calling이 필요한 상황입니다. 이때는 함수를 실행하고 결과를 제출해야 합니다.

💡 여러 사용자의 Run을 동시에 처리할 때는 비동기 프로그래밍(asyncio, celery 등)을 사용하세요. 폴링이 서버 리소스를 많이 사용하므로 백그라운드 작업으로 분리하는 게 좋습니다.

💡 Run이 실패하면 run.last_error를 확인해서 원인을 파악하세요. 토큰 제한 초과, 잘못된 Assistant ID 등 다양한 원인이 있을 수 있습니다.


5. 답변 조회하기 - AI의 응답 가져오기

시작하며

여러분이 친구에게 카톡을 보내고 답장이 왔다는 알림을 받았다고 생각해보세요. 알림만 받고 채팅방을 안 열면 답장 내용을 볼 수 없죠?

채팅방을 열어야 친구가 뭐라고 답했는지 확인할 수 있습니다. AI Assistant도 마찬가지예요.

Run이 완료되었다는 것은 AI가 답변을 다 작성했다는 의미지만, 그 답변 내용이 자동으로 여러분에게 오는 건 아닙니다. Thread에 새로운 Message로 추가되어 있을 뿐이죠.

바로 이럴 때 필요한 것이 Messages 조회 API입니다. Thread에서 메시지 목록을 가져와서, 가장 최신 메시지(AI의 답변)를 읽어오면 됩니다.

이 과정이 AI 대화의 마지막 단계예요.

개요

간단히 말해서, Messages 조회는 Thread에 쌓인 대화 내용을 가져오는 작업입니다. 사용자의 질문도, AI의 답변도 모두 Message 형태로 Thread에 저장되어 있으니, 이를 조회해서 화면에 보여주면 됩니다.

왜 Messages 조회가 필요한지 실무 관점에서 보면, 사용자에게 AI의 답변을 보여주는 것뿐 아니라 전체 대화 기록을 표시하기 위해서도 필요합니다. 예를 들어, 카카오톡처럼 스크롤을 올리면 이전 대화가 보이는 UI를 만들려면 Thread의 모든 Message를 조회해야 해요.

기존 Chat API에서는 메시지 배열을 직접 관리했기 때문에 조회가 필요 없었다면, Assistant API에서는 OpenAI가 메시지를 관리하므로 API로 조회해야 합니다. 하지만 그 덕분에 여러분은 데이터베이스를 운영할 필요가 없어요.

Messages 조회의 핵심 특징은 첫째, 시간 역순 정렬(최신 메시지가 먼저), 둘째, 페이지네이션 지원(limit, after 파라미터), 셋째, role 필터링(user/assistant 구분) 가능입니다. 이를 통해 효율적으로 대화 기록을 관리할 수 있어요.

코드 예제

from openai import OpenAI

client = OpenAI(api_key="your-api-key-here")

thread_id = "thread_abc123xyz"

# Thread의 메시지 목록 조회 (최신 순)
messages = client.beta.threads.messages.list(
    thread_id=thread_id,
    order="desc",  # 최신 메시지부터
    limit=20  # 최근 20개만
)

# 가장 최신 메시지(AI 답변) 출력
for message in messages.data:
    role = message.role  # "user" 또는 "assistant"
    content = message.content[0].text.value  # 메시지 내용

    print(f"{role}: {content}\n")

    if role == "assistant":
        # 최신 AI 답변을 사용자에게 반환
        ai_response = content
        break

설명

이것이 하는 일: 이 코드는 Thread에 저장된 메시지들을 조회해서, 가장 최신 AI 답변을 찾아내는 작업입니다. 마치 카카오톡 채팅방을 열어서 최신 메시지를 확인하는 것과 같아요.

첫 번째로, threads.messages.list()로 Thread의 메시지 목록을 가져옵니다. order="desc"는 최신 메시지부터 가져오라는 의미예요.

limit=20은 최근 20개만 가져오는데, 이렇게 하면 대화가 길어져도 성능에 문제가 없습니다. 두 번째로, 반환된 messages.data는 배열 형태로, 각 항목이 하나의 Message 객체입니다.

최신 메시지가 배열의 첫 번째에 있어요. 이 배열을 순회하면서 각 메시지의 role과 content를 확인합니다.

세 번째로, message.content는 배열 형태인데, 보통 첫 번째 요소가 텍스트 내용입니다. content[0].text.value로 실제 메시지 문자열을 추출할 수 있어요.

만약 이미지나 파일이 포함되어 있다면 content 배열에 여러 요소가 있을 수 있습니다. 네 번째로, for 루프에서 role이 "assistant"인 메시지를 찾으면, 그것이 가장 최신 AI 답변입니다.

이 답변을 변수에 저장하고 break로 루프를 빠져나와요. 그리고 이 답변을 웹 페이지나 모바일 앱에 표시하면 사용자가 AI의 응답을 볼 수 있습니다.

여러분이 이 패턴을 사용하면 실시간 채팅 UI를 쉽게 구현할 수 있습니다. 예를 들어, 사용자가 질문을 보내면 → Run을 실행하고 → Run이 완료되면 → Messages를 조회해서 AI 답변을 화면에 추가하는 흐름이에요.

이는 Slack, Discord 같은 채팅 앱과 동일한 사용자 경험을 제공합니다.

실전 팁

💡 실무에서는 마지막으로 조회한 메시지 ID를 저장해두고, after 파라미터로 그 이후 메시지만 가져오면 효율적입니다. 매번 전체 대화를 다시 가져올 필요가 없어요.

💡 content는 배열이므로 항상 길이를 확인하세요. 빈 content가 있을 수 있고, 여러 타입(text, image_file 등)이 섞여 있을 수 있습니다.

💡 대화가 매우 길면 페이지네이션을 구현하세요. limit을 20으로 하고, "더보기" 버튼을 클릭하면 has_more를 확인해서 추가 메시지를 로드합니다.

💡 Messages는 캐싱하면 성능이 크게 향상됩니다. Redis나 메모리에 최근 메시지를 저장해두고, Run이 완료될 때만 새로 조회하세요.

💡 메시지에 파일이 첨부되어 있으면 file_ids를 확인하고, 별도로 파일을 다운로드해야 합니다. 이미지라면 화면에 표시할 수 있어요.


6. Function Calling 사용하기 - AI가 함수 호출하기

시작하며

여러분이 식당에서 점원에게 "테이블 예약해줘"라고 말했다고 상상해보세요. 점원은 예약 시스템에 접근해서 실제로 테이블을 예약해야 하죠.

하지만 AI는 기본적으로 텍스트만 생성할 수 있고, 실제 시스템을 조작할 수 없어요. 만약 AI 챗봇이 "오늘 날씨 알려줘"라는 질문에 답하려면 실제 날씨 API를 호출해야 합니다.

또는 "내 주문 상태 확인해줘"라면 데이터베이스를 조회해야 하죠. AI 혼자서는 이런 작업을 할 수 없어요.

바로 이럴 때 필요한 것이 Function Calling입니다. AI가 "이 함수를 호출해야겠다"고 판단하면, 여러분에게 어떤 함수를 어떤 파라미터로 호출해야 하는지 알려줍니다.

그러면 여러분이 실제로 함수를 실행하고 결과를 AI에게 돌려주는 거예요.

개요

간단히 말해서, Function Calling은 AI가 외부 시스템(API, 데이터베이스, 서비스 등)과 상호작용할 수 있게 해주는 메커니즘입니다. AI는 함수 호출이 필요하다고 판단하고, 여러분이 실제 함수를 실행하고, 결과를 AI에게 전달하는 협업 과정이에요.

왜 Function Calling이 필요한지 실무 관점에서 보면, AI의 능력을 무한대로 확장할 수 있기 때문입니다. 예를 들어, 쇼핑몰 챗봇이라면 "상품 검색", "주문 조회", "배송 추적", "환불 요청" 같은 실제 비즈니스 로직과 연결해야 해요.

Function Calling이 바로 그 다리 역할을 합니다. 기존에는 AI의 답변을 파싱해서 "주문 조회"라는 키워드를 찾고, 별도 로직을 실행했다면, 이제는 AI가 구조화된 JSON 형태로 정확히 어떤 함수를 호출해야 하는지 알려줍니다.

훨씬 정확하고 안정적이에요. Function Calling의 핵심 특징은 첫째, 함수 스키마 정의(이름, 설명, 파라미터), 둘째, AI의 자동 함수 선택(여러 함수 중 적절한 것 선택), 셋째, 파라미터 자동 추출(사용자 질문에서 필요한 값 추출)입니다.

AI가 마치 개발자처럼 적절한 함수를 판단하고 호출해요.

코드 예제

from openai import OpenAI
import json

client = OpenAI(api_key="your-api-key-here")

# Assistant 생성 시 사용 가능한 함수 정의
assistant = client.beta.assistants.create(
    name="날씨 봇",
    instructions="사용자의 위치에 맞는 날씨 정보를 제공합니다.",
    model="gpt-4-turbo-preview",
    tools=[{
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "특정 도시의 현재 날씨를 조회합니다",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "도시 이름 (예: 서울, 부산)"
                    }
                },
                "required": ["city"]
            }
        }
    }]
)

설명

이것이 하는 일: 이 코드는 AI가 사용할 수 있는 함수를 정의하는 작업입니다. 마치 직원에게 업무 매뉴얼을 주는 것처럼, AI에게 "이런 함수가 있고, 이렇게 사용하면 돼"라고 알려주는 거예요.

첫 번째로, Assistant를 만들 때 tools 배열에 함수를 정의합니다. type: "function"은 이것이 함수 도구라는 의미예요.

여러 함수를 배열로 추가할 수 있습니다. 두 번째로, function 객체에서 함수의 이름(name), 설명(description), 파라미터(parameters)를 상세히 정의합니다.

description은 매우 중요한데, AI가 이 설명을 보고 언제 이 함수를 사용해야 할지 판단하기 때문이에요. "현재 날씨를 조회합니다"처럼 명확하게 작성해야 합니다.

세 번째로, parameters는 JSON Schema 형식으로 작성합니다. 여기서는 city라는 문자열 파라미터를 정의했어요.

required 배열에 넣으면 필수 파라미터가 됩니다. AI는 사용자 질문에서 "서울"이라는 단어를 찾아서 자동으로 city: "서울"로 매핑합니다.

네 번째로, 실제 Run을 실행하면 AI가 "오늘 서울 날씨 어때?"라는 질문을 보고 get_weather 함수를 호출해야겠다고 판단합니다. 그러면 Run 상태가 "requires_action"이 되고, required_action 필드에 어떤 함수를 어떤 파라미터로 호출해야 하는지 JSON으로 알려줘요.

여러분이 이 패턴을 사용하면 AI를 실제 비즈니스 로직과 연결할 수 있습니다. 예를 들어, "내 계좌 잔액 알려줘" → get_account_balance(user_id=123) 호출 → 실제 은행 API에서 잔액 조회 → 결과를 AI에게 전달 → AI가 자연어로 "현재 잔액은 50만원입니다" 라고 답변하는 전체 플로우가 가능해집니다.

실전 팁

💡 함수 description은 매우 상세하게 작성하세요. "날씨 조회" 보다는 "사용자가 특정 도시의 현재 날씨, 온도, 습도를 물어볼 때 사용합니다"처럼 구체적으로 작성하면 AI가 정확하게 판단합니다.

💡 민감한 작업(결제, 삭제 등)은 함수로 노출하지 마세요. AI가 잘못 판단해서 호출할 수 있습니다. 읽기 전용 작업만 함수로 만드는 게 안전해요.

💡 파라미터 타입과 검증을 철저히 하세요. AI가 전달한 파라미터를 그대로 신뢰하지 말고, 서버에서 한번 더 검증하는 게 좋습니다.

💡 함수 실행 결과는 문자열로 변환해서 AI에게 전달하세요. JSON이나 객체를 그대로 보내지 말고, "잔액: 50만원" 처럼 자연어로 변환하면 AI가 더 잘 이해합니다.

💡 함수가 많아지면 AI가 혼란스러워할 수 있습니다. 5-10개 정도로 제한하고, 비슷한 기능은 하나의 함수로 통합하세요.


7. Function 결과 제출하기 - 함수 실행 후 AI에게 알리기

시작하며

여러분이 직장에서 상사에게 "이 자료 좀 조사해줘"라는 지시를 받았다고 상상해보세요. 열심히 조사해서 자료를 찾았는데, 그냥 책상에 놓고 말을 안 하면 상사는 모르겠죠?

"조사 완료했습니다, 결과는 이렇습니다"라고 보고해야 상사가 다음 작업을 진행할 수 있어요. AI Function Calling도 똑같습니다.

AI가 "get_weather 함수를 호출해줘"라고 요청하면, 여러분이 실제로 날씨 API를 호출하고 결과를 받아옵니다. 하지만 그 결과를 AI에게 전달하지 않으면 AI는 계속 기다리기만 해요.

바로 이럴 때 필요한 것이 Function 결과 제출입니다. 함수 실행 결과를 AI에게 돌려주면, AI가 그 정보를 바탕으로 최종 답변을 생성합니다.

이 과정이 Function Calling의 마지막 단계예요.

개요

간단히 말해서, Function 결과 제출은 여러분이 실행한 함수의 결과를 AI에게 전달하는 작업입니다. AI는 이 결과를 보고 사용자에게 자연어로 된 최종 답변을 만들어냅니다.

왜 이 과정이 필요한지 실무 관점에서 보면, AI와 여러분의 시스템이 협업하는 완전한 사이클을 완성하기 위해서입니다. 예를 들어, 사용자가 "오늘 서울 날씨 어때?"라고 물으면 → AI가 get_weather 함수 호출 요청 → 여러분이 날씨 API 호출 → "맑음, 20도" 결과를 AI에게 전달 → AI가 "오늘 서울은 맑고 기온은 20도입니다"라고 자연스럽게 답변합니다.

기존에는 함수 결과를 직접 문자열로 만들어서 사용자에게 보여줬다면, 이제는 AI가 결과를 해석하고 문맥에 맞게 자연어로 변환해줍니다. 훨씬 사용자 친화적인 답변이 만들어져요.

Function 결과 제출의 핵심 특징은 첫째, tool_outputs 형태로 제출(함수별 결과 매핑), 둘째, Run 재개(결과 제출 후 AI가 다시 작동), 셋째, 여러 함수 결과 동시 제출 가능입니다. 복잡한 작업도 한 번에 처리할 수 있어요.

코드 예제

from openai import OpenAI

client = OpenAI(api_key="your-api-key-here")

thread_id = "thread_abc123xyz"
run_id = "run_abc123xyz"

# Run 상태 확인 - requires_action인 경우
run = client.beta.threads.runs.retrieve(thread_id=thread_id, run_id=run_id)

if run.status == "requires_action":
    # AI가 요청한 함수 정보 추출
    tool_call = run.required_action.submit_tool_outputs.tool_calls[0]
    function_name = tool_call.function.name  # "get_weather"
    arguments = json.loads(tool_call.function.arguments)  # {"city": "서울"}

    # 실제 함수 실행 (예시)
    weather_result = "맑음, 기온 20도, 습도 60%"

    # 함수 결과를 AI에게 제출
    run = client.beta.threads.runs.submit_tool_outputs(
        thread_id=thread_id,
        run_id=run_id,
        tool_outputs=[{
            "tool_call_id": tool_call.id,
            "output": weather_result
        }]
    )

print("함수 결과 제출 완료! AI가 최종 답변 생성 중...")

설명

이것이 하는 일: 이 코드는 AI가 요청한 함수를 실제로 실행하고, 그 결과를 AI에게 돌려주는 전체 프로세스입니다. 마치 상사의 지시를 받아 조사하고 보고하는 것과 같아요.

첫 번째로, Run의 상태를 확인해서 "requires_action"인지 체크합니다. 이 상태는 AI가 "이 함수를 실행해줘"라고 요청하는 상태예요.

required_action 필드에 어떤 함수를 어떤 파라미터로 호출해야 하는지 상세 정보가 들어있습니다. 두 번째로, tool_calls 배열에서 함수 정보를 추출합니다.

보통 첫 번째 요소가 호출할 함수인데, 여러 함수를 동시에 요청할 수도 있어요. function.name은 함수 이름(예: "get_weather"), function.arguments는 JSON 문자열로 파라미터가 들어있습니다.

세 번째로, 추출한 정보를 바탕으로 실제 함수를 실행합니다. 예제에서는 간단히 하드코딩했지만, 실무에서는 실제 날씨 API를 호출하거나 데이터베이스를 조회해요.

중요한 건 결과를 문자열 형태로 만드는 거예요. 네 번째로, submit_tool_outputs로 함수 결과를 AI에게 제출합니다.

tool_outputs 배열에 각 함수별 결과를 넣는데, tool_call_id로 어떤 함수의 결과인지 매핑하고, output에 실제 결과 문자열을 넣습니다. 여러 함수를 호출했다면 배열에 여러 개를 넣을 수 있어요.

다섯 번째로, 결과를 제출하면 Run이 자동으로 재개됩니다. AI는 제출된 결과를 읽고, "오늘 서울 날씨는 맑고 기온은 20도입니다"처럼 자연스러운 문장으로 최종 답변을 만들어요.

이 답변은 Thread에 새로운 Message로 추가됩니다. 여러분이 이 패턴을 사용하면 AI가 실시간 데이터를 활용해서 답변할 수 있습니다.

예를 들어, "우리 재고에 iPhone 15 있어?" → AI가 재고 조회 함수 요청 → 실제 데이터베이스 조회 → "재고 5개 있습니다" 결과 제출 → AI가 "네, iPhone 15는 현재 5개 재고가 있습니다" 라고 자연스럽게 답변하는 완전한 AI 비서를 만들 수 있습니다.

실전 팁

💡 함수 결과는 가능한 자연어로 변환하세요. JSON이나 숫자만 전달하면 AI가 오해할 수 있어요. "재고: 5개, 가격: 120만원" 처럼 명확하게 작성하세요.

💡 에러가 발생하면 에러 메시지도 output에 포함하세요. "날씨 정보를 가져올 수 없습니다: API 서버 오류" 처럼 전달하면 AI가 사용자에게 적절히 안내합니다.

💡 tool_call_id를 정확히 매핑하는 게 중요합니다. 여러 함수를 호출했을 때 결과가 뒤섞이면 AI가 잘못된 답변을 생성할 수 있어요.

💡 함수 실행 시간이 오래 걸리면 비동기로 처리하세요. 웹훅을 사용하거나 백그라운드 작업으로 분리하면 서버가 블로킹되지 않습니다.

💡 함수 결과가 너무 길면 요약해서 전달하세요. 1000자 이상의 긴 결과는 토큰을 많이 소모하고 AI가 핵심을 놓칠 수 있습니다. 중요한 정보만 추출하세요.


8. Code Interpreter 사용하기 - AI가 코드 실행하기

시작하며

여러분이 복잡한 데이터 분석을 해야 하는데, Python 코드를 직접 짜기는 어렵고, 계산기로는 불가능한 상황을 생각해보세요. 예를 들어, 1000개의 매출 데이터에서 평균, 표준편차를 구하고, 그래프까지 그려야 한다면 어떻게 하시겠어요?

기존에는 데이터 분석가에게 의뢰하거나, 직접 Python을 배워서 코드를 작성해야 했습니다. 시간도 오래 걸리고, 전문 지식도 필요했죠.

하지만 이제는 AI에게 "이 데이터 분석해줘"라고 말만 하면 됩니다. 바로 이럴 때 필요한 것이 Code Interpreter입니다.

AI가 Python 코드를 직접 작성하고, 실행하고, 결과를 분석해서 여러분에게 알려줍니다. 데이터 분석, 수식 계산, 그래프 생성 등이 모두 가능해요.

개요

간단히 말해서, Code Interpreter는 AI가 Python 코드를 샌드박스 환경에서 실행할 수 있는 기능입니다. 여러분이 파일을 업로드하면 AI가 그 파일을 읽고, 코드를 작성하고, 실행해서 결과를 보여줍니다.

왜 이 기능이 필요한지 실무 관점에서 보면, 복잡한 계산이나 데이터 처리를 AI에게 맡길 수 있기 때문입니다. 예를 들어, CSV 파일을 업로드하면 AI가 pandas로 데이터를 읽고, 분석하고, matplotlib으로 그래프를 그려줘요.

여러분은 코드를 한 줄도 쓸 필요가 없습니다. 기존에는 AI가 코드를 보여주기만 하고 실행은 여러분이 직접 해야 했다면, 이제는 AI가 실행까지 다 해줍니다.

실행 결과를 보고 코드를 수정하고 다시 실행하는 반복 작업도 AI가 스스로 합니다. Code Interpreter의 핵심 특징은 첫째, 파일 업로드/다운로드 지원(CSV, Excel, 이미지 등), 둘째, numpy, pandas, matplotlib 등 주요 라이브러리 사전 설치, 셋째, 자동 오류 수정(코드 에러 발생 시 AI가 스스로 수정)입니다.

마치 전문 데이터 분석가를 고용한 것 같은 효과예요.

코드 예제

from openai import OpenAI

client = OpenAI(api_key="your-api-key-here")

# CSV 파일 업로드
with open("sales_data.csv", "rb") as file:
    uploaded_file = client.files.create(
        file=file,
        purpose="assistants"
    )

# Code Interpreter가 활성화된 Assistant 생성
assistant = client.beta.assistants.create(
    name="데이터 분석가",
    instructions="CSV 데이터를 분석하고 시각화합니다.",
    model="gpt-4-turbo-preview",
    tools=[{"type": "code_interpreter"}]
)

# Thread 생성하고 파일과 함께 질문
thread = client.beta.threads.create()
message = client.beta.threads.messages.create(
    thread_id=thread.id,
    role="user",
    content="이 파일의 월별 매출 평균을 구하고 그래프로 그려줘",
    file_ids=[uploaded_file.id]  # 파일 첨부
)

# Run 실행 - AI가 코드를 작성하고 실행
run = client.beta.threads.runs.create(
    thread_id=thread.id,
    assistant_id=assistant.id
)

설명

이것이 하는 일: 이 코드는 파일을 업로드하고, AI가 그 파일을 Python 코드로 분석하도록 지시하는 전체 프로세스입니다. 마치 데이터 분석가에게 엑셀 파일을 주고 "이거 분석해줘"라고 말하는 것과 같아요.

첫 번째로, files.create()로 로컬 파일을 OpenAI 서버에 업로드합니다. purpose="assistants"는 이 파일을 Assistant API에서 사용하겠다는 의미예요.

CSV, Excel, JSON, 이미지 등 다양한 형식을 지원합니다. 두 번째로, Assistant를 만들 때 tools에 "code_interpreter"를 추가합니다.

이렇게 하면 이 Assistant가 Python 코드를 실행할 수 있는 권한을 얻어요. 코드 실행 환경은 OpenAI가 샌드박스로 관리하기 때문에 보안 걱정은 없습니다.

세 번째로, Thread를 만들고 Message를 추가할 때 file_ids 파라미터로 업로드한 파일을 첨부합니다. 이렇게 하면 AI가 파일에 접근할 수 있어요.

여러 파일을 배열로 첨부할 수도 있습니다. 네 번째로, Run을 실행하면 AI가 질문을 읽고 "아, 월별 매출 평균을 구하고 그래프를 그려야겠네" 라고 이해합니다.

그리고 Python 코드를 작성해요. 예를 들어, pandas.read_csv()로 파일을 읽고, groupby()로 월별 집계를 하고, matplotlib.pyplot으로 그래프를 그립니다.

다섯 번째로, AI가 작성한 코드가 자동으로 실행됩니다. 만약 에러가 발생하면 AI가 에러 메시지를 보고 코드를 수정한 후 다시 실행해요.

이 과정이 성공할 때까지 반복됩니다. 최종적으로 그래프가 생성되면 이미지 파일로 저장되고, AI의 답변에 파일 ID가 포함됩니다.

여러분이 이 패턴을 사용하면 전문 지식 없이도 복잡한 데이터 작업을 처리할 수 있습니다. 예를 들어, 고객 데이터 CSV를 업로드하고 "이탈률 높은 고객 특징을 분석해줘"라고 하면, AI가 통계 분석, 상관관계 계산, 시각화까지 모두 해줍니다.

이는 비즈니스 의사결정을 훨씬 빠르게 만들어줘요.

실전 팁

💡 파일 크기는 512MB까지 지원되지만, 큰 파일은 처리 시간이 오래 걸립니다. 가능하면 100MB 이하로 유지하세요.

💡 생성된 그래프나 결과 파일은 file_ids로 반환됩니다. files.content() API로 다운로드해서 사용자에게 보여줄 수 있어요.

💡 복잡한 분석을 요청할 때는 단계별로 나누어 질문하세요. "데이터 요약해줘" → "이상치 제거해줘" → "그래프 그려줘" 순서로 하면 더 정확합니다.

💡 AI가 작성한 코드를 확인하고 싶다면 Messages를 조회할 때 코드 블록이 포함되어 있습니다. 검토나 재사용이 가능해요.

💡 민감한 데이터는 업로드 전에 익명화하세요. OpenAI 서버에 파일이 저장되므로, 개인정보나 기밀 정보는 마스킹 처리 후 업로드하는 게 안전합니다.


9. File Search 사용하기 - AI가 문서 검색하기

시작하며

여러분이 회사의 500페이지짜리 업무 매뉴얼에서 특정 정보를 찾아야 한다고 상상해보세요. 처음부터 끝까지 다 읽기엔 너무 오래 걸리고, 검색 기능으로 키워드를 찾아도 관련 내용이 여러 곳에 흩어져 있어서 파악하기 어렵습니다.

또는 수십 개의 PDF 계약서 중에서 "위약금 조항"을 찾아야 하는 상황이라면? 일일이 파일을 열어보고, 읽고, 비교하는 작업은 정말 시간 낭비죠.

이런 문서 검색 작업을 AI에게 맡길 수 있다면 얼마나 편할까요? 바로 이럴 때 필요한 것이 File Search(이전 Retrieval) 기능입니다.

여러 문서를 업로드하면 AI가 알아서 문서를 읽고, 임베딩하고, 질문에 맞는 관련 내용을 찾아서 답변해줍니다. 마치 전문 사서를 고용한 것 같아요.

개요

간단히 말해서, File Search는 AI가 업로드된 문서들을 검색하고 관련 정보를 찾아 답변하는 기능입니다. RAG(Retrieval Augmented Generation) 기술을 사용해서, 문서의 관련 부분만 찾아서 답변에 활용합니다.

왜 이 기능이 필요한지 실무 관점에서 보면, 대용량 문서 기반 질의응답 시스템을 쉽게 구축할 수 있기 때문입니다. 예를 들어, 고객 지원 챗봇에 제품 매뉴얼 100개를 업로드하면, "A 제품 설치 방법"같은 질문에 정확한 페이지를 찾아서 답변해줍니다.

기존에는 문서를 직접 파싱하고, 벡터 DB를 구축하고, 임베딩 생성하고, 유사도 검색 로직을 만들어야 했다면, 이제는 파일만 업로드하면 OpenAI가 모든 것을 자동으로 처리해줍니다. 개발 시간이 몇 주에서 몇 시간으로 단축돼요.

File Search의 핵심 특징은 첫째, 다중 파일 지원(최대 10,000개), 둘째, 자동 청킹과 임베딩(OpenAI가 문서를 최적 크기로 분할), 셋째, 출처 표시(답변이 어느 문서의 어느 부분에서 왔는지 표시)입니다. 신뢰성 있는 문서 기반 답변이 가능해요.

코드 예제

from openai import OpenAI

client = OpenAI(api_key="your-api-key-here")

# 문서 파일들 업로드
file1 = client.files.create(
    file=open("product_manual.pdf", "rb"),
    purpose="assistants"
)
file2 = client.files.create(
    file=open("faq.pdf", "rb"),
    purpose="assistants"
)

# Vector Store 생성 - 문서들을 모아두는 저장소
vector_store = client.beta.vector_stores.create(
    name="제품 문서",
    file_ids=[file1.id, file2.id]
)

# File Search가 활성화된 Assistant 생성
assistant = client.beta.assistants.create(
    name="제품 지원 봇",
    instructions="업로드된 문서를 참고하여 제품 관련 질문에 답변합니다.",
    model="gpt-4-turbo-preview",
    tools=[{"type": "file_search"}],
    tool_resources={
        "file_search": {
            "vector_store_ids": [vector_store.id]
        }
    }
)

설명

이것이 하는 일: 이 코드는 여러 문서 파일을 업로드하고, AI가 그 문서들을 검색해서 답변할 수 있도록 설정하는 작업입니다. 마치 도서관에 책을 정리하고, 사서에게 "이 책들을 참고해서 질문에 답해줘"라고 말하는 것과 같아요.

첫 번째로, files.create()로 PDF, DOCX, TXT 등의 문서 파일을 OpenAI 서버에 업로드합니다. 각 파일마다 고유한 file ID가 생성되는데, 이걸 다음 단계에서 사용해요.

수백 개의 파일을 업로드할 수 있습니다. 두 번째로, vector_stores.create()로 Vector Store를 만듭니다.

이건 문서들을 모아두는 저장소 같은 거예요. 여기에 파일 ID들을 넣으면 OpenAI가 자동으로 문서를 읽고, 작은 조각(chunk)으로 나누고, 각 조각을 벡터로 변환(임베딩)해서 저장합니다.

이 과정은 몇 분 정도 걸릴 수 있어요. 세 번째로, Assistant를 만들 때 tools에 "file_search"를 추가하고, tool_resources로 어느 Vector Store를 사용할지 지정합니다.

이렇게 하면 이 Assistant가 해당 문서들을 검색할 수 있는 능력을 얻게 됩니다. 네 번째로, 사용자가 질문하면 AI가 자동으로 Vector Store에서 관련 문서 조각을 찾습니다.

예를 들어, "제품 A 보증 기간은?"이라고 물으면, AI가 "보증", "제품 A" 같은 키워드로 관련 문서 부분을 찾아서 읽고, 그 내용을 바탕으로 답변을 생성해요. 다섯 번째로, AI의 답변에는 어느 파일의 어느 부분에서 정보를 가져왔는지 출처가 포함됩니다.

annotations 필드에 파일 ID와 인용 텍스트가 들어있어서, 사용자가 원본 문서를 확인할 수 있어요. 이는 답변의 신뢰성을 크게 높여줍니다.

여러분이 이 패턴을 사용하면 방대한 문서 기반 지식베이스를 쉽게 구축할 수 있습니다. 예를 들어, 법률 상담 챗봇에 수천 개의 판례 문서를 업로드하면, "부동산 계약 위반 시 손해배상 기준은?"같은 전문적인 질문에도 정확한 답변을 할 수 있어요.

개발자가 법률 전문가가 아니어도 전문가 수준의 서비스를 만들 수 있습니다.

실전 팁

💡 Vector Store는 재사용하세요. 한 번 만들면 여러 Assistant에서 공유할 수 있어서 비용과 시간을 절약할 수 있습니다.

💡 파일이 많으면 업로드와 임베딩에 시간이 걸립니다. 배치 작업으로 미리 처리해두고, 실시간 서비스에서는 준비된 Vector Store를 사용하세요.

💡 문서가 업데이트되면 Vector Store에서 기존 파일을 삭제하고 새 파일을 추가해야 합니다. 자동화 스크립트를 만들어두면 편리해요.

💡 답변에 출처를 표시하려면 message.content[0].text.annotations를 확인하세요. 여기에 파일 이름과 인용 텍스트가 있어서 사용자에게 보여줄 수 있습니다.

💡 검색 정확도를 높이려면 문서에 메타데이터를 추가하세요. 예: 파일 업로드 시 {"category": "manual", "product": "A"}처럼 정보를 넣으면 나중에 필터링할 수 있습니다.


10. Streaming 응답받기 - 실시간으로 답변 표시하기

시작하며

여러분이 ChatGPT를 사용할 때를 떠올려보세요. 질문을 보내면 답변이 한 글자씩 타이핑되듯이 나타나죠?

만약 전체 답변이 완성될 때까지 아무것도 안 보이고 기다려야 한다면, 정말 답답할 거예요. 특히 긴 답변은 1-2분씩 걸릴 수도 있으니까요.

Assistant API를 사용할 때도 마찬가지입니다. Run이 완료될 때까지 기다렸다가 한 번에 답변을 보여주면, 사용자는 "혹시 멈춘 건 아닐까?"하고 불안해할 수 있어요.

실시간으로 답변이 생성되는 걸 보여주는 게 훨씬 좋은 사용자 경험입니다. 바로 이럴 때 필요한 것이 Streaming입니다.

AI가 답변을 생성하는 동안 실시간으로 텍스트 조각을 받아서 화면에 표시할 수 있어요. ChatGPT처럼 자연스러운 타이핑 효과를 만들 수 있습니다.

개요

간단히 말해서, Streaming은 AI의 응답을 완성될 때까지 기다리지 않고, 생성되는 즉시 조금씩 받아오는 방식입니다. Server-Sent Events(SSE) 프로토콜을 사용해서 서버가 클라이언트에게 실시간으로 데이터를 푸시합니다.

왜 Streaming이 필요한지 실무 관점에서 보면, 사용자 경험이 크게 개선되기 때문입니다. 예를 들어, 긴 에세이나 코드 설명처럼 답변이 길 때, 바로바로 내용이 보이면 사용자가 먼저 읽기 시작할 수 있어요.

전체 답변을 기다릴 필요가 없습니다. 기존 방식에서는 Run이 완료될 때까지 폴링하면서 기다렸다면, Streaming에서는 Run을 시작하자마자 텍스트가 흘러나오기 시작합니다.

응답 시간이 체감상 훨씬 빨라지고, 서버 부하도 줄어들어요(폴링 횟수 감소). Streaming의 핵심 특징은 첫째, 실시간 텍스트 델타(text delta) 수신, 둘째, 이벤트 기반 처리(thread.message.delta, thread.run.completed 등), 셋째, 도구 사용 중간 상태 확인 가능입니다.

AI가 Code Interpreter를 실행하는 과정도 실시간으로 볼 수 있어요.

코드 예제

from openai import OpenAI

client = OpenAI(api_key="your-api-key-here")

thread_id = "thread_abc123xyz"
assistant_id = "asst_abc123xyz"

# Streaming으로 Run 실행
with client.beta.threads.runs.stream(
    thread_id=thread_id,
    assistant_id=assistant_id
) as stream:
    for event in stream:
        # 텍스트 델타 이벤트 - 답변이 조금씩 생성됨
        if event.event == "thread.message.delta":
            delta = event.data.delta.content[0].text.value
            print(delta, end="", flush=True)  # 실시간 출력

        # Run 완료 이벤트
        elif event.event == "thread.run.completed":
            print("\n\n답변 완료!")

print("Streaming 종료")

설명

이것이 하는 일: 이 코드는 AI 답변을 한 번에 받는 대신, 생성되는 즉시 조금씩 받아서 실시간으로 화면에 표시하는 작업입니다. 마치 누군가가 타이핑하는 걸 지켜보는 것처럼, 자연스러운 대화 경험을 만들어요.

첫 번째로, threads.runs.stream()을 사용하면 일반 create() 대신 스트림 객체를 반환합니다. with 문을 사용해서 자동으로 연결을 관리하는 게 좋아요.

스트림이 열리면 이벤트가 실시간으로 흘러들어오기 시작합니다. 두 번째로, for event in stream으로 이벤트를 하나씩 받습니다.

이벤트는 여러 종류가 있는데, 가장 중요한 게 "thread.message.delta"예요. 이건 AI가 답변의 일부를 생성했다는 의미로, delta에 새로 생성된 텍스트 조각이 들어있습니다.

세 번째로, delta에서 텍스트를 추출해서 print()로 출력합니다. end=""로 줄바꿈 없이 이어서 출력하고, flush=True로 즉시 화면에 표시되도록 합니다.

이렇게 하면 ChatGPT처럼 텍스트가 한 글자씩 타이핑되는 효과가 나타나요. 네 번째로, "thread.run.completed" 이벤트가 오면 답변이 완전히 완성된 거예요.

이때 최종 처리(예: 데이터베이스 저장, 로그 기록 등)를 하면 됩니다. 만약 "thread.run.failed" 이벤트가 오면 에러 처리를 해야겠죠.

다섯 번째로, 웹 애플리케이션에서 사용할 때는 WebSocket이나 Server-Sent Events를 통해 클라이언트에게 실시간으로 전달합니다. 예를 들어, FastAPI의 StreamingResponse나 Flask의 stream_with_context를 사용하면 브라우저에서도 실시간 타이핑 효과를 볼 수 있어요.

여러분이 이 패턴을 사용하면 전문적인 채팅 UI를 만들 수 있습니다. 사용자가 질문을 보내는 순간부터 답변이 흐르기 시작하고, 긴 답변도 지루하지 않게 읽을 수 있어요.

특히 코드 생성이나 장문의 설명처럼 시간이 걸리는 작업에서 체감 속도가 크게 향상됩니다.

실전 팁

💡 웹에서 Streaming을 구현할 때는 Server-Sent Events(SSE)를 사용하세요. WebSocket보다 간단하고, 브라우저 지원도 좋습니다.

💡 네트워크 오류로 스트림이 중단될 수 있으니 예외 처리를 꼭 하세요. try-except로 감싸고, 오류 시 일반 방식으로 폴백하는 게 안전합니다.

💡 이벤트 종류가 많으니 필요한 것만 처리하세요. "thread.message.delta", "thread.run.completed", "thread.run.failed" 정도만 처리하면 충분합니다.

💡 프론트엔드에서는 delta를 받을 때마다 상태를 업데이트하면 됩니다. React라면 useState에 텍스트를 누적하고, Vue라면 reactive 변수를 업데이트하세요.

💡 Code Interpreter나 Function Calling 중간 과정도 스트림에 포함됩니다. "thread.run.step.delta" 이벤트를 처리하면 "코드 실행 중...", "함수 호출 중..." 같은 상태를 사용자에게 보여줄 수 있어요.


11. Assistant 업데이트하기 - AI 설정 변경하기

시작하며

여러분이 직원을 고용했는데, 처음에는 고객 응대만 시키다가 나중에 판매까지 맡기고 싶다면 어떻게 하시겠어요? 직원을 해고하고 새로 뽑는 게 아니라, 기존 직원에게 새로운 역할을 주고 교육을 시키죠.

Assistant API도 마찬가지입니다. 처음 만든 Assistant의 역할이나 능력을 바꾸고 싶을 때, 매번 새로 만들 필요가 없어요.

이미 많은 Thread와 대화 기록이 연결되어 있는데, 새로 만들면 다 날아가버립니다. 바로 이럴 때 필요한 것이 Assistant 업데이트입니다.

기존 Assistant의 instructions, 사용 가능한 tools, model 등을 언제든지 수정할 수 있어요. 사용자는 전혀 모르게 AI의 능력을 업그레이드할 수 있습니다.

개요

간단히 말해서, Assistant 업데이트는 이미 생성된 Assistant의 설정을 변경하는 작업입니다. ID는 그대로 유지하면서 내용만 바꾸는 거예요.

마치 소프트웨어 업데이트처럼, 기능을 추가하거나 개선할 수 있습니다. 왜 업데이트가 필요한지 실무 관점에서 보면, 서비스를 운영하면서 지속적으로 개선하기 위해서입니다.

예를 들어, 초기에는 간단한 FAQ 봇이었다가, 나중에 주문 조회 기능을 추가하고, 더 나중에는 개인화된 추천까지 하도록 발전시킬 수 있어요. 기존에는 새로운 Assistant를 만들고 모든 Thread를 마이그레이션해야 했다면, 이제는 한 번의 API 호출로 즉시 업데이트됩니다.

다운타임도 없고, 사용자에게 영향도 없어요. Assistant 업데이트의 핵심 특징은 첫째, ID 유지(기존 Thread와의 연결 유지), 둘째, 부분 업데이트 가능(원하는 필드만 수정), 셋째, 즉시 적용(다음 Run부터 새 설정 사용)입니다.

유연한 서비스 운영이 가능해요.

코드 예제

from openai import OpenAI

client = OpenAI(api_key="your-api-key-here")

assistant_id = "asst_abc123xyz"

# 기존 Assistant 업데이트
updated_assistant = client.beta.assistants.update(
    assistant_id=assistant_id,
    # 역할 변경
    instructions="당신은 Python과 JavaScript 전문가입니다. 초보자도 이해하기 쉽게 설명하고, 실전 예제를 제공하세요.",
    # 모델 업그레이드
    model="gpt-4-turbo-preview",
    # 새로운 도구 추가
    tools=[
        {"type": "code_interpreter"},
        {"type": "file_search"}
    ],
    # 메타데이터 추가
    metadata={
        "version": "2.0",
        "updated_at": "2024-01-15"
    }
)

print(f"Assistant 업데이트 완료! 버전: {updated_assistant.metadata['version']}")

설명

이것이 하는 일: 이 코드는 이미 만들어진 Assistant의 설정을 업데이트하는 작업입니다. 마치 직원의 업무 매뉴얼을 수정하거나, 새로운 도구를 지급하는 것처럼, AI의 능력과 성격을 바꿀 수 있어요.

첫 번째로, assistants.update()에 수정하고 싶은 Assistant의 ID를 전달합니다. 이 ID는 생성할 때 받았던 "asst_"로 시작하는 문자열이에요.

데이터베이스나 환경변수에 저장해둔 값을 사용하면 됩니다. 두 번째로, 수정하고 싶은 필드만 파라미터로 전달합니다.

예제에서는 instructions를 바꿔서 AI의 역할을 변경했어요. "FAQ 봇"에서 "Python/JavaScript 전문가"로 역할이 바뀌면, 다음 Run부터는 새로운 역할에 맞게 답변합니다.

세 번째로, model을 업그레이드할 수도 있습니다. 처음에는 비용 절약을 위해 gpt-3.5-turbo를 사용하다가, 더 정확한 답변이 필요해지면 gpt-4-turbo-preview로 변경할 수 있어요.

기존 Thread는 그대로 유지되고, 다음 대화부터 더 똑똑한 AI가 답변합니다. 네 번째로, tools 배열을 수정해서 새로운 기능을 추가하거나 제거할 수 있습니다.

예제에서는 code_interpreter와 file_search를 모두 활성화했어요. 이제 이 Assistant는 코드도 실행하고, 문서도 검색할 수 있습니다.

다섯 번째로, metadata에 버전 정보나 업데이트 시간을 저장하면 나중에 관리하기 편합니다. "언제 마지막으로 업데이트했지?", "지금 버전이 뭐지?" 같은 정보를 추적할 수 있어요.

이는 프로덕션 환경에서 매우 유용합니다. 여러분이 이 패턴을 사용하면 지속적으로 서비스를 개선할 수 있습니다.

예를 들어, 사용자 피드백을 받아서 instructions를 더 명확하게 수정하거나, 새로운 비즈니스 요구사항이 생기면 Function을 추가하는 식으로요. 서비스 중단 없이 점진적으로 발전시킬 수 있어서 애자일한 개발이 가능합니다.

실전 팁

💡 업데이트는 즉시 반영되지만, 진행 중인 Run에는 영향을 주지 않습니다. 새로운 Run을 시작할 때부터 적용되니 안전하게 업데이트할 수 있어요.

💡 instructions를 자주 실험하면서 최적화하세요. A/B 테스트처럼 여러 버전을 만들어 보고, 어떤 지시문이 더 좋은 답변을 만드는지 비교할 수 있습니다.

💡 업데이트 전에 백업 목적으로 현재 설정을 조회해두면 좋습니다. assistants.retrieve()로 현재 설정을 JSON으로 저장한 후 업데이트하세요.

💡 tools를 제거할 때는 주의하세요. 예를 들어, code_interpreter를 제거하면 이전 대화에서 생성한 그래프를 참조하지 못할 수 있습니다.

💡 버전 관리 전략을 세우세요. metadata에 버전 번호를 넣고, 중요한 변경은 로그를 남겨서 나중에 문제가 생겼을 때 롤백할 수 있도록 하세요.


12. 비용 최적화하기 - 효율적으로 API 사용하기

시작하며

여러분이 Assistant API로 서비스를 만들어서 사용자가 늘어나기 시작했다고 상상해보세요. 처음엔 한 달에 몇 달러 나오던 비용이 어느새 수백 달러, 수천 달러로 늘어나고 있습니다.

이대로 가다간 서비스 수익보다 API 비용이 더 많이 나올 수도 있어요. 특히 Assistant API는 대화 기록을 계속 저장하고, 파일을 업로드하고, Code Interpreter를 실행하면서 토큰을 많이 소모합니다.

아무 생각 없이 사용하면 불필요한 비용이 엄청나게 발생할 수 있죠. 바로 이럴 때 필요한 것이 비용 최적화 전략입니다.

서비스 품질은 유지하면서 비용을 절감하는 여러 테크닉들이 있어요. 작은 최적화들이 모이면 비용을 50% 이상 줄일 수 있습니다.

개요

간단히 말해서, 비용 최적화는 Assistant API 사용 시 발생하는 토큰 소모, 파일 저장, API 호출을 줄이는 모든 기법을 의미합니다. 똑똑하게 사용하면 같은 기능을 훨씬 저렴하게 구현할 수 있어요.

왜 최적화가 필요한지 실무 관점에서 보면, 스타트업이나 개인 프로젝트에서는 비용이 생존 문제이기 때문입니다. 예를 들어, 월 1000명의 사용자가 각각 10번씩 대화하면 만 건의 Run이 발생하는데, 최적화 여부에 따라 비용이 10배 차이날 수 있어요.

기존에는 "일단 만들고 보자" 식으로 개발했다가 나중에 비용 폭탄을 맞았다면, 이제는 설계 단계부터 비용을 고려하는 게 중요합니다. 몇 가지 베스트 프랙티스만 적용해도 큰 차이가 나요.

비용 최적화의 핵심 영역은 첫째, 모델 선택(gpt-4 vs gpt-3.5), 둘째, 컨텍스트 관리(오래된 메시지 정리), 셋째, 캐싱 활용(중복 질문 처리), 넷째, 도구 사용 최소화(불필요한 Function Calling 방지)입니다. 이 영역들을 전략적으로 최적화하면 비용 대비 성능을 극대화할 수 있어요.

코드 예제

from openai import OpenAI
import time

client = OpenAI(api_key="your-api-key-here")

# 1. 저렴한 모델 사용 (간단한 작업)
cheap_assistant = client.beta.assistants.create(
    name="간단한 FAQ 봇",
    model="gpt-3.5-turbo",  # gpt-4보다 10배 저렴
    instructions="간단한 질문에 답변합니다."
)

# 2. Thread 메시지 정리 (오래된 대화 삭제)
def cleanup_old_messages(thread_id, keep_last=10):
    messages = client.beta.threads.messages.list(thread_id=thread_id, limit=100)
    total = len(messages.data)

    if total > keep_last:
        # 오래된 메시지 삭제는 직접 불가능하므로, 새 Thread 생성하고 최근 N개만 복사
        # 실무에서는 주기적으로 새 Thread로 마이그레이션
        print(f"{total - keep_last}개의 오래된 메시지 정리 필요")

# 3. 응답 캐싱 (자주 묻는 질문)
faq_cache = {
    "영업시간": "평일 9시-6시, 주말 휴무입니다.",
    "배송기간": "주문 후 2-3일 소요됩니다."
}

def get_answer(question):
    # 캐시 확인 먼저
    for key, answer in faq_cache.items():
        if key in question:
            return answer  # API 호출 없이 즉시 반환

    # 캐시에 없으면 AI에게 질문
    # ... Run 실행 코드 ...

설명

이것이 하는 일: 이 코드는 Assistant API를 사용할 때 발생하는 비용을 줄이는 여러 가지 기법을 보여줍니다. 마치 전기 절약하듯이, 불필요한 API 호출과 토큰 소모를 최소화하는 거예요.

첫 번째 기법은 모델 선택입니다. gpt-4-turbo는 강력하지만 비쌉니다.

간단한 FAQ나 인사말 응답에는 gpt-3.5-turbo면 충분해요. 예제처럼 용도에 따라 다른 모델을 사용하는 여러 Assistant를 만들면, 복잡한 질문만 비싼 모델로 처리하고 나머지는 저렴한 모델로 처리할 수 있습니다.

두 번째 기법은 컨텍스트 관리입니다. Thread에 메시지가 계속 쌓이면, 매번 Run을 실행할 때마다 모든 대화를 읽어야 해서 토큰이 많이 소모됩니다.

실무에서는 100개 이상의 메시지가 쌓이면 새 Thread를 만들고 최근 10-20개만 복사하는 방식으로 리셋해요. 사용자는 대화가 이어지는 것처럼 느끼지만, 비용은 크게 절감됩니다.

세 번째 기법은 캐싱입니다. "영업시간이 어떻게 되나요?" 같은 질문은 하루에 수십 번씩 반복됩니다.

이런 자주 묻는 질문(FAQ)은 미리 답변을 준비해두고, AI를 거치지 않고 바로 반환하면 비용이 제로예요. Redis 같은 캐시 시스템을 사용하면 더 고급 캐싱이 가능합니다.

네 번째 기법은 도구 사용 최소화입니다. Code Interpreter나 File Search는 추가 비용이 발생해요.

정말 필요한 경우만 활성화하고, 단순한 텍스트 질문에는 도구를 비활성화하세요. Function Calling도 꼭 필요한 함수만 등록하면 AI가 불필요하게 함수를 호출하는 걸 방지할 수 있습니다.

다섯 번째 기법은 Streaming 활용입니다. 사용자가 답변을 읽다가 중간에 만족하면 "중단" 버튼을 누르게 할 수 있어요.

그러면 Run을 취소해서 나머지 토큰 생성을 막을 수 있습니다. 긴 답변에서 이 방식으로 20-30% 토큰을 절약할 수 있어요.

여러분이 이런 최적화 기법들을 조합하면 비용을 절반 이하로 줄이면서도 서비스 품질은 유지할 수 있습니다. 예를 들어, 월 1000달러 나오던 비용을 400-500달러로 줄이면, 그 차이로 더 많은 기능을 개발하거나 마케팅에 투자할 수 있어요.

스타트업에서는 이런 최적화가 생존을 결정할 수 있습니다.

실전 팁

💡 OpenAI 대시보드에서 Usage 페이지를 매일 확인하세요. 어떤 Assistant가, 어떤 모델이 비용을 많이 쓰는지 파악하고 집중적으로 최적화할 수 있습니다.

💡 프로덕션 배포 전에 부하 테스트를 하세요. 1000명이 동시에 사용할 때 비용이 얼마나 나오는지 미리 계산하고, 예산을 초과하면 최적화 계획을 세우세요.

💡 사용자별 월 사용량 제한을 두는 것도 방법입니다. 무료 사용자는 월 10회, 유료 사용자는 무제한처럼 구분하면 비용을 예측 가능하게 관리할 수 있어요.

💡 Instructions를 짧고 명확하게 작성하세요. 불필요하게 긴 지시문은 매번 토큰을 소모합니다. "간결하게 답변하세요" 같은 지침을 넣으면 응답 길이도 줄어들어요.

💡 파일은 필요한 경우만 업로드하고, 사용 후 삭제하세요. File Storage도 비용이 발생하므로, 일시적인 파일은 분석 후 즉시 삭제하는 게 좋습니다.


#OpenAI#Assistant API#AI Integration#ChatBot#Function Calling#AI

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.