OpenAI API 비용 절감하는 3가지 방법은?

AI API 청구서가 늘었다

당연한 소리긴 하지만 AI를 사용하고 나서 부터는 일이 편해졌다. 이부분은 진짜 최고지만 문제는 돈도 그만큼 늘었다는 것이 문제다. 프로그램을 개발해서 잘 쓰는 것도 있지만 안쓰거나 문제가 발생하는 경우가 종종 있었다.

아무튼 비용이 늘어가는 상황에서 이유를 찾아보았는데 그중에 하나가 최고급 모델인 gpt-4o를 너무 자주 쓰면서 발생하고 있었다. 진짜 “안녕” 이거 하나 입력하려고 4o 모델 쓰는건 비용 낭비 그 자체였다. 그래서 주말 동안 OpenAI 공식 문서와 깃허브를 샅샅이 뒤지며 코드를 분석했다. 결과적으로 OpenAI API 비용을 무려 92%까지 줄이는 세 가지 방법을 찾아냈다.

 

openai-model-list

 



1. 비용 측정준비

만약 이미 운영하고 있는 실제 서비스에 바로 손대면 예상치 못한 장애가 발생할 가능성이 너무 크다.(하지 말란 말이다) 그래서 나는 먼저 독립된 테스트 환경을 만들어서 API 호출 시 발생하는 토큰 사용량을 직접 눈으로 확인할 수 있게 했다. 이렇게 하면 어디서 낭비가 발생하는지 쉽게 파악할 수 있다. 자 세팅한 환경은 다음과 같다.

  • 운영체제: Windows 11 Pro, Python 3.12.2
  • 편집기: VS Code
  • 핵심 라이브러리: openai (API 통신), tiktoken (OpenAI 공식 토큰 카운터), python-dotenv (키 보안)
  • 비교 대상: 기존 gpt-4o 모델과 경량화된 gpt-4o-mini

특히 tiktoken 라이브러리를 처음 써봤는데, API를 호출하기 전에 서버 쪽에서 토큰을 미리 계산해볼 수 있게 해준 단다. 비용이 늘었다는것을 알고 난 이후에 이 도구가 있다는 걸 알았는데, 진작 알았으면 비용 폭탄을 피할 수 있었을 텐데 하는 아쉬움이 크다.

2. 토큰 낭비를 막는 3가지 핵심 최적화와 전체 코드

방법 ① gpt-4o-mini로 즉시 교체 — 가장 빠른 효과

단순 CS 문의나 텍스트 요약에 굳이~ 안써도 되는 최고급 모델 gpt-4o를 쓰는 건 완전 과잉이다. 이건 마치 공사 현장에 스포츠카를 끌고 가는 꼴이다. gpt-4o-minigpt-4o 대비 입력 토큰 기준으로 약 95% 저렴한데, 일상 대화 품질은 거의 차이가 없었다. 이걸 바꾸기만 해도 청구서가 눈에 띄게 줄었다.

슬라이딩 윈도우 — 대화 기록 무한 누적 막기

제가 직접 코드를 뜯어보니, 원래 구조가 너무 무책임하게 짜여 있었다는 걸 알게 됐다. 예를 들어 누군가 10번에 질문을 던질 때, 이전 9번의 대화 기록이 다 배열에 쌓여서 매번 API 호출 시 함께 보내는 방식이었다. 결과적으로 10번째 질문 하나 처리하려고 앞선 모든 대화 내용을 다시 보내면서 입력 토큰이 엄청나게 불어나는 구조였음. 이래서 비용이 삽시간에 폭등 할 수 밖에.

그래서 나는 파이썬 리스트 슬라이싱을 활용해 ‘시스템 프롬프트 1개 + 최근 대화 4개’만 유지하는 슬라이딩 윈도우 방식을 썼다. 이 방법 덕분에 대화 내용이 누적되지 않고, 토큰 수가 불필요하게 늘어나는 걸 막을 수 있었다. 물론, 너무 짧게 유지하면 문맥이 끊겨서 답변 품질이 떨어질 수 있으니 주의해야 한다.

tiktoken으로 토큰 수 미리 점검 + max_tokens 제한 걸기

API 호출 전, tiktoken 라이브러리를 써서 입력 텍스트의 토큰 수를 직접 계산하는 건 필수다. 이걸 안 하면 대화가 너무 길어져서 API가 오류를 내거나, 비용이 걷잡을 수 없이 커지는 걸 겪어 보면 수정하게된다. 그래서 토큰 수가 2000개를 넘으면 아예 API 호출을 막도록 해버렸다. 이렇게 하니까 불필요한 호출이 줄어들면서 비용 관리가 가능했다.

