> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sunra.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# 토큰 사용량

모든 LLM 응답에는 해당 요청이 소비한 토큰을 보고하는 `usage` 객체가 포함됩니다. Sunra는 호출한 endpoint의 시맨틱으로 사용량을 보고합니다. Messages 응답은 Anthropic 계약을 따르고, Chat Completions 또는 Responses 응답은 OpenAI 계약을 따릅니다.

이 형태들이 서로 다른 것은 의도된 것입니다. Chat Completions 응답을 읽는 OpenAI SDK는 OpenAI 시맨틱을 가정하고, Messages 응답을 읽는 Anthropic SDK는 Anthropic 시맨틱을 가정합니다. 각 endpoint는 하나의 공통된 형태로 강제되는 대신 자신의 명세가 약속하는 바를 지킵니다.

| Endpoint               | 프롬프트 카운트 필드     | 캐시된 토큰이 여기에 포함되나요?                                    |
| ---------------------- | --------------- | ----------------------------------------------------- |
| `/v1/messages`         | `input_tokens`  | 아니요 — 세 가지 입력 버킷은 상호 배타적입니다                           |
| `/v1/chat/completions` | `prompt_tokens` | 예 — 캐시된 토큰은 부분집합이며 `prompt_tokens_details`에 상세히 표시됩니다 |
| `/v1/responses`        | `input_tokens`  | 예 — 캐시된 토큰은 부분집합이며 `input_tokens_details`에 상세히 표시됩니다  |

<Warning>
  `/v1/messages`와 `/v1/responses`는 모두 `input_tokens`라는 필드 이름을 사용하지만, 두 곳에서 의미가 같지 않습니다. Messages에서는 신규 입력만을 뜻합니다. Responses에서는 캐시된 토큰을 포함한 프롬프트 전체를 뜻합니다.
</Warning>

## Messages

`/v1/messages`에서는 세 가지 입력 버킷이 **상호 배타적**입니다. 어떤 토큰도 두 번 계산되지 않으므로, 프롬프트 총합은 이들의 합입니다:

```
total prompt = input_tokens + cache_creation_input_tokens + cache_read_input_tokens
```

<ResponseField name="input_tokens" type="integer">
  신규 입력 토큰만 해당합니다. 두 캐시 버킷은 모두 제외합니다.
</ResponseField>

<ResponseField name="cache_creation_input_tokens" type="integer">
  이 요청이 캐시에 기록한 토큰.
</ResponseField>

<ResponseField name="cache_read_input_tokens" type="integer">
  이 요청에 캐시에서 제공된 토큰.
</ResponseField>

<ResponseField name="output_tokens" type="integer">
  모델이 생성한 토큰.
</ResponseField>

<ResponseField name="total_tokens" type="integer">
  세 가지 입력 버킷과 `output_tokens`의 합. 비스트리밍 응답에 존재합니다.
</ResponseField>

존재하지 않거나 `null`인 버킷은 0으로 계산됩니다.

### 실제 예시

동일한 19,000 토큰 프리픽스를 `claude-opus-4-8`에 두 번 전송한 경우입니다. 첫 번째 요청은 캐시를 기록하고, 두 번째 요청은 캐시를 읽습니다.

```json 콜드 요청(캐시 쓰기) theme={null}
{
  "usage": {
    "input_tokens": 8,
    "cache_creation_input_tokens": 19349,
    "cache_read_input_tokens": 0,
    "output_tokens": 2,
    "total_tokens": 19359,
    "sunra_usage_semantics": "anthropic.exclusive.v1"
  }
}
```

```json 웜 요청(캐시 읽기) theme={null}
{
  "usage": {
    "input_tokens": 8,
    "cache_creation_input_tokens": 0,
    "cache_read_input_tokens": 19349,
    "output_tokens": 2,
    "total_tokens": 19359,
    "sunra_usage_semantics": "anthropic.exclusive.v1"
  }
}
```

`input_tokens`는 두 요청 모두에서 8로 유지되는데, 매번 실제로 새로 추가된 입력이 8이기 때문입니다. 19,349 토큰 프리픽스는 생성 버킷에서 읽기 버킷으로 이동합니다. 두 요청 모두 합계는 동일한 19,359 토큰이지만 비용은 같지 않습니다. 캐시 쓰기와 캐시 읽기는 각각 자체 토큰당 요율로 과금되며, 그래서 별도의 버킷으로 보고됩니다.

## Chat Completions와 Responses

이 endpoint들은 OpenAI 시맨틱을 변경 없이 그대로 보고합니다. 프롬프트 카운트는 프롬프트 **전체**이며, 캐시된 토큰은 그 **부분집합**으로 별도로 보고됩니다.

```json /v1/chat/completions theme={null}
{
  "usage": {
    "prompt_tokens": 19357,
    "completion_tokens": 2,
    "total_tokens": 19359,
    "prompt_tokens_details": {
      "cached_tokens": 19349
    }
  }
}
```

