fix(streaming_handler.py): include cost in streaming usage object (#13319)
Fixes https://github.com/BerriAI/litellm/issues/12689
This commit is contained in:
parent
2e3f564a39
commit
324cfe8bdc
@ -269,6 +269,7 @@ blocked_user_list: Optional[Union[str, List]] = None
|
||||
banned_keywords_list: Optional[Union[str, List]] = None
|
||||
llm_guard_mode: Literal["all", "key-specific", "request-specific"] = "all"
|
||||
guardrail_name_config_map: Dict[str, GuardrailItem] = {}
|
||||
include_cost_in_streaming_usage: bool = False
|
||||
### PROMPTS ###
|
||||
from litellm.types.prompts.init_prompts import PromptSpec
|
||||
|
||||
|
||||
@ -1584,7 +1584,9 @@ class CustomStreamWrapper:
|
||||
except StopIteration:
|
||||
if self.sent_last_chunk is True:
|
||||
complete_streaming_response = litellm.stream_chunk_builder(
|
||||
chunks=self.chunks, messages=self.messages
|
||||
chunks=self.chunks,
|
||||
messages=self.messages,
|
||||
logging_obj=self.logging_obj,
|
||||
)
|
||||
|
||||
response = self.model_response_creator()
|
||||
@ -1768,7 +1770,9 @@ class CustomStreamWrapper:
|
||||
if self.sent_last_chunk is True:
|
||||
# log the final chunk with accurate streaming values
|
||||
complete_streaming_response = litellm.stream_chunk_builder(
|
||||
chunks=self.chunks, messages=self.messages
|
||||
chunks=self.chunks,
|
||||
messages=self.messages,
|
||||
logging_obj=self.logging_obj,
|
||||
)
|
||||
response = self.model_response_creator()
|
||||
if complete_streaming_response is not None:
|
||||
|
||||
@ -5704,7 +5704,11 @@ def stream_chunk_builder_text_completion(
|
||||
|
||||
|
||||
def stream_chunk_builder( # noqa: PLR0915
|
||||
chunks: list, messages: Optional[list] = None, start_time=None, end_time=None
|
||||
chunks: list,
|
||||
messages: Optional[list] = None,
|
||||
start_time=None,
|
||||
end_time=None,
|
||||
logging_obj: Optional[Logging] = None,
|
||||
) -> Optional[Union[ModelResponse, TextCompletionResponse]]:
|
||||
try:
|
||||
if chunks is None:
|
||||
@ -5829,6 +5833,12 @@ def stream_chunk_builder( # noqa: PLR0915
|
||||
|
||||
setattr(response, "usage", usage)
|
||||
|
||||
# Add cost to usage object if include_cost_in_streaming_usage is True
|
||||
if litellm.include_cost_in_streaming_usage and logging_obj is not None:
|
||||
setattr(
|
||||
usage, "cost", logging_obj._response_cost_calculator(result=response)
|
||||
)
|
||||
|
||||
return response
|
||||
except Exception as e:
|
||||
verbose_logger.exception(
|
||||
|
||||
@ -887,6 +887,7 @@ class Usage(CompletionUsage):
|
||||
) # hidden param for prompt caching. Might change, once openai introduces their equivalent.
|
||||
|
||||
server_tool_use: Optional[ServerToolUse] = None
|
||||
cost: Optional[float] = None
|
||||
|
||||
completion_tokens_details: Optional[CompletionTokensDetailsWrapper] = None
|
||||
"""Breakdown of tokens used in a completion."""
|
||||
@ -907,6 +908,7 @@ class Usage(CompletionUsage):
|
||||
Union[CompletionTokensDetailsWrapper, dict]
|
||||
] = None,
|
||||
server_tool_use: Optional[ServerToolUse] = None,
|
||||
cost: Optional[float] = None,
|
||||
**params,
|
||||
):
|
||||
# handle reasoning_tokens
|
||||
@ -978,6 +980,11 @@ class Usage(CompletionUsage):
|
||||
else: # maintain openai compatibility in usage object if possible
|
||||
del self.server_tool_use
|
||||
|
||||
if cost is not None:
|
||||
self.cost = cost
|
||||
else:
|
||||
del self.cost
|
||||
|
||||
## ANTHROPIC MAPPING ##
|
||||
if "cache_creation_input_tokens" in params and isinstance(
|
||||
params["cache_creation_input_tokens"], int
|
||||
|
||||
@ -3948,3 +3948,45 @@ def test_is_delta_empty():
|
||||
audio=None,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def test_streaming_with_cost_calculation():
|
||||
from litellm.types.utils import Usage
|
||||
from typing import Optional
|
||||
|
||||
litellm.include_cost_in_streaming_usage = True
|
||||
|
||||
## Test 1: check if usage object can handle 'cost' field
|
||||
usage_object = Usage(
|
||||
prompt_tokens=100,
|
||||
completion_tokens=100,
|
||||
total_tokens=200,
|
||||
cost=1.0,
|
||||
)
|
||||
assert usage_object.cost is not None
|
||||
|
||||
print(f"usage_object: {usage_object}")
|
||||
|
||||
## Test 2: check if usage object has 'cost' field when streaming
|
||||
|
||||
response = litellm.completion(
|
||||
model="gpt-4o-mini",
|
||||
messages=[{"role": "user", "content": "What is the capital of France?"}],
|
||||
stream=True,
|
||||
stream_options={"include_usage": True},
|
||||
)
|
||||
|
||||
usage_object: Optional[Usage] = None
|
||||
for chunk in response:
|
||||
_usage_obj = getattr(chunk, "usage", None)
|
||||
if _usage_obj is not None:
|
||||
usage_object = _usage_obj
|
||||
break
|
||||
|
||||
assert usage_object is not None
|
||||
assert usage_object.total_tokens is not None
|
||||
assert usage_object.total_tokens > 0
|
||||
assert usage_object.prompt_tokens is not None
|
||||
assert usage_object.prompt_tokens > 0
|
||||
assert usage_object.cost is not None
|
||||
assert usage_object.cost > 0
|
||||
|
||||
Loading…
Reference in New Issue
Block a user