Skip to content

fix: Claude 流式断流时不再整份覆盖 usage,保留 cache 计费字段 - #4128

Merged
Calcium-Ion merged 2 commits into
QuantumNous:mainfrom
zuiho-kai:fix/claude-stream-usage-overwrite
Apr 8, 2026
Merged

fix: Claude 流式断流时不再整份覆盖 usage,保留 cache 计费字段#4128
Calcium-Ion merged 2 commits into
QuantumNous:mainfrom
zuiho-kai:fix/claude-stream-usage-overwrite

Conversation

@zuiho-kai

@zuiho-kai zuiho-kai commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

问题

HandleStreamFinalResponse 在流式未正常结束(!claudeInfo.DoneCompletionTokens == 0)时,调用 ResponseText2Usage 整份覆盖 claudeInfo.Usage

claudeInfo.Usage = service.ResponseText2Usage(c, ..., claudeInfo.Usage.PromptTokens)

ResponseText2Usage 返回一个全新的空 dto.Usage,只填了 3 个字段。赋值后,message_start 阶段已获取的 CachedTokensCacheCreationInputTokensClaudeCacheCreation5mTokens 等字段全部归零。

同时,PromptTokens 使用了 claudeInfo.Usage.PromptTokens(message_start 的占位值,常为 1),而其他所有渠道(OpenAI、Gemini、XAI、Cohere、Palm……)在类似 fallback 场景下都使用 info.GetEstimatePromptTokens()

影响

  • 上游 Anthropic 在 input 接收时即计费(含 cache_creation),但本地日志只记录 prompt_tokens=1
  • 计费严重错配:上游扣 $9.26,本地只扣 $0.000024
  • cache 相关字段在日志中全部为 0

修复

不整份覆盖 usage,只补缺失字段:

fallback := service.ResponseText2Usage(c, ..., info.GetEstimatePromptTokens())
if claudeInfo.Usage.CompletionTokens == 0 {
    claudeInfo.Usage.CompletionTokens = fallback.CompletionTokens
}
if claudeInfo.Usage.PromptTokens == 0 {
    claudeInfo.Usage.PromptTokens = fallback.PromptTokens
}
claudeInfo.Usage.TotalTokens = claudeInfo.Usage.PromptTokens + claudeInfo.Usage.CompletionTokens

改动点:

  1. 不再整份覆盖 claudeInfo.Usage,只补 CompletionTokensPromptTokens 的缺失值
  2. 保留 CachedTokensCacheCreationInputTokens 等已从 message_start 获取的字段
  3. PromptTokens 兜底改用 info.GetEstimatePromptTokens()(与其他渠道对齐)

Fixes #4129

Summary by CodeRabbit

  • Bug Fixes
    • Improved token usage tracking accuracy by preserving cached usage information while intelligently filling in missing token counts.
    • Enhanced token calculation to merge fallback values with existing data instead of overwriting, preventing loss of cache-related information.

HandleStreamFinalResponse 在 !Done 时调用 ResponseText2Usage 整份覆盖
claudeInfo.Usage,导致 message_start 已获取的 CacheReadInputTokens、
CacheCreationInputTokens 等字段丢失,prompt 退化为占位值 1。

修复:
- 只补缺失的 CompletionTokens/PromptTokens,保留已有 cache 数据
- PromptTokens 兜底改用 info.GetEstimatePromptTokens()(与其他渠道对齐)

Fixes QuantumNous#4127
@coderabbitai

coderabbitai Bot commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 228a8e31-a53e-4904-9a77-7cca0d03f261

📥 Commits

Reviewing files that changed from the base of the PR and between f7cdc72 and c66636a.

📒 Files selected for processing (1)
  • relay/channel/claude/relay-claude.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • relay/channel/claude/relay-claude.go

Walkthrough

HandleStreamFinalResponse in the Claude relay now preserves existing usage fields from message_start and selectively backfills only missing PromptTokens and CompletionTokens (using an estimated prompt token when needed), then recomputes TotalTokens instead of replacing the entire Usage object.

Changes

Cohort / File(s) Summary
Claude Usage Backfilling
relay/channel/claude/relay-claude.go
Replaced full claudeInfo.Usage overwrite with field-level backfill: compute a fallback usage, update CompletionTokens only when zero (or when response not Done and fallback completion is larger), update PromptTokens only when zero (using estimated prompt tokens), and recompute TotalTokens; preserves cache-related fields from message_start.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Suggested reviewers

  • seefs001

Poem

