ChatGPT 웹에서 대화하는 것과 API를 직접 다루는 것은 완전히 다른 경험이다. API를 쓰면 내가 만든 서비스에 AI를 집어넣을 수 있고, 프롬프트와 응답을 완전히 제어할 수 있다. SDK 설치부터 스트리밍, 히스토리 관리, 실전 챗봇 완성까지 -- 직접 만들어본 과정을 코드 중심으로 정리했다.
왜 API를 직접 써야 하는가
ChatGPT 웹사이트에서 대화하는 것만으로도 많은 것을 할 수 있다. 그런데 왜 굳이 API를 직접 호출해야 하는가? 핵심은 제어권이다.
웹 인터페이스에서는 OpenAI가 정한 규칙 안에서만 움직인다. 하지만 API를 쓰면 System Prompt로 AI의 성격을 설계하고, 응답 형식을 JSON으로 강제하고, 내 서비스의 로직 안에 AI를 자연스럽게 녹여넣을 수 있다. 슬랙봇, 자동 이메일 분류기, 코드 리뷰 도구 -- 이런 것들은 웹 인터페이스로는 만들 수 없다.
- 자동화 -- 반복 업무(메일 요약, 보고서 초안, 데이터 분류)를 코드로 자동화할 수 있다. 사람이 복사-붙여넣기하는 과정이 사라진다.
- 서비스 통합 -- 내가 만든 웹앱이나 모바일앱에 AI 기능을 직접 내장할 수 있다. 사용자가 ChatGPT를 따로 열 필요 없이, 내 서비스 안에서 AI와 대화한다.
- 비용 통제 -- ChatGPT Plus는 월 $20 고정이다. API는 사용한 만큼만 낸다. 가벼운 작업에는 저렴한 모델(gpt-4o-mini)을, 복잡한 작업에만 고성능 모델(gpt-4o)을 쓰면 비용을 크게 줄일 수 있다.
- 프롬프트 완전 제어 -- temperature, max_tokens, response_format 같은 파라미터를 직접 조절해서 응답의 창의성, 길이, 형식을 세밀하게 통제한다.
환경 설정과 API 키 보안
SDK 설치
OpenAI는 공식 Python 라이브러리를 제공한다. 2023년 말에 나온 v1.0 이후로 API 사용법이 완전히 바뀌었기 때문에, 반드시 1.x 이상인지 확인해야 한다.
터미널
# 가상환경 생성 (권장)
python -m venv .venv
source .venv/bin/activate # macOS/Linux
.venv\Scripts\activate # Windows
# SDK 설치
pip install openai python-dotenv
# 버전 확인
python -c "import openai; print(openai.__version__)"
API 키 발급과 보안
platform.openai.com에서 회원가입 후 API Keys 메뉴에서 키를 발급받는다. 키는 발급 시 한 번만 표시되므로 즉시 안전한 곳에 저장해야 한다.
Python
# .env 파일 (프로젝트 루트에 생성)
OPENAI_API_KEY=sk-proj-xxxxxxxxxxxxxxxxxxxx
# main.py
import os
from dotenv import load_dotenv
from openai import OpenAI
load_dotenv()
client = OpenAI(
api_key=os.getenv("OPENAI_API_KEY")
)
보안 원칙
API 키를 GitHub에 올리면 수분 내로 악용된다. .gitignore에 .env를 반드시 추가하고, 키가 노출됐다면 즉시 폐기 후 재발급해야 한다. 코드에 키를 하드코딩하는 것은 절대 금지다.
ChatCompletion 기본 사용법
OpenAI API의 핵심은 chat.completions.create()다. 대화 형식의 메시지 배열을 보내고 응답을 받는 구조다.
Python
from openai import OpenAI
client = OpenAI() # OPENAI_API_KEY 환경변수 자동 사용
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "user", "content": "파이썬으로 피보나치 수열을 구현해줘"}
],
temperature=0.7, # 0(일관성) ~ 2(창의성)
max_tokens=1000, # 응답 최대 토큰 수
)
# 응답 텍스트 추출
answer = response.choices[0].message.content
print(answer)
# 토큰 사용량 확인
print(f"사용 토큰: {response.usage.total_tokens}")
메시지 역할(role) 구조
OpenAI API는 세 가지 역할로 대화를 구성한다. 이 구조를 이해하는 것이 API 활용의 첫 번째 열쇠다.
| role |
역할 |
사용 예시 |
system |
AI의 역할, 성격, 제약 조건 설정 |
"당신은 한국어로만 답변하는 개발자 도우미다." |
user |
사용자의 입력 |
"파이썬으로 정렬 알고리즘 설명해줘" |
assistant |
AI의 이전 응답 (히스토리 유지용) |
직전 AI 응답 내용을 그대로 넣는다 |
System Prompt 설계
System Prompt는 AI의 행동 방식을 결정하는 가장 중요한 요소다. 같은 모델이라도 System Prompt에 따라 완전히 다른 캐릭터가 된다. 잘 짠 프롬프트 하나가 파인튜닝보다 효과적인 경우가 많다.
Python
SYSTEM_PROMPT = """당신은 Python 개발 전문 어시스턴트다.
역할:
- Python 코드 작성, 디버깅, 코드 리뷰를 담당한다.
- 항상 실행 가능한 코드를 제공하고, 주석을 충분히 단다.
- 보안이나 성능 이슈가 있으면 반드시 언급한다.
답변 규칙:
- 한국어로 답변한다.
- 코드는 ```python 코드블록```으로 감싼다.
- 복잡한 개념은 단계별로 설명한다.
- 모르는 것은 솔직하게 모른다고 한다.
금지사항:
- 허위 정보 제공 금지
- 악의적인 코드 작성 금지"""
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": "SQL 인젝션 방어 코드 작성해줘"}
]
)
프롬프트 설계 4원칙
좋은 System Prompt에는 네 가지 요소가 들어간다. (1) 역할을 명확히 정의한다 (2) 원하는 응답 형식을 명시한다 (3) 금지 사항을 열거한다 (4) 예시(few-shot)를 포함한다. 이 네 가지를 갖추면 응답 품질이 눈에 띄게 올라간다.
스트리밍 응답 구현
일반 호출은 응답 전체가 생성될 때까지 기다려야 한다. 긴 답변이면 수 초간 빈 화면을 보게 되는데, 사용자 입장에서 이 대기 시간은 상당히 불쾌하다. 스트리밍을 쓰면 ChatGPT처럼 글자가 하나씩 나타나는 경험을 구현할 수 있다.
Python
stream = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "user", "content": "파이썬의 장점 5가지를 설명해줘"}
],
stream=True # 핵심: 스트리밍 활성화
)
full_response = ""
for chunk in stream:
delta = chunk.choices[0].delta
if delta.content is not None:
print(delta.content, end="", flush=True)
full_response += delta.content
print() # 줄바꿈
stream=True를 설정하면 응답이 chunk 단위로 나눠서 도착한다. 각 chunk에서 delta.content를 꺼내 이어붙이면 된다. flush=True는 버퍼 없이 즉시 출력하겠다는 의미다. 웹 서비스에서는 이 chunk를 Server-Sent Events(SSE)로 클라이언트에 전달하는 방식이 일반적이다.
대화 히스토리 관리
OpenAI API는 stateless다. 매 요청이 독립적이라 이전 대화를 기억하지 못한다. "방금 말한 거 수정해줘"라고 해도, API는 "방금"이 뭔지 모른다. 대화 흐름을 유지하려면 이전 메시지들을 직접 messages 배열에 누적해서 보내야 한다.
Python
class ChatBot:
def __init__(self, system_prompt: str):
self.messages = [
{"role": "system", "content": system_prompt}
]
self.client = OpenAI()
def chat(self, user_input: str) -> str:
self.messages.append({"role": "user", "content": user_input})
response = self.client.chat.completions.create(
model="gpt-4o-mini",
messages=self.messages,
max_tokens=1500
)
assistant_msg = response.choices[0].message.content
self.messages.append({"role": "assistant", "content": assistant_msg})
return assistant_msg
def reset(self):
"""system prompt만 남기고 대화 초기화"""
self.messages = [self.messages[0]]
# 사용
bot = ChatBot("당신은 Python 튜터다.")
print(bot.chat("리스트 컴프리헨션이 뭐야?"))
print(bot.chat("방금 설명한 거 예시 코드 보여줘")) # 이전 대화를 기억한다
주의할 점은, 대화가 길어질수록 매 요청에 보내는 토큰 수가 누적된다는 것이다. 20턴 대화를 이어가면 이전 대화 전체가 매번 입력으로 들어간다. 비용이 기하급수적으로 올라가므로, 적절한 시점에 오래된 메시지를 잘라내는 로직이 필수다.
모델 선택 -- GPT-4o vs GPT-4o-mini
2026년 기준 OpenAI의 주력 모델을 비교하면 다음과 같다. 용도에 맞는 모델을 선택하는 것만으로도 비용을 90% 이상 절감할 수 있다.
| 모델 |
특징 |
입력 가격 (1M 토큰) |
출력 가격 (1M 토큰) |
적합한 용도 |
| gpt-4o |
최고 성능, 멀티모달(이미지 입력) |
$2.5 |
$10 |
복잡한 분석, 코딩, 이미지 이해 |
| gpt-4o-mini |
빠른 속도, 저비용 |
$0.15 |
$0.6 |
대화봇, 분류, 간단한 요약 |
| o1-mini |
추론 특화 (chain-of-thought) |
$1.1 |
$4.4 |
수학, 알고리즘, 논리 추론 |
내가 실제로 개발할 때 쓰는 전략은 이렇다. 개발과 테스트 단계에서는 gpt-4o-mini로 빠르게 반복하고, 응답 품질이 부족한 특정 기능에만 gpt-4o를 적용한다. 일반적인 대화봇, 텍스트 분류, 간단한 요약 같은 작업은 mini로 충분하다. gpt-4o를 꺼내야 하는 경우는 복잡한 코드 생성이나 이미지 분석처럼 높은 추론 능력이 필요한 작업뿐이다.
토큰 관리와 비용 최적화
토큰은 API 요금의 기본 단위다. 영어는 단어 하나가 대략 1~1.3토큰이지만, 한국어는 글자 하나가 2~3토큰을 소비한다. 같은 내용이라도 한국어로 주고받으면 영어의 1.5~2배 비용이 든다.
Python
import tiktoken # pip install tiktoken
def count_tokens(text: str, model: str = "gpt-4o") -> int:
"""텍스트의 토큰 수를 계산한다."""
encoding = tiktoken.encoding_for_model(model)
return len(encoding.encode(text))
def estimate_cost(input_tokens: int, output_tokens: int,
model: str = "gpt-4o-mini") -> float:
"""예상 비용을 USD로 계산한다."""
prices = {
"gpt-4o": {"input": 2.5, "output": 10.0},
"gpt-4o-mini": {"input": 0.15, "output": 0.6},
}
p = prices.get(model, prices["gpt-4o-mini"])
return (input_tokens * p["input"] + output_tokens * p["output"]) / 1_000_000
def trim_history(messages: list, max_tokens: int = 3000) -> list:
"""토큰 한도 초과 시 오래된 대화를 삭제한다."""
system = messages[0]
history = messages[1:]
while history:
total = sum(count_tokens(m["content"]) for m in [system] + history)
if total <= max_tokens:
break
history.pop(0)
return [system] + history
비용 절감 팁
System Prompt를 간결하게 유지하는 것만으로도 비용이 줄어든다. 매 요청마다 System Prompt가 입력 토큰에 포함되기 때문이다. 500토큰짜리 프롬프트를 200토큰으로 줄이면, 1000번 호출 시 30만 토큰을 절약한다. 또한 .lean() 대신 projection을 활용해 응답 길이를 제한하는 것도 효과적이다.
실전 챗봇 완성 코드
지금까지 다룬 내용을 전부 결합한 터미널 챗봇이다. 스트리밍, 히스토리 관리, 자동 정리가 포함되어 있고, 그대로 복사해서 실행할 수 있다.
Python -- chatbot.py
"""
실전 터미널 챗봇
pip install openai python-dotenv
"""
import os
from openai import OpenAI
from dotenv import load_dotenv
load_dotenv()
SYSTEM_PROMPT = """당신은 유능한 한국어 AI 어시스턴트다.
사용자의 질문에 정확하고 간결하게 답변한다.
모르는 것은 솔직하게 모른다고 한다."""
class TerminalChatBot:
def __init__(self):
self.client = OpenAI()
self.messages = [{"role": "system", "content": SYSTEM_PROMPT}]
def chat(self, user_input: str):
self.messages.append({"role": "user", "content": user_input})
print("\nAI: ", end="", flush=True)
full_response = ""
stream = self.client.chat.completions.create(
model="gpt-4o-mini",
messages=self.messages,
max_tokens=2000,
stream=True
)
for chunk in stream:
delta = chunk.choices[0].delta
if delta.content:
print(delta.content, end="", flush=True)
full_response += delta.content
print("\n")
self.messages.append({"role": "assistant", "content": full_response})
# 히스토리 자동 정리 (10턴 초과 시)
if len(self.messages) > 21:
self.messages = [self.messages[0]] + self.messages[-20:]
def main():
bot = TerminalChatBot()
print("AI 챗봇 시작 (종료: quit)\n")
while True:
user_input = input("You: ").strip()
if not user_input:
continue
if user_input.lower() in ["quit", "exit", "종료"]:
print("종료.")
break
bot.chat(user_input)
if __name__ == "__main__":
main()
에러 처리와 운영 팁
프로덕션에서 OpenAI API를 쓸 때 반드시 처리해야 하는 에러들이 있다. 특히 Rate Limit은 피할 수 없으므로 재시도 로직이 필수다.
Python
from openai import OpenAI, APIError, RateLimitError, AuthenticationError
import time
def safe_chat(client: OpenAI, messages: list, retries: int = 3) -> str:
"""에러 처리 + 재시도 로직이 포함된 API 호출"""
for attempt in range(retries):
try:
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
max_tokens=1000,
timeout=30
)
return response.choices[0].message.content
except AuthenticationError:
print("API 키가 유효하지 않다.")
raise
except RateLimitError:
wait = 2 ** attempt # 지수 백오프: 1, 2, 4초
print(f"Rate limit. {wait}초 후 재시도...")
time.sleep(wait)
except APIError as e:
print(f"API 오류 (시도 {attempt+1}/{retries}): {e}")
if attempt == retries - 1:
raise
time.sleep(1)
return "응답을 가져올 수 없다."
운영 체크리스트
- API 키 -- 환경변수로 관리하고, .gitignore에 .env 등록. 키 노출 시 즉시 폐기 후 재발급.
- Rate Limit -- 지수 백오프(exponential backoff) 재시도 구현. 동시 요청이 많은 서비스라면 요청 큐를 두는 것도 방법이다.
- max_tokens -- 반드시 설정한다. 설정하지 않으면 모델이 가능한 한 길게 응답하려고 하므로 비용이 폭증할 수 있다.
- 히스토리 관리 -- 대화가 길어지면 오래된 메시지를 잘라낸다. Context window를 초과하면 API 자체가 에러를 낸다.
- 타임아웃 -- timeout 파라미터를 설정해서 무한 대기를 방지한다. 네트워크 문제로 응답이 안 올 때를 대비한다.
- Prompt Injection 방어 -- 사용자 입력을 그대로 프롬프트에 넣으면 위험하다. 입력 길이 제한, 금지 패턴 필터링 등을 적용해야 한다.
OpenAI API의 기본기를 다뤘다. 이것만으로도 실용적인 챗봇이나 자동화 도구를 만들 수 있다. 여기서 더 나아가고 싶다면 Function Calling(외부 API 연동), Structured Output(JSON 응답 강제), Assistants API(파일 분석, 코드 실행) 같은 고급 기능을 살펴보면 된다.
참고 자료
OpenAI Platform Documentation, platform.openai.com/docs
OpenAI Python SDK GitHub, github.com/openai/openai-python
OpenAI Cookbook, cookbook.openai.com