chore(doc):移除无用文档
This commit is contained in:
1 parent
0bac1668bb
commit
9161ddb5c8
7 files changed
+462
-3322
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -1,210 +0,0 @@
|
||||
# Relaykit 迁移方案执行摘要
|
||||
|
||||
> **文档版本**:v1.0
|
||||
> **生成日期**:2026-07-28
|
||||
> **完整方案**:[relaykit-migration-plan.md](./relaykit-migration-plan.md)
|
||||
>
|
||||
> **状态(2026-07-29 更新)**:✅ 代码完成 + 切换已落地(阶段 0–7)。特性开关已移除、relaykit 常开、旧转换器作容错回退保留;DTO 去重完成。本文下方「建议批准执行 / 75 天计划」为立项时历史内容,实际执行状态以 `relaykit-migration-plan.md` 阶段 7「最终执行状态」为准。
|
||||
|
||||
---
|
||||
|
||||
## 一、项目背景
|
||||
|
||||
### 当前问题
|
||||
|
||||
team-api 的 `relay/` 模块(26,138 行代码)存在以下痛点:
|
||||
|
||||
1. **转换逻辑分散**:24 个供应商的协议转换代码散落在各自的 `channel/` 目录,大量重复代码
|
||||
2. **难以测试**:转换函数缺乏独立测试,依赖完整的 HTTP 链路
|
||||
3. **维护成本高**:新增供应商需要 5 天开发时间,协议变更适配需要 2-3 天
|
||||
4. **无法复用**:转换逻辑与 GoFrame/Gin 框架耦合,无法在其他项目中使用
|
||||
|
||||
### 解决方案
|
||||
|
||||
参考 new-api 项目的 **relaykit 模块化架构**,将协议转换逻辑提取为独立的 Go 模块:
|
||||
|
||||
- **零依赖**:不依赖任何框架,纯粹的协议转换库
|
||||
- **高可测试**:Golden 测试保证字节级精确性
|
||||
- **易扩展**:注册表模式,新增供应商只需实现接口
|
||||
- **可复用**:可以开源或在其他项目中使用
|
||||
|
||||
---
|
||||
|
||||
## 二、核心架构
|
||||
|
||||
### Relaykit 模块结构
|
||||
|
||||
```
|
||||
relaykit/
|
||||
├── types/ # 核心类型定义(RelayFormat、EndpointType)
|
||||
├── relayconvert/ # 协议转换核心
|
||||
│ ├── convmeta/ # Meta 接口(宿主信息契约)
|
||||
│ ├── internal/ # 转换器实现
|
||||
│ │ ├── oai_chat/ # OpenAI Chat Completions 转换器
|
||||
│ │ ├── claude_messages/ # Claude Messages API 转换器
|
||||
│ │ ├── gemini_chat/ # Gemini generateContent 转换器
|
||||
│ │ └── oai_responses/ # OpenAI Responses API 转换器
|
||||
│ ├── request_registry.go # 请求转换器注册表
|
||||
│ ├── response_registry.go # 响应转换器注册表
|
||||
│ └── text_converter_registry.go # 文本转换器注册表(统一接口)
|
||||
└── dto/ # 数据传输对象(兼容层)
|
||||
```
|
||||
|
||||
### 关键设计
|
||||
|
||||
1. **Meta 接口**:宿主应用(team-api)实现该接口,转换器通过它访问渠道信息、模型名称等
|
||||
2. **Options 快照**:请求级配置快照,避免并发读取配置时的竞态条件
|
||||
3. **注册表模式**:转换器通过注册表管理,支持运行时查找和多跳转换
|
||||
4. **StreamState 接口**:有状态的流式转换(如 Gemini → OpenAI 需要累积 tool_use)
|
||||
5. **Golden 测试**:每个转换器都有 testdata/ 目录存储预期输出,测试时逐字节对比
|
||||
|
||||
---
|
||||
|
||||
## 三、迁移策略
|
||||
|
||||
### 核心原则
|
||||
|
||||
- **零停机**:双轨并行,功能开关控制,随时可回滚
|
||||
- **降低风险**:分 7 个阶段渐进式迁移,每个阶段独立验收
|
||||
- **质量保证**:测试覆盖率 ≥ 85%,Golden 测试通过率 100%
|
||||
|
||||
### 7 个阶段
|
||||
|
||||
| 阶段 | 工作内容 | 预计时间 | 交付物 |
|
||||
|------|---------|---------|--------|
|
||||
| 0 | 准备工作 | 3 天 | 功能开关 + 监控配置 + 测试基线 |
|
||||
| 1 | relaykit 模块骨架 | 5 天 | 类型定义 + Meta 接口 + 注册表 |
|
||||
| 2 | DTO 迁移 | 5 天 | 类型别名(向后兼容) |
|
||||
| 3 | 核心转换器实现 | 10 天 | OpenAI ↔ Claude ↔ Gemini + Golden 测试 |
|
||||
| 4 | 集成到主项目 | 7 天 | RelayInfo 实现 Meta + Handler 调用 relaykit |
|
||||
| 5 | 迁移其他供应商 | 24 天 | 24 个供应商全部迁移 |
|
||||
| 6 | 测试与验证 | 14 天 | 单元测试 + 集成测试 + 压力测试 + 灰度验证 |
|
||||
| 7 | 清理旧代码 | 7 天 | 删除旧转换函数 + 移除功能开关 + 文档更新 |
|
||||
|
||||
**总计**:**75 天**(11 周,约 2.5 个月)
|
||||
|
||||
---
|
||||
|
||||
## 四、关键里程碑
|
||||
|
||||
| 里程碑 | 日期 | 验收标准 |
|
||||
|--------|------|---------|
|
||||
| M1: relaykit 模块可用 | Day 13 | DTO + 核心转换器实现完成,测试通过 |
|
||||
| M2: 首个供应商集成 | Day 30 | Anthropic 在生产环境使用 relaykit,成功率 ≥ 99.5% |
|
||||
| M3: 核心供应商迁移完成 | Day 44 | Anthropic + OpenAI + Gemini + Qwen 迁移完成 |
|
||||
| M4: 全部供应商迁移完成 | Day 54 | 24 个供应商全部使用 relaykit |
|
||||
| M5: 测试验证完成 | Day 68 | 单元 + 集成 + 压力测试全部通过 |
|
||||
| M6: 迁移完成 | Day 75 | 旧代码清理完毕,文档更新,项目交付 |
|
||||
|
||||
---
|
||||
|
||||
## 五、风险与回滚
|
||||
|
||||
### 高风险项
|
||||
|
||||
| 风险 | 缓解措施 |
|
||||
|------|---------|
|
||||
| 转换逻辑错误导致请求失败 | Golden 测试 + 灰度验证 + 监控告警 + 自动回滚 |
|
||||
| 性能回退导致服务不可用 | 基准测试 + 压力测试 + 流量控制 + P95 延迟阈值告警 |
|
||||
| 边界情况未覆盖 | 多样化测试用例 + 生产流量回放 + 灰度分批放量 |
|
||||
|
||||
### 当前回滚方式\r\n\r\n阶段 7 已移除运行时功能开关。relaykit 在已覆盖方向上始终优先,转换器未匹配、解析失败或转换失败时由现有 bridge 回退到旧实现。若需要整体停用 relaykit,必须 revert 阶段 7 cutover 提交并重新构建部署,不能再通过修改配置并重启完成。
|
||||
|
||||
---
|
||||
|
||||
## 六、成功指标(KPI)
|
||||
|
||||
### 质量指标
|
||||
|
||||
- ✅ 单元测试覆盖率 ≥ 85%
|
||||
- ✅ Golden 测试通过率 = 100%
|
||||
- ✅ 转换成功率 ≥ 99.5%(生产环境 30 天滚动)
|
||||
|
||||
### 性能指标
|
||||
|
||||
- ✅ P50 转换延迟 < 0.5ms
|
||||
- ✅ P95 转换延迟 < 1ms
|
||||
- ✅ P99 端到端延迟增加 < 20ms
|
||||
|
||||
### 可维护性指标
|
||||
|
||||
- ✅ 代码行数减少 ≥ 20%(长期)
|
||||
- ✅ 新增供应商开发时间 < 3 天(原 5 天)
|
||||
- ✅ 协议变更适配时间 < 1 天(原 2-3 天)
|
||||
|
||||
---
|
||||
|
||||
## 七、资源需求
|
||||
|
||||
### 人力投入
|
||||
|
||||
| 角色 | 人数 | 投入时间 |
|
||||
|------|------|---------|
|
||||
| 后端开发(Go) | 2 人 | 全职 11 周 |
|
||||
| 测试工程师 | 1 人 | 全职 4 周 |
|
||||
| 架构师/Tech Lead | 1 人 | 兼职 20%(Code Review + 架构审查) |
|
||||
| 运维工程师 | 1 人 | 兼职 10%(监控 + 灰度发布) |
|
||||
|
||||
### 基础设施
|
||||
|
||||
- 测试环境:与现有环境共用
|
||||
- 监控系统:扩展现有 Prometheus/Grafana
|
||||
- CI/CD:扩展现有 Jenkins/GitLab CI
|
||||
|
||||
---
|
||||
|
||||
## 八、预期收益
|
||||
|
||||
### 短期收益(迁移完成后 3 个月内)
|
||||
|
||||
1. **代码量减少 20%**:从 26,138 行降至约 21,000 行
|
||||
2. **测试覆盖率提升**:从 < 50% 提升至 85%+
|
||||
3. **转换逻辑集中**:24 个供应商的转换代码统一管理
|
||||
|
||||
### 中期收益(6 个月内)
|
||||
|
||||
1. **开发效率提升 60%**:新增供应商从 5 天降至 2 天
|
||||
2. **维护成本降低 80%**:协议变更适配从 2-3 天降至 < 1 天
|
||||
3. **质量稳定性提升**:Golden 测试防止转换回归
|
||||
|
||||
### 长期收益(1 年内)
|
||||
|
||||
1. **可复用性**:relaykit 可开源或在其他项目中使用
|
||||
2. **社区贡献**:接受社区贡献新的供应商适配器
|
||||
3. **技术影响力**:作为 AI 协议转换标准库推广
|
||||
|
||||
---
|
||||
|
||||
## 九、决策建议
|
||||
|
||||
### 建议批准执行
|
||||
|
||||
**理由**:
|
||||
|
||||
1. ✅ **风险可控**:分阶段迁移 + 双轨并行 + 功能开关 + < 5 分钟回滚
|
||||
2. ✅ **收益显著**:开发效率提升 60%,维护成本降低 80%
|
||||
3. ✅ **方案完善**:7 个阶段 + 75 天时间线 + 详细回滚方案 + KPI 指标
|
||||
4. ✅ **长期价值**:零依赖独立模块,可开源复用
|
||||
|
||||
### 关键成功因素
|
||||
|
||||
1. **严格执行 Golden 测试**:每个转换器必须有 3+ 个测试用例
|
||||
2. **监控先行**:灰度验证前必须配置好成功率和延迟监控
|
||||
3. **及时回滚决策**:成功率 < 99.5% 或 P95 延迟增加 > 50ms 立即回滚
|
||||
4. **充分的测试时间**:阶段 6 预留 14 天进行全面测试,不压缩
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 十、附录
|
||||
|
||||
### 相关文档
|
||||
|
||||
- [完整迁移方案](./relaykit-migration-plan.md)(2645 行,93 个主章节)
|
||||
|
||||
|
||||
### 参考项目
|
||||
|
||||
- **new-api/relaykit**:D:\github\new-api\relaykit\(11,264 行,架构参考)
|
||||
- **team-api/relay**:D:\github\team-api\relay\(26,138 行,迁移对象)
|
||||
|
||||
File renamed without changes.
@@ -0,0 +1,462 @@
|
||||
# 大模型 Usage 字段与缓存 Token 语义规范
|
||||
|
||||
> **文档用途**:四大主流 AI 协议(OpenAI Chat/Responses、Claude、Gemini)的 usage 字段规格、缓存 token 语义、计费换算公式,以及项目代码适配指南。
|
||||
>
|
||||
> **调研日期**:2026-08-16 ~ 2026-08-17(基于官方文档联网核验)
|
||||
>
|
||||
> **覆盖范围**:OpenAI Chat Completions、OpenAI Responses、Claude Messages API、Gemini GenerateContent API
|
||||
|
||||
---
|
||||
|
||||
## 一、核心结论:缓存 Token 语义差异
|
||||
|
||||
四家协议中**三家是「包含」口径,Claude 是唯一的「排除」口径**:
|
||||
|
||||
| 协议 | 顶层输入字段 | 缓存 token 是否含在内 | 未缓存输入怎么得到 |
|
||||
|---|---|---|---|
|
||||
| OpenAI Chat Completions | `prompt_tokens` | ✅ **包含**,`cached_tokens` 是它的子集 | `prompt_tokens - cached_tokens - cache_write_tokens` |
|
||||
| OpenAI Responses | `input_tokens` | ✅ **包含**,`cached_tokens` 是它的子集 | `input_tokens - cached_tokens - cache_write_tokens` |
|
||||
| **Claude Messages** | `input_tokens` | ❌ **排除**,缓存是独立并列字段 | `input_tokens` 本身就是未缓存部分 |
|
||||
| Gemini GenerateContent | `promptTokenCount` | ✅ **包含**,`cachedContentTokenCount` 是它的子集 | `promptTokenCount - cachedContentTokenCount` |
|
||||
|
||||
**Claude 总输入的计算方式(与其他三家不同)**:
|
||||
|
||||
```text
|
||||
Claude 总输入 token = input_tokens + cache_read_input_tokens + cache_creation_input_tokens
|
||||
```
|
||||
|
||||
官方原文(Anthropic):
|
||||
> "Total input tokens in a request is the summation of `input_tokens`, `cache_creation_input_tokens`, and `cache_read_input_tokens`."
|
||||
|
||||
> "The `input_tokens` field represents only the tokens that come after the last cache breakpoint in your request - not all the input tokens you sent."
|
||||
|
||||
---
|
||||
|
||||
## 二、各协议详细字段规格
|
||||
|
||||
### 2.1 OpenAI Chat Completions (`/v1/chat/completions`)
|
||||
|
||||
**顶层字段**:
|
||||
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| `prompt_tokens` | 总输入 token,**包含**缓存命中部分 |
|
||||
| `completion_tokens` | 总输出 token,**包含** reasoning tokens |
|
||||
| `total_tokens` | = prompt_tokens + completion_tokens |
|
||||
|
||||
**输入明细(`prompt_tokens_details`)**:
|
||||
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| `cached_tokens` | 缓存命中读到的输入 token(`prompt_tokens` 的子集) |
|
||||
| `cache_write_tokens` | 本次写入缓存的 token(`prompt_tokens` 的子集) |
|
||||
| `audio_tokens` / `text_tokens` / `image_tokens` | 按模态细分(`prompt_tokens` 的子集) |
|
||||
|
||||
**输出明细(`completion_tokens_details`)**:
|
||||
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| `reasoning_tokens` | 推理 token(`completion_tokens` 的子集) |
|
||||
| `audio_tokens` / `text_tokens` | 按模态细分(`completion_tokens` 的子集) |
|
||||
| `accepted_prediction_tokens` / `rejected_prediction_tokens` | Predicted Outputs 功能相关 |
|
||||
|
||||
**缓存计费**:
|
||||
- 命中部分按折扣价计费(gpt-4o 为 0.5×)
|
||||
- 可缓存的最小前缀为 1024 token,按 128 token 增量命中
|
||||
|
||||
**官方示例**(prompt caching guide):
|
||||
```json
|
||||
{
|
||||
"usage": {
|
||||
"prompt_tokens": 2006,
|
||||
"prompt_tokens_details": {
|
||||
"cached_tokens": 1920
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
解读:1920 是 2006 的一部分,剩余 86 个未命中。
|
||||
|
||||
---
|
||||
|
||||
### 2.2 OpenAI Responses API (`/v1/responses`)
|
||||
|
||||
**顶层字段**:
|
||||
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| `input_tokens` | 总输入,**包含**缓存命中和缓存写入 |
|
||||
| `output_tokens` | 总输出,**包含** reasoning_tokens |
|
||||
| `total_tokens` | = input_tokens + output_tokens |
|
||||
|
||||
**输入明细(`input_tokens_details`)**:
|
||||
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| `cached_tokens` | 缓存命中(子集) |
|
||||
| `cache_write_tokens` | 本次写入缓存(子集,Responses 特有字段) |
|
||||
|
||||
**输出明细(`output_tokens_details`)**:
|
||||
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| `reasoning_tokens` | 推理 token(子集) |
|
||||
|
||||
**注意事项**:
|
||||
- 没有模态细分、没有预测 token 字段(较 Chat Completions 精简)
|
||||
- 复用 `previous_response_id` 时每次都为整个已见对话历史计费,`input_tokens` 会随轮次增长
|
||||
|
||||
**官方示例**:
|
||||
```json
|
||||
{
|
||||
"usage": {
|
||||
"input_tokens": 2600,
|
||||
"input_tokens_details": {
|
||||
"cached_tokens": 2000,
|
||||
"cache_write_tokens": 400
|
||||
},
|
||||
"output_tokens": 300,
|
||||
"output_tokens_details": {
|
||||
"reasoning_tokens": 120
|
||||
},
|
||||
"total_tokens": 2900
|
||||
}
|
||||
}
|
||||
```
|
||||
解读:2600 = 2000 cached + 400 write + 200 新输入。
|
||||
|
||||
---
|
||||
|
||||
### 2.3 Claude Messages API (`/v1/messages`)
|
||||
|
||||
**输入侧字段**:
|
||||
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| `input_tokens` | **仅计最后一个 cache 断点之后的输入**(未走缓存的常规输入) |
|
||||
| `cache_read_input_tokens` | 从缓存读到的 |
|
||||
| `cache_creation_input_tokens` | 本次写入缓存的 |
|
||||
| `cache_creation.ephemeral_5m_input_tokens` | 创建 5 分钟缓存条目的 token |
|
||||
| `cache_creation.ephemeral_1h_input_tokens` | 创建 1 小时缓存条目的 token |
|
||||
|
||||
**输出侧字段**:
|
||||
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| `output_tokens` | 总输出(含 thinking),**是计费权威值** |
|
||||
| `output_tokens_details.thinking_tokens` | 思考 token(`output_tokens` 的子集,只读观测用) |
|
||||
|
||||
**非 token 元数据**:
|
||||
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| `server_tool_use.web_search_requests` / `web_fetch_requests` | 服务端工具调用次数(按次计费) |
|
||||
| `service_tier` | standard / priority / batch |
|
||||
| `inference_geo` | 推理所在地理区域 |
|
||||
|
||||
**计费倍率**(官方 prompt caching 文档):
|
||||
|
||||
| Token 类型 | 倍率(相对基础输入价) |
|
||||
|---|---|
|
||||
| 常规输入 | 1.0× |
|
||||
| 5 分钟缓存写入(ephemeral_5m) | 1.25× |
|
||||
| 1 小时缓存写入(ephemeral_1h) | 2× |
|
||||
| 缓存读取 | 0.1× |
|
||||
|
||||
**注意事项**:
|
||||
- Claude 没有原生 `total_tokens` 字段
|
||||
- `cache_creation.ephemeral_5m_input_tokens + ephemeral_1h_input_tokens` 等于 `cache_creation_input_tokens`,它们是 TTL 细分,不能重复相加
|
||||
- 老响应没有 `cache_creation` 细分时,`cache_creation_input_tokens` 默认按 5m(1.25×)计
|
||||
|
||||
**官方完整示例**:
|
||||
```json
|
||||
{
|
||||
"usage": {
|
||||
"input_tokens": 2048,
|
||||
"cache_read_input_tokens": 1800,
|
||||
"cache_creation_input_tokens": 248,
|
||||
"output_tokens": 503,
|
||||
"cache_creation": {
|
||||
"ephemeral_5m_input_tokens": 148,
|
||||
"ephemeral_1h_input_tokens": 100
|
||||
},
|
||||
"output_tokens_details": { "thinking_tokens": 0 },
|
||||
"server_tool_use": { "web_search_requests": 0, "web_fetch_requests": 2 },
|
||||
"service_tier": "standard",
|
||||
"inference_geo": "global"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.4 Gemini GenerateContent API
|
||||
|
||||
**注意**:Gemini 的用量不在 `usage` 字段里,而是响应顶层的 `usageMetadata`,且为 camelCase。
|
||||
|
||||
**字段规格**:
|
||||
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| `promptTokenCount` | 总输入,**包含**缓存部分 |
|
||||
| `cachedContentTokenCount` | 其中命中缓存的(显式 context caching;2.5 系列隐式缓存命中也体现在此字段) |
|
||||
| `candidatesTokenCount` | 输出 token(不含思考) |
|
||||
| `thoughtsTokenCount` | 思考 token,**输出侧**,按输出价计费 |
|
||||
| `toolUsePromptTokenCount` | 工具使用 prompt 的 token |
|
||||
| `totalTokenCount` | = promptTokenCount + thoughtsTokenCount + candidatesTokenCount |
|
||||
| `promptTokensDetails[]` / `cacheTokensDetails[]` / `candidatesTokensDetails[]` / `toolUsePromptTokensDetails[]` | 按模态(TEXT/IMAGE/AUDIO/VIDEO)细分的列表 |
|
||||
| `serviceTier` | 服务层级 |
|
||||
|
||||
**官方原文**(关键):
|
||||
> "When `cachedContent` is set, this is still the total effective prompt size meaning this includes the number of tokens in the cached content."
|
||||
|
||||
> "Total token count for the generation request (prompt + thoughts + response candidates)."
|
||||
|
||||
**官方示例**:
|
||||
```json
|
||||
{
|
||||
"usageMetadata": {
|
||||
"promptTokenCount": 1050,
|
||||
"cachedContentTokenCount": 1000,
|
||||
"candidatesTokenCount": 200,
|
||||
"thoughtsTokenCount": 150,
|
||||
"toolUsePromptTokenCount": 0,
|
||||
"totalTokenCount": 1400,
|
||||
"promptTokensDetails": [{ "modality": "TEXT", "tokenCount": 1050 }],
|
||||
"cacheTokensDetails": [{ "modality": "TEXT", "tokenCount": 1000 }],
|
||||
"candidatesTokensDetails": [{ "modality": "TEXT", "tokenCount": 200 }]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、计费换算公式
|
||||
|
||||
```text
|
||||
【OpenAI 系(Chat Completions / Responses 同构)】
|
||||
输入成本 = (input_tokens − cached_tokens − cache_write_tokens) × P_in
|
||||
+ cached_tokens × P_cached
|
||||
+ cache_write_tokens × P_cache_write
|
||||
输出成本 = output_tokens × P_out
|
||||
# reasoning 已含在 output 里,不另计
|
||||
|
||||
【Claude】
|
||||
输入成本 = input_tokens × P_in
|
||||
+ cache_creation_5m × P_in × 1.25
|
||||
+ cache_creation_1h × P_in × 2
|
||||
+ cache_read × P_in × 0.1
|
||||
输出成本 = output_tokens × P_out
|
||||
# thinking 已含在 output 里
|
||||
|
||||
# 总输入量展示用 = 三项相加,切勿把 input_tokens 当总输入
|
||||
|
||||
【Gemini】
|
||||
输入成本 = (promptTokenCount − cachedContentTokenCount) × P_in
|
||||
+ cachedContentTokenCount × P_cached
|
||||
输出成本 = (candidatesTokenCount + thoughtsTokenCount) × P_out
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、协议互转的坑
|
||||
|
||||
### 4.1 最容易踩的坑:Claude ↔ 其他三家
|
||||
|
||||
**问题**:把 Claude 的 `input_tokens` 直接映射成 OpenAI 的 `prompt_tokens`,会导致缓存场景下输入量少算(Claude 侧)或多算(反向)。
|
||||
|
||||
**正确做法**:
|
||||
|
||||
```text
|
||||
Claude → OpenAI / Gemini(加法):
|
||||
顶层输入 = input_tokens + cache_read_input_tokens + cache_creation_input_tokens
|
||||
缓存明细 = cache_read_input_tokens(映射为 cached_tokens)
|
||||
|
||||
OpenAI / Gemini → Claude(减法):
|
||||
input_tokens = 顶层输入 − 缓存命中 − 缓存写入
|
||||
cache_read_input_tokens = 缓存命中
|
||||
cache_creation_input_tokens = 缓存写入
|
||||
```
|
||||
|
||||
**注意**:OpenAI 的 `cached_tokens` 是「命中读取」语义,没有 Claude 的「写入 1.25×/2×」概念;反向转换时 Claude 的写入 token 一般并入常规输入或缓存读取,需按计费策略明确选一边。
|
||||
|
||||
---
|
||||
|
||||
## 五、流式响应中 usage 的位置
|
||||
|
||||
| 协议 | 流式 usage 出现位置 |
|
||||
|---|---|
|
||||
| OpenAI Chat | 需设 `stream_options: {"include_usage": true}`,最后一个 chunk 带 usage |
|
||||
| OpenAI Responses | `response.completed` 事件的 `response.usage` |
|
||||
| Claude | `message_start` 带输入侧 usage,`message_delta` 累计 `output_tokens`<br>**已知问题**:部分流式场景 `message_delta` 只带 `output_tokens`,输入侧字段缺失,需保留 `message_start` 的值合并 |
|
||||
| Gemini | `streamGenerateContent` 最后一个 chunk 的 `usageMetadata` |
|
||||
|
||||
---
|
||||
|
||||
## 六、推荐的内部统一模型
|
||||
|
||||
**不建议只保存通用的 `input_tokens` 和 `output_tokens`**。为了准确计费和协议转换,建议至少保存:
|
||||
|
||||
```text
|
||||
input_total # 总输入(含缓存)
|
||||
input_ordinary # 普通输入(不含缓存)
|
||||
cache_read # 缓存读取
|
||||
cache_write # 缓存写入
|
||||
output_total # 总输出
|
||||
visible_output # 可见输出
|
||||
reasoning_or_thinking # 推理/思考 token
|
||||
provider_total # 供应商返回的 total_tokens
|
||||
```
|
||||
|
||||
**推荐映射**:
|
||||
|
||||
| 协议 | `input_total` | `input_ordinary` | `cache_read` | `cache_write` |
|
||||
|---|---|---|---|---|
|
||||
| OpenAI Chat | `prompt_tokens` | `prompt_tokens - cached_tokens - cache_write_tokens` | `cached_tokens` | `cache_write_tokens` |
|
||||
| OpenAI Responses | `input_tokens` | `input_tokens - cached_tokens - cache_write_tokens` | `cached_tokens` | `cache_write_tokens` |
|
||||
| Claude | 三个输入桶之和 | `input_tokens` | `cache_read_input_tokens` | `cache_creation_input_tokens` |
|
||||
| Gemini | `promptTokenCount` | `promptTokenCount - cachedContentTokenCount` | `cachedContentTokenCount` | 无对应生成响应字段 |
|
||||
|
||||
**输出侧注意事项**:
|
||||
|
||||
- OpenAI 的 `completion_tokens` / `output_tokens` 已包含 `reasoning_tokens`
|
||||
- Claude 的 `output_tokens` 已包含 `thinking_tokens`
|
||||
- Gemini 的 `candidatesTokenCount` 不包含 `thoughtsTokenCount`;其完整生成侧数量应依据 `totalTokenCount - promptTokenCount` 或直接分别保留 candidates 与 thoughts
|
||||
- **所有减法都应做非负保护**,以防第三方兼容服务返回不一致数据
|
||||
|
||||
---
|
||||
|
||||
## 七、项目代码需要改进的地方
|
||||
|
||||
### 7.1 已正确处理的部分
|
||||
|
||||
✅ OpenAI Responses 已设置 `CacheIncludedInPrompt=true`,符合 OpenAI 顶层输入包含缓存的语义
|
||||
|
||||
✅ Gemini 已设置 `CacheIncludedInPrompt=true`,符合 `promptTokenCount` 包含 `cachedContentTokenCount` 的语义
|
||||
|
||||
✅ Claude 将缓存读取和缓存创建保留为独立桶,符合 Claude 语义
|
||||
|
||||
### 7.2 需要更新或明确标注的部分
|
||||
|
||||
#### 问题 1:OpenAI Chat 通用明细缺少官方 `cache_write_tokens`
|
||||
|
||||
**位置**:`relaykit/dto/usage.go`
|
||||
|
||||
**现状**:目前有项目自定义的 `cached_creation_tokens`,但它不是 OpenAI 官方 JSON 字段 `cache_write_tokens`。
|
||||
|
||||
**建议**:增加官方 `cache_write_tokens` 字段,不要与自定义字段混用。
|
||||
|
||||
---
|
||||
|
||||
#### 问题 2:OpenAI Responses 输入明细缺少 `cache_write_tokens`
|
||||
|
||||
**位置**:`relay/dto/openai_responses.go`
|
||||
|
||||
**现状**:`InputTokenDetails` 当前只有 `cached_tokens` 及模态扩展字段。
|
||||
|
||||
**建议**:增加官方 `cache_write_tokens` 字段。
|
||||
|
||||
---
|
||||
|
||||
#### 问题 3:Responses DTO 中存在非当前官方 schema 的扩展字段
|
||||
|
||||
**位置**:`relay/dto/openai_responses.go`
|
||||
|
||||
**现状**:`input_tokens_details.text_tokens/audio_tokens/image_tokens` 以及输出侧的文本、音频、预测 token 字段不在官方 Responses schema 中。
|
||||
|
||||
**建议**:如果为了兼容第三方上游而保留,应明确标记为兼容扩展,不能当作 OpenAI 官方字段。
|
||||
|
||||
---
|
||||
|
||||
#### 问题 4:Claude DTO 缺少最新 Usage 字段
|
||||
|
||||
**位置**:`relaykit/dto/claude.go`
|
||||
|
||||
**现状**:当前缺少:
|
||||
- `output_tokens_details.thinking_tokens`
|
||||
- `inference_geo`
|
||||
- `server_tool_use.web_fetch_requests`
|
||||
|
||||
**建议**:补充上述官方字段。
|
||||
|
||||
---
|
||||
|
||||
#### 问题 5:Gemini DTO 缺少 `usageMetadata.serviceTier`
|
||||
|
||||
**位置**:`relaykit/dto/gemini.go`
|
||||
|
||||
**现状**:已覆盖主要 token 字段和模态明细,但未覆盖当前官方 `serviceTier`。
|
||||
|
||||
**建议**:补充该字段。
|
||||
|
||||
---
|
||||
|
||||
#### 问题 6:Claude 转 OpenAI 的 usage 口径不符合 OpenAI 子集语义 ⚠️
|
||||
|
||||
**位置**:`relaykit/relayconvert/internal/oai_chat/claude_to_openai_response.go`
|
||||
|
||||
**现状**:当前将 Claude `input_tokens` 直接映射为 OpenAI `prompt_tokens`,同时把 `cache_read_input_tokens` 放入 `cached_tokens`。这样 `cached_tokens` 并未包含在 `prompt_tokens` 中,与 OpenAI 官方语义不一致。
|
||||
|
||||
**正确的协议映射应为**:
|
||||
|
||||
```go
|
||||
prompt_tokens = input_tokens + cache_read_input_tokens + cache_creation_input_tokens
|
||||
|
||||
prompt_tokens_details.cached_tokens = cache_read_input_tokens
|
||||
prompt_tokens_details.cache_write_tokens = cache_creation_input_tokens
|
||||
|
||||
total_tokens = prompt_tokens + completion_tokens
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 问题 7:Claude 内部 `TotalTokens` 不是跨协议总量
|
||||
|
||||
**位置**:Claude 路径
|
||||
|
||||
**现状**:当前使用 `input_tokens + output_tokens` 生成内部 `TotalTokens`,缓存 token 另存。
|
||||
|
||||
**说明**:这可以作为内部计费表示,但不能把该值解释为 Claude 请求实际处理的全部 token,也不能直接映射为 OpenAI `total_tokens`。
|
||||
|
||||
---
|
||||
|
||||
## 八、参考来源
|
||||
|
||||
以下页面均在 2026-08-16 ~ 2026-08-17 实际联网获取并读取正文:
|
||||
|
||||
### OpenAI
|
||||
|
||||
- [Chat Completions API Reference](https://developers.openai.com/api/reference/resources/chat/subresources/completions/methods/create)
|
||||
- [Responses API Reference](https://developers.openai.com/api/reference/resources/responses/methods/create)
|
||||
- [Prompt Caching Guide](https://developers.openai.com/api/docs/guides/prompt-caching)
|
||||
- [openai-python — completion_usage.py(PromptTokensDetails/CompletionTokensDetails 权威 schema)](https://github.com/openai/openai-python/blob/main/src/openai/types/completion_usage.py)
|
||||
- [openai-python — response_usage.py(Responses usage 权威 schema)](https://github.com/openai/openai-python/blob/main/src/openai/types/responses/response_usage.py)
|
||||
|
||||
### Anthropic
|
||||
|
||||
- [Messages API Reference](https://platform.claude.com/docs/en/api/messages)
|
||||
- [Prompt Caching](https://platform.claude.com/docs/en/build-with-claude/prompt-caching)
|
||||
|
||||
### Google Gemini
|
||||
|
||||
- [GenerateContent API Reference — UsageMetadata](https://ai.google.dev/api/generate-content#UsageMetadata)
|
||||
- [Gemini API — Generating content](https://ai.google.dev/api/generate-content)
|
||||
|
||||
---
|
||||
|
||||
## 九、总结
|
||||
|
||||
对于缓存输入,四种协议应按两类处理:
|
||||
|
||||
```text
|
||||
包含型:OpenAI Chat、OpenAI Responses、Gemini
|
||||
独立桶型:Claude
|
||||
```
|
||||
|
||||
最新版 OpenAI 又进一步把包含型输入拆成"缓存读取、缓存写入、普通输入"三个计费类别。因此,项目后续不应只维护 `CacheIncludedInPrompt` 一个布尔值,还应显式支持 `cache_write_tokens`,并在协议转换时分别处理缓存读、缓存写和普通输入。
|
||||
|
||||
**核心规则**:
|
||||
- OpenAI/Gemini → 顶层输入包含缓存(减法得普通输入)
|
||||
- Claude → 三个独立桶相加才是总输入(加法)
|
||||
- 协议转换时必须显式处理缓存读/写/普通输入的拆分与合并
|
||||
- 所有减法操作必须做非负保护
|
||||
@@ -1,331 +0,0 @@
|
||||
# 大模型协议 Usage 与缓存 Token 语义调研(官方联网核验版)
|
||||
|
||||
## 文档信息
|
||||
|
||||
- 核验日期:2026-08-16
|
||||
- 核验方式:联网读取 OpenAI、Anthropic、Google 官方 API Reference 与 Prompt Caching 文档正文。
|
||||
- 覆盖范围:OpenAI Chat Completions、OpenAI Responses、Claude Messages API、Gemini GenerateContent API。
|
||||
- 不包含:Azure OpenAI、Amazon Bedrock、Vertex AI 等托管平台对字段的二次封装,也不把第三方兼容网关的扩展字段视为官方字段。
|
||||
|
||||
## 核心结论
|
||||
|
||||
| 协议 | 顶层输入字段 | 缓存读取字段 | 缓存写入字段 | 顶层输入是否包含缓存 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| OpenAI Chat Completions | `prompt_tokens` | `prompt_tokens_details.cached_tokens` | `prompt_tokens_details.cache_write_tokens` | **包含缓存读取和缓存写入** |
|
||||
| OpenAI Responses | `input_tokens` | `input_tokens_details.cached_tokens` | `input_tokens_details.cache_write_tokens` | **包含缓存读取和缓存写入** |
|
||||
| Claude Messages | `input_tokens` | `cache_read_input_tokens` | `cache_creation_input_tokens` | **不包含,三个字段是独立桶** |
|
||||
| Gemini GenerateContent | `promptTokenCount` | `cachedContentTokenCount` | GenerateContent usage 无对应字段 | **包含缓存读取** |
|
||||
|
||||
不能把四种协议的“输入 token”直接按同一口径使用:
|
||||
|
||||
```text
|
||||
OpenAI / Responses:
|
||||
普通输入 = 顶层输入 - cached_tokens - cache_write_tokens
|
||||
|
||||
Claude:
|
||||
总输入 = input_tokens + cache_read_input_tokens + cache_creation_input_tokens
|
||||
|
||||
Gemini:
|
||||
普通输入 = promptTokenCount - cachedContentTokenCount
|
||||
```
|
||||
|
||||
其中 `cache_write_tokens` 是 OpenAI 官方文档中的新字段。官方 Prompt Caching 文档说明:GPT-5.6 及后续模型系列会报告缓存写入 token;早期模型没有该字段或不产生对应的额外缓存写入计费。
|
||||
|
||||
## OpenAI Chat Completions
|
||||
|
||||
### 官方 Usage 结构
|
||||
|
||||
顶层字段:
|
||||
|
||||
| 字段 | 官方语义 |
|
||||
| --- | --- |
|
||||
| `prompt_tokens` | prompt 使用的 token 数 |
|
||||
| `completion_tokens` | 生成 completion 使用的 token 数 |
|
||||
| `total_tokens` | `prompt_tokens + completion_tokens` |
|
||||
| `prompt_tokens_details` | 输入 token 明细 |
|
||||
| `completion_tokens_details` | 输出 token 明细 |
|
||||
|
||||
截至核验日期,`prompt_tokens_details` 的官方字段为:
|
||||
|
||||
| 字段 | 官方语义 |
|
||||
| --- | --- |
|
||||
| `audio_tokens` | prompt 中的音频输入 token |
|
||||
| `cache_write_tokens` | 写入缓存的 prompt token 原始数量 |
|
||||
| `cached_tokens` | prompt 中命中缓存的 token |
|
||||
| `image_tokens` | prompt 中的图像输入 token |
|
||||
| `text_tokens` | prompt 中的文本输入 token |
|
||||
|
||||
`completion_tokens_details` 的官方字段为:
|
||||
|
||||
| 字段 | 官方语义 |
|
||||
| --- | --- |
|
||||
| `accepted_prediction_tokens` | Predicted Outputs 中被实际采用的预测 token |
|
||||
| `audio_tokens` | 模型生成的音频 token |
|
||||
| `reasoning_tokens` | 模型生成的推理 token |
|
||||
| `rejected_prediction_tokens` | Predicted Outputs 中未被采用的预测 token;官方明确说明仍计入 completion token、计费及上下文限制 |
|
||||
| `text_tokens` | 模型生成的文本 token |
|
||||
|
||||
### 缓存口径
|
||||
|
||||
OpenAI Prompt Caching 官方示例表明,顶层输入包含缓存读取、缓存写入和普通输入。例如:
|
||||
|
||||
```json
|
||||
{
|
||||
"usage": {
|
||||
"input_tokens": 2600,
|
||||
"input_tokens_details": {
|
||||
"cached_tokens": 2000,
|
||||
"cache_write_tokens": 400
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
该示例的官方解释是:2000 个 token 从缓存读取,400 个 token 写入缓存,剩余 200 个既未读取也未写入。因此 Chat Completions 对应公式为:
|
||||
|
||||
```text
|
||||
普通输入 token =
|
||||
prompt_tokens
|
||||
- prompt_tokens_details.cached_tokens
|
||||
- prompt_tokens_details.cache_write_tokens
|
||||
```
|
||||
|
||||
`text_tokens`、`image_tokens`、`audio_tokens` 是按模态的拆分;`cached_tokens`、`cache_write_tokens` 是按缓存状态的拆分。两组维度可能重叠,不能把所有 details 字段直接求和。
|
||||
|
||||
## OpenAI Responses
|
||||
|
||||
### 官方 Usage 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"usage": {
|
||||
"input_tokens": 2600,
|
||||
"output_tokens": 120,
|
||||
"total_tokens": 2720,
|
||||
"input_tokens_details": {
|
||||
"cached_tokens": 2000,
|
||||
"cache_write_tokens": 400
|
||||
},
|
||||
"output_tokens_details": {
|
||||
"reasoning_tokens": 50
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
截至核验日期,Responses 官方 API Reference 中的字段为:
|
||||
|
||||
| 位置 | 字段 | 官方语义 |
|
||||
| --- | --- | --- |
|
||||
| `usage` | `input_tokens` | 输入 token 数 |
|
||||
| `usage` | `output_tokens` | 输出 token 数 |
|
||||
| `usage` | `total_tokens` | 使用的总 token 数 |
|
||||
| `input_tokens_details` | `cached_tokens` | 从缓存取回的 token 数 |
|
||||
| `input_tokens_details` | `cache_write_tokens` | 写入缓存的输入 token 数 |
|
||||
| `output_tokens_details` | `reasoning_tokens` | 推理 token 数 |
|
||||
|
||||
缓存公式为:
|
||||
|
||||
```text
|
||||
普通输入 token =
|
||||
input_tokens
|
||||
- input_tokens_details.cached_tokens
|
||||
- input_tokens_details.cache_write_tokens
|
||||
```
|
||||
|
||||
Responses 当前官方 schema 没有在 `input_tokens_details` 中声明 `text_tokens`、`image_tokens`、`audio_tokens`,也没有在 `output_tokens_details` 中声明预测或模态明细。兼容服务可能返回扩展字段,但不能将它们标注成 OpenAI Responses 官方字段。
|
||||
|
||||
## Claude Messages API
|
||||
|
||||
### 官方 Usage 结构
|
||||
|
||||
Claude 官方 API Reference 当前声明的 token 字段包括:
|
||||
|
||||
| 字段 | 官方语义 |
|
||||
| --- | --- |
|
||||
| `input_tokens` | 未从缓存读取、也未用于创建缓存的输入 token;Prompt Caching 文档进一步说明,它通常对应最后一个缓存断点之后的 token |
|
||||
| `cache_creation_input_tokens` | 用于创建缓存条目的输入 token |
|
||||
| `cache_read_input_tokens` | 从缓存读取的输入 token |
|
||||
| `output_tokens` | 输出 token 的权威总数,包含 thinking token |
|
||||
| `cache_creation.ephemeral_5m_input_tokens` | 创建 5 分钟缓存条目的 token |
|
||||
| `cache_creation.ephemeral_1h_input_tokens` | 创建 1 小时缓存条目的 token |
|
||||
| `output_tokens_details.thinking_tokens` | 内部推理 token 明细,是 `output_tokens` 的子集 |
|
||||
|
||||
Usage 中还包含以下非 token 元数据:
|
||||
|
||||
- `inference_geo`
|
||||
- `server_tool_use.web_fetch_requests`
|
||||
- `server_tool_use.web_search_requests`
|
||||
- `service_tier`,可能为 `standard`、`priority`、`batch` 或 `null`
|
||||
|
||||
### 缓存口径
|
||||
|
||||
Anthropic 官方 API Reference 和 Prompt Caching 文档均明确给出:
|
||||
|
||||
```text
|
||||
总输入 token =
|
||||
input_tokens
|
||||
+ cache_creation_input_tokens
|
||||
+ cache_read_input_tokens
|
||||
```
|
||||
|
||||
因此,Claude 的 `input_tokens` **不包含**缓存读取和缓存创建 token。它与 OpenAI、Gemini 的顶层输入字段语义不同。
|
||||
|
||||
另外:
|
||||
|
||||
- Claude 没有原生 `total_tokens` 字段。
|
||||
- `cache_creation.ephemeral_5m_input_tokens + ephemeral_1h_input_tokens` 等于 `cache_creation_input_tokens`,它们是 TTL 细分,不能重复相加。
|
||||
- `output_tokens_details.thinking_tokens <= output_tokens`;`output_tokens` 仍是计费使用的权威输出总量。
|
||||
- Amazon Bedrock 等托管平台可能使用不同字段名和缓存规则,应单独核验对应平台文档。
|
||||
|
||||
## Gemini GenerateContent API
|
||||
|
||||
### 官方 UsageMetadata 结构
|
||||
|
||||
截至核验日期,Gemini 官方 `UsageMetadata` 字段为:
|
||||
|
||||
| 字段 | 官方语义 |
|
||||
| --- | --- |
|
||||
| `promptTokenCount` | prompt 的总有效 token 数;官方明确说明设置 `cachedContent` 时仍包含缓存内容 token |
|
||||
| `cachedContentTokenCount` | prompt 中缓存内容的 token 数 |
|
||||
| `candidatesTokenCount` | 所有生成候选响应的 token 总数 |
|
||||
| `toolUsePromptTokenCount` | tool-use prompt 中的 token 数 |
|
||||
| `thoughtsTokenCount` | thinking 模型的思考 token 数 |
|
||||
| `totalTokenCount` | 生成请求的总 token 数,官方定义为 prompt + thoughts + response candidates |
|
||||
| `promptTokensDetails[]` | 请求输入按模态的 token 明细 |
|
||||
| `cacheTokensDetails[]` | 缓存输入按模态的 token 明细 |
|
||||
| `candidatesTokensDetails[]` | 响应候选按模态的 token 明细 |
|
||||
| `toolUsePromptTokensDetails[]` | tool-use 请求输入按模态的 token 明细 |
|
||||
| `serviceTier` | 请求使用的服务层级,非 token 字段 |
|
||||
|
||||
### 缓存口径
|
||||
|
||||
Google 官方 API Reference 的原意非常明确:`promptTokenCount` 在设置 `cachedContent` 后仍表示总有效 prompt 大小,包含缓存内容。因此:
|
||||
|
||||
```text
|
||||
普通输入 token = promptTokenCount - cachedContentTokenCount
|
||||
```
|
||||
|
||||
Gemini 的 `thoughtsTokenCount` 不是 `candidatesTokenCount` 的子集。官方给出的总量定义是:
|
||||
|
||||
```text
|
||||
totalTokenCount = promptTokenCount + thoughtsTokenCount + candidatesTokenCount
|
||||
```
|
||||
|
||||
所以有 thinking 时,不能使用 `promptTokenCount + candidatesTokenCount` 代替 `totalTokenCount`。程序应优先采用上游返回的 `totalTokenCount`。
|
||||
|
||||
## 推荐的内部统一模型
|
||||
|
||||
不建议只保存通用的 `input_tokens` 和 `output_tokens`。为了准确计费和协议转换,建议至少保存:
|
||||
|
||||
```text
|
||||
input_total
|
||||
input_ordinary
|
||||
cache_read
|
||||
cache_write
|
||||
output_total
|
||||
visible_output
|
||||
reasoning_or_thinking
|
||||
provider_total
|
||||
```
|
||||
|
||||
推荐映射:
|
||||
|
||||
| 协议 | `input_total` | `input_ordinary` | `cache_read` | `cache_write` |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| OpenAI Chat | `prompt_tokens` | `prompt_tokens - cached_tokens - cache_write_tokens` | `cached_tokens` | `cache_write_tokens` |
|
||||
| OpenAI Responses | `input_tokens` | `input_tokens - cached_tokens - cache_write_tokens` | `cached_tokens` | `cache_write_tokens` |
|
||||
| Claude | 三个输入桶之和 | `input_tokens` | `cache_read_input_tokens` | `cache_creation_input_tokens` |
|
||||
| Gemini | `promptTokenCount` | `promptTokenCount - cachedContentTokenCount` | `cachedContentTokenCount` | 无对应生成响应字段 |
|
||||
|
||||
输出侧需要额外注意:
|
||||
|
||||
- OpenAI 的 `completion_tokens` / `output_tokens` 已包含 `reasoning_tokens`。
|
||||
- Claude 的 `output_tokens` 已包含 `thinking_tokens`。
|
||||
- Gemini 的 `candidatesTokenCount` 不包含 `thoughtsTokenCount`;其完整生成侧数量应依据 `totalTokenCount - promptTokenCount` 或直接分别保留 candidates 与 thoughts。
|
||||
- 所有减法都应做非负保护,以防第三方兼容服务返回不一致数据。
|
||||
|
||||
## 本仓库与最新官方协议的差异
|
||||
|
||||
### 已正确处理的部分
|
||||
|
||||
- OpenAI Responses 已设置 `CacheIncludedInPrompt=true`,符合 OpenAI 顶层输入包含缓存的语义:[`relay/channel/openai/responses.go`](relay/channel/openai/responses.go)。
|
||||
- Gemini 已设置 `CacheIncludedInPrompt=true`,符合 `promptTokenCount` 包含 `cachedContentTokenCount` 的语义:[`relay/channel/gemini/response.go`](relay/channel/gemini/response.go)。
|
||||
- Claude 将缓存读取和缓存创建保留为独立桶,符合 Claude 语义:[`relay/channel/claude/response.go`](relay/channel/claude/response.go)。
|
||||
|
||||
### 需要更新或明确标注的部分
|
||||
|
||||
1. OpenAI Chat 通用明细缺少官方 `cache_write_tokens`。
|
||||
|
||||
[`relaykit/dto/usage.go`](relaykit/dto/usage.go) 目前有项目自定义的 `cached_creation_tokens`,但它不是 OpenAI 官方 JSON 字段 `cache_write_tokens`。两者不应使用同一个 JSON 名称或隐式混用。
|
||||
|
||||
2. OpenAI Responses 输入明细缺少 `cache_write_tokens`。
|
||||
|
||||
[`relay/dto/openai_responses.go`](relay/dto/openai_responses.go) 的 `InputTokenDetails` 当前只有 `cached_tokens` 及模态扩展字段,应增加官方 `cache_write_tokens`。
|
||||
|
||||
3. Responses DTO 中存在非当前官方 schema 的扩展字段。
|
||||
|
||||
`input_tokens_details.text_tokens/audio_tokens/image_tokens` 以及输出侧的文本、音频、预测 token 字段不在本次联网核验到的 OpenAI Responses 官方 schema 中。如果为了兼容第三方上游而保留,应明确标记为兼容扩展,不能当作 OpenAI 官方字段。
|
||||
|
||||
4. Claude DTO 缺少最新 Usage 字段。
|
||||
|
||||
[`relaykit/dto/claude.go`](relaykit/dto/claude.go) 当前缺少:
|
||||
|
||||
- `output_tokens_details.thinking_tokens`
|
||||
- `inference_geo`
|
||||
- `server_tool_use.web_fetch_requests`
|
||||
|
||||
5. Gemini DTO 缺少 `usageMetadata.serviceTier`。
|
||||
|
||||
[`relaykit/dto/gemini.go`](relaykit/dto/gemini.go) 已覆盖主要 token 字段和模态明细,但未覆盖当前官方 `serviceTier`。
|
||||
|
||||
6. Claude 转 OpenAI 的 usage 口径不符合 OpenAI 子集语义。
|
||||
|
||||
[`relaykit/relayconvert/internal/oai_chat/claude_to_openai_response.go`](relaykit/relayconvert/internal/oai_chat/claude_to_openai_response.go) 当前将 Claude `input_tokens` 直接映射为 OpenAI `prompt_tokens`,同时把 `cache_read_input_tokens` 放入 `cached_tokens`。这样 `cached_tokens` 并未包含在 `prompt_tokens` 中,与 OpenAI 官方语义不一致。
|
||||
|
||||
严格的协议映射应为:
|
||||
|
||||
```text
|
||||
prompt_tokens =
|
||||
input_tokens
|
||||
+ cache_read_input_tokens
|
||||
+ cache_creation_input_tokens
|
||||
|
||||
prompt_tokens_details.cached_tokens = cache_read_input_tokens
|
||||
prompt_tokens_details.cache_write_tokens = cache_creation_input_tokens
|
||||
total_tokens = prompt_tokens + completion_tokens
|
||||
```
|
||||
|
||||
7. Claude 内部 `TotalTokens` 不是跨协议总量。
|
||||
|
||||
当前 Claude 路径使用 `input_tokens + output_tokens` 生成内部 `TotalTokens`,缓存 token 另存。这可以作为内部计费表示,但不能把该值解释为 Claude 请求实际处理的全部 token,也不能直接映射为 OpenAI `total_tokens`。
|
||||
|
||||
## 官方来源
|
||||
|
||||
以下页面均在 2026-08-16 实际联网获取并读取正文:
|
||||
|
||||
### OpenAI
|
||||
|
||||
- [Chat Completions API Reference](https://developers.openai.com/api/reference/resources/chat/subresources/completions/methods/create)
|
||||
- [Responses API Reference](https://developers.openai.com/api/reference/resources/responses/methods/create)
|
||||
- [Prompt Caching Guide](https://developers.openai.com/api/docs/guides/prompt-caching)
|
||||
|
||||
### Anthropic
|
||||
|
||||
- [Messages API Reference](https://platform.claude.com/docs/en/api/messages)
|
||||
- [Prompt Caching](https://platform.claude.com/docs/en/build-with-claude/prompt-caching)
|
||||
|
||||
### Google Gemini
|
||||
|
||||
- [GenerateContent API Reference - UsageMetadata](https://ai.google.dev/api/generate-content#UsageMetadata)
|
||||
|
||||
## 结论
|
||||
|
||||
对于缓存输入,四种协议应按两类处理:
|
||||
|
||||
```text
|
||||
包含型:OpenAI Chat、OpenAI Responses、Gemini
|
||||
独立桶型:Claude
|
||||
```
|
||||
|
||||
最新版 OpenAI 又进一步把包含型输入拆成“缓存读取、缓存写入、普通输入”三个计费类别。因此,项目后续不应只维护 `CacheIncludedInPrompt` 一个布尔值,还应显式支持 `cache_write_tokens`,并在协议转换时分别处理缓存读、缓存写和普通输入。
|
||||
@@ -1,219 +0,0 @@
|
||||
# 大模型协议 Usage 字段调研
|
||||
|
||||
> 调研对象:OpenAI Chat Completions、OpenAI Responses、Claude Messages、Gemini generateContent 四个协议响应中的 token 用量(usage)结构。
|
||||
> 核心关注点:**存在缓存时,输入 token 是否包含缓存 token**。
|
||||
> 调研日期:2026-08-17(基于各官方文档当时的版本)
|
||||
|
||||
## 核心结论
|
||||
|
||||
四家协议里三家是「包含」口径,**Claude 是唯一的「排除」口径**:
|
||||
|
||||
| 协议 | 输入字段 | 缓存 token 是否含在内 | 未缓存输入怎么得到 |
|
||||
|---|---|---|---|
|
||||
| OpenAI Chat Completions | `prompt_tokens` | ✅ 包含,`cached_tokens` 是它的子集 | `prompt_tokens - cached_tokens` |
|
||||
| OpenAI Responses | `input_tokens` | ✅ 包含,`cached_tokens` 是它的子集 | `input_tokens - cached_tokens - cache_write_tokens` |
|
||||
| **Claude Messages** | `input_tokens` | ❌ **排除**,缓存是独立并列字段 | `input_tokens` 本身就是未缓存部分;总输入 = 三个字段**相加** |
|
||||
| Gemini | `promptTokenCount` | ✅ 包含,`cachedContentTokenCount` 是它的子集 | `promptTokenCount - cachedContentTokenCount` |
|
||||
|
||||
## 1. OpenAI Chat Completions(`/v1/chat/completions`)
|
||||
|
||||
官方文档示例(chat/object):
|
||||
|
||||
```json
|
||||
"usage": {
|
||||
"prompt_tokens": 1117,
|
||||
"completion_tokens": 46,
|
||||
"total_tokens": 1163,
|
||||
"prompt_tokens_details": {
|
||||
"cached_tokens": 0,
|
||||
"audio_tokens": 0
|
||||
},
|
||||
"completion_tokens_details": {
|
||||
"reasoning_tokens": 0,
|
||||
"audio_tokens": 0,
|
||||
"accepted_prediction_tokens": 0,
|
||||
"rejected_prediction_tokens": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
字段全集(据官方 SDK `completion_usage.py` 的 schema):
|
||||
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| `prompt_tokens` | 总输入 token,**包含**缓存命中部分 |
|
||||
| `completion_tokens` | 总输出 token,**包含** reasoning tokens |
|
||||
| `total_tokens` | = prompt_tokens + completion_tokens |
|
||||
| `prompt_tokens_details.cached_tokens` | 缓存命中读到的输入 token(prompt_tokens 的子集) |
|
||||
| `prompt_tokens_details.cache_write_tokens` | 本次写入缓存的 token(较新字段,prompt_tokens 的子集) |
|
||||
| `prompt_tokens_details.audio_tokens / text_tokens / image_tokens` | 按模态细分(prompt_tokens 的子集) |
|
||||
| `completion_tokens_details.reasoning_tokens` | 推理 token(completion_tokens 的子集) |
|
||||
| `completion_tokens_details.audio_tokens / text_tokens` | 按模态细分(completion_tokens 的子集) |
|
||||
| `completion_tokens_details.accepted_prediction_tokens / rejected_prediction_tokens` | Predicted Outputs 功能相关 |
|
||||
|
||||
包含关系的官方例证(prompt caching guide):`prompt_tokens: 2006, cached_tokens: 1920` —— 1920 是 2006 的一部分,剩 86 个未命中。
|
||||
|
||||
缓存计费:命中部分按折扣价计费(guide 现行口径 0.1×,实际各模型定价表不同,如 gpt-4o 为 0.5×)。可缓存的最小前缀为 1024 token,按 128 token 增量命中。
|
||||
|
||||
## 2. OpenAI Responses API(`/v1/responses`)
|
||||
|
||||
usage 结构明显比 Chat Completions 精简(据官方 SDK `response_usage.py`):
|
||||
|
||||
```json
|
||||
"usage": {
|
||||
"input_tokens": 2600,
|
||||
"input_tokens_details": {
|
||||
"cached_tokens": 2000,
|
||||
"cache_write_tokens": 400
|
||||
},
|
||||
"output_tokens": 300,
|
||||
"output_tokens_details": {
|
||||
"reasoning_tokens": 120
|
||||
},
|
||||
"total_tokens": 2900
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| `input_tokens` | 总输入,**包含**缓存命中和缓存写入(示例:2600 = 2000 cached + 400 write + 200 新输入) |
|
||||
| `input_tokens_details.cached_tokens` | 缓存命中(子集) |
|
||||
| `input_tokens_details.cache_write_tokens` | 本次写入缓存(子集)——Responses 特有字段,Chat Completions 侧较新才补上 |
|
||||
| `output_tokens` | 总输出,**包含** reasoning_tokens(子集) |
|
||||
| `output_tokens_details.reasoning_tokens` | 推理 token |
|
||||
| `total_tokens` | = input_tokens + output_tokens |
|
||||
|
||||
注意:
|
||||
- **没有**模态细分、没有预测 token 字段。
|
||||
- 复用 `previous_response_id` 时每次都为整个已见对话历史计费,`input_tokens` 会随轮次增长,`cached_tokens` 会很大。
|
||||
|
||||
## 3. Claude Messages API(`/v1/messages`)
|
||||
|
||||
官方 API reference 的 usage 字段全集:
|
||||
|
||||
```json
|
||||
"usage": {
|
||||
"input_tokens": 2048,
|
||||
"cache_read_input_tokens": 1800,
|
||||
"cache_creation_input_tokens": 248,
|
||||
"output_tokens": 503,
|
||||
"cache_creation": {
|
||||
"ephemeral_5m_input_tokens": 148,
|
||||
"ephemeral_1h_input_tokens": 100
|
||||
},
|
||||
"output_tokens_details": { "thinking_tokens": 0 },
|
||||
"server_tool_use": { "web_search_requests": 0, "web_fetch_requests": 2 },
|
||||
"service_tier": "standard",
|
||||
"inference_geo": "global"
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| `input_tokens` | **仅计最后一个 cache 断点之后的输入**(未走缓存的常规输入) |
|
||||
| `cache_read_input_tokens` | 从缓存读到的 |
|
||||
| `cache_creation_input_tokens` | 本次写入缓存的 |
|
||||
| `cache_creation.ephemeral_5m_input_tokens / ephemeral_1h_input_tokens` | 按 TTL 细分的缓存写入,两者之和 = cache_creation_input_tokens |
|
||||
| `output_tokens` | 总输出(含 thinking),是计费权威值 |
|
||||
| `output_tokens_details.thinking_tokens` | 思考 token(output_tokens 的子集,只读观测用) |
|
||||
| `server_tool_use.web_search_requests / web_fetch_requests` | 服务端工具调用次数(按次计费) |
|
||||
| `service_tier` | standard / priority / batch |
|
||||
| `inference_geo` | 推理所在地理区域 |
|
||||
|
||||
官方原文(关键):
|
||||
|
||||
> "Total input tokens in a request is the summation of `input_tokens`, `cache_creation_input_tokens`, and `cache_read_input_tokens`."
|
||||
|
||||
> "The `input_tokens` field represents only the tokens that come after the last cache breakpoint in your request - not all the input tokens you sent."
|
||||
|
||||
计费倍率(官方 prompt caching 文档):
|
||||
|
||||
| Token 类型 | 倍率(相对基础输入价) |
|
||||
|---|---|
|
||||
| 常规输入 | 1.0× |
|
||||
| 5 分钟缓存写入(ephemeral_5m) | 1.25× |
|
||||
| 1 小时缓存写入(ephemeral_1h) | 2× |
|
||||
| 缓存读取 | 0.1× |
|
||||
|
||||
老响应没有 `cache_creation` 细分时,`cache_creation_input_tokens` 默认按 5m(1.25×)计。
|
||||
|
||||
## 4. Gemini(`generateContent` 的 `usageMetadata`)
|
||||
|
||||
注意:Gemini 的用量不在 `usage` 字段里,而是响应顶层的 `usageMetadata`,且为 camelCase:
|
||||
|
||||
```json
|
||||
"usageMetadata": {
|
||||
"promptTokenCount": 1050,
|
||||
"cachedContentTokenCount": 1000,
|
||||
"candidatesTokenCount": 200,
|
||||
"thoughtsTokenCount": 150,
|
||||
"toolUsePromptTokenCount": 0,
|
||||
"totalTokenCount": 1400,
|
||||
"promptTokensDetails": [{ "modality": "TEXT", "tokenCount": 1050 }],
|
||||
"cacheTokensDetails": [{ "modality": "TEXT", "tokenCount": 1000 }],
|
||||
"candidatesTokensDetails": [{ "modality": "TEXT", "tokenCount": 200 }]
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 说明 |
|
||||
|---|---|
|
||||
| `promptTokenCount` | 总输入,**包含**缓存部分 |
|
||||
| `cachedContentTokenCount` | 其中命中缓存的(显式 context caching;2.5 系列隐式缓存命中也体现在此字段) |
|
||||
| `candidatesTokenCount` | 输出 token(不含思考) |
|
||||
| `thoughtsTokenCount` | 思考 token,**输出侧**,按输出价计费 |
|
||||
| `toolUsePromptTokenCount` | 工具使用 prompt 的 token |
|
||||
| `totalTokenCount` | = promptTokenCount + thoughtsTokenCount + candidatesTokenCount |
|
||||
| `promptTokensDetails[] / cacheTokensDetails[] / candidatesTokensDetails[] / toolUsePromptTokensDetails[]` | 按模态(TEXT/IMAGE/AUDIO/VIDEO)细分的列表 |
|
||||
| `serviceTier` | 服务层级 |
|
||||
|
||||
官方原文(关键):
|
||||
|
||||
> "When `cachedContent` is set, this is still the total effective prompt size meaning this includes the number of tokens in the cached content."
|
||||
|
||||
> "Total token count for the generation request (prompt + thoughts + response candidates)."
|
||||
|
||||
## 计费换算公式
|
||||
|
||||
```
|
||||
OpenAI 系(两协议同构):
|
||||
输入成本 = (input_tokens − cached_tokens) × P_in + cached_tokens × P_cached
|
||||
输出成本 = output_tokens × P_out # reasoning 已含在 output 里,不另计
|
||||
|
||||
Claude:
|
||||
输入成本 = input_tokens × P_in
|
||||
+ cache_creation_5m × P_in × 1.25 + cache_creation_1h × P_in × 2
|
||||
+ cache_read × P_in × 0.1
|
||||
输出成本 = output_tokens × P_out # thinking 已含在 output 里
|
||||
(总输入量展示用 = 三项相加,切勿把 input_tokens 当总输入)
|
||||
|
||||
Gemini:
|
||||
输入成本 = (promptTokenCount − cachedContentTokenCount) × P_in + cachedContentTokenCount × P_cached
|
||||
输出成本 = (candidatesTokenCount + thoughtsTokenCount) × P_out
|
||||
```
|
||||
|
||||
## 协议互转的坑
|
||||
|
||||
最容易踩的坑是 **Claude ↔ 其他三家**:
|
||||
|
||||
- 把 Claude 的 `input_tokens` 直接映射成 OpenAI 的 `prompt_tokens`,会导致缓存场景下输入量少算(Claude 侧)或多算(反向)。
|
||||
- 正确做法:Claude → 其他,做**加法**(`input + cache_read + cache_creation` → 对方口径的「总输入」);其他 → Claude,做**减法**(总输入 − 缓存命中 → `input_tokens`,缓存命中 → `cache_read_input_tokens`)。
|
||||
- 注意 OpenAI 的 `cached_tokens` 是「命中读取」语义,没有 Claude 的「写入 1.25×/2×」概念;反向转换时 Claude 的写入 token 一般并入常规输入或缓存读取,需按计费策略明确选一边。
|
||||
|
||||
## 流式响应中 usage 的位置
|
||||
|
||||
| 协议 | 流式 usage 出现位置 |
|
||||
|---|---|
|
||||
| OpenAI chat | 需设 `stream_options: {"include_usage": true}`,最后一个 chunk 带 usage |
|
||||
| Responses | `response.completed` 事件的 `response.usage` |
|
||||
| Claude | `message_start` 带输入侧 usage,`message_delta` 累计 `output_tokens`(已知问题:部分流式场景 `message_delta` 只带 `output_tokens`,输入侧字段缺失,需保留 `message_start` 的值合并) |
|
||||
| Gemini | `streamGenerateContent` 最后一个 chunk 的 `usageMetadata` |
|
||||
|
||||
## 参考来源
|
||||
|
||||
- [Gemini API — Generating content(usageMetadata 完整规格)](https://ai.google.dev/api/generate-content)
|
||||
- [OpenAI — Chat Completions API Reference(chat object)](https://platform.openai.com/docs/api-reference/chat/object)
|
||||
- [OpenAI — Prompt caching guide](https://developers.openai.com/api/docs/guides/prompt-caching)
|
||||
- [openai-python — completion_usage.py(PromptTokensDetails/CompletionTokensDetails 权威 schema)](https://github.com/openai/openai-python/blob/main/src/openai/types/completion_usage.py)
|
||||
- [openai-python — response_usage.py(Responses usage 权威 schema)](https://github.com/openai/openai-python/blob/main/src/openai/types/responses/response_usage.py)
|
||||
- [Anthropic — Messages API Reference(usage object)](https://platform.claude.com/docs/en/api/messages)
|
||||
- [Anthropic — Prompt caching(计费倍率与 token 拆解)](https://platform.claude.com/docs/en/docs/build-with-claude/prompt-caching)
|
||||
-77
@@ -1,77 +0,0 @@
|
||||
# feat/responses 分支待办事项
|
||||
|
||||
> 来源:2026-08-17 对照《docs/大模型协议Usage字段调研.md》的 token 处理审计。
|
||||
> 已完成:P0(Gemini thoughts 计费)、P1(Claude→OpenAI 加法口径 + bridge 联动)、
|
||||
> P3(input_tokens 口径统一 + 000017 回填迁移)、P4(cache_write_tokens 解析与可见)、
|
||||
> P5(插件 TokenUsage 扩展)。以下为**未处理**事项。
|
||||
|
||||
---
|
||||
|
||||
## A. 已确认未修的代码问题
|
||||
|
||||
### 1. Claude 5m/1h 缓存写入未分别计价(原 P2,收入项)⚠️ 高
|
||||
|
||||
- **位置**:`internal/logic/billing/pricing.go` `computeCost` / `resolveTokenCounts`
|
||||
- **问题**:全部 `cacheCreationTokens` 按单一 `CacheCreationPrice` 计价;`CachedCreation5mTokens/1hTokens` 仅入库不参与计价。官方倍率:5m=1.25×、1h=2×,老响应无 TTL 细分时默认按 5m。
|
||||
- **影响**:`CacheCreationPrice` 按 5m 配置则 1h 写入低估约 37.5%;反之 5m 高估。1h 缓存用量越大偏差越大。
|
||||
- **修复思路**:
|
||||
1. `PricingResult` 新增 1h 价格字段(或 1h 倍率),需管理后台配置入口
|
||||
2. `resolveTokenCounts` 分桶返回 5m/1h
|
||||
3. `computeCost` 分别计价;无 TTL 细分时全部按 5m 兜底
|
||||
- **前置**:需先决策定价配置方案(独立字段 vs 倍率)
|
||||
|
||||
### 2. OpenAI→Gemini 反向转换 reasoning 双计(小修)✅ 建议顺手修
|
||||
|
||||
- **位置**:`relay/channel/openai/gemini_response.go:177,225`
|
||||
- **问题**:`CandidatesTokenCount = usage.CompletionTokens`——OpenAI 的 completion **含** reasoning,而 Gemini 语义 candidates **不含**思考,且同处又单独设了 `ThoughtsTokenCount`。Gemini 客户端按 `total = prompt + candidates + thoughts` 自行汇总时 reasoning 被双计。
|
||||
- **修复**:`CandidatesTokenCount: completion - reasoning`(判空 details)
|
||||
- **影响**:仅 Gemini 格式客户端展示,不影响计费
|
||||
|
||||
### 3. Claude 服务端工具调用未计费(收入项)⚠️ 高
|
||||
|
||||
- **位置**:`relaykit/dto/claude.go` `ClaudeServerToolUsage` 已解析,计费链路(`internal/logic/billing/`)完全忽略
|
||||
- **问题**:Claude `usage.server_tool_use.web_search_requests / web_fetch_requests` 按次计费(如 web search $10/1k 次),当前漏收。
|
||||
- **修复思路**:`TokenDetails` 增加按次计数字段 → usage log 落库 → `computeCost` 增加按次项;需模型定价新增服务端工具单价配置
|
||||
|
||||
### 4. 流中断请求的用量报表缺口
|
||||
|
||||
- **位置**:`relay/handler/relay_handler.go` 流中断路径(`SettleStreamInterrupted` → `recordFailedUsage`)
|
||||
- **问题**:计费按 `ApplyInterruptedUsageFallback` 估算实际收了钱,但 `recordFailedUsage` 写的 usage log **token 列全为 0**——计费与报表脱节,token 统计系统性偏低。
|
||||
- **修复思路**:中断路径将估算 usage 传入 `recordFailedUsage`(或改用带 token 的记录路径)
|
||||
- **建议**:与 #1 一起做,同动结算/报表链路
|
||||
|
||||
---
|
||||
|
||||
## B. 部署顺序注意事项(非代码 bug)
|
||||
|
||||
### 5. 000017 迁移执行时序 ⚠️ 必须遵守
|
||||
|
||||
回填 UPDATE 会给所有 `channel_type=2 AND cache>0` 的行加缓存。若新代码先上线、迁移后执行,窗口期新写入的行(已含缓存口径)会被**二次加 cache 双计**。
|
||||
|
||||
正确顺序:**停旧服务 → `goose up`(000017)→ `team-api aggregate-usage --from <最早用量日期> --to <今天>` → 启新服务**
|
||||
|
||||
### 6. 部署当天 Redis 热桶混合口径
|
||||
|
||||
模型性能页"今天"数据(热桶)当天混合新旧口径,次日 01:00 聚合进 `bil_usage_daily` 后自动消解,无需处理。
|
||||
|
||||
---
|
||||
|
||||
## C. 待确认 / 未审计项
|
||||
|
||||
| # | 项 | 说明 |
|
||||
|---|---|---|
|
||||
| 7 | Gemini `toolUsePromptTokenCount` | 官方口径模糊(是否含于 promptTokenCount 不明),当前忽略,需查证后决定是否计费 |
|
||||
| 8 | Gemini 渠道 → Claude 格式客户端 | relaykit 注册了 `gemini→claude` 转换器但 bridge 未接该方向;该路径是否可达、usage 口径是否正确未验证 |
|
||||
| 9 | Vertex Code Assist 聚合流 | `handleCodeAssistAggregatedStream` 的 usage 处理未验证 |
|
||||
| 10 | 音频 token 计费 | `audio_input/output_tokens` 已入库,是否有独立单价、计费是否正确未审计 |
|
||||
| 11 | 插件 `RelayInterceptor` 接线 | P5 只扩展了 `TokenUsage` 结构;填充点(`RelayResponse.Usage` 构造)待插件系统接入 relay 流程时实现 |
|
||||
| 12 | playground `calculateCost.ts` 无缓存价 | P1 后 prompt 已含缓存,按全价估算偏高;仅影响演示页展示 |
|
||||
|
||||
---
|
||||
|
||||
## 建议处理顺序
|
||||
|
||||
1. **#2**(一处减法,最小成本)
|
||||
2. **#1 + #4**(收入项 + 同链路报表,需先定 #1 定价配置方案)
|
||||
3. **#3**(收入项,需新增定价配置)
|
||||
4. C 组按需排查
|
||||
Reference in new issue
Block a user