Skip to content

fix: claude affinity cache counter - #2980

Merged
Calcium-Ion merged 3 commits into
QuantumNous:mainfrom
seefs001:fix/channel-affinity-claude
Feb 22, 2026
Merged

fix: claude affinity cache counter#2980
Calcium-Ion merged 3 commits into
QuantumNous:mainfrom
seefs001:fix/channel-affinity-claude

Conversation

@seefs001

@seefs001 seefs001 commented Feb 21, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • Usage modal shows richer token statistics (prompt/cached/completion/total when available), dynamic rows, and explanatory text about token-rate semantics; updated empty-state message ("暂无可展示数据").
  • Bug Fixes

    • More reliable quota consumption handling with additional validation.
    • Improved detection of request relay format to ensure correct token-rate calculations across providers.

@coderabbitai

coderabbitai Bot commented Feb 21, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Adds relay-format–aware channel-affinity cache observation and token-rate mode propagation in backend, a small nil-check in quota consumption, frontend UI changes to display/interpret cached-token rates, and unit tests for RelayInfo final-format resolution and cache behavior.

Changes

Cohort / File(s) Summary
Quota / Cache Observation
service/quota.go
Added nil-check for usage in PostClaudeConsumeQuota and conditionally call to observe channel-affinity usage by relay format.
Channel-affinity backend + tests
service/channel_affinity.go, service/channel_affinity_usage_cache_test.go
Introduce cached-token-rate modes (cached_over_prompt, cached_over_prompt_plus_cached, mixed), propagate mode via new ObserveChannelAffinityUsageCacheByRelayFormat/ObserveChannelAffinityUsageCacheFromContext APIs, store mode in stats/counters, add normalization/mapping helpers, and tests verifying behavior across relay formats.
RelayInfo helper + tests
relay/common/relay_info.go, relay/common/relay_info_test.go
Add RelayInfo.GetFinalRequestRelayFormat() (with fallbacks) and unit tests covering precedence, fallbacks, and nil receiver.
Compatible handler adjustment
relay/compatible_handler.go
Replace direct field access with relayInfo.GetFinalRequestRelayFormat() and call ObserveChannelAffinityUsageCacheByRelayFormat where origin usage exists.
Frontend: Channel affinity modal
web/src/components/table/usage-logs/modals/ChannelAffinityUsageCacheModal.jsx
Expand modal to compute/display cached-token rates under multiple modes, add helpers to format/validate token stats, dynamic rows and explanatory UI text, and conditional rendering when token stats are unsupported.

Sequence Diagram(s)

sequenceDiagram
    participant Browser
    participant Backend
    participant RelayInfo as RelayInfo/Adaptors
    participant Cache

    Browser->>Backend: request that consumes Claude quota (may include usage)
    Backend->>RelayInfo: determine final relay format (GetFinalRequestRelayFormat)
    alt usage != nil
        Backend->>Cache: ObserveChannelAffinityUsageCacheByRelayFormat(usage, relayFormat)
        Cache-->>Backend: ack/update counters (including cached_token_rate_mode)
    end
    Backend-->>Browser: quota consume response

    Browser->>Backend: request channel-affinity stats for modal
    Backend-->>Browser: stats (including cached_token_rate_mode, token counts)
    Browser->>Browser: formatCachedTokenRate(mode, promptTokens, cachedTokens)
    Browser-->>User: render modal with token rows and explanatory text
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • creamlike1024

Poem

🐰 A hop, a sniff, the cache awakes,
Tokens counted in clever takes,
Relay formats guide the way,
Modal lights explain the play,
Hooray — the rabbit logs the flakes! 🥕

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.35% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'fix: claude affinity cache counter' is vague and generic. While it mentions 'claude' and 'cache counter', it fails to clearly convey the actual scope of changes, which include relay format detection logic, token rate mode categorization, UI enhancements, and cross-file integration across multiple services. Consider a more specific title like 'feat: implement cached token rate mode tracking by relay format' or 'feat: add relay format-based token rate mode for channel affinity cache' to accurately reflect the comprehensive changes across service and frontend layers.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

