Skip to content

修复:/v1/messages 流式 message_start 的 input_tokens 恒为 0(自定义 provider 上下文用量失准) - #425

Open
YeStarOne wants to merge 1 commit into
aimod-cc:betafrom
YeStarOne:fix/anthropic-message-start-input-tokens
Open

YeStarOne wants to merge 1 commit into
aimod-cc:betafrom
YeStarOne:fix/anthropic-message-start-input-tokens

Conversation

@YeStarOne

Copy link
Copy Markdown

问题

/v1/messages 流式响应里,首帧 message_start 的 usage.input_tokens 恒为 0。

AnthropicStream::emit_start 用 usage_to_anthropic(self.usage) 拼首帧 usage,而上游 chat usage 要到流末尾才出现 —— 首帧时 self.usage 还是 None,于是落进 {"input_tokens": 0, "output_tokens": 0} 的兜底分支。

收尾帧 message_delta 已经回传完整 usage(这块上已修好,不用动),但**「读到就记账」的客户端在 message_start 那一步就已经把本轮记成没有输入了**。

实测影响(ZCode Desktop 3.17.1,自定义 anthropic-messages provider)

通道 输入栏「上下文容量」
直连官方 GLM(标准 Anthropic SSE) 正常,上万级 token
经 Agent2API 的自定义 provider 退化成只算输出:866/100万 (0.1%)

同一会话的 model_usage 表里,经网关的自定义 provider 请求 99% 记成 input_tokens = 0(上游 message_delta 里其实带着真实值)。Claude Code 的 context-hud 是同一套口径,同样受影响。

改动

core/protocol/anthropic.rs

  • 新增 estimate_chat_tokens(&Value):CJK 一字一 token、其余约四字符一 token 的粗估,与前端估算同量级,取保守偏高。
  • 新增 AnthropicStream::with_input_tokens(model, input_tokens);new() 保留为 with_input_tokens(model, 0),现有调用方行为不变。
  • emit_start:首帧拿不到 input_tokens 时用估算值占位。真实 usage 到手后仍以 message_delta 为准 —— 估算只做占位,不覆盖真实值。

api/protocol.rs

  • messages_endpoint 流式分支按下游原始 Anthropic 请求体(含 system / tools)估算后传给构造器。纯内存扫描,不增加任何网络等待。

测试

新增 stream_usage_tests(3 条):

  1. 首帧 message_start 带估算值,且 output_tokens 为 0;
  2. 收尾 message_delta 用上游真实 usage 覆盖估算(input_tokens 不含缓存命中部分);
  3. 估算随 system 增长、空请求也给正数。

需要说明的

  • 本机缺可用的 Rust 构建环境,上面的测试没有实际跑过,请以 CI 结果为准。没有拿未验证的测试结果冒充验证。
  • 分支基于 beta,PR 也请合到 beta。
  • 已知的取舍:估算值不是 provider 真实计数,仅覆盖「message_delta 到达之前」的窗口;客户端应以收尾帧为准。

🤖 本条回复由 AI Agent 生成
Generated by an AI agent.

问题
----
`AnthropicStream::emit_start` 的首帧 `message_start` 用
`usage_to_anthropic(self.usage)` 拼 usage,而上游 chat usage 要到流末尾
才出现,首帧时 `self.usage` 还是 None → 落进
`{ "input_tokens": 0, "output_tokens": 0 }` 的兜底分支,即把
input_tokens 报成 0 占位。

收尾帧 `message_delta` 现在已回传完整 usage,但「读到就记账」的客户端
在 `message_start` 那一步就已经把本轮记成没有输入了。实测 ZCode
Desktop 3.17.1 接自定义 anthropic-messages provider:输入栏「上下文
容量」退化成只算输出(866/100万,0.1%),而同一账号直连官方 GLM 通道
是正常的上万级数字;Claude Code 的 context-hud 同理。

改动
----
- `anthropic.rs`
  - 新增 `estimate_chat_tokens(&Value)`:CJK 一字一 token、其余约四字符
    一 token 的粗估,取保守偏高。
  - 新增 `AnthropicStream::with_input_tokens(model, input_tokens)`;
    `new()` 保留为 `with_input_tokens(model, 0)`,行为不变。
  - `emit_start`:首帧若拿不到 input_tokens 就用估算值占位。真实 usage
    到手后仍以 `message_delta` 为准,估算只做占位。
- `api/protocol.rs`:`messages_endpoint` 流式分支按下游原始 Anthropic
  请求体(含 system / tools)估算后传给构造器。纯内存扫描,不增加任何
  网络等待。

测试
----
新增 `stream_usage_tests`(3 条):
- 首帧 message_start 带估算值且 output_tokens 为 0;
- 收尾 message_delta 用上游真实 usage 覆盖估算(input 不含缓存命中部分);
- 估算随 system 增长、空请求也给正数。

未验证:本机缺可用的 Rust 构建环境(依赖下载),以上测试未实际执行,
请 CI 复核。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant