mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-21 00:21:49 +00:00
Merge pull request #41336 from BerriAI/litellm_fix_anthropic_stream_absent_usage
fix(anthropic): tolerate message_delta events without usage when streaming
This commit is contained in:
commit
4b84fa9230
4 changed files with 21 additions and 6 deletions
|
|
@ -1167,7 +1167,9 @@ class ModelResponseIterator:
|
|||
# (matches OpenAI behavior and non-streaming Anthropic implementation)
|
||||
if self.converted_response_format_tool:
|
||||
finish_reason = "stop"
|
||||
usage: Final = self._handle_usage(anthropic_usage_chunk=message_delta["usage"])
|
||||
usage: Final = (
|
||||
self._handle_usage(anthropic_usage_chunk=message_delta["usage"]) if "usage" in message_delta else None
|
||||
)
|
||||
container: Final = message_delta["delta"].get("container")
|
||||
return finish_reason, usage, container
|
||||
|
||||
|
|
|
|||
|
|
@ -376,10 +376,9 @@ class AnthropicStreamWrapper(AdapterCompletionStreamWrapper):
|
|||
usage_dict: UsageDelta = LiteLLMAnthropicMessagesAdapter._translate_openai_usage_to_anthropic_usage_delta(
|
||||
chunk.usage
|
||||
)
|
||||
merged_chunk["usage"] = usage_dict
|
||||
if self.applied_edits and "context_management" not in merged_chunk:
|
||||
merged_chunk["context_management"] = ContextManagementResponse(applied_edits=list(self.applied_edits))
|
||||
return self._augment_message_delta_usage(merged_chunk)
|
||||
return self._augment_message_delta_usage({**merged_chunk, "usage": usage_dict})
|
||||
|
||||
def _handle_choiceless_chunk(self, chunk: "ModelResponseStream") -> bool:
|
||||
"""Consume an OpenAI-compatible chunk that carries no ``choices``.
|
||||
|
|
@ -448,8 +447,7 @@ class AnthropicStreamWrapper(AdapterCompletionStreamWrapper):
|
|||
}
|
||||
iterations.append(message_iteration)
|
||||
augmented_usage["iterations"] = iterations
|
||||
augmented["usage"] = augmented_usage
|
||||
return augmented
|
||||
return {**augmented, "usage": augmented_usage}
|
||||
|
||||
def _next_compaction_event(self) -> dict[str, object] | None:
|
||||
"""Return the next compaction content-block SSE event, or ``None``.
|
||||
|
|
|
|||
|
|
@ -586,7 +586,7 @@ class MessageBlockDelta(TypedDict):
|
|||
|
||||
type: Literal["message_delta"]
|
||||
delta: MessageDelta
|
||||
usage: UsageDelta
|
||||
usage: NotRequired[ReadOnly[UsageDelta]]
|
||||
context_management: NotRequired[ContextManagementResponse]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import json
|
||||
import threading
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
from typing import Final
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import httpx
|
||||
|
|
@ -579,6 +580,20 @@ def test_text_only_streaming_has_index_zero():
|
|||
), f"Expected index=0, got {parsed.choices[0].index}"
|
||||
|
||||
|
||||
def test_message_delta_without_usage_returns_chunk_with_no_usage():
|
||||
iterator: Final = ModelResponseIterator(None, sync_stream=True)
|
||||
|
||||
model_response: Final = iterator.chunk_parser(
|
||||
{
|
||||
"type": "message_delta",
|
||||
"delta": {"stop_reason": "end_turn", "stop_sequence": None},
|
||||
}
|
||||
)
|
||||
|
||||
assert model_response.choices[0].finish_reason == "stop"
|
||||
assert model_response.usage is None
|
||||
|
||||
|
||||
def test_streaming_thinking_deltas_count_reasoning_tokens_in_usage():
|
||||
"""Anthropic streaming usage should account for emitted thinking deltas."""
|
||||
chunks = [
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue