영리의 테크블로그

OpenRouter 비용 계산 본문

dev/AI

OpenRouter 비용 계산

영리0 2026. 9. 9. 21:36

배경

우리 서비스는 도매 상품 하나를 마켓에 올릴 수 있는 상태로 가공하는 데 LLM을 다섯 번 부른다. 상품명 정제, 검색어 분석, 키워드 분류, 카테고리 선택(마켓별로 2~3회), 그리고 상세 이미지를 보고 속성을 뽑는 비전 호출. 이 파이프라인을 OpenAI gpt-5.6-luna로 돌리고 있었는데, 상품 1만 개당 약 $57이 나갔다.

GLM-5.3-flash가 입력 $0.075, 출력 $0.25(1M 토큰)라는 가격에 비전까지 지원한다기에 OpenRouter 경유로 바꾸기로 했다. 단가만 보면 3배 이상 싸다. 계산상 1만 건에 $18. 이 글은 그 계산이 어떻게 틀렸고, 어떻게 다시 맞췄는지에 대한 기록이다.

1. 단가 비교는 시작일 뿐이다

먼저 실제 토큰 사용량이 필요했다. 우리는 LLM 호출마다 입력·출력 토큰과 소요 시간을 운영 DB에 기록해 두고 있었다. 이게 없었으면 이 글의 나머지는 전부 추측이었을 것이다.

상품 1건당 luna 실측치는 입력 약 19,600 토큰, 출력 약 1,450 토큰. 입력의 3분의 2는 비전 호출의 이미지였다. 여기에 단가를 곱하면 luna $5.7 / GLM $1.8 (1,000건 기준). 여기까지는 예상대로였다.

2. 첫 번째 함정: 프로모션 가격

Z.ai 공식 가격 페이지를 다시 읽다가 작은 글씨를 발견했다. $0.075/$0.25는 50% 할인가이고 할인은 우리가 테스트하던 바로 그날 밤에 끝난다고 적혀 있었다. 정가는 $0.15/$0.50. 절감률은 68%에서 35%로 반토막이 났다.

교훈: 모델 가격표에서 취소선이 그어진 숫자를 먼저 봐라.

3. 두 번째 함정: 출력 토큰이 4.7배

OpenRouter로 10건을 실제로 돌려 보니 뭔가 이상했다. 비전 호출의 출력 토큰이 luna에서는 423이었는데 GLM에서는 평균 2,771, 최대 6,567이었다. 5배 넘게 늘어난 것이다.

원인은 GLM이 기본적으로 thinking 모드로 동작하고 OpenRouter는 추론 토큰을 출력 토큰으로 과금하기 때문이었다. 우리 프롬프트는 "이 이미지에서 이 속성들을 찾아 JSON으로 답해라" 수준이라 추론이 필요 없는데, 모델은 매번 몇천 토큰씩 생각하고 있었다.

이 상태로 정가를 적용하면 1만 건에 $69. luna보다 비싸다. 싼 모델로 바꿨는데 청구서가 늘어나는 상황이었다.

4. 세 번째 함정: 라우팅이 최저가로 가지 않는다

OpenRouter 로그를 보니 호출이 Z.ai만이 아니라 StreamLake, Wafer, Novita 같은 프로바이더로도 가고 있었다. 이들은 같은 모델을 $0.135/$0.45처럼 더 높은 가격에 서빙한다.

OpenRouter의 기본 라우팅은 "가장 싼 곳"이 아니다. 가격의 역제곱으로 가중치를 매겨 분산한다. 싼 곳이 더 자주 선택되긴 하지만 비싼 곳도 꾸준히 걸린다. 가용성을 위한 설계라 이해는 되지만 비용을 계산할 때는 알고 있어야 한다.

5. 실험: 두 개의 레버를 따로, 그리고 같이

가설은 두 개였다. 추론을 끄면 출력 토큰이 줄고 가격순 라우팅을 강제하면 단가가 줄 것이다. 둘은 독립적이므로 4가지 조합을 모두 돌렸다.

실험 설계에서 신경 쓴 것은 충실도다. 손으로 쓴 프롬프트가 아니라 실제 클라이언트 코드를 그대로 호출했다. 프롬프트는 운영 DB 값, 속성 스키마는 운영 Redis 캐시, 이미지는 실제 상세 이미지를 썼다. 상품 5건 × 호출 6회 × 설정 4가지 = 120회. 비용은 OpenRouter의 generation 엔드포인트에서 호출별 실제 청구액을 가져왔다. 총 지출 $0.066.