`prompt_tokens`와 `cached_tokens`를 더하면 이중 계산이 됩니다. 이 endpoint들에서 신규 입력을 구하려면 빼야 합니다:

```
fresh input = prompt_tokens - prompt_tokens_details.cached_tokens
```

같은 규칙이 `/v1/responses`의 `input_tokens`와 `input_tokens_details.cached_tokens`에도 적용됩니다.

## `sunra_usage_semantics` 마커

Sunra가 정규화한 응답에는 `usage` 안에 마커가 포함됩니다:

```json theme={null}
"sunra_usage_semantics": "anthropic.exclusive.v1"
```

이 마커는 개별 이벤트나 필드가 아니라 **응답**에 대한 약속입니다. 비스트리밍 응답에서는 루트 `usage` 객체에 나타나고, 스트리밍 응답에서는 입력 버킷을 전달하는 이벤트인 `message_start`에 나타납니다. 이 값으로 존재할 때 이 페이지의 보장이 성립합니다. 세 가지 입력 버킷은 상호 배타적이며, `total_tokens`는 — 존재하는 경우 — 이들의 합에 `output_tokens`를 더한 값입니다.

**마커의 부재 또한 하나의 약속입니다.** Sunra는 자신이 측정한 응답 형태만 정규화합니다. 그 외의 것은 업스트림 제공자로부터 손대지 않은 채 전달되며 마커가 붙지 않습니다. 마커가 없는 응답은 그 버킷을 Sunra가 보증하지 않는 응답입니다.

응답이 어떤 규약을 따르는지 숫자를 들여다보며 추측하지 말고, 마커를 검증하세요. 형태를 짐작하는 방식(shape sniffing)이야말로 이 필드가 대체하려는 대상입니다. “버킷의 합이 프롬프트 카운트보다 크므로 버킷은 배타적일 것이다” 같은 휴리스틱은 둘 이상의 규약에서 성립하며, 언젠가는 응답을 잘못 읽게 됩니다.

```python theme={null}
usage = response["usage"]

if usage.get("sunra_usage_semantics") == "anthropic.exclusive.v1":
    total_prompt = (
        usage["input_tokens"]
        + usage.get("cache_creation_input_tokens", 0)
        + usage.get("cache_read_input_tokens", 0)
    )
else:
    # Not normalized by Sunra. Treat the upstream shape as unverified
    # and consult that provider's own documentation.
    total_prompt = None
```

마커를 포함하는 것은 `/v1/messages` 응답뿐입니다. Chat Completions와 Responses는 OpenAI 명세를 따르며 마커가 붙지 않습니다.

이 값에는 버전이 있습니다. 이 필드들의 의미에 대한 파괴적 변경은 새로운 값으로 배포되므로, `anthropic.exclusive.v1`에 대한 동등성 검사가 조용히 다른 계약을 읽기 시작하는 일은 없습니다.

## Streaming

스트리밍 Messages 요청에서는 사용량이 두 개의 이벤트에 걸쳐 도착합니다. `message_start`는 입력 측과 마커를 전달합니다. 마지막 `message_delta`는 `output_tokens`만 전달합니다. 마커 검증은 `message_start`에서 수행하고, 그런 다음 두 이벤트를 통상적인 방식으로 병합 — 나중 이벤트의 usage를 앞선 이벤트 위에 적용 — 하면 위에 문서화된 값이 나옵니다.

`total_tokens`는 스트리밍 응답에서 생략됩니다. 어떤 단일 이벤트도 입력 측과 출력 측을 모두 알지 못하므로, 스트림 도중에 계산한 총합은 잘못된 값이 됩니다. 스트림이 완료된 후 직접 버킷을 합산하세요.

## 이전 동작에서의 마이그레이션

Sunra는 이전에 `/v1/messages`에서 업스트림 `usage` 객체를 그대로 전달했습니다. 일부 제공자 앞단의 변환 계층은 캐시 생성 토큰을 `input_tokens` **안으로** 접어 넣기 때문에, Anthropic 계약을 따라 세 버킷을 합산한 호출자는 캐시 생성 토큰을 두 번 계산했습니다.

* **세 버킷을 합산한다면**, Anthropic 계약이 설명하는 그대로이며, 이제 올바릅니다. 여러분 쪽에서 변경할 것은 없습니다.
* **이전 동작을 보정하고 있었다면** — `input_tokens`에서 `cache_creation_input_tokens`를 직접 빼거나, 접어 넣는 동작을 역으로 계산하고 있었다면 — **중단하세요**. 그 보정은 이제 이미 제외된 토큰을 빼는 것이 되어 입력을 과소 계상하게 됩니다.
* **`total_tokens`를 읽는다면**, 이 값은 계속해서 전체 카운트를 보고합니다. 신규 입력, 두 캐시 버킷, 그리고 출력입니다.

배포 날짜가 아니라 마커를 기준으로 변경을 게이팅하세요. 그래야 마커가 있는 응답과 없는 응답 모두에 대해 동일한 코드 경로가 올바르게 동작합니다.
