블로그로 돌아가기

API & SDK가이드

Python에서 AI 에이전트 토큰 비용 추적: usage, 예산, 중단

모든 요청의 usage 필드를 기록하고, 작업별로 합산하며, 달러 예산에서 에이전트를 중단하세요. OpenAI 호환 API를 위한 작동하는 Python 예제입니다.

작성자: CheapTokens Team 게시됨읽는 시간 6분

터미널 스타일 코드 카드: 단계별 usage 로그와 AI 에이전트를 중단시키는 예산 확인
이 페이지의 내용
  1. 숫자가 있는 곳: usage 객체
  2. 에이전트를 중단시키는 미터
  3. 40단계 실행이 합산되는 값
  4. 모델이 사고할 때
  5. 스트리밍: usage는 마지막에 옵니다
  6. 숫자가 잘못 보인다면

에이전트가 10분 동안 실행되었는데 청구서가 작업보다 큽니다. 이유를 확인하려면 모든 API 응답에서 usage 객체를 읽고, 작업별로 합산한 뒤 모델의 1M 토큰당 가격을 곱하세요. 이를 제한하려면 각 단계 후에 그 누적 합계를 예산과 비교하고 초과하면 루프를 중단하세요. 아래는 정확히 이 작업을 수행하는 작은 Python 클래스입니다.

이 예제는 OpenAI 호환 엔드포인트 https://api.cheaptokens.dev/v1과 모델 ID deepseek-ai/DeepSeek-V4-Flash-0731(380K 컨텍스트, 답변당 최대 32,768 출력 토큰)를 사용합니다. 실행을 시작하기 전에 추정하려는 것은 다른 작업이며, AI 에이전트 비용을 추정하는 방법에서 다룹니다. 이 글은 실행 중에 측정하고 제한하는 것에 관한 것입니다.

숫자가 있는 곳: usage 객체#

모든 비스트리밍 채팅 완료는 usage 객체와 함께 반환됩니다. DeepSeek의 API 레퍼런스(2026년 10월 26일 확인)에 나열된 필드는 다음과 같습니다:

  • prompt_tokens — 이번에 보낸 모든 것: 시스템 프롬프트, 히스토리, 도구 결과.
  • completion_tokens — 모델이 다시 작성한 것.
  • total_tokens — 둘을 더한 값.

DeepSeek는 선택적 추가 항목도 문서화합니다: prompt_cache_hit_tokens과 prompt_cache_miss_tokens(둘을 더하면 prompt_tokens), 그리고 사고 모델용 completion_tokens_details.reasoning_tokens. 이 페이지에는 게시 날짜가 없고, 이러한 추가 항목은 OpenAI 형식의 확장이므로 아래 코드는 이를 선택 사항으로 취급하고 처음 두 필드에만 의존합니다.

왜 마지막 단계만이 아니라 모든 단계를 기록해야 할까요? 에이전트는 매번 커지는 컨텍스트를 다시 보내기 때문입니다. 1단계는 2,000 입력 토큰을 보낼 수 있고, 30단계는 9,000을 보낼 수 있으며, 청구서는 이 모든 것의 합계입니다. 그 이유는 긴 채팅이 더 비싼 이유에 설명되어 있고, 입력/출력 분할은 입력 vs 출력 토큰에 있습니다.

에이전트를 중단시키는 미터#

전체는 이렇습니다: 토큰을 합산하고, 단계당 한 줄을 출력하며, 예산을 초과하면 오류를 발생시키는 미터와 그 주위의 최소한의 에이전트 루프입니다. SDK를 pip install openai로 설치하고 키를 CHEAPTOKENS_API_KEY에 넣으세요.

python
import os
from openai import OpenAI

client = OpenAI(
    base_url='https://api.cheaptokens.dev/v1',
    api_key=os.environ['CHEAPTOKENS_API_KEY'],
)
MODEL = 'deepseek-ai/DeepSeek-V4-Flash-0731'

# example prices, USD per 1M tokens: use the current ones from the pricing table
PRICE_IN = 0.10
PRICE_OUT = 0.40


class BudgetExceeded(Exception):
    pass


