采集 completion_tokens_details.reasoning_tokens:推理开销目前无法与生成开销分开归因 #6
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
需求
推理模型会在 usage 里单独上报推理消耗的 token,例如 Kimi K3 经中转网关返回的:
completion_tokens_details.reasoning_tokens目前没有被采集。这是 OpenAI 兼容格式里和
prompt_tokens_details.cached_tokens(issue #3 已支持)对称的一个字段,采集方式可以完全照搬
_coerce_cached_tokens。为什么需要它
推理 token 已经计入
completion_tokens,所以成本总额是对的——这不是计费缺口,是归因缺口。缺了它,"这次调用花的钱里有多少是花在推理上"就无法区分。
对下游项目(dissect)而言这个区分是必需的。该项目要测各个设计因子的边际成本,而
其中一个因子恰好是"反思模型用多强";推理开销是这个因子最主要的成本通道,如果它和
生成答案的开销混在一列里,这个因子的成本效应就没法单独报告。上面那条真实响应里
10 个输出 token 有 7 个是推理,比例并不小。
顺带一个具体的用途:判断"推理到底关掉了没有"。issue #5 提到 minimax 的
enable_thinking是空声明,而reasoning_tokens == 0是比输出长度更直接的判据。建议
LLMResponse加一个字段,与cached_prompt_tokens同款语义(None = 该源不报,0 = 上报了且确实没推理,两者对下游处置不同):
在 transport 侧照
_coerce_cached_tokens的形态从usage.completion_tokens_details里取,非负整数才收,
bool同样要排除。需要留意流式:
completion_tokens_details只在最后的 usage 帧里,走打捞路径(
missing_done)时可能拿不到,这时记 None 而不是 0 就是正确的。更正上文的一处论证错误:我把 K3 说成了"反思模型",实际它在下游项目里是被试底座之一,
不是反思模型。这条 issue 的诉求不变,但理由应当改成下面两条,它们比原来的更贴切:
一,底座开着推理时,推理 token 是"生成账"的大头,而不是"反思账"的。 下游项目把 LLM
调用分成生成 / 评估 / 反思三本账分开记,用来回答各设计因子的边际成本。被试 agent 每做一道
题都要多轮调用,如果它的底座在推理,那部分 token 全部落在生成账上。现在只能看到
completion_tokens这一个总数,无法回答"这道题的成本里有多少花在推理上"。二,
reasoning_tokens == 0是"推理确实关掉了"的直接判据。 这一条其实更要紧。配合issue #5:那边说的是
enable_thinking=False对 minimax 静默失效,而发现它失效靠的是比较输出长度——一个间接、需要反复试的信号。如果 usage 里的推理 token 数被采集上来,
"关掉了没有"就是一次调用即可确认的事实,而不需要设计对照实验去猜。
对下游项目而言这不是锦上添花:它的公共设定要求关闭 thinking 以隔离思维链效应,而"是否
真的关上了"目前无法在运行时验证——只能靠事后看输出长度异常。
已在 1.0.6 落地,关闭。
89ff916按建议照搬_coerce_cached_tokens的形态,新增transports/openai_compat.py的_coerce_reasoning_tokens,从usage.completion_tokens_details取值,非负整数才收、排除 bool;非流式与流式两条路径都接了。LLMResponse/TransportResult新增reasoning_tokens: int | None;遥测表llm_calls补reasoning_tokens列,TelemetryRecorder端口 21 → 22 字段,补列纪律与 issue #3/#4 逐字相同。一条与本 issue 原文预期不同、下游需要知道的点:
None的语义是「本次调用未上报」,不是「该源不上报」,与cached_prompt_tokens的 NULL 语义不同——中转网关在上游不返回 usage 时会用本地 tokenizer 补算并整体替换 usage 对象,把completion_tokens_details一并吃掉(同一请求 10 轮实测呈 6:4 双峰)。所以判「是否发生推理」要写in (None, 0);实测三家供应商未推理时都是整个 details 缺失,无人上报字面0,写== 0的条件永远不成立。