> ## 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`。在非 streaming 响应中存在。
</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"
```

这个标记是一项关于**响应**的承诺，而不是关于某个单独事件或字段的承诺。在非 streaming 的响应中，它位于根部的 `usage` 对象内；在 streaming 的响应中，它出现在携带输入桶的那个事件 `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

在 streaming 的 Messages 请求中，用量分两个事件到达。`message_start` 携带输入侧和标记。末尾的 `message_delta` 只携带 `output_tokens`。请对 `message_start` 中的标记做断言，然后按通常的方式把两个事件合并——用后一个事件的 usage 覆盖前一个——即可得到上文记录的取值。

streaming 响应中会省略 `total_tokens`。没有哪个单独的事件同时知道输入侧和输出侧，因此在流进行过程中算出的任何总数都会是错的。请在流结束后自行把各个桶相加。

## 从旧行为迁移

Sunra 此前在 `/v1/messages` 上原样转发上游的 `usage` 对象。部分服务商前面的翻译层会把缓存创建令牌折叠**进** `input_tokens`，因此按 Anthropic 契约把三个桶相加的调用方，会把缓存创建令牌计算两次。

* **如果您把三个桶相加**，正如 Anthropic 契约所描述的那样，那么您现在是正确的。您这边无需改动。
* **如果您曾为旧行为做过补偿**——自行从 `input_tokens` 中减去 `cache_creation_input_tokens`，或以其他方式逆向还原这种折叠——请**停止**。该修正现在减掉的是本已被排除的令牌，会低估您的输入。
* **如果您读取 `total_tokens`**，它仍然报告完整计数：全新输入、两个缓存桶以及输出。

请以该标记而不是部署日期作为这次变更的判定条件，这样同一条代码路径对带标记和不带标记的响应都是正确的。