class TaskMeter:
    def __init__(self, budget_usd):
        self.budget = budget_usd
        self.steps = 0
        self.tokens_in = 0
        self.tokens_out = 0
        self.reasoning = 0

    @property
    def cost(self):
        return (self.tokens_in * PRICE_IN + self.tokens_out * PRICE_OUT) / 1_000_000

    def add(self, usage):
        self.steps += 1
        self.tokens_in += usage.prompt_tokens
        self.tokens_out += usage.completion_tokens
        details = getattr(usage, 'completion_tokens_details', None)
        self.reasoning += getattr(details, 'reasoning_tokens', 0) or 0
        print(f'step {self.steps}: in={usage.prompt_tokens} '
              f'out={usage.completion_tokens} task=${self.cost:.4f}')
        if self.cost > self.budget:
            raise BudgetExceeded(
                f'budget ${self.budget:.2f} crossed at step {self.steps}: '
                f'${self.cost:.4f}')


def run_task(goal, budget_usd=0.50, max_steps=40):
    meter = TaskMeter(budget_usd)
    messages = [
        {'role': 'system', 'content': 'Work step by step. Reply DONE when finished.'},
        {'role': 'user', 'content': goal},
    ]
    for _ in range(max_steps):
        resp = client.chat.completions.create(
            model=MODEL, messages=messages, max_tokens=1024)
        meter.add(resp.usage)  # log first, then decide
        reply = resp.choices[0].message.content or ''
        messages.append({'role': 'assistant', 'content': reply})
        if 'DONE' in reply:
            break
        messages.append({'role': 'user', 'content': 'Continue.'})
    return meter


try:
    meter = run_task('Summarise the main risks of a database migration.')
    print(f'finished: {meter.steps} steps, ${meter.cost:.4f}')
except BudgetExceeded as err:
    print('stopped:', err)

그 안의 세 가지 선택은 따라 할 가치가 있습니다.

결정하기 전에 기록하세요. meter.add은 먼저 단계를 기록한 다음에야 예산을 확인하므로, 중단된 실행도 완전한 추적을 남깁니다. 확인은 선을 넘은 호출 이후에 발생하므로 최대 한 단계까지 초과할 수 있습니다. max_tokens=1024은 그 마지막 단계의 출력을 제한합니다.

하나가 아닌 두 개의 하드 스톱. max_steps은 많이 쓰지 않고 루프하는 에이전트를 잡아내고, 달러 예산은 단계당 많이 쓰는 에이전트를 잡아냅니다. 둘 다 필요합니다.

가격은 한 곳에 있습니다. PRICE_IN와 PRICE_OUT은 자리 표시자 숫자입니다. 가격 페이지에서 모델의 현재 값을 복사하세요. 현재 DeepSeek-V4-Flash는 1M 입력 토큰당 $0.015이고 1M 출력 토큰당 $0.10입니다.

40단계 실행이 합산되는 값#

에이전트가 40단계를 거치고, 컨텍스트가 커지기 때문에 평균 단계가 6,000 입력 토큰을 보내고 400 토큰을 돌려받는다고 가정해 봅시다. 그러면 작업에 대해 40 × 6,000 = 240,000 입력 토큰과 40 × 400 = 16,000 출력 토큰입니다.

1단계는 2,000 입력 토큰을 보내고, 30단계는 9,000을 보냅니다; 작업 총계는 모든 단계의 합입니다
글의 가정을 사용한 삽화이며, 실행 결과가 아닙니다

DeepSeek-V4-Flash에서 비용은 $0.0052입니다. 표는 동일한 워크로드를 다른 모델에서 실행하고 OpenRouter의 참조 가격을 우리 가격 옆에 놓습니다:

모델 비용 OpenRouter
GLM-5.3-Flash $0.0054 $0.0111
DeepSeek-V4-Flash $0.0052 $0.0139
MiniMax-M2.7 $0.0144 $0.0912

총 입력 240,000개 및 출력 16,000개 토큰입니다. 100만 토큰당 가격은 현재 기준이며 당사 요금제에 따라 함께 변경됩니다. OpenRouter 열은 동일한 작업량에 대한 참고 가격을 보여줍니다.

