ChatGPT API 실전 가이드 | OpenAI API로 AI 애플리케이션 만들기

이 글의 핵심

ChatGPT API를 실제 서비스에 넣을 때 필요한 것들을 순서대로 다룹니다. 모델 선택과 메시지 구조, 스트리밍, Function Calling, 비용 추정과 상한, 컨텍스트 초과·비용 폭발·응답 흔들림 같은 흔한 실수, 프롬프트 인젝션과 개인정보 처리까지 정리합니다.

들어가며

ChatGPT API를 사용하면 자신만의 AI 애플리케이션을 만들 수 있습니다. 챗봇, 콘텐츠 생성기, 코드 어시스턴트, 데이터 분석 도구 등 다양한 서비스를 구축할 수 있습니다.

챗봇 데모는 하루면 만들지만, 서비스에 넣고 나면 시간이 드는 곳은 다른 데 있습니다. 대화가 길어지면서 토큰이 불어나 청구서가 예상보다 커지는 문제, 같은 분류 요청에 매번 다른 답이 나오는 문제, 사용자가 “이전 지시를 무시해”라고 입력하는 문제입니다. 이 글은 기본 사용법과 함께 그런 지점을 어떻게 막는지까지 다룹니다.

이 글에서 다룰 내용:

  • OpenAI API 키 발급 및 설정
  • 기본 Chat Completions API 사용법
  • 스트리밍 응답 처리
  • Function Calling으로 외부 도구 연동
  • 프롬프트 엔지니어링 기법
  • 비용 최적화 전략
  • 실전 예제: 챗봇, 문서 요약, 코드 생성

API 키 발급과 첫 호출

API 키 발급

  1. OpenAI 계정 생성: https://platform.openai.com
  2. API 키 발급: Settings → API keys → Create new secret key
  3. 결제 정보 등록: Billing → Add payment method
# API 키 환경 변수로 설정
export OPENAI_API_KEY='sk-...'

# 또는 .env 파일
echo "OPENAI_API_KEY=sk-..." > .env

라이브러리 설치

# Python
pip install openai

# Node.js
npm install openai

# 환경 변수 관리
pip install python-dotenv  # Python
npm install dotenv         # Node.js

첫 API 호출

# Python
from openai import OpenAI
import os

client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[
        {"role": "user", "content": "Hello, ChatGPT!"}
    ]
)

print(response.choices[0].message.content)
// Node.js
import OpenAI from 'openai';
import dotenv from 'dotenv';

dotenv.config();

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY
});

const response = await openai.chat.completions.create({
  model: 'gpt-4o-mini',
  messages: [
    { role: 'user', content: 'Hello, ChatGPT!' }
  ]
});

console.log(response.choices[0].message.content);

모델 선택과 messages·파라미터 구성

모델 선택

모델특징가격 (입력/출력)추천 용도
gpt-4o최신, 가장 강력$2.50 / $10복잡한 추론, 코딩
gpt-4o-mini빠르고 저렴$0.15 / $0.60일반 챗봇, 간단한 작업
gpt-4-turbo이전 최신 모델$10 / $30복잡한 작업
gpt-3.5-turbo가장 저렴$0.50 / $1.50대량 처리, 간단한 작업

메시지 구조

messages = [
    # System: AI의 역할과 행동 지침
    {
        "role": "system",
        "content": "You are a helpful coding assistant specialized in Python."
    },
    
    # User: 사용자 입력
    {
        "role": "user",
        "content": "How do I read a CSV file in Python?"
    },
    
    # Assistant: AI의 이전 응답 (대화 이력)
    {
        "role": "assistant",
        "content": "You can use pandas: `pd.read_csv('file.csv')`"
    },
    
    # User: 후속 질문
    {
        "role": "user",
        "content": "What if the file has no header?"
    }
]

response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=messages
)

주요 파라미터

