---
url: /wave-agent/specs/enterprise/usage-tracking-callback.md
description: SDK 用量追踪：`onUsagesChange` 回调、`usages` 读取与 CLI 退出 token 摘要
---

# 功能规格说明：SDK 用量追踪与回调系统

**创建日期**：2025-11-11

## 澄清

### 2025-11-11 会议

* 问：用量数据的存储机制 → 答：将用量数据作为元数据存储在每个助手消息的会话中
* 问：回调数据范围 → 答：回调接收包含所有会话用量数据的 Usage\[] 数组
* 问：失败操作的处理 → 答：失败操作不进行用量追踪（未消耗 AI 服务）
* 问：回调错误处理策略 → 答：记录回调错误并继续正常 SDK 操作，不中断
* 问：用量数据格式结构 → 答：使用 openai Usage 类型，usages: Usage\[]
* 问：实现方法 → 答：复用当前回调系统，在 Message 类型中添加 usage 字段，保存到会话文件

## 用户场景与测试 *（必填）*

### 用户故事：实时用量监控（优先级：P1）

SDK 用户需要实时监控 AI 服务用量（token、成本、API 调用），以便在应用程序进行代理调用和消息压缩请求时追踪成本、实施用量限制并提供用户反馈。

**为什么是这个优先级**：提供即时价值的核心功能 - 在生产应用中实现成本监控和用户体验改善。

**独立测试**：可以通过注册回调函数并进行单次代理调用来完整测试，验证回调接收准确的用量数据并提供即时成本追踪价值。

**验收场景**：

1. **假设** 开发者注册了 onUsagesChange 回调，**当** 其应用程序通过 callAgent() 进行代理调用，**则** 回调被触发，接收包含所有会话用量数据的 Usage\[] 数组
2. **假设** 开发者注册了 onUsagesChange 回调，**当** 其应用程序通过 compressMessages() 压缩消息，**则** 回调被触发，接收包含所有操作的更新 Usage\[] 数组
3. **假设** 连续进行多次代理调用，**当** 每次调用完成，**则** 回调接收包含会话中所有操作用量数据的 Usage\[] 数组

***

### 用户故事：用量数据检索（优先级：P2）

SDK 用户需要随时以编程方式检索当前用量统计，无需等待回调触发。这支持仪表板显示、定期报告和与外部监控系统集成。

**为什么是这个优先级**：对于需要按需用量数据进行报告的应用至关重要，但次于提供即时运营价值的实时通知。

**独立测试**：可以通过进行多次代理调用，然后调用 `public get usages()` 方法并验证返回准确的累计统计来完整测试。

**验收场景**：

1. **假设** 多次代理操作已完成，**当** 开发者调用 `public get usages()` 方法，**则** 返回包含当前用量统计和操作元数据的 Usage\[] 数组
2. **假设** 未执行任何操作，**当** 开发者调用 `public get usages()`，**则** 返回空 Usage\[] 数组
3. **假设** 混合了代理调用和消息压缩操作，**当** 调用 `public get usages()`，**则** 返回 Usage\[] 数组，每种操作类型有单独的 Usage 对象

***

### 用户故事：消息级用量记录（优先级：P3）

SDK 用户需要嵌入在会话助手消息中的用量数据，以便每个 AI 操作的成本和 token 消耗可追溯到特定交互。这支持详细的对话分析、按交互计费以及对话流程中的历史用量审查。

**为什么是这个优先级**：对详细分析和审计追踪很重要，但可以在核心实时追踪功能工作后实现。

**独立测试**：可以通过执行代理操作，然后检查会话消息以验证用量元数据是否正确附加到每个助手响应来完整测试。

**验收场景**：

1. **假设** 代理调用操作完成，**当** 检查会话中的结果助手消息，**则** 消息包含用量元数据，包括该特定操作消耗的 token
2. **假设** 发生消息压缩操作，**当** 操作完成，**则** 压缩的用量数据与相应的消息上下文一起记录
3. **假设** 会话中发生多次操作，**当** 审查会话历史，**则** 每个助手消息都有自己的用量元数据，允许按交互追踪成本

***

### 用户故事：CLI 退出 Token 摘要（优先级：P2）

CLI 用户需要在 CLI 应用退出时查看按模型分类的总 token 用量摘要。这使他们能够了解会话的成本影响、追踪预算消耗，并就未来使用模式做出明智决策。

**为什么是这个优先级**：为 CLI 用户提供有价值的成本可见性，无需实现回调，但次于核心追踪功能。

**独立测试**：可以通过使用不同模型运行 CLI 操作，然后验证退出摘要显示按模型名称分组的准确 token 总量来完整测试。

**验收场景**：

1. **假设** CLI 会话中使用不同模型的代理调用，**当** CLI 正常退出，**则** console.log 显示每个模型消耗的总 token
2. **假设** CLI 会话中混合了代理调用和压缩操作，**当** CLI 退出，**则** 摘要显示代理模型和快速模型用量的单独 token 总量
3. **假设** CLI 会话中无 AI 操作，**当** CLI 退出，**则** 不显示 token 摘要或显示零用量
4. **假设** CLI 会话因错误退出，**当** 进程终止，**则** 仍在退出前显示 token 摘要

***

### 用户故事：流式调用必须显式索要用量（优先级：P1）

SDK 用户在主对话、上下文压缩、子代理等所有流式调用上都能拿到真实 token 用量，而不是依赖上游的默认行为。

**为什么是这个优先级**：Chat Completions 的流式响应默认不带 usage，上游要收到 `stream_options.include_usage` 才会在流尾补一帧 usage。不发这个参数时，上下文占用百分比、自动压缩的估算基数、turn 输出预算、`onUsagesChange` 回调与 telemetry 全部静默降级（不报错，只是数值缺失），所以它必须由 Wave 主动声明，不能寄希望于网关自己给。

**独立测试**：用请求体快照断言流式请求带 `stream_options: { include_usage: true }`、非流式请求不带，以及用户显式配置（含关闭）优先生效。

**验收场景**：

1. **假设** 主对话 / 压缩 / 子代理发起流式 Chat Completions 请求，**当** 检查请求体，**则** 带 `stream_options: { include_usage: true }`
2. **假设** 这次请求不是流式（非流式调用，或流式中途断连后的非流式重发），**当** 检查请求体，**则** 不带 `stream_options`——该字段只在 `stream: true` 时合法
3. **假设** 用户在 `models[<id>].options.stream_options` 写了自定义值，**当** 发起流式请求，**则** 原样透传用户的值，不被默认值覆盖
4. **假设** 用户把 `models[<id>].options.stream_options` 显式设为 `null`，**当** 发起流式请求，**则** 不带该字段（给不支持它的网关留出口）
5. **假设** 请求走 Responses 或 Anthropic Messages 端点，**当** 检查请求体，**则** 不出现 `stream_options`——这两个端点各自在响应流里自带 usage（`response.completed` / `message_start` + `message_delta`）

***

### 边界情况

* 当上游网关拒收 `stream_options`（严格实现返回 400）时会怎样？（用户可在 `models[<id>].options` 里写 `stream_options: null` 关掉；SDK 不做「400 后剥参重发」的静默兜底）
* 当 onUsagesChange 回调抛出错误或失败时会怎样？（错误被记录，SDK 继续正常操作不中断）
* 当 API 调用失败或中止时系统如何处理用量追踪？（失败操作不进行用量追踪，因为未消耗 AI 服务）
* 会话消息存储满或写保护时会怎样？
* 用量追踪在并发代理操作下的行为如何？
* 长时间运行的会话中用量数据变得非常大时会怎样？
