Skip to content

Token 统计未按 model 拆分,多模型切换场景下数据失真 #71

@Lellansin

Description

@Lellansin

背景

目前 /model 命令已经支持在会话中切换模型了,但 token 统计那一层完全没有跟上——所有模型的 usage 都混在一个桶里,导致退出时的 summary 和状态栏展示的数据都有问题。

具体问题

1. SessionEntry.usage 是「一锅粥」

所有 API 调用返回的 usage(prompt_tokens / completion_tokens / cached_tokens / total_tokens)通过 accumulateUsage 递归 deep-merge 到一起,不区分模型。

一个 session 里先用 deepseek-v4-pro 聊了几轮,然后切到 deepseek-v4-flash 继续——最后 usage 全部混在一起,根本没法分清哪个模型消耗了多少。

2. Exit Summary 展示的模型名可能不对

exitSummary.ts 里拿的是 resolveCurrentSettings().model(当前配置的模型),但 usage 数据是所有模型的累计。如果用户中途切换过模型,退出的表格会显示「Model Usage: deepseek-v4-flash」但数字里包含了之前的 pro 消耗。

3. activeTokens 语义混乱

SessionEntry.activeTokens 存的是最后一次 API 调用total_tokens,不是会话级累计,也不是真正的「当前活跃 token 量」。但它被用在:

  • 状态栏 buildStatusLine 的展示,用户以为这是累计消耗
  • Compaction 阈值比较 (getCompactPromptTokenThreshold(model)),但阈值取的又是当前模型而不是历史模型

如果从 V4(512K 窗口)切到非 V4(128K 窗口),阈值骤降,但 activeTokens 可能因为刚切模型第一轮调用很短而偏小——compaction 根本不会触发,实际上下文可能已经爆了。

4. 成本完全没法算

不同模型价格不一样,也没有做 per-model 的 caching token 拆分,用户想知道「这个 session 大概花了多少钱」完全不可能。

大致修复思路

核心是把 SessionEntry.usage 从单值改成 per-model 的映射:

// 现状
usage: unknown | null;

// 改成
usage: Record<string, ModelUsage> | null;  // key = model name

accumulateUsage 增加 model 参数,调用处把 createOpenAIClient() 返回的 model 传进去。Exit summary 的表格改成多行(每个模型一行),最下面加个汇总行。

activeTokens 的逻辑也要重新想——要么直接砍掉改为基于实际消息量的估算,要么也拆成 per-model。

Compaction 那块,getCompactPromptTokenThreshold 不应该用「当前激活的模型」,应该基于实际上下文消息量来判断。

建议 PR 拆分

PR1 — 数据结构改造

  • SessionEntry.usageunknownRecord<string, ModelUsage>
  • accumulateUsage 签名加上 model 参数
  • 所有调用处传 model(activateSession / compactSession)

PR2 — 前端的展示修复

  • Exit Summary 表格支持多行 per-model 展示 + 汇总行
  • buildStatusLine 的 token 展示修正(展示累计 total_tokens 而非最后一次的值)
  • 确保模型切换消息(isModelChange: true)里也带当时的模型名

PR3 — activeTokens 和 compaction 修正

  • 重新定义 activeTokens 的语义(改为真正的会话级估算,或基于消息内容估算)
  • getCompactPromptTokenThreshold 基于实际会话模型历史判断,或保守取最小值

PR4(可选)— 成本估算

  • 如果后续有价格数据源,可以基于 per-model usage 做成本展示

讨论点

@qorzj 想确认几个事情:

  1. usage 的 per-model 拆分方向你觉得 OK 吗?还是有更简单的中间方案?
  2. activeTokens 你更倾向于「砍掉 + 用消息量估算替代」还是「保留 + 改成 per-model 累计」?
  3. PR 拆分粒度合不合适?要不要 PR1+PR2 一起做?

分析来自 session.ts / exitSummary.ts / App.tsx / settings.ts 的 token 统计链路。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions