AI API 청구서가 늘었다
당연한 소리긴 하지만 AI를 사용하고 나서 부터는 일이 편해졌다. 이부분은 진짜 최고지만 문제는 돈도 그만큼 늘었다는 것이 문제다. 프로그램을 개발해서 잘 쓰는 것도 있지만 안쓰거나 문제가 발생하는 경우가 종종 있었다.
아무튼 비용이 늘어가는 상황에서 이유를 찾아보았는데 그중에 하나가 최고급 모델인 gpt-4o를 너무 자주 쓰면서 발생하고 있었다. 진짜 “안녕” 이거 하나 입력하려고 4o 모델 쓰는건 비용 낭비 그 자체였다. 그래서 주말 동안 OpenAI 공식 문서와 깃허브를 샅샅이 뒤지며 코드를 분석했다. 결과적으로 OpenAI API 비용을 무려 92%까지 줄이는 세 가지 방법을 찾아냈다.

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-mini는 gpt-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”)처럼 모델 이름으로 직접 인코딩을 매핑하는 방식으로 코드를 바꾸니 오차가 완전히 사라졌다. 인코딩 방식을 하드코딩하면 모델이 업데이트될 때 조용히 깨질 위험이 있어서, 매핑 방식을 쓰는 게 훨씬 안전하다는 교훈을 얻었다.