NousResearch/hermes-agent

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

0open issues
0open PRs
5items grouped
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

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #65678 fix(usage): read DeepSeek's native prompt_cache_hit_tokens cache field

    merged to main

    files: agent/transports/chat_completions.py, agent/usage_pricing.py, tests/agent/test_usage_pricing.py, tests/agent/transports/test_chat_completions.py

    Show 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.00 means 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 Lab

At a glance

Subsystem
agent
Merged PRs
#65678
Closed issues
1
Attempts
3 PRs closed without merging
Fix link checked
16 Jul 2026

For your agent

The same facts, typed.