블로그 목록
ai

Claude 토큰 카운팅 API로 요청 전에 비용 계산하기 — count_tokens 써보기

RAG 파이프라인을 만들다가 문제가 하나 생겼다. 검색된 문서를 프롬프트에 붙여서 Claude에 넘기는데, 문서 개수가 들쭉날쭉하다 보니 어떤 요청은 입력이 몇 천 토큰이고 어떤 요청은 몇 만 토큰이었다. 컨텍스트 윈도우를 넘기면 그냥 에러가 나버리고, 비용도 요청마다 크게 달라졌다.

그래서 요청을 실제로 보내기 전에 "이 프롬프트가 토큰 몇 개짜리인지" 먼저 알고 싶었다. tiktoken 같은 라이브러리로 어림잡아 세는 방법도 있지만, Claude는 토크나이저가 공개돼 있지 않아서 추정값이 실제와 안 맞는다. 다행히 Anthropic은 정확한 값을 알려주는 전용 엔드포인트를 제공한다.

count_tokens는 뭘 하나

messages/count_tokensmessages 요청과 똑같은 형식을 받아서, 응답을 생성하지 않고 입력 토큰 수만 돌려준다. 실제 추론이 안 돌기 때문에 무료고, 응답도 즉시 온다.

import Anthropic from '@anthropic-ai/sdk';

const client = new Anthropic();

const { input_tokens } = await client.messages.countTokens({
  model: 'claude-sonnet-5',
  system: '너는 문서 요약 도우미다.',
  messages: [
    { role: 'user', content: '이 문서를 3줄로 요약해줘:\n\n' + docText },
  ],
});

console.log(`입력 토큰: ${input_tokens}`);

핵심은 messages.create에 넘길 파라미터를 그대로 countTokens에 넣을 수 있다는 점이다. system, 여러 턴의 messages, 심지어 tools나 이미지 블록까지 다 반영해서 세준다. 도구 정의가 붙으면 그 스키마도 토큰을 잡아먹으니, 실제 요청과 동일하게 넣어야 값이 정확하다.

보내기 전에 게이트로 쓰기

나는 이걸 요청 전 방어선으로 썼다. 컨텍스트 한도에 가까우면 문서를 잘라내는 식이다.

const LIMIT = 180_000; // 200K 윈도우에 여유 두기

async function safeAsk(system: string, prompt: string) {
  const { input_tokens } = await client.messages.countTokens({
    model: 'claude-sonnet-5',
    system,
    messages: [{ role: 'user', content: prompt }],
  });

  if (input_tokens > LIMIT) {
    throw new Error(`입력이 너무 큼: ${input_tokens} 토큰`);
  }

  return client.messages.create({
    model: 'claude-sonnet-5',
    max_tokens: 1024,
    system,
    messages: [{ role: 'user', content: prompt }],
  });
}

입력 토큰 수에 모델별 100만 토큰당 단가를 곱하면 요청 하나의 예상 입력 비용도 바로 나온다. 로그에 찍어두니 어떤 요청이 비싼지 한눈에 보였다.

실전 팁

  • 출력 토큰은 못 잰다. count_tokens는 입력만 센다. 총비용은 max_tokens를 상한으로 잡고 따로 계산해야 한다.
  • 매 요청마다 부를 필요는 없다. 입력 크기가 뻔한 경로라면 생략하고, 사용자 업로드처럼 크기가 튀는 곳에만 쓰면 된다.
  • 프롬프트 캐싱과 같이 보면 좋다. 어느 부분이 토큰을 잡아먹는지 알면 캐시할 접두부를 정하기 쉬워진다.

토큰을 어림짐작하던 걸 실측으로 바꾸니, 컨텍스트 초과 에러가 사라지고 요청별 비용도 예측 가능해졌다. 호출 한 번이 공짜라는 게 제일 크다.