🐝매일 한입
AI Tutorial & How-to📖 15분 읽기

LLM 토큰 수 세기: API 없이 토큰·비용 추정기를 Node.js로 [2026]

LLM 토큰 수를 API 호출 없이 추정하는 카운터를 의존성 0 Node.js로 만듭니다. 실제 BPE와 비교한 오차율과 모델별 비용 표까지 직접 실행해 정리했습니다.

📖 15분 읽기
#LLM 토큰#토큰 카운터#비용 추정#tiktoken#count_tokens#Node.js

TL;DR

  • LLM 비용은 결국 토큰 수 × 단가입니다. 그런데 매 요청마다 count_tokens API를 부르면 그 자체가 왕복 비용이에요.
  • 그래서 API 없이 대략적인 토큰 수를 추정하는 카운터를 의존성 0 Node.js로 만듭니다. 문자 기반·단어 기반·하이브리드 세 가지를 직접 구현했어요.
  • 실제 BPE 토크나이저와 비교해보니 오차가 **영어 +39%, 코드 −21%, 한국어 +6%**까지 벌어졌습니다. 왜 이렇게 튀는지, 그래서 언제 추정치를 쓰고 언제 count_tokens를 써야 하는지 나눕니다.
  • 모델별 비용 표와 "월 100만 요청" 시나리오도 실제로 돌려서 뽑았습니다.

주판 — '세는 것'의 오래된 은유 출처: Wikimedia Commons (Pearson Scott Foresman, public domain) | 토큰을 '센다'는 건 결국 계산의 문제

왜 토큰을 미리 세야 할까

지난달에 사이드 프로젝트 요금 청구서를 보고 좀 놀랐습니다. 프롬프트에 문서를 통째로 붙여 넣는 코드가 있었는데, 입력 토큰이 생각보다 훨씬 컸던 거예요. "이 요청 하나가 대체 몇 토큰이지?"를 보내기 전에 알았더라면 컨텍스트를 잘라내거나 더 싼 모델로 라우팅했을 겁니다.

토큰 수를 미리 알면 할 수 있는 게 많습니다. 컨텍스트 윈도우 초과를 사전에 막고, 요청 전에 대략 비용을 계산해 예산 가드레일을 걸고, 긴 입력은 자동으로 청킹하죠. 문제는 정확한 토큰 수를 알려면 보통 모델 제공사의 토크나이저나 count_tokens 엔드포인트를 호출해야 한다는 겁니다. 매 요청마다 그걸 부르는 건, 특히 프론트엔드 미리보기나 대량 배치에서는 부담이에요.

그래서 오늘은 API 없이 로컬에서 즉시 돌아가는 근사 카운터를 만들어 봅니다. 이건 검색 파이프라인에서 청크 크기를 가늠하거나(관련해서는 RAG의 핵심 코사인 유사도를 Node.js로 직접 구현하기에서 다뤘습니다) 비용 대시보드의 실시간 추정치를 띄우는 데 딱 맞습니다.

가설: 문자 수만으로 토큰을 얼마나 맞출 수 있을까

업계에서 흔히 도는 규칙이 있습니다. "영어는 대략 4자당 1토큰." 이게 얼마나 맞는지, 그리고 한국어와 코드에서는 어떻게 깨지는지를 직접 측정해 보는 게 이 글의 실험입니다.

토큰 추정 방식은 크게 세 가지로 잡았습니다.

  • 문자 기반: 전체 글자 수 ÷ 4
  • 단어 기반: 공백 단어 수 × 1.3 (구두점 몫을 더한 값)
  • 하이브리드: 한글·CJK 문자와 나머지를 나눠서 각각 다른 계수로 추정

추정기 구현 (의존성 0)

먼저 세 추정기입니다. 외부 패키지 없이 String 하나로 끝납니다.

// token-estimator.mjs — 의존성 0

// (a) 문자 기반: 영어 텍스트는 대략 4자당 1토큰
export function estimateByChars(text) {
  return Math.ceil(text.length / 4);
}

// (b) 단어 기반: 영어는 대략 1단어 = 1.3토큰 (구두점 포함)
export function estimateByWords(text) {
  const words = text.trim().split(/\s+/).filter(Boolean);
  return Math.ceil(words.length * 1.3);
}