response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=messages,
    
    # 온도: 0(결정적) ~ 2(창의적)
    temperature=0.7,
    
    # 최대 출력 토큰 수
    max_tokens=1000,
    
    # Top-p 샘플링 (temperature 대신 사용 가능)
    top_p=0.9,
    
    # 반복 페널티 (-2.0 ~ 2.0)
    frequency_penalty=0.0,
    presence_penalty=0.0,
    
    # 여러 응답 생성
    n=1,
    
    # 특정 토큰에서 중단
    stop=["\n\n", "END"]
)

대화 이력 관리

class ChatSession:
    def __init__(self, system_message="You are a helpful assistant."):
        self.messages = [
            {"role": "system", "content": system_message}
        ]
    
    def add_user_message(self, content):
        self.messages.append({"role": "user", "content": content})
    
    def add_assistant_message(self, content):
        self.messages.append({"role": "assistant", "content": content})
    
    def get_response(self, user_message):
        self.add_user_message(user_message)
        
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=self.messages
        )
        
        assistant_message = response.choices[0].message.content
        self.add_assistant_message(assistant_message)
        
        return assistant_message
    
    def clear_history(self):
        system_msg = self.messages[0]
        self.messages = [system_msg]

# 사용
chat = ChatSession("You are a Python expert.")
print(chat.get_response("What is a list comprehension?"))
print(chat.get_response("Can you give me an example?"))

stream=True로 토큰 단위 응답 받기

기본 스트리밍

# Python
response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "Write a short story"}],
    stream=True
)

for chunk in response:
    if chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end='', flush=True)
// Node.js
const stream = await openai.chat.completions.create({
  model: 'gpt-4o-mini',
  messages: [{ role: 'user', content: 'Write a short story' }],
  stream: true
});

for await (const chunk of stream) {
  const content = chunk.choices[0]?.delta?.content || '';
  process.stdout.write(content);
}

웹 애플리케이션에서 스트리밍

# FastAPI 예제
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import asyncio

app = FastAPI()

@app.post("/chat/stream")
async def chat_stream(message: str):
    async def generate():
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": message}],
            stream=True
        )
        
        for chunk in response:
            if chunk.choices[0].delta.content:
                content = chunk.choices[0].delta.content
                yield f"data: {content}\n\n"
                await asyncio.sleep(0.01)
    
    return StreamingResponse(generate(), media_type="text/event-stream")
// Express.js 예제
app.post('/chat/stream', async (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  const stream = await openai.chat.completions.create({
    model: 'gpt-4o-mini',
    messages: [{ role: 'user', content: req.body.message }],
    stream: true
  });

  for await (const chunk of stream) {
    const content = chunk.choices[0]?.delta?.content || '';
    if (content) {
      res.write(`data: ${content}\n\n`);
    }
  }

  res.end();
});

Function Calling으로 외부 함수 연결하기

기본 사용법

# 함수 정의
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get the current weather for a location",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "City name, e.g. Seoul"
                    },
                    "unit": {
                        "type": "string",
                        "enum": ["celsius", "fahrenheit"],
                        "description": "Temperature unit"
                    }
                },
                "required": [location]
            }
        }
    }
]

# API 호출
response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[
        {"role": "user", "content": "What's the weather in Seoul?"}
    ],
    tools=tools,
    tool_choice="auto"
)

# 함수 호출 확인
message = response.choices[0].message
if message.tool_calls:
    tool_call = message.tool_calls[0]
    function_name = tool_call.function.name
    function_args = json.loads(tool_call.function.arguments)
    
    print(f"Function: {function_name}")
    print(f"Arguments: {function_args}")
    # {'location': 'Seoul', 'unit': 'celsius'}

실제 함수 실행

import json

def get_weather(location, unit="celsius"):
    """실제 날씨 API 호출 (예시)"""
    # 실제로는 weather API 호출
    return {
        "location": location,
        "temperature": 15,
        "unit": unit,
        "condition": "Sunny"
    }

