Skip to content

Commit 2041ee7

Browse files
committed
fix(trae): 解析 TRAE 官方缓存字段 cache_read_input_tokens
实时请求验证:TRAE token_usage 真实返回 cache_read_input_tokens / cache_creation_input_tokens(未命中为 0), 此前解析器只找 OpenAI 惯例字段导致 cached_tokens 恒 NULL。 - _parse_usage 解析链:prompt_tokens_details.cached_tokens → 顶层 cached_tokens → TRAE cache_read_input_tokens(0 是有效值,保留) - 补充 0/缺失/优先级测试;PROPOSAL 同步字段事实 - Credit 确认无此字段,TRAE 恒 None 不变
1 parent 3b57273 commit 2041ee7

3 files changed

Lines changed: 21 additions & 3 deletions

File tree

‎PROPOSAL.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@
8080
- Token 刷新:`POST /cloudide/api/v3/trae/oauth/ExchangeToken`(refreshToken 轮换)
8181
- 签到:`/trae/api/v2/ug/checkin_credits/{status,claim}`;额度:`/trae/api/v2/pay/ide_user_ent_usage`
8282
- SSE 事件序列:`metadata` → `timing_cost` → `output`×N → `extra_info` → `token_usage` → `done`
83-
- SSE **只有 `token_usage`,无 per-request credit**
83+
- SSE `token_usage` 含缓存字段:`cache_read_input_tokens` / `cache_creation_input_tokens`(未命中时为 0,非缺失),映射为统计的 cached_tokens;**无 per-request credit**
8484
- 错误码 `1005` = 权益不足;上游仅流式,非流式需聚合
8585

8686
### 3.2 冲突与陷阱

‎src/provider/trae/events.py‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,15 @@ def as_int(key: str) -> int | None:
135135
value = payload.get(key)
136136
return value if isinstance(value, int) and not isinstance(value, bool) else None
137137

138-
# 缓存命中:OpenAI 惯例在 prompt_tokens_details.cached_tokens,顶层 cached_tokens 兜底
138+
# 缓存命中解析链:OpenAI 惯例(prompt_tokens_details.cached_tokens)→ 顶层
139+
# cached_tokens 兜底 → TRAE 官方字段 cache_read_input_tokens(实测真实返回该
140+
# 字段,且未命中时值为 0——0 是有效值必须保留,不能当缺失)。
139141
details = payload.get("prompt_tokens_details")
140142
cached = (details or {}).get("cached_tokens") if isinstance(details, dict) else None
141143
if not isinstance(cached, int) or isinstance(cached, bool):
142144
cached = as_int("cached_tokens")
145+
if not isinstance(cached, int) or isinstance(cached, bool):
146+
cached = as_int("cache_read_input_tokens")
143147

144148
return Usage(
145149
input_tokens=as_int("prompt_tokens"),

‎tests/test_m1a_trae.py‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1127,7 +1127,7 @@ def test_parse_all_events_non_dict_tool_call_entry_skipped():
11271127
assert EventKind.TOOL_CALLS not in kinds
11281128

11291129
def test_trae_usage_cached_tokens():
1130-
"""TRAE usage:details 路径 + 顶层兜底 + 缺省 None。"""
1130+
"""TRAE usage:details 路径 + 顶层兜底 + TRAE 官方字段 + 缺省 None。"""
11311131
from src.provider.trae.events import SSEFrame
11321132

11331133
frame = SSEFrame(event="token_usage", data=(
@@ -1138,6 +1138,20 @@ def test_trae_usage_cached_tokens():
11381138
frame = SSEFrame(event="token_usage", data='{"prompt_tokens":9,"cached_tokens":2}')
11391139
assert trae_events.parse_frame(frame).usage.cached_tokens == 2
11401140

1141+
# TRAE 官方字段:命中>0 与未命中=0 都要保留(0 是有效值不是缺失)
1142+
frame = SSEFrame(event="token_usage", data=(
1143+
'{"prompt_tokens":13,"completion_tokens":159,'
1144+
'"cache_read_input_tokens":7,"reasoning_tokens":148}'))
1145+
assert trae_events.parse_frame(frame).usage.cached_tokens == 7
1146+
frame = SSEFrame(event="token_usage", data=(
1147+
'{"prompt_tokens":13,"cache_read_input_tokens":0}'))
1148+
assert trae_events.parse_frame(frame).usage.cached_tokens == 0
1149+
1150+
# OpenAI 惯例字段优先于 TRAE 字段
1151+
frame = SSEFrame(event="token_usage", data=(
1152+
'{"prompt_tokens":9,"cached_tokens":2,"cache_read_input_tokens":5}'))
1153+
assert trae_events.parse_frame(frame).usage.cached_tokens == 2
1154+
11411155
frame = SSEFrame(event="token_usage", data='{"prompt_tokens":9}')
11421156
assert trae_events.parse_frame(frame).usage.cached_tokens is None
11431157

0 commit comments

Comments
 (0)