입력과 출력을 함께 계산한 총계에서 DeepSeek-V4-Flash는 이 작업에 대해 OpenRouter보다 저렴합니다. 토큰이 어디에 있는지도 주목하세요: 256,000 중 240,000이 입력입니다. 에이전트가 끌고 다니는 컨텍스트가 청구서이며, 이것이 로그의 단계당 in= 숫자가 주시해야 할 값인 이유입니다. 위 예제의 작업당 $0.50 예산은 목표가 아니라 폭주에 대한 상한입니다.

모델 전환은 MODEL을 변경하는 것만을 의미합니다. CheapTokens 문서에서 ID를 고르세요: 공급자마다 이름이 다르며, 가격 라벨의 짧은 ID는 API에서 작동하지 않습니다. 긴 에이전트 루프의 경우 DeepSeek-V4-Flash는 하루 중 어느 시간에도 하나의 가격이므로 작업 비용이 정오와 오전 3시에 동일합니다. 한도는 DeepSeek 페이지를 참조하세요.

모델이 사고할 때#

답변이 보이는 텍스트보다 토큰이 훨씬 길다면 reasoning_tokens을 확인하세요. 사고 토큰은 출력으로 계산되므로, 추론이 많은 단계는 보이는 텍스트가 시사하는 것보다 훨씬 많은 출력 토큰을 생성할 수 있고, 출력으로 청구됩니다. 위의 미터는 API가 필드를 반환할 때 meter.reasoning에 이를 수집하고, 그렇지 않으면 0으로 두므로 누락된 키로 절대 충돌하지 않습니다. 출력 중 얼마나 사고였는지 보고 싶다면 out= 옆에 출력하세요. 이유는 추론 노력과 사고 토큰에 설명되어 있습니다.

스트리밍: usage는 마지막에 옵니다#

에이전트가 스트리밍하면 일반 청크에는 usage이 없습니다. OpenAI 형식에서는 stream_options={'include_usage': True}로 요청합니다. DeepSeek의 레퍼런스(2026년 10월 26일 확인)에 따르면 이 옵션은 stream=True와 함께만 작동하고(그렇지 않으면 API가 400을 응답), usage은 하나를 제외한 모든 청크에서 null이며, data: [DONE] 전의 마지막 청크가 전체 요청의 합계를 담고 있습니다. 따라서 스트리밍 루프에서는 usage가 비어 있지 않은 청크에서 meter.add을 한 번 호출하세요. 위의 비스트리밍 루프가 시작하기에 가장 간단한 곳입니다; Python에서 스트리밍에 루프 자체가 있습니다.

숫자가 잘못 보인다면#

  • usage이 None입니다. include_usage 없이 스트리밍하고 있습니다. 추가하거나 비스트리밍 호출로 전환하세요.
  • 총계가 잔액 감소보다 낮습니다. 모든 호출이 미터를 통과하는지 확인하세요: 재시도, 요약 호출 또는 run_task 밖에서 이루어진 도구 선택 호출은 놓치기 쉽습니다. client.chat.completions.create를 하나의 함수로 감싸고 모든 것을 그리로 라우팅하세요.
  • 단계당 비용이 계속 올라갑니다. 모든 것을 messages에 추가하는 루프에서는 정상입니다. 오래된 도구 출력을 잘라내거나 히스토리를 요약하세요; 컨텍스트 밖의 도구 출력에 그 아이디어가 있습니다.
  • BudgetExceeded가 2단계에서 발생합니다. 예산이 한 단계보다 작습니다. PRICE_IN이 1K당이 아니라 1M 토큰당인지 확인하세요.

숫자를 저장하려면 meter.steps, meter.tokens_in, meter.tokens_out, meter.cost을 작업당 하나의 JSON 줄로 작성하세요. 일주일치 그 줄들은 에이전트의 실제 단계당 토큰을 알려주며, 이는 이후 모든 추정에 넣어야 할 숫자입니다. 모델, 가격 및 첫 요청은 문서에 있습니다; 도구 호출은 Python에서 함수 호출에서 다룹니다.