def run_conversation(user_message):
    messages = [{"role": "user", "content": user_message}]
    
    # 1단계: GPT가 함수 호출 필요 여부 판단
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=messages,
        tools=tools,
        tool_choice="auto"
    )
    
    message = response.choices[0].message
    messages.append(message)
    
    # 2단계: 함수 호출 실행
    if message.tool_calls:
        for tool_call in message.tool_calls:
            function_name = tool_call.function.name
            function_args = json.loads(tool_call.function.arguments)
            
            # 함수 실행
            if function_name == "get_weather":
                function_response = get_weather(**function_args)
            
            # 함수 결과를 메시지에 추가
            messages.append({
                "role": "tool",
                "tool_call_id": tool_call.id,
                "name": function_name,
                "content": json.dumps(function_response)
            })
    
    # 3단계: 함수 결과를 포함하여 최종 응답 생성
    final_response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=messages
    )
    
    return final_response.choices[0].message.content

# 사용
print(run_conversation("What's the weather in Seoul?"))
# "The weather in Seoul is currently sunny with a temperature of 15°C."

여러 함수 정의

tools = [
    {
        "type": "function",
        "function": {
            "name": "search_database",
            "description": "Search for users in the database",
            "parameters": {
                "type": "object",
                "properties": {
                    "query": {"type": "string"},
                    "limit": {"type": "integer", "default": 10}
                },
                "required": [query]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "send_email",
            "description": "Send an email to a user",
            "parameters": {
                "type": "object",
                "properties": {
                    "to": {"type": "string"},
                    "subject": {"type": "string"},
                    "body": {"type": "string"}
                },
                "required": ["to", "subject", "body"]
            }
        }
    }
]

System 메시지·Few-Shot·CoT 프롬프트 작성법

System 메시지 최적화

# ❌ 나쁜 예
system = "You are helpful."

# ✅ 좋은 예
system = """You are an expert Python developer with 10+ years of experience.

Your responses should:
- Be concise and practical
- Include code examples with comments
- Explain trade-offs when multiple solutions exist
- Follow PEP 8 style guidelines

Format your code blocks with ```python
"""

Few-Shot Learning

messages = [
    {"role": "system", "content": "Extract key information from text."},
    
    # 예시 1
    {"role": "user", "content": "John Doe, age 30, lives in Seoul"},
    {"role": "assistant", "content": '{"name": "John Doe", "age": 30, "city": "Seoul"}'},
    
    # 예시 2
    {"role": "user", "content": "Jane Smith, 25 years old, from Busan"},
    {"role": "assistant", "content": '{"name": "Jane Smith", "age": 25, "city": "Busan"}'},
    
    # 실제 질문
    {"role": "user", "content": "Mike Johnson, aged 35, living in Tokyo"}
]

Chain of Thought (CoT)

# ❌ 직접 답변 요청
prompt = "What is 15% of 240?"

# ✅ 단계별 사고 유도
prompt = """What is 15% of 240?

Let's solve this step by step:
1. Convert percentage to decimal
2. Multiply by the number
3. Calculate the result"""

출력 형식 지정

prompt = """Analyze this text and return JSON:

Text: "The iPhone 15 Pro costs $999 and has 256GB storage."

Return format:
{
  "product": "product name",
  "price": number,
  "storage": "storage capacity"
}

JSON:"""

response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": prompt}],
    response_format={"type": "json_object"}  # JSON 모드 강제
)

역할 지정 (Role Playing)

system_messages = {
    "code_reviewer": """You are a senior code reviewer. 
    Review code for bugs, performance issues, and best practices.
    Be constructive and specific in your feedback.""",
    
    "translator": """You are a professional translator specializing in 
    technical documentation. Maintain technical terms accurately.""",
    
    "tutor": """You are a patient programming tutor. 
    Explain concepts clearly with examples. 
    Ask questions to check understanding."""
}

tiktoken으로 토큰 세고 비용 줄이기

토큰 계산

import tiktoken

def count_tokens(text, model="gpt-4o-mini"):
    encoding = tiktoken.encoding_for_model(model)
    return len(encoding.encode(text))

