Skip to content

fix: recognize non-standard cache tokens from OpenAI-compatible channels - #3371

Closed
majiayu000 wants to merge 2 commits into
QuantumNous:mainfrom
majiayu000:fix/issue-3309-openai-cache-recognition
Closed

fix: recognize non-standard cache tokens from OpenAI-compatible channels#3371
majiayu000 wants to merge 2 commits into
QuantumNous:mainfrom
majiayu000:fix/issue-3309-openai-cache-recognition

Conversation

@majiayu000

@majiayu000 majiayu000 commented Mar 21, 2026

Copy link
Copy Markdown

Fixes #3309

Summary

Three changes to fix cache token recognition for OpenAI-compatible providers (Qwen, DeepSeek, StepFun, etc.):

  1. dto/openai_response.go — Change CachedCreationTokens json tag from json:"-" to json:"cache_creation_input_tokens". The - tag prevented deserialization from any OpenAI-compatible JSON response. This field was only populated manually in the Claude adapter. The tag change allows natural deserialization from providers like Qwen. Note: this field is NOT serialized back to clients (the Usage struct uses InputTokenDetails which controls client-facing serialization).

  2. relay/channel/openai/relay-openai.go — Add a default case in applyUsagePostProcessing() to handle all other OpenAI-compatible channels:

    • If CachedTokens == 0: check PromptCacheHitTokens > 0 first, then fallback to extractCachedTokensFromBody() (covers StepFun's top-level cached_tokens)
    • If CachedCreationTokens == 0: call new extractCacheCreationTokensFromBody()
  3. relay/channel/openai/relay-openai.go — Add extractCacheCreationTokensFromBody() function following the same pattern as extractCachedTokensFromBody(). Parses usage.prompt_tokens_details.cache_creation_input_tokens from raw JSON.

Test Plan

  • Added relay/channel/openai/relay_openai_test.go with unit tests:
    • Default case extracts cached_tokens from body (StepFun scenario)
    • Default case uses PromptCacheHitTokens when available
    • Default case extracts cache_creation_input_tokens (Qwen scenario)
    • Already-populated values are not overwritten
    • Existing DeepSeek/Zhipu/Moonshot cases unaffected (no regression)
    • extractCacheCreationTokensFromBody handles valid and empty/invalid input
go test ./relay/channel/openai/... -v  # all PASS
go test ./dto/... -v                    # all PASS
go vet ./...                            # clean (web/dist warning is pre-existing)

Summary by CodeRabbit

  • Bug Fixes

    • More accurate token usage reporting, now capturing cached token-creation metrics.
    • Improved fallback logic to extract cached token information from API responses when primary values are missing.
  • Tests

    • Added comprehensive tests covering token usage post-processing, fallback behaviors, and extraction of cached-creation token metrics (including invalid or empty responses).

- Change CachedCreationTokens json tag from "-" to
  "cache_creation_input_tokens" so providers like Qwen can deserialize
  this field naturally from prompt_tokens_details.
- Add default case in applyUsagePostProcessing to handle generic
  OpenAI-compatible channels: extract cached_tokens (StepFun) and
  cache_creation_input_tokens from response body when not already
  populated via standard fields.
- Add extractCacheCreationTokensFromBody helper following the existing
  extractCachedTokensFromBody pattern.

Closes QuantumNous#3309

Signed-off-by: majiayu000 <1835304752@qq.com>
@coderabbitai

coderabbitai Bot commented Mar 21, 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: d9d38b1a-61e2-456e-b47b-90aaf6809577

📥 Commits

Reviewing files that changed from the base of the PR and between d7da948 and c825318.

📒 Files selected for processing (1)
  • relay/channel/openai/relay_openai_test.go
✅ Files skipped from review due to trivial changes (1)
  • relay/channel/openai/relay_openai_test.go

Walkthrough

Adds JSON deserialization for cache-creation tokens and a default fallback in OpenAI relay usage post-processing that extracts cached read and cached-creation token values from upstream response bodies; includes unit tests for the new extraction behavior.

Changes

Cohort / File(s) Summary
DTO Schema Update
dto/openai_response.go
Changed InputTokenDetails.CachedCreationTokens JSON tag from json:"-" to json:"cache_creation_input_tokens" so it can be deserialized from upstream responses.
Usage Post-Processing & Token Extraction
relay/channel/openai/relay-openai.go
Added a default branch in applyUsagePostProcessing that populates PromptTokensDetails.CachedTokens and CachedCreationTokens from PromptCacheHitTokens or by extracting usage.cached_tokens and usage.prompt_tokens_details.cache_creation_input_tokens from the response body. Added helper extractCacheCreationTokensFromBody(body []byte) (int, bool).
Test Coverage
relay/channel/openai/relay_openai_test.go
New tests covering default-case behavior: preferring PromptCacheHitTokens, extracting cached_tokens and cache_creation_input_tokens from response bodies, preserving existing values, ChannelTypeDeepSeek exception, and edge cases for the extractor.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Relay as Relay (applyUsagePostProcessing)
    participant Upstream as Upstream Response Body
    participant DTO as Usage DTO

    Client->>Relay: Send request
    Relay->>Upstream: Forward request to upstream
    Upstream-->>Relay: Response (body with usage fields)
    Relay->>Relay: Unmarshal usage into DTO
    alt PromptTokensDetails.CachedTokens == 0
        alt PromptCacheHitTokens > 0
            Relay->>DTO: Set CachedTokens = PromptCacheHitTokens
        else PromptCacheHitTokens == 0
            Relay->>Upstream: extractCachedTokensFromBody(body)
            Relay->>DTO: Set CachedTokens = cached_tokens (if present)
        end
    end
    alt PromptTokensDetails.CachedCreationTokens == 0
        Relay->>Upstream: extractCacheCreationTokensFromBody(body)
        Relay->>DTO: Set CachedCreationTokens = cache_creation_input_tokens (if present)
    end
    Relay->>Client: Return processed response (DTO populated)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • seefs001
  • creamlike1024

Poem

🐰 I nibble at JSON in moonlit code,
Found hidden tokens on the road,
A default branch and extractor song,
Now cached reads and creations belong. 🥕✨

🚥 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 clearly summarizes the main change: adding support for non-standard cache token fields from OpenAI-compatible providers by modifying JSON deserialization and adding fallback extraction logic.
Linked Issues check ✅ Passed The PR fully addresses both sub-problems from issue #3309: changing CachedCreationTokens JSON tag to enable deserialization, adding default branch for cache read fallback, and implementing extractCacheCreationTokensFromBody for cache creation extraction.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #3309 objectives: DTO tag modification, fallback extraction logic, helper function, and comprehensive unit tests covering specified scenarios.

✏️ 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.

Tip

You can disable the changed files summary in the walkthrough.

Disable the reviews.changed_files_summary setting to disable the changed files summary in the walkthrough.

@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.

🧹 Nitpick comments (1)
relay/channel/openai/relay_openai_test.go (1)

28-36: Consider adding a test for priority verification.

When both PromptCacheHitTokens and body cached_tokens are present, the default case should prefer PromptCacheHitTokens. A test confirming this priority would strengthen the coverage.

🧪 Optional test for priority verification
func TestApplyUsagePostProcessing_DefaultCase_PrefersPromptCacheHitTokens(t *testing.T) {
	body := []byte(`{"usage":{"cached_tokens":100}}`)
	info := newRelayInfo(0)
	usage := &dto.Usage{
		PromptCacheHitTokens: 200,
	}
	applyUsagePostProcessing(info, usage, body)
	if usage.PromptTokensDetails.CachedTokens != 200 {
		t.Errorf("CachedTokens = %d, want 200 (should prefer PromptCacheHitTokens over body)", usage.PromptTokensDetails.CachedTokens)
	}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@relay/channel/openai/relay_openai_test.go` around lines 28 - 36, Add a new
unit test to verify priority when both PromptCacheHitTokens and
body.cached_tokens exist: create a test named
TestApplyUsagePostProcessing_DefaultCase_PrefersPromptCacheHitTokens that
constructs body := []byte(`{"usage":{"cached_tokens":100}}`), info :=
newRelayInfo(0), and usage := &dto.Usage{PromptCacheHitTokens: 200}, call
applyUsagePostProcessing(info, usage, body), and assert that
usage.PromptTokensDetails.CachedTokens == 200 (fail the test if it equals 100)
to ensure PromptCacheHitTokens takes precedence over the body value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@relay/channel/openai/relay_openai_test.go`:
- Around line 28-36: Add a new unit test to verify priority when both
PromptCacheHitTokens and body.cached_tokens exist: create a test named
TestApplyUsagePostProcessing_DefaultCase_PrefersPromptCacheHitTokens that
constructs body := []byte(`{"usage":{"cached_tokens":100}}`), info :=
newRelayInfo(0), and usage := &dto.Usage{PromptCacheHitTokens: 200}, call
applyUsagePostProcessing(info, usage, body), and assert that
usage.PromptTokensDetails.CachedTokens == 200 (fail the test if it equals 100)
to ensure PromptCacheHitTokens takes precedence over the body value.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 48b28f54-3062-4505-b450-f48e3b7b2e43

📥 Commits

Reviewing files that changed from the base of the PR and between 42846c6 and d7da948.

📒 Files selected for processing (3)
  • dto/openai_response.go
  • relay/channel/openai/relay-openai.go
  • relay/channel/openai/relay_openai_test.go

@majiayu000
majiayu000 marked this pull request as ready for review March 21, 2026 13:13
…dy cached_tokens

Signed-off-by: majiayu000 <1835304752@qq.com>
@ghost

This comment was marked as spam.

@majiayu000 majiayu000 closed this Jun 3, 2026
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.

OpenAI 兼容渠道无法识别非标准位置的缓存读取和缓存创建信息

1 participant