🧹 Nitpick comments (1)
web/src/components/table/usage-logs/modals/ChannelAffinityUsageCacheModal.jsx (1)

47-48: cached > prompt heuristic can produce false positives on aggregated non-Claude stats.

For a rule that mixes Claude and non-Claude upstream requests over time, cumulative cached_tokens could exceed prompt_tokens for purely numeric reasons unrelated to Claude's API token-reporting structure. This would silently flip the denominator to prompt + cached, changing the displayed percentage without user awareness.

The comment on line 46 already acknowledges this as a known hack. Consider at minimum adding a TODO noting the intent to replace this with an explicit per-channel model-family tag once that metadata is available, so the heuristic is easier to find and remove.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@web/src/components/table/usage-logs/modals/ChannelAffinityUsageCacheModal.jsx`
around lines 47 - 48, Add a TODO comment above the likelyClaudeStyle assignment
explaining that the cached > prompt heuristic is a temporary hack for detecting
Claude-style token reporting and should be replaced once per-channel
model-family metadata exists; also annotate the line referencing
normalizedRuleName, cached, and prompt so future maintainers can find and remove
this heuristic easily (keep the expression using
normalizedRuleName.includes('claude') || cached > prompt but add the TODO and a
short explanatory comment referencing likelyClaudeStyle, normalizedRuleName,
cached and prompt).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@web/src/components/table/usage-logs/modals/ChannelAffinityUsageCacheModal.jsx`:
- Around line 163-167: The displayed rule name passed into
formatCachedTokenRateAuto may be undefined because s.rule_name comes from the
API; update the call that builds the value (the value using cachedTokens,
promptTokens, and ruleName) to pass params.rule_name as a fallback: use
s.rule_name ?? params.rule_name (or similar) when calling
formatCachedTokenRateAuto so the normalizedRuleName logic in
formatCachedTokenRateAuto sees the expected rule name; adjust the same call that
builds the value string for cachedTokens to use this fallback.

---

Nitpick comments:
In
`@web/src/components/table/usage-logs/modals/ChannelAffinityUsageCacheModal.jsx`:
- Around line 47-48: Add a TODO comment above the likelyClaudeStyle assignment
explaining that the cached > prompt heuristic is a temporary hack for detecting
Claude-style token reporting and should be replaced once per-channel
model-family metadata exists; also annotate the line referencing
normalizedRuleName, cached, and prompt so future maintainers can find and remove
this heuristic easily (keep the expression using
normalizedRuleName.includes('claude') || cached > prompt but add the TODO and a
short explanatory comment referencing likelyClaudeStyle, normalizedRuleName,
cached and prompt).

Comment thread web/src/components/table/usage-logs/modals/ChannelAffinityUsageCacheModal.jsx Outdated

@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

🧹 Nitpick comments (1)
service/channel_affinity.go (1)

724-733: Consider whether other relay formats (e.g., Gemini with cached tokens) should map to a mode.

Currently Gemini returns "", which means token stats are hidden in the UI even though usageCacheSignals will still count cache hits for Gemini. If Gemini's cache semantics follow the OpenAI convention (cached tokens included in prompt tokens), it could map to cached_over_prompt.

This is just a consideration for future — the current behavior is safe (defaults to hidden).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@service/channel_affinity.go` around lines 724 - 733, Update the mapping in
cachedTokenRateModeByRelayFormat to include Gemini-style relay formats: add a
case for the Gemini relay format constant (e.g., types.RelayFormatGemini) and
return cacheTokenRateModeCachedOverPrompt so Gemini will surface cached token
stats in the UI when its cache semantics match OpenAI's; leave the default
branch untouched so unknown formats still return "".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@service/quota.go`:
- Around line 238-244: PostClaudeConsumeQuota currently checks usage only to
call ObserveChannelAffinityUsageCacheByRelayFormat but then unconditionally
accesses usage.PromptTokens (and other fields) causing a nil-pointer panic;
modify the function so that if usage == nil it returns early (e.g., change the
initial guard to "if usage == nil { return }" before any further use) or
otherwise wrap all subsequent logic (useTimeSeconds, promptTokens, etc.) in a
usage != nil branch; reference PostClaudeConsumeQuota,
ObserveChannelAffinityUsageCacheByRelayFormat, and usage.PromptTokens when
making the change.