text = "Hello, how are you?"
tokens = count_tokens(text)
print(f"Tokens: {tokens}")  # ~5 tokens

# 비용 계산
def estimate_cost(input_tokens, output_tokens, model="gpt-4o-mini"):
    prices = {
        "gpt-4o-mini": {"input": 0.15, "output": 0.60},  # per 1M tokens
        "gpt-4o": {"input": 2.50, "output": 10.00},
        "gpt-3.5-turbo": {"input": 0.50, "output": 1.50}
    }
    
    price = prices[model]
    cost = (input_tokens * price["input"] + output_tokens * price["output"]) / 1_000_000
    return cost

# 예시
input_tokens = 1000
output_tokens = 500
cost = estimate_cost(input_tokens, output_tokens, "gpt-4o-mini")
print(f"Cost: ${cost:.4f}")  # $0.0004

비용 절감 전략

# 1. 짧은 프롬프트 사용
# ❌ 장황한 프롬프트
prompt = """I would like you to help me with something. 
Could you please analyze the following text and tell me 
what the sentiment is? Here is the text: ..."""

# ✅ 간결한 프롬프트
prompt = "Analyze sentiment: ..."

# 2. max_tokens 제한
response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=messages,
    max_tokens=100  # 출력 제한
)

# 3. 대화 이력 관리
def trim_conversation(messages, max_messages=10):
    """최근 N개 메시지만 유지"""
    system_msg = messages[0]
    recent_messages = messages[-max_messages:]
    return [system_msg] + recent_messages

# 4. 저렴한 모델 사용
# 간단한 작업: gpt-3.5-turbo 또는 gpt-4o-mini
# 복잡한 작업: gpt-4o

# 5. 캐싱 활용
from functools import lru_cache

@lru_cache(maxsize=100)
def get_cached_response(prompt):
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}]
    )
    return response.choices[0].message.content

챗봇·문서 요약·코드 생성·데이터 추출 예제

대화 이력을 유지하는 챗봇

class Chatbot:
    def __init__(self, system_prompt):
        self.messages = [{"role": "system", "content": system_prompt}]
        self.client = OpenAI()
    
    def chat(self, user_input):
        self.messages.append({"role": "user", "content": user_input})
        
        response = self.client.chat.completions.create(
            model="gpt-4o-mini",
            messages=self.messages,
            temperature=0.7
        )
        
        assistant_message = response.choices[0].message.content
        self.messages.append({"role": "assistant", "content": assistant_message})
        
        return assistant_message

# 사용
bot = Chatbot("You are a friendly customer support agent.")
print(bot.chat("I have a problem with my order"))
print(bot.chat("Order #12345"))

긴 문서 요약

def summarize_document(text, max_length=100):
    prompt = f"""Summarize the following text in {max_length} words or less:

{text}

Summary:"""
    
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}],
        temperature=0.3,
        max_tokens=max_length * 2
    )
    
    return response.choices[0].message.content

# 사용
long_text = """..."""  # 긴 문서
summary = summarize_document(long_text, max_length=50)

코드 생성

def generate_code(description, language="python"):
    prompt = f"""Generate {language} code for the following task:

{description}

Requirements:
- Include comments
- Handle errors
- Follow best practices

Code:"""
    
    response = client.chat.completions.create(
        model="gpt-4o",  # 코드는 더 강력한 모델 사용
        messages=[{"role": "user", "content": prompt}],
        temperature=0.2  # 낮은 온도로 일관성 유지
    )
    
    return response.choices[0].message.content

# 사용
code = generate_code("Read a CSV file and calculate the average of a column")
print(code)

비정형 텍스트에서 데이터 추출

def extract_entities(text):
    prompt = f"""Extract the following entities from the text:
- Person names
- Organizations
- Locations
- Dates

Text: {text}

Return as JSON:"""
    
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}],
        response_format={"type": "json_object"}
    )
    
    return json.loads(response.choices[0].message.content)