// (c) 하이브리드: CJK와 나머지를 나눠서 추정
export function estimateHybrid(text) {
  if (!text) return 0;
  const cjk = (text.match(/[ -鿿가-힯]/g) || []).length;
  const rest = text.length - cjk;
  const restTokens = rest / 4;      // 라틴·숫자·기호: 4자당 1토큰
  const cjkTokens = cjk * 0.7;      // 한글·CJK: 측정상 문자당 약 0.7토큰
  return Math.ceil(restTokens + cjkTokens);
}

estimateByChars는 한 줄이죠. 핵심은 하이브리드입니다. 한글은 한 글자가 여러 토큰으로 쪼개지는 경우가 많아서, 라틴 문자와 같은 계수를 쓰면 크게 어긋납니다. 그래서 유니코드 범위로 CJK를 골라내 계수를 따로 줬어요.

CJK 계수 0.7은 제가 임의로 넣은 값이 아니라 실제 측정해서 맞춘 값입니다. 뒤에서 나오는 한국어 샘플의 오차가 그 근거예요.

실험: 진짜 토큰 수와 비교

추정치가 얼마나 맞는지 보려면 기준이 될 "진짜 토큰 수"가 필요합니다. 여기서는 순수 JS로 된 실제 BPE 토크나이저(gpt-tokenizer, OpenAI o200k 계열)를 기준선으로 썼습니다. 네 종류의 대표 텍스트를 각 방식으로 세고 오차를 계산했어요.

import { encode } from "gpt-tokenizer"; // 기준선: 실제 BPE 토크나이저
import { estimateByChars, estimateByWords, estimateHybrid } from "./token-estimator.mjs";

const real = encode(text).length;         // 진짜 토큰 수
const err = ((estimateHybrid(text) - real) / real) * 100;

실제로 돌리면 이렇게 나옵니다.

샘플실제(BPE)char/4word×1.3hybridhybrid 오차
영어 산문28393039+39.3%
한국어 산문48233051+6.3%
코드 스니펫34272527−20.6%
JSON 페이로드3225325−21.9%

숫자를 보면 몇 가지가 바로 눈에 들어옵니다.

첫째, "4자당 1토큰"은 깔끔한 영어 산문에서 오히려 과대추정합니다. 이 샘플은 글자당 밀도가 5.57자/토큰이라 ÷4가 토큰을 40% 가까이 부풀렸어요. 반대로 영어에서는 단어 기반(×1.3)이 훨씬 정확했습니다(30 대 28).

둘째, 한국어는 문자 기반이 절반으로 무너집니다(23 대 48). 측정해 보니 한국어 밀도가 1.85자/토큰, 즉 한 글자가 거의 반 토큰 이상이었어요. 그래서 하이브리드에 CJK 계수 0.7을 넣자 오차가 +6%로 확 줄었습니다.

셋째, 코드와 JSON은 어느 방식도 잘 못 맞춥니다. 밀도가 3.1자/토큰으로 산문보다 훨씬 촘촘하거든요. 중괄호·따옴표·연산자가 다 토큰 경계를 만들기 때문입니다. 단어 기반은 JSON을 3토큰으로 봤는데 실제는 32였어요. 공백이 거의 없으니 단어 수 세기가 통째로 실패한 거죠.

정리하면, 콘텐츠 종류가 오차를 지배합니다. 하나의 마법 계수로 산문·한국어·코드를 다 맞추는 건 원리적으로 불가능해요.

주판으로 계산하는 모습 — 도구가 정확도를 좌우한다 출처: Wikimedia Commons (CC BY-SA) | 추정도 도구지만, 정확한 값이 필요할 땐 제대로 된 토크나이저가 답이다

비용 추정기 붙이기

토큰 수를 얻었으면 비용은 곱셈입니다. 2026년 7월 기준 Anthropic 공식 단가(100만 토큰당 USD)를 넣었습니다.

export const PRICING = {
  "claude-opus-4-8": { input: 5.0, output: 25.0 },
  "claude-sonnet-5": { input: 3.0, output: 15.0 }, // 도입 특가 $2/$10 (2026-08-31까지)
  "claude-haiku-4-5": { input: 1.0, output: 5.0 },
};

