DeepSeek cache-hit token extraction and pricing fix
Cluster 61871 · agent. Data as of
Nothing open on GitHub. Fix merged in #65678. Check it covers your case.
The short answer
- Known?
- Yes. 5 related items grouped here, 1 issue already closed.
- Fix event
- A fix was merged to main in PR #65678. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
resolved, deciding PR #65678. That is separate from the fix event. - Still open
- Nothing open on GitHub.
A bug report and several fix attempts addressing that DeepSeek's native prompt_cache_hit_tokens field was not being read, causing cache-hit tokens to go unextracted when using the direct DeepSeek API. Related changes also touch pricing updates and cache token fallback handling.
Open issues0
Open PRs0
Fixes on record
-
#65678 fix(usage): read DeepSeek's native prompt_cache_hit_tokens cache field
files:
agent/transports/chat_completions.py,agent/usage_pricing.py,tests/agent/test_usage_pricing.py,tests/agent/transports/test_chat_completions.pyShow the diff
-
agent/transports/chat_completions.py@@ -776,15 +776,18 @@ def validate_response(self, response: Any) -> bool: return True def extract_cache_stats(self, response: Any) -> dict[str, int] | None: - """Extract OpenRouter/OpenAI cache stats from prompt_tokens_details.""" + """Extract cache stats from prompt_tokens_details (OpenRouter/OpenAI) + or DeepSeek's native top-level prompt_cache_hit_tokens field.""" usage = getattr(response, "usage", None) if usage is None: return None details = getattr(usage, "prompt_tokens_details", None) - if details is None: - return None - cached = getattr(details, "cached_tokens", 0) or 0 - written = getattr(details, "cache_write_tokens", 0) or 0 + cached = getattr(details, "cached_tokens", 0) or 0 if details else 0 + written = getattr(details, "cache_write_tokens", 0) or 0 if details else 0 + if not cached: + # DeepSeek native API shape (api.deepseek.com): top-level + # prompt_cache_hit_tokens / prompt_cache_miss_tokens (#61871). + cached = getattr(usage, "prompt_cache_hit_tokens", 0) or 0 if cached or written: return {"cached_tokens": cached, "creation_tokens": written} return None -
agent/usage_pricing.py@@ -1074,6 +1074,15 @@ def normalize_usage( cache_read_tokens = [redacted:secret-assignment] "cached_tokens", 0) if details else 0) if not cache_read_tokens: [redacted:secret-assignment] = _to_int(getattr(response_usage, "cache_read_input_tokens", 0)) + if not cache_read_tokens: + # DeepSeek's native API (api.deepseek.com) reports context-cache + # hits as top-level prompt_cache_hit_tokens (+ the complementary + # prompt_cache_miss_tokens; prompt_tokens = hit + miss), not the + # OpenAI nested shape. Without this, direct DeepSeek sessions + # always showed 0 cache-hit tokens (#61871). + cache_read_tokens = [redacted:secret-assignment] + getattr(response_usage, "prompt_cache_hit_tokens", 0) + ) cache_write_tokens = [redacted:secret-assignment] getattr(details, "cache_write_tokens", 0) if details else 0 ) -
tests/agent/test_usage_pricing.py@@ -39,6 +39,44 @@ def test_normalize_usage_openai_subtracts_cached_prompt_tokens(): assert normalized.output_tokens == 700 +def test_normalize_usage_reads_deepseek_native_cache_hit_tokens(): + """DeepSeek's native API (api.deepseek.com) reports context-cache hits as + top-level prompt_cache_hit_tokens / prompt_cache_miss_tokens (with + prompt_tokens = hit + miss), not OpenAI's nested + prompt_tokens_details.cached_tokens. Before this fix, direct DeepSeek + sessions always normalized to cache_read_tokens=0 — cache hits were + invisible in accounting and billed at the full input rate (#61871).""" + usage = SimpleNamespace( + prompt_tokens=2000, + completion_tokens=400, + prompt_cache_hit_tokens=1500, + prompt_cache_miss_tokens=500, + ) + + normalized = normalize_usage(usage, provider="deepseek", api_mode="chat_completions") + + assert normalized.cache_read_tokens == 1500 + # prompt_tokens includes cached; input = 2000 - 1500 = the miss bucket + assert normalized.input_tokens == 500 + assert normalized.output_tokens == 400 + + +def test_normalize_usage_nested_details_win_over_deepseek_top_level(): + """When a proxy forwards both shapes, the OpenAI nested value wins and + the DeepSeek top-level field is not double-read.""" + usage = SimpleNamespace( + prompt_tokens=2000, + completion_tokens=100, + prompt_tokens_details=[redacted:secret-assignment] + prompt_cache_hit_tokens=1500, + ) + + normalized = normalize_usage(usage, provider="deepseek", api_mode="chat_completions") + + assert normalized.cache_read_tokens == 900 + assert normalized.input_tokens == 1100 + + def test_normalize_usage_openai_reads_top_level_anthropic_cache_fields(): """Some OpenAI-compatible proxies (OpenRouter, Cline) expose Anthropic-style cache token counts at the top level of the usage object when -
tests/agent/transports/test_chat_completions.py@@ -1115,6 +1115,33 @@ def test_with_cache(self, transport): result = transport.extract_cache_stats(r) assert result == {"cached_tokens": 500, "creation_tokens": 100} + def test_deepseek_native_top_level_cache_hit_tokens(self, transport): + """DeepSeek's native API (api.deepseek.com) reports cache hits as + top-level prompt_cache_hit_tokens, not the OpenAI nested shape — + the extractor must read it or direct DeepSeek sessions show 0% + cache hit rate (#61871).""" + r = SimpleNamespace( + usage=SimpleNamespace( + prompt_tokens_details=None, + prompt_cache_hit_tokens=1500, + prompt_cache_miss_tokens=500, + ) + ) + result = transport.extract_cache_stats(r) + assert result == {"cached_tokens": 1500, "creation_tokens": 0} + + def test_nested_details_win_over_deepseek_top_level(self, transport): + """When both shapes are present, the OpenAI nested value wins.""" + details = SimpleNamespace(cached_tokens=800, cache_write_tokens=0) + r = SimpleNamespace( + usage=SimpleNamespace( + prompt_tokens_details=[redacted:secret-assignment] + prompt_cache_hit_tokens=1500, + ) + ) + result = transport.extract_cache_stats(r) + assert result == {"cached_tokens": 800, "creation_tokens": 0} + class TestChatCompletionsGeminiNativeExtraBodyStrip: """Profile extra_body (e.g. Nous portal tags) must not reach a native
-
3 PRs closed without merging
From the close comments: done elsewhere 1 · duplicate 1 · other 1
-
#61878 fix(transports): extract DeepSeek cache-hit tokens from top-level prompt_cache_hit_tokens · other →
#65678,#61871 -
#62303 fix(deepseek): extract prompt_cache_hit_tokens + update Jul 2026 pricing · duplicate →
#61878 -
#65676 usage_pricing: add prompt_cache_hit/miss_tokens as cache token fallback · done elsewhere →
03c0b00,#65678,#61871
Closed issues · 1
- Closed#61871[Bug]: direct DeepSeek API cache-hit tokens never extracted — only OpenAI/OpenRouter prompt_tokens_details.cached_tokens is read
How this cluster was grouped
-
Link evidence (
structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference. -
Pairwise link density
0.40: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means every pair is linked.
Pick up a Lab task
Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.
Open the LabAt a glance
- Subsystem
- agent
- Merged PRs
- #65678
- Closed issues
- 1
- Attempts
- 3 PRs closed without merging
- Fix link checked
- 16 Jul 2026