max_tokens 옵션으로 출력 토큰 최대치를 제한했는데, 이건 좀 생각보다 까다롭다. 예를 너무 낮춰서 50 정도로 잡으면 챗봇 답변이 중간에 끊겨서 나오다 말기도 한다. 그래서 고객센터용 챗봇 기준으로 250~300 사이에서 안정적인 답변 길이를 써야 했다 만약 짧게 답변을 유도하고 싶다면 무조건 숫자를 낮출 게 아니라, 시스템 프롬프트에서 ‘3문장 이내로 답해라’ 같은 구체적인 지침을 명령어로 넣는 게 훨씬 깔끔.

import os
import tiktoken
from dotenv import load_dotenv
from openai import OpenAI

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

chat_history = [
    {"role": "system", "content": "너는 쇼핑몰 CS 담당자야. 반드시 3문장 이내로 짧게 대답해."}
]

def count_tokens(text, model_name="gpt-4o-mini"):
    # 모델명으로 직접 매핑해야 오차가 없다
    encoding = tiktoken.encoding_for_model(model_name)
    return len(encoding.encode(text))

def ask_chatbot(user_message):
    global chat_history
    chat_history.append({"role": "user", "content": user_message})

    # 슬라이딩 윈도우: 시스템 프롬프트 + 최근 4개 대화만 유지
    if len(chat_history) > 5:
        chat_history = [chat_history[0]] + chat_history[-4:]

    total_text = " ".join([msg["content"] for msg in chat_history])
    estimated_tokens = count_tokens(total_text)
    print(f"[시스템] 예상 입력 토큰: {estimated_tokens}")

    if estimated_tokens > 2000:
        return "대화 내용이 너무 깁니다. 핵심만 다시 질문해주세요."

    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=chat_history,
        max_tokens=300,  # 출력 토큰 상한 — 너무 낮으면 응답이 잘린다
        temperature=0.5
    )

    bot_reply = response.choices[0].message.content
    chat_history.append({"role": "assistant", "content": bot_reply})
    return bot_reply

if __name__ == "__main__":
    print(ask_chatbot("배송은 보통 며칠 걸리나요?"))
    print(ask_chatbot("어제 주문한 제 상품은 언제 와요? 주문번호 1234"))

3. 최적화하다 서버를 날린 에러 2가지

에러 ① max_tokens=50으로 서비스 전체가 뻗었다

내가 비용을 무리하게 줄이려고 max_tokens=50 으로 설정한 날이 있었다. 결과는 처참했다. 챗봇이 JSON 응답을 중괄호도 다 닫기 전에 멈춰서 {"status": "success", "message": "배송은 내일 같은 불완전한 문자열을 던졌다. 프론트엔드에서는 이걸 json.loads()로 파싱하려고 시도했고, 당연히 예외가 터졌다. 에러 핸들링도 전혀 준비하지 않은 상태였기에, 서버가 아예 멈추는 사태가 벌어졌다. 이 사건 이후로는 max_tokens를 300 아래로 내리는 일은 절대 하지 않는다. 출력 토큰 최대치를 직접 제한하는 것보다 프롬프트 지시문으로 출력 길이를 조절하는 쪽이 훨씬 안정적이란 걸 뼈저리게 느꼈다.

에러 ② tiktoken 인코딩 불일치 — 20% 오차의 함정

몇번은 인터넷에서 코드를 복사해 썼었느데 한참 시간을 잡아먹은 게 바로 이 문제였다. 원래 예제로 나온 tiktoken.get_encoding("p50k_base")가 있길래 이걸 그대로 썼는데, 파이썬에서 계산한 토큰 수와 OpenAI 대시보드에 찍힌 실제 청구 토큰 수 사이에 20%가량 차이가 났다. 이틀 동안 왜 이런 오차가 나는지 알 수 없어서 곤란했다. 문제는 gpt-4o 계열 모델이 o200k_base라는 새로운 인코딩 방식을 쓰는 점이었다. 결국 tiktoken.encoding_for_model(“gpt-4o-mini”)처럼 모델 이름으로 직접 인코딩을 매핑하는 방식으로 코드를 바꾸니 오차가 완전히 사라졌다. 인코딩 방식을 하드코딩하면 모델이 업데이트될 때 조용히 깨질 위험이 있어서, 매핑 방식을 쓰는 게 훨씬 안전하다는 교훈을 얻었다.