export function estimateCost(model, inputTokens, outputTokens) {
  const p = PRICING[model];
  const inCost = (inputTokens / 1_000_000) * p.input;
  const outCost = (outputTokens / 1_000_000) * p.output;
  return { inCost, outCost, total: inCost + outCost };
}

입력 10,000 토큰 + 출력 2,000 토큰짜리 요청 하나를 각 모델로 계산하면 이렇게 나옵니다.

모델입력 비용출력 비용합계
claude-opus-4-8$0.0500$0.0500$0.1000
claude-sonnet-5$0.0300$0.0300$0.0600
claude-haiku-4-5$0.0100$0.0100$0.0200

한 건으로는 푼돈처럼 보이죠. 그런데 규모가 붙으면 얘기가 달라집니다. 요청당 입력 3,000 + 출력 500 토큰짜리를 월 100만 건 돌린다고 하면:

  • claude-opus-4-8: 요청당 $0.02750 → 월 $27,500
  • claude-sonnet-5: 요청당 $0.01650 → 월 $16,500
  • claude-haiku-4-5: 요청당 $0.00550 → 월 $5,500

같은 트래픽인데 모델만 바꿔도 월 2만 달러가 왔다 갔다 합니다. 그래서 요청을 보내기 전에 토큰을 가늠해 싼 모델로 라우팅할지 판단하는 게 중요한 거예요. 모델별 실전 선택 기준은 Claude Opus 4.8 vs Sonnet 5: API 개발자 실전 선택 가이드에서 더 자세히 다뤘습니다.

🔪 이 추정기의 한계 (중요)

솔직하게 말하면, 위 추정기는 가드레일이지 회계장부가 아닙니다. 두 가지를 꼭 짚어야 해요.

첫째, 제가 기준선으로 쓴 gpt-tokenizerOpenAI의 토크나이저입니다. Claude는 다른 토크나이저를 쓰고, Anthropic 문서 기준으로 비(非)Claude 토크나이저는 Claude 토큰을 실제보다 적게 셉니다. 일반 텍스트에서 15~20%, 코드나 비영어에서는 더 벌어져요. 즉 위 표의 "실제(BPE)"조차 Claude 입장에서는 근사치입니다.

둘째, 그래서 정확한 값이 필요하면 제공사의 토크나이저나 count_tokens를 쓰세요. Claude라면 client.messages.count_tokens(...) 한 번이면 정확한 입력 토큰이 나옵니다. 이건 왕복이 있지만, 청구 직전 검증이나 컨텍스트 윈도우 하드 리밋 체크에는 이 정확도가 필요합니다.

정리하면 이렇게 씁니다.

  • 로컬 추정기(오늘 만든 것) → 실시간 미리보기, 대략적 예산 경고, 청킹 크기 가늠. 왕복 0, 오차 ±20~40% 감수.
  • count_tokens API → 실제 청구 예측, 윈도우 초과 방지, 배치 비용 산정. 정확하지만 왕복 비용 있음.

마치며

토큰을 세는 건 화려하지 않지만, LLM 비용의 90%는 여기서 갈립니다. 오늘 만든 40여 줄짜리 추정기는 "이 요청 대략 얼마짜리지?"를 왕복 없이 답해주는 도구예요. 다만 그 답이 ±20~40% 틀릴 수 있다는 걸 알고 쓰는 게 핵심입니다.

다음 단계로는, 추정 토큰이 임계값을 넘으면 자동으로 입력을 잘라내는 청커를 붙이거나, 429가 떴을 때 재시도하는 로직(LLM API가 자꾸 429를 뱉을 때)과 묶어 비용·안정성 가드레일을 한 세트로 만들면 좋습니다. 더 많은 실전 구현 글은 AI Tutorial & How-to 카테고리에서 이어집니다.


참고 자료

  • Token counting — Anthropic 공식 문서, 2026년 7월 기준
  • Pricing — Anthropic 공식 가격표, 2026년 7월 기준
  • tiktoken — OpenAI BPE 토크나이저, 2026년 기준
  • gpt-tokenizer — 순수 JS 토크나이저(기준선으로 사용), 2026년 기준

함께 읽으면 좋은 글:

📚 관련 글

💬 댓글