# 사용
text = "John Doe met with Apple CEO in San Francisco on Jan 15, 2024"
entities = extract_entities(text)

RateLimitError 재시도와 타임아웃·비용 상한

기본 에러 처리

from openai import OpenAI, APIError, RateLimitError, APIConnectionError
import time

def chat_with_retry(messages, max_retries=3):
    for attempt in range(max_retries):
        try:
            response = client.chat.completions.create(
                model="gpt-4o-mini",
                messages=messages
            )
            return response.choices[0].message.content
        
        except RateLimitError:
            if attempt < max_retries - 1:
                wait_time = 2 ** attempt  # Exponential backoff
                print(f"Rate limit hit. Waiting {wait_time}s...")
                time.sleep(wait_time)
            else:
                raise
        
        except APIConnectionError:
            if attempt < max_retries - 1:
                print(f"Connection error. Retrying...")
                time.sleep(1)
            else:
                raise
        
        except APIError as e:
            print(f"API error: {e}")
            raise
    
    raise Exception("Max retries exceeded")

타임아웃 설정

from openai import OpenAI

client = OpenAI(
    timeout=30.0,  # 30초 타임아웃
    max_retries=2
)

비용 제한

class CostLimitedClient:
    def __init__(self, max_cost=1.0):
        self.client = OpenAI()
        self.total_cost = 0.0
        self.max_cost = max_cost
    
    def chat(self, messages, model="gpt-4o-mini"):
        if self.total_cost >= self.max_cost:
            raise Exception(f"Cost limit ${self.max_cost} exceeded")
        
        response = self.client.chat.completions.create(
            model=model,
            messages=messages
        )
        
        # 비용 계산
        usage = response.usage
        cost = estimate_cost(
            usage.prompt_tokens,
            usage.completion_tokens,
            model
        )
        self.total_cost += cost
        
        print(f"Cost: ${cost:.6f} | Total: ${self.total_cost:.6f}")
        
        return response.choices[0].message.content

컨텍스트 초과·비용 폭증·들쭉날쭉한 답

입력이 컨텍스트 한도를 넘는다

긴 문서를 그대로 넣으면 context_length_exceeded 에러가 나거나, 한도 안이더라도 토큰 비용이 그대로 청구됩니다. 보내기 전에 토큰 수를 세고, 넘으면 자르거나(요약·검색으로 필요한 부분만 뽑는 게 더 낫습니다) 나눠서 처리합니다.

import tiktoken

def truncate_to_tokens(text: str, max_tokens: int, model: str = "gpt-4o-mini") -> str:
    enc = tiktoken.encoding_for_model(model)
    tokens = enc.encode(text)
    return text if len(tokens) <= max_tokens else enc.decode(tokens[:max_tokens])

단순히 앞부분만 자르면 문서 뒤쪽의 중요한 내용이 사라집니다. 질문에 답할 근거가 필요한 경우라면 문서를 청크로 나눠 관련 부분만 검색해 넣는 RAG 방식이 품질과 비용 모두에서 낫습니다.

반복 호출로 비용이 폭발한다

1,000건을 루프로 하나씩 보내는 코드는 개발 중에는 문제없다가 운영 데이터에서 청구서로 돌아옵니다. 대응은 세 가지입니다.

  • 비용 상한: 위 CostLimitedClient처럼 누적 비용이 한도를 넘으면 멈추게 합니다. 대시보드의 사용 한도(usage limit)도 함께 설정합니다.
  • 즉시 응답이 필요 없는 대량 작업은 Batch API: 요청을 파일로 모아 제출하면 24시간 안에 결과를 받는 대신 요금이 할인됩니다. 야간 분류·요약 작업에 적합합니다.
  • 여러 항목을 한 요청에 묶기: 짧은 항목 여러 개를 한 프롬프트에 넣고 JSON 배열로 답하게 하면 호출 수와 시스템 프롬프트 반복 비용이 줄어듭니다. 대신 한 항목이 잘못 처리되면 전체를 다시 보내야 하고, 묶음이 크면 출력이 한도에 걸릴 수 있어 수십 개 단위가 현실적입니다.

