> ## 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.

Эти структуры различаются, и это сделано намеренно. OpenAI SDK, читающий ответ Chat Completions, предполагает семантику OpenAI, а Anthropic SDK, читающий ответ Messages, предполагает семантику 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` считается нулём.

### Разобранный пример

Один и тот же префикс в 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 не ручается.

Проверяйте маркер, а не изучайте числа, чтобы угадать, какому соглашению следует ответ. Именно определение соглашения по структуре это поле и призвано заменить: эвристика вроде «корзины должны быть взаимоисключающими, потому что их сумма больше количества токенов промпта» удовлетворяется более чем одним соглашением и рано или поздно прочитает ответ неверно.

```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 данные usage приходят в двух событиях. `message_start` несёт входную сторону и маркер. Завершающее `message_delta` несёт только `output_tokens`. Проверяйте маркер в `message_start`, а затем выполните обычное слияние двух событий — наложите usage более позднего события поверх более раннего — чтобы получить значения, описанные выше.

`total_tokens` не включается в потоковые ответы. Ни одно отдельное событие не знает одновременно входную и выходную сторону, поэтому любой итог, вычисленный в середине потока, будет неверным. Просуммируйте корзины самостоятельно после завершения потока.

## Переход с прежнего поведения

Раньше Sunra передавала на `/v1/messages` объект `usage` от вышестоящего провайдера дословно. Слой преобразования перед некоторыми провайдерами включает токены создания кэша **внутрь** `input_tokens`, поэтому вызывающая сторона, которая следовала контракту Anthropic и суммировала три корзины, учитывала токены создания кэша дважды.

* **Если вы суммируете три корзины**, как описано в контракте Anthropic, теперь вы считаете правильно. Изменений с вашей стороны не требуется.
* **Если вы компенсировали старое поведение** — самостоятельно вычитая `cache_creation_input_tokens` из `input_tokens` или иным образом отменяя это включение — **прекратите**. Теперь эта поправка вычитает токены, которые уже исключены, и занизит ваш ввод.
* **Если вы читаете `total_tokens`**, он по-прежнему сообщает полное количество: свежий ввод, обе корзины кэша и вывод.

Привязывайте это изменение к маркеру, а не к дате развёртывания, чтобы один и тот же путь кода был корректен и для маркированных, и для немаркированных ответов.