설정 1만 건 환산 평균 응답
기본 (추론 켬, 기본 라우팅) $37.6 19.6초
추론 최소만 $31.8 27.3초
가격순 라우팅만 $42.3 20.7초
둘 다 $20.9 4.3초

예상 밖의 결과가 두 개 있었다.

가격순 라우팅만 켜면 오히려 비싸진다. 목록 가격으로 정렬하지 추론 토큰 소모량으로 정렬하지 않기 때문이다. 싼 프로바이더가 추론을 더 많이 하면 결과적으로 더 비싸다. 두 레버를 같이 당겨야 효과가 난다.

응답 시간이 5분의 1로 줄었다. 비용 때문에 시작한 실험이었는데 속도가 가장 큰 수확이었다. 배치 하나가 20초 걸리던 것이 4초로 줄면 처리량 자체가 달라진다.

6. "추론 끄기"는 불가능했다

OpenRouter 문서에는 reasoning: {effort: "none"}으로 추론을 끌 수 있다고 되어 있다. 보내 봤더니 400 오류.

Reasoning is mandatory for this endpoint and cannot be disabled.

enabled: false도, max_tokens: 0도 같은 답이었다. Z.ai 네이티브 파라미터인 thinking: {type: "disabled"}는 오류 없이 받아들여지지만 무시됐다.

동작한 것은 effort: "minimal" 하나뿐이었다. 이 값을 주면 Z.ai에서는 추론 토큰이 정확히 0이 된다. 문서를 믿지 말고 토큰 수를 직접 세라는 교훈.

7. 프로바이더 하나가 규칙을 안 지킨다

minimal을 줘도 추론 토큰이 남는 호출들이 있었다. 프로바이더별로 나눠 보니 Wafer로 간 호출은 4건 모두 풀 추론(최대 5,230 토큰)이었고 나머지 프로바이더는 전부 지켰다. 심지어 baseline에서 Wafer는 추론 1토큰이었는데, minimal을 요청하자 오히려 풀 추론으로 바뀌었다. 파라미터 해석이 뒤집혀 있는 것으로 보인다.

같은 모델 이름이라도 프로바이더마다 파라미터 처리가 다르다. OpenRouter의 provider.ignore로 Wafer만 빼고 나머지 페일오버는 유지했다.

8. 품질은 떨어졌나

추론을 줄이면 품질이 나빠질 거라는 걱정이 당연히 있었다. 결과는 반대였다. 검증 항목 8개 가운데 카테고리 선택이 운영 결과와 일치한 비율은 "둘 다" 설정이 5건 중 4건으로 가장 높았고 기본 설정은 2건이었다. 속성 추출도 "둘 다" 설정이 더 많은 속성을 찾았고 모두 스키마 안의 값이었다. 유일한 결함(한글과 한자가 섞인 속성 키)은 Wafer로 간 호출에서 나왔다.

5건은 작은 표본이다. 하지만 "추론이 품질을 지켜준다"는 가정을 뒷받침하는 증거는 하나도 없었다. 우리 작업은 분류와 추출이지 수학 문제가 아니다.

최종 설정

extra_body = {
    "reasoning": {"effort": "minimal"},
    "provider": {"sort": "price", "ignore": ["Wafer"]},
}

OpenAI SDK는 스키마에 없는 필드를 걸러내므로 extra_body로만 실을 수 있다. 코드 변경은 이 dict 하나와 그것을 생성자에서 받아 모든 호출에 넘기는 일곱 줄이다.

정리

  • 단가 비교로 시작했지만 실제 비용은 토큰 수 × 실제 라우팅된 단가 × 프로모션 여부다. 세 개 다 확인해야 한다.
  • 호출마다 토큰과 시간을 기록하는 습관이 이 모든 분석의 전제였다. 로그가 없으면 "느낌상 비싸다"에서 못 벗어난다.
  • 추론 모델을 분류·추출 작업에 쓸 때는 추론이 기본으로 켜져 있는지 반드시 확인하라. 청구서에서 가장 먼저 보이는 항목이 될 수 있다.
  • 문서의 파라미터는 직접 쳐서 토큰 수로 검증하라. 400이 나오는 것도, 조용히 무시되는 것도, 프로바이더마다 다르게 해석되는 것도 다 겪었다.
  • 최종적으로 luna 대비 63% 절감, 응답 시간 5분의 1. 정가 적용 후에도 순위는 같다.