같은 입력에 매번 다른 답이 나온다

분류·추출처럼 정답이 정해진 작업에 temperature를 높게 두면 결과가 흔들립니다. temperature=0으로 낮추고, 선택지를 시스템 프롬프트에 못박고, 출력 형식을 JSON 모드나 구조화 출력으로 제한합니다. temperature=0이어도 완전히 결정적이지는 않으므로, 결과가 꼭 같아야 하면 아래 seed까지 함께 쓰고 응답을 캐싱합니다.

response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[
        {"role": "system", "content": "다음 중 하나로만 답하세요: 버그, 기능요청, 질문"},
        {"role": "user", "content": ticket_text},
    ],
    temperature=0,
    max_tokens=5,
)

Vision·JSON 모드·seed

이미지 입력 (Vision)

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[
        {
            "role": "user",
            "content": [
                {"type": "text", "text": "What's in this image?"},
                {
                    "type": "image_url",
                    "image_url": {
                        "url": "https://example.com/image.jpg"
                    }
                }
            ]
        }
    ]
)

JSON 모드

response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[
        {"role": "system", "content": "Extract user info as JSON"},
        {"role": "user", "content": "John Doe, 30, engineer"}
    ],
    response_format={"type": "json_object"}
)

data = json.loads(response.choices[0].message.content)

Seed (재현 가능한 출력)

response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=messages,
    seed=12345,  # 동일한 seed = 동일한 출력
    temperature=0
)

키 관리·예외 로깅·테스트 습관

API 키와 프롬프트 인젝션 방어

# ✅ 환경 변수 사용
import os
api_key = os.getenv("OPENAI_API_KEY")

# ❌ 코드에 직접 하드코딩
api_key = "sk-..."  # 절대 하지 마세요!

# ✅ .gitignore에 .env 추가
# .env
# .env.local

키 관리만큼 중요한 게 입력과 출력입니다. 사용자 입력은 길이 상한을 두고, 프롬프트 인젝션(“이전 지시를 무시하고…”)에 대비합니다. 다만 금지어 목록으로 막는 방식은 표현을 조금만 바꿔도 뚫리므로 보조 수단일 뿐입니다. 실질적인 방어는 모델에게 준 권한을 좁히는 것입니다. Function Calling으로 DB를 조회하게 한다면 읽기 전용 계정을 쓰고, 사용자 본인의 데이터만 조회되도록 서버 코드에서 조건을 강제합니다. 모델이 무엇을 요청하든 서버가 허용한 범위만 실행되게 만드는 것입니다.

MAX_INPUT_CHARS = 4000

def validate_input(user_input: str) -> str:
    if len(user_input) > MAX_INPUT_CHARS:
        raise ValueError("입력이 너무 깁니다")
    return user_input

# 출력에 개인정보가 섞여 나갈 수 있는 서비스라면 로그·화면에 내보내기 전에 마스킹
import re

def mask_pii(text: str) -> str:
    text = re.sub(r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}", "***@***", text)
    text = re.sub(r"01[016789]-?\d{3,4}-?\d{4}", "***-****-****", text)  # 국내 휴대폰 번호
    return text

폴백을 위한 예외 로깅

# ✅ 모든 API 호출에 try-except
try:
    response = client.chat.completions.create(...)
except Exception as e:
    logger.error(f"OpenAI API error: {e}")
    # 폴백 로직

요청·토큰 사용량 로깅

import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def chat(messages):
    logger.info(f"Sending {len(messages)} messages")
    response = client.chat.completions.create(...)
    logger.info(f"Received response: {response.usage.total_tokens} tokens")
    return response

Mock으로 API 없이 테스트하기

# 단위 테스트
def test_chatbot():
    bot = Chatbot("You are helpful")
    response = bot.chat("Hello")
    assert len(response) > 0
    assert isinstance(response, str)