---

Nitpick comments:
In `@service/channel_affinity.go`:
- Around line 724-733: Update the mapping in cachedTokenRateModeByRelayFormat to
include Gemini-style relay formats: add a case for the Gemini relay format
constant (e.g., types.RelayFormatGemini) and return
cacheTokenRateModeCachedOverPrompt so Gemini will surface cached token stats in
the UI when its cache semantics match OpenAI's; leave the default branch
untouched so unknown formats still return "".

Comment thread service/quota.go
Comment on lines 238 to 244
func PostClaudeConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage *dto.Usage) {
if usage != nil {
ObserveChannelAffinityUsageCacheByRelayFormat(ctx, usage, relayInfo.GetFinalRequestRelayFormat())
}

useTimeSeconds := time.Now().Unix() - relayInfo.StartTime.Unix()
promptTokens := usage.PromptTokens

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 | 🔴 Critical

Nil-check for usage doesn't prevent nil-pointer dereference below.

The guard at line 239 only protects ObserveChannelAffinityUsageCacheByRelayFormat, but execution continues to line 244 where usage.PromptTokens is accessed unconditionally. If usage is nil, this will panic.

Either return early when usage == nil (consistent with how the rest of the function uses usage), or wrap the entire function body after the observe call.

🐛 Proposed fix
 func PostClaudeConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage *dto.Usage) {
+	if usage == nil {
+		return
+	}
+	ObserveChannelAffinityUsageCacheByRelayFormat(ctx, usage, relayInfo.GetFinalRequestRelayFormat())
-	if usage != nil {
-		ObserveChannelAffinityUsageCacheByRelayFormat(ctx, usage, relayInfo.GetFinalRequestRelayFormat())
-	}
 
 	useTimeSeconds := time.Now().Unix() - relayInfo.StartTime.Unix()
 	promptTokens := usage.PromptTokens
📝 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
func PostClaudeConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage *dto.Usage) {
if usage != nil {
ObserveChannelAffinityUsageCacheByRelayFormat(ctx, usage, relayInfo.GetFinalRequestRelayFormat())
}
useTimeSeconds := time.Now().Unix() - relayInfo.StartTime.Unix()
promptTokens := usage.PromptTokens
func PostClaudeConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage *dto.Usage) {
if usage == nil {
return
}
ObserveChannelAffinityUsageCacheByRelayFormat(ctx, usage, relayInfo.GetFinalRequestRelayFormat())
useTimeSeconds := time.Now().Unix() - relayInfo.StartTime.Unix()
promptTokens := usage.PromptTokens
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@service/quota.go` around lines 238 - 244, PostClaudeConsumeQuota currently
checks usage only to call ObserveChannelAffinityUsageCacheByRelayFormat but then
unconditionally accesses usage.PromptTokens (and other fields) causing a
nil-pointer panic; modify the function so that if usage == nil it returns early
(e.g., change the initial guard to "if usage == nil { return }" before any
further use) or otherwise wrap all subsequent logic (useTimeSeconds,
promptTokens, etc.) in a usage != nil branch; reference PostClaudeConsumeQuota,
ObserveChannelAffinityUsageCacheByRelayFormat, and usage.PromptTokens when
making the change.

@Calcium-Ion
Calcium-Ion merged commit a01a77f into QuantumNous:main Feb 22, 2026
1 check passed
dreamlx pushed a commit to dreamlx/new-api that referenced this pull request Mar 3, 2026
* fix: claude affinity cache counter

* fix: claude affinity cache counter

* fix: stabilize cache usage stats format and simplify modal rendering
ennnnny pushed a commit to ennnnny/new-api that referenced this pull request Mar 17, 2026
* fix: claude affinity cache counter

* fix: claude affinity cache counter

* fix: stabilize cache usage stats format and simplify modal rendering
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.

2 participants