🐰 I hopped through streams where tokens went awry,
I patched the usage so cache won't say goodbye.
We backfill gently, keep totals in line,
Billing stays honest — carrot cake time! 🥕✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: preventing overwriting of usage data and preserving cache billing fields in Claude streaming scenarios.
Linked Issues check ✅ Passed The PR implementation fully addresses all requirements from issue #4129: it prevents overwriting usage data, preserves cache-related fields from message_start, and uses GetEstimatePromptTokens() for prompt token fallback.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the identified problem in HandleStreamFinalResponse, with no out-of-scope modifications to other functions or unrelated features.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@relay/channel/claude/relay-claude.go`:
- Around line 814-816: The fallback only backfills
claudeInfo.Usage.CompletionTokens when it's exactly 0, but interrupted streams
can leave the placeholder value 1 (from message_start) and still be
!claudeInfo.Done; update the check in the block that touches claudeInfo (the
clause using claudeInfo.Usage.CompletionTokens and fallback.CompletionTokens) to
treat the placeholder as unset (e.g., CompletionTokens <= 1) so you copy
fallback.CompletionTokens when CompletionTokens is 0 or the placeholder 1;
ensure this logic executes in the same place where claudeInfo.Done is considered
so interrupted-stream counts get corrected.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 74d324a0-02f0-464d-82ff-fd0eafea2a48

📥 Commits

Reviewing files that changed from the base of the PR and between 960bf9c and f7cdc72.

📒 Files selected for processing (1)
  • relay/channel/claude/relay-claude.go

Comment thread relay/channel/claude/relay-claude.go Outdated
Comment on lines +814 to +816
if claudeInfo.Usage.CompletionTokens == 0 {
claudeInfo.Usage.CompletionTokens = fallback.CompletionTokens
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fallback still undercounts interrupted streams when CompletionTokens is placeholder 1.

When !claudeInfo.Done, CompletionTokens can still be the message_start placeholder value (e.g., 1), but Line 814 only backfills when it is 0. This leaves severe undercounting in exactly the abnormal-stream path this block is handling (see relay/channel/claude/relay_claude_test.go:12-48, where message_start uses OutputTokens: 1).

💡 Suggested fix
-       if claudeInfo.Usage.CompletionTokens == 0 {
+       if claudeInfo.Usage.CompletionTokens == 0 ||
+           (!claudeInfo.Done && fallback.CompletionTokens > claudeInfo.Usage.CompletionTokens) {
            claudeInfo.Usage.CompletionTokens = fallback.CompletionTokens
        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if claudeInfo.Usage.CompletionTokens == 0 {
claudeInfo.Usage.CompletionTokens = fallback.CompletionTokens
}
if claudeInfo.Usage.CompletionTokens == 0 ||
(!claudeInfo.Done && fallback.CompletionTokens > claudeInfo.Usage.CompletionTokens) {
claudeInfo.Usage.CompletionTokens = fallback.CompletionTokens
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@relay/channel/claude/relay-claude.go` around lines 814 - 816, The fallback
only backfills claudeInfo.Usage.CompletionTokens when it's exactly 0, but
interrupted streams can leave the placeholder value 1 (from message_start) and
still be !claudeInfo.Done; update the check in the block that touches claudeInfo
(the clause using claudeInfo.Usage.CompletionTokens and
fallback.CompletionTokens) to treat the placeholder as unset (e.g.,
CompletionTokens <= 1) so you copy fallback.CompletionTokens when
CompletionTokens is 0 or the placeholder 1; ensure this logic executes in the
same place where claudeInfo.Done is considered so interrupted-stream counts get
corrected.

message_start 阶段可能给 CompletionTokens 非零占位值,
只检查 == 0 不够,加上 !Done && fallback > current 条件。
@Calcium-Ion Calcium-Ion added the ready to merge It will eventually merge, requiring a final check. label Apr 7, 2026
@seefs001 seefs001 removed the ready to merge It will eventually merge, requiring a final check. label Apr 8, 2026
seefs001
seefs001 previously approved these changes Apr 8, 2026
@Calcium-Ion
Calcium-Ion merged commit 49648d8 into QuantumNous:main Apr 8, 2026
1 check passed
@zuiho-kai
zuiho-kai deleted the fix/claude-stream-usage-overwrite branch April 8, 2026 15:25
Jinxuans referenced this pull request in TokFlux-Org/TokFlux May 9, 2026
…rite

fix: Claude 流式断流时不再整份覆盖 usage,保留 cache 计费字段
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.

Claude 流式断流时 HandleStreamFinalResponse 整份覆盖 usage 导致 cache 计费丢失

3 participants