# Mock 사용
from unittest.mock import Mock

def test_with_mock():
    client.chat.completions.create = Mock(return_value=mock_response)
    # 테스트 코드

FastAPI + React로 AI 챗봇 웹앱 만들기

FastAPI 백엔드

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from openai import OpenAI
import os

app = FastAPI()
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

class ChatRequest(BaseModel):
    message: str
    conversation_id: str = None

# 간단한 메모리 저장소 (실제로는 Redis/DB 사용)
conversations = {}

@app.post("/chat")
async def chat(request: ChatRequest):
    # 대화 이력 가져오기
    if request.conversation_id not in conversations:
        conversations[request.conversation_id] = [
            {"role": "system", "content": "You are a helpful assistant."}
        ]
    
    messages = conversations[request.conversation_id]
    messages.append({"role": "user", "content": request.message})
    
    try:
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=messages
        )
        
        assistant_message = response.choices[0].message.content
        messages.append({"role": "assistant", "content": assistant_message})
        
        return {
            "response": assistant_message,
            "conversation_id": request.conversation_id
        }
    
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

React 프론트엔드

import { useState } from 'react';

function ChatApp() {
  const [messages, setMessages] = useState([]);
  const [input, setInput] = useState('');
  const [conversationId] = useState(Math.random().toString(36));

  const sendMessage = async () => {
    const userMessage = { role: 'user', content: input };
    setMessages([...messages, userMessage]);
    setInput('');

    const response = await fetch('/chat', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        message: input,
        conversation_id: conversationId
      })
    });

    const data = await response.json();
    const assistantMessage = { role: 'assistant', content: data.response };
    setMessages(prev => [...prev, assistantMessage]);
  };

  return (
    <div>
      <div className="messages">
        {messages.map((msg, i) => (
          <div key={i} className={msg.role}>
            {msg.content}
          </div>
        ))}
      </div>
      <input
        value={input}
        onChange={e => setInput(e.target.value)}
        onKeyPress={e => e.key === 'Enter' && sendMessage()}
      />
      <button onClick={sendMessage}>Send</button>
    </div>
  );
}

모델별 가격과 Rate Limit

가격표 (2026년 4월 기준)

모델입력 ($/1M 토큰)출력 ($/1M 토큰)
gpt-4o$2.50$10.00
gpt-4o-mini$0.15$0.60
gpt-4-turbo$10.00$30.00
gpt-3.5-turbo$0.50$1.50

Rate Limits

티어RPMTPM
Free340,000
Tier 1500200,000
Tier 25,0002,000,000

자주 묻는 질문 (FAQ)

Q. 한국어로 써도 품질이 괜찮나요?

A. 최근 모델은 한국어 이해·생성 품질이 충분히 높습니다. 다만 한국어는 같은 내용을 영어로 쓸 때보다 토큰이 더 많이 나오는 편이라, 비용을 추정할 때는 실제 한국어 샘플로 tiktoken 토큰 수를 재 보는 게 정확합니다. 시스템 프롬프트를 영어로 쓰고 “답변은 한국어로” 지시하는 방식으로 입력 토큰을 줄이기도 합니다.

Q. 개인정보를 API로 보내도 되나요?

A. OpenAI는 API로 보낸 데이터를 기본적으로 모델 학습에 쓰지 않는다고 밝히고 있지만, 오남용 감시 목적으로 일정 기간 보관될 수 있습니다. 주민번호·카드번호처럼 민감한 정보는 보내기 전에 마스킹하고, 서비스 약관과 개인정보 처리방침에 외부 AI 처리 사실을 적어 두세요. 규제 산업이라면 데이터 보관 정책을 계약으로 확인해야 합니다.

참고 자료

ChatGPT API로 챗봇, 문서 요약, 코드 생성 등 다양한 AI 애플리케이션을 만들 수 있으며, Function Calling과 프롬프트 엔지니어링을 활용하면 더욱 강력한 서비스를 구축할 수 있습니다.


같이 보면 좋은 글