mirror of
https://github.com/tiennm99/litellm.git
synced 2026-07-25 22:24:23 +00:00
fix(adapter): populate cache_read_input_tokens from prompt_tokens_details for OpenAI/Azure (#22090)
* fix(adapter): populate cache_read_input_tokens from prompt_tokens_details The Anthropic adapter's translate_openai_response_to_anthropic checked only the private _cache_read_input_tokens attr (set by Anthropic/DeepSeek) but not prompt_tokens_details.cached_tokens (set by OpenAI/Azure). Use prompt_tokens_details.cached_tokens directly — it is already extracted and is the standard field populated by all providers. Fixes #22089 * fix(adapter): apply same cache_read_input_tokens fix to streaming path The streaming path in translate_streaming_openai_response_to_anthropic had the same bug — relying on _cache_read_input_tokens instead of prompt_tokens_details.cached_tokens.
This commit is contained in:
@@ -1106,19 +1106,19 @@ class LiteLLMAnthropicMessagesAdapter:
|
||||
# extract usage
|
||||
usage: Usage = getattr(response, "usage")
|
||||
uncached_input_tokens = usage.prompt_tokens or 0
|
||||
cached_tokens = 0
|
||||
if hasattr(usage, "prompt_tokens_details") and usage.prompt_tokens_details:
|
||||
cached_tokens = getattr(usage.prompt_tokens_details, "cached_tokens", 0) or 0
|
||||
uncached_input_tokens -= cached_tokens
|
||||
|
||||
|
||||
anthropic_usage = AnthropicUsage(
|
||||
input_tokens=uncached_input_tokens,
|
||||
output_tokens=usage.completion_tokens or 0,
|
||||
)
|
||||
# Add cache tokens if available (for prompt caching support)
|
||||
if hasattr(usage, "_cache_creation_input_tokens") and usage._cache_creation_input_tokens > 0:
|
||||
anthropic_usage["cache_creation_input_tokens"] = usage._cache_creation_input_tokens
|
||||
if hasattr(usage, "_cache_read_input_tokens") and usage._cache_read_input_tokens > 0:
|
||||
anthropic_usage["cache_read_input_tokens"] = usage._cache_read_input_tokens
|
||||
if cached_tokens > 0:
|
||||
anthropic_usage["cache_read_input_tokens"] = cached_tokens
|
||||
|
||||
translated_obj = AnthropicMessagesResponse(
|
||||
id=response.id,
|
||||
@@ -1271,19 +1271,19 @@ class LiteLLMAnthropicMessagesAdapter:
|
||||
litellm_usage_chunk = None
|
||||
if litellm_usage_chunk is not None:
|
||||
uncached_input_tokens = litellm_usage_chunk.prompt_tokens or 0
|
||||
cached_tokens = 0
|
||||
if hasattr(litellm_usage_chunk, "prompt_tokens_details") and litellm_usage_chunk.prompt_tokens_details:
|
||||
cached_tokens = getattr(litellm_usage_chunk.prompt_tokens_details, "cached_tokens", 0) or 0
|
||||
uncached_input_tokens -= cached_tokens
|
||||
|
||||
|
||||
usage_delta = UsageDelta(
|
||||
input_tokens=uncached_input_tokens,
|
||||
output_tokens=litellm_usage_chunk.completion_tokens or 0,
|
||||
)
|
||||
# Add cache tokens if available (for prompt caching support)
|
||||
if hasattr(litellm_usage_chunk, "_cache_creation_input_tokens") and litellm_usage_chunk._cache_creation_input_tokens > 0:
|
||||
usage_delta["cache_creation_input_tokens"] = litellm_usage_chunk._cache_creation_input_tokens
|
||||
if hasattr(litellm_usage_chunk, "_cache_read_input_tokens") and litellm_usage_chunk._cache_read_input_tokens > 0:
|
||||
usage_delta["cache_read_input_tokens"] = litellm_usage_chunk._cache_read_input_tokens
|
||||
if cached_tokens > 0:
|
||||
usage_delta["cache_read_input_tokens"] = cached_tokens
|
||||
else:
|
||||
usage_delta = UsageDelta(input_tokens=0, output_tokens=0)
|
||||
return MessageBlockDelta(
|
||||
|
||||
+45
@@ -1813,6 +1813,51 @@ def test_translate_openai_response_to_anthropic_input_tokens_no_cache():
|
||||
assert anthropic_response["usage"]["output_tokens"] == 50
|
||||
|
||||
|
||||
def test_translate_openai_response_to_anthropic_cache_tokens_from_prompt_tokens_details():
|
||||
"""
|
||||
OpenAI/Azure providers set prompt_tokens_details.cached_tokens but not
|
||||
_cache_read_input_tokens. The adapter should populate cache_read_input_tokens
|
||||
from prompt_tokens_details.cached_tokens directly.
|
||||
"""
|
||||
from litellm.types.utils import PromptTokensDetailsWrapper
|
||||
|
||||
# OpenAI-style usage: only prompt_tokens_details, no cache_read_input_tokens kwarg
|
||||
usage = Usage(
|
||||
prompt_tokens=100,
|
||||
completion_tokens=50,
|
||||
total_tokens=150,
|
||||
prompt_tokens_details=PromptTokensDetailsWrapper(
|
||||
cached_tokens=30
|
||||
),
|
||||
)
|
||||
|
||||
response = ModelResponse(
|
||||
id="test-id",
|
||||
choices=[
|
||||
Choices(
|
||||
index=0,
|
||||
finish_reason="stop",
|
||||
message=Message(
|
||||
role="assistant",
|
||||
content="Test response",
|
||||
),
|
||||
)
|
||||
],
|
||||
model="gpt-4o-2024-08-06",
|
||||
usage=usage,
|
||||
)
|
||||
|
||||
adapter = LiteLLMAnthropicMessagesAdapter()
|
||||
anthropic_response = adapter.translate_openai_response_to_anthropic(
|
||||
response=response,
|
||||
tool_name_mapping=None,
|
||||
)
|
||||
|
||||
assert anthropic_response["usage"]["input_tokens"] == 70
|
||||
assert anthropic_response["usage"]["output_tokens"] == 50
|
||||
assert anthropic_response["usage"]["cache_read_input_tokens"] == 30
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# Web Search Tool Transformation Tests
|
||||
# =====================================================================
|
||||
|
||||
Reference in